feat: Add typed Copilot metrics download helpers#4177
feat: Add typed Copilot metrics download helpers#4177kyungseopk1m wants to merge 4 commits intogoogle:masterfrom
Conversation
Add DownloadDailyMetrics, DownloadPeriodicMetrics,
DownloadUserDailyMetrics, and DownloadUserPeriodicMetrics to
decode the payloads served at the download links returned by
the new Get*MetricsReport endpoints. User metrics payloads are
newline-delimited JSON; aggregate payloads are JSON objects
(1-day) or a { day_totals: [...] } wrapper (28-day).
Deprecate DownloadCopilotMetrics because the []*CopilotMetrics
shape it produces does not match the new payloads. It is kept
for GitHub Enterprise Server installations that may still serve
the legacy shape.
Fixes google#4136
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4177 +/- ##
==========================================
+ Coverage 93.18% 93.73% +0.55%
==========================================
Files 210 210
Lines 24568 19804 -4764
==========================================
- Hits 22894 18564 -4330
+ Misses 1478 1044 -434
Partials 196 196 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
gmlewis
left a comment
There was a problem hiding this comment.
Thank you, @kyungseopk1m!
I'm stopping the review now, but please address the concerns and I will continue later when I have more time.
Go initialism convention requires acronyms to be all caps. Rename the Go identifiers introduced for the Copilot metrics download helpers so that "CLI" is written as an initialism: - `CopilotMetricsCli` -> `CopilotMetricsCLI` - `CopilotMetricsCliVersion` -> `CopilotMetricsCLIVersion` - `CopilotMetricsCliTokenUsage` -> `CopilotMetricsCLITokenUsage` - `CliVersion`, `LastKnownCliVersion`, `DailyActiveCliUsers`, `TotalsByCli`, and `UsedCli` field renames accordingly JSON tags on the wire (`cli_version`, `used_cli`, `totals_by_cli`, `last_known_cli_version`, `daily_active_cli_users`) are left unchanged so the decoder still matches the GitHub API payload. Also add `"CLI"` to the `structfield` linter's initialism list so the rule catches future regressions. Accessors are regenerated.
gmlewis
left a comment
There was a problem hiding this comment.
Thank you, @kyungseopk1m!
LGTM.
Awaiting second LGTM+Approval from any other contributor to this repo before merging.
cc: @stevehipwell - @alexandear - @zyfy29 - @Not-Dhananjay-Mishra - @munlicode
| LocSuggestedToAddSum int `json:"loc_suggested_to_add_sum"` | ||
| LocSuggestedToDeleteSum int `json:"loc_suggested_to_delete_sum"` | ||
| LocAddedSum int `json:"loc_added_sum"` | ||
| LocDeletedSum int `json:"loc_deleted_sum"` |
There was a problem hiding this comment.
LOC is the abbreviation for "Lines of code":
| LocSuggestedToAddSum int `json:"loc_suggested_to_add_sum"` | |
| LocSuggestedToDeleteSum int `json:"loc_suggested_to_delete_sum"` | |
| LocAddedSum int `json:"loc_added_sum"` | |
| LocDeletedSum int `json:"loc_deleted_sum"` | |
| LOCSuggestedToAddSum int `json:"loc_suggested_to_add_sum"` | |
| LOCSuggestedToDeleteSum int `json:"loc_suggested_to_delete_sum"` | |
| LOCAddedSum int `json:"loc_added_sum"` | |
| LOCDeletedSum int `json:"loc_deleted_sum"` |
| CodeGenerationActivityCount int `json:"code_generation_activity_count"` | ||
| CodeAcceptanceActivityCount int `json:"code_acceptance_activity_count"` | ||
| LocSuggestedToAddSum int `json:"loc_suggested_to_add_sum"` | ||
| LocSuggestedToDeleteSum int `json:"loc_suggested_to_delete_sum"` | ||
| LocAddedSum int `json:"loc_added_sum"` | ||
| LocDeletedSum int `json:"loc_deleted_sum"` |
There was a problem hiding this comment.
These fields are the same for CopilotMetricsIDE, CopilotMetricsLanguageFeature, CopilotMetricsModelFeature. Maybe we can move them to a separate struct?
| resp, r, err := s.fetchMetricsReport(ctx, url) | ||
| if err != nil { | ||
| return nil, r, err | ||
| } | ||
| defer resp.Body.Close() | ||
|
|
||
| var metrics *CopilotDailyMetrics | ||
| if err := json.NewDecoder(resp.Body).Decode(&metrics); err != nil { | ||
| return nil, r, err | ||
| } | ||
|
|
||
| return metrics, r, nil |
There was a problem hiding this comment.
I'm curious: can we use the same implementation style for download metrics endpoints as for other endpoints in the repo?
Something like:
| resp, r, err := s.fetchMetricsReport(ctx, url) | |
| if err != nil { | |
| return nil, r, err | |
| } | |
| defer resp.Body.Close() | |
| var metrics *CopilotDailyMetrics | |
| if err := json.NewDecoder(resp.Body).Decode(&metrics); err != nil { | |
| return nil, r, err | |
| } | |
| return metrics, r, nil | |
| req, err := s.client.NewRequest("GET", url, nil) | |
| if err != nil { | |
| return nil, nil, err | |
| } | |
| var metrics *CopilotDailyMetrics | |
| resp, err := s.client.Do(ctx, req, &metrics) | |
| if err != nil { | |
| return nil, resp, err | |
| } | |
| return metrics, resp, nil |
There was a problem hiding this comment.
The pattern matches DownloadCopilotMetrics (copilot.go:837) and DownloadContentsWithMeta (repos_contents.go:178) — both download from a server-returned URL using raw http.NewRequestWithContext + s.client.client.Do(req), and I wanted these four to stay consistent with that. Two of them are also NDJSON, which s.client.Do(ctx, req, &v) can't decode in a single call. Happy to revisit if you'd prefer otherwise.
There was a problem hiding this comment.
@kyungseopk1m thanks for the explanation.
@gmlewis is it possible to hit rate limits when downloading metrics? The raw http.NewRequestWithContext + s.client.client.Do(req) approach doesn't handle that. That's why I proposed using our custom HTTP client.
There was a problem hiding this comment.
Great question. Sorry, but I don't know.
This question should probably be sent to the official GitHub tech support team.
|
@alexandear Done in 90c1208 — Loc* → LOC* (added LOC to the initialism map) and CopilotMetricsCodeActivity embedded in the six smaller per-breakdown structs. Kept CopilotDailyMetrics, CopilotUserDailyMetrics, CopilotUserPeriodicMetrics inline since embedding a 6-field chunk in the middle of a 20+ field struct didn't help readability. Let me know if you'd prefer them uniform too. |
Not-Dhananjay-Mishra
left a comment
There was a problem hiding this comment.
I am curious why aren't we using * + omitempty for all fields since there is no official response schema present, so we don't know which fields will always be returned.
example - DailyActiveCLIUsers is present in struct but it is absent from comment.
and some fields like chat_panel_agent_mode, chat_panel_ask_mode, chat_panel_custom_mode, chat_panel_edit_mode and chat_panel_unknown_mode are missing from structs but present in Data available in Copilot usage metrics docs
|
Fair points, thanks. Will switch the int/bool fields to |
Fixes #4136
Summary
The new Copilot usage metrics endpoints (
/copilot/metrics/reports/*) return download links whose payloads do not match theCopilotMetricsshape, so the existingDownloadCopilotMetricshelper fails to decode live data from the eightGet*MetricsReportmethods on public GitHub.This PR adds four typed helpers and deprecates the legacy one:
Get{Enterprise,Organization}DailyMetricsReportDownloadDailyMetricsGet{Enterprise,Organization}MetricsReportDownloadPeriodicMetricsGet{Enterprise,Organization}UsersDailyMetricsReportDownloadUserDailyMetricsGet{Enterprise,Organization}UsersMetricsReportDownloadUserPeriodicMetricsUser metrics payloads are newline-delimited JSON; aggregate payloads are JSON objects (1-day) or a
{ day_totals: [...] }wrapper (28-day).Payload schemas
GitHub's public OpenAPI and REST documentation only describe the top-level response (
download_links,report_start_day/end_day) and do not publish the schema of the downloaded files. The struct definitions in this PR are based on the live-data structs posted by @Roemer in #4149 (comment) and the official Copilot usage metrics documentation. Follow-up PRs can adjust any field names that differ from what live endpoints emit.Notes
DownloadCopilotMetricsis kept with aDeprecated:comment so GitHub Enterprise Server installations that may still serve the legacy shape continue to work.tools/metadata/metadata.goskip list, following the same pattern as the existingDownloadCopilotMetrics(helper methods without direct GitHub v3 API correspondence).script/generate.shfor the new typed structs.Testing
script/fmt.sh,script/generate.sh,script/lint.sh,script/test.shall pass locally.cc @gmlewis