feat(vitals): per-version crash/ANR breakdown + stalled-feed warning - #25
feat(vitals): per-version crash/ANR breakdown + stalled-feed warning#25TimeToBuildBob wants to merge 3 commits into
Conversation
Two gaps surfaced by 'are crash rates better on v0.14.0b2?' (ActivityWatch/aw-android#176), which the existing tooling cannot answer. 1. The app-wide daily series cannot attribute a rate to a release. During a partial rollout the aggregate is dominated by the install base still on the old build, so a genuinely fixed version barely moves it for weeks. Add 'vitals.py by-version', which breaks the same metric down by the API's versionCode dimension and compares versions directly. 2. A stalled feed was invisible. _daily_freshness anchors the query to the API's latest available DAILY date; when that date stops advancing the collector still exits 0, rewrites identical rows, and 'git diff --quiet' skips the commit — so the daily job reports success while the series silently stops. Warn on stderr when the feed is more than STALE_FRESHNESS_DAYS behind, so a stall is output rather than absence of it. Add offline tests for the dimensioned query body, per-version row parsing, and the freshness warning. ruff/mypy/pytest all green.
Greptile SummaryThe PR adds per-version crash and ANR reporting and warns when Play’s daily vitals feed becomes stale.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the current implementation uses the raw version code as its grouping identity and delegates CSV field encoding to Important Files Changed
Reviews (2): Last reviewed commit: "fix(tests): split imports and remove unu..." | Re-trigger Greptile |
Two correctness issues found in Greptile review: P1 (unstable version identity): `by-version` keyed `per_version` on the display string from `_dimension_value`, which includes `valueLabel`. When Play omits the label for some rows of the same versionCode the same release splits into separate buckets, corrupting day counts and means. Fix: add `_dimension_raw` that returns only the typed value (no label), group by that as a stable key, and track the best display string in a separate dict. P2 (unescaped CSV): the `--as-csv` branch interpolated version labels directly into an f-string, so a comma or newline in a Play label would produce malformed CSV. Fix: write through `csv.writer`. Tests: 3 new tests covering `_dimension_raw`, stable-grouping behaviour, and CSV escaping with a comma-bearing label (16 pass total).
|
Fixed both Greptile findings in e50c0da (16 tests pass):
|
|
CI lint failure fixed in 617a113: split |
|
CI is green, both Greptile findings (P1 version-identity grouping, P2 CSV escaping) are fixed. Ready for maintainer review and merge. |
|
@greptileai review |
|
Current-head re-review is complete at 5/5 with no blocking findings; CI is green. I do not have merge permission on |
Two gaps surfaced by trying to answer "are crash rates better on v0.14.0b2?" (ActivityWatch/aw-android#176). Neither is answerable with the tooling as it stands.
1. The app-wide series can't attribute a rate to a release
data/android-crash-rate.csvis oneuserPerceivedCrashRatenumber per day across all installs. During a partial rollout that number is dominated by the install base still on the old build, so a genuinely fixed release barely moves the aggregate for weeks — and when it does move you can't tell the release from seasonality.Adds
vitals.py by-version, which requests the same metric with the API'sversionCodedimension and compares versions directly:2. A stalled feed was invisible
_daily_freshnessanchors each query to the API's latest available DAILY date. When that date stops advancing, the collector still exits 0 and rewrites identical rows — sogit diff --cached --quietskips the commit and the daily job keeps reporting success while the series silently stops.That is not hypothetical:
android-crash-rate.csvandandroid-anr-rate.csvwere last updated 2026-08-22 (data through 08-19) whileinstalled.csvupdated today and everycollect-play.ymlrun in that window is green._freshness_warningnow prints the lag to stderr pastSTALE_FRESHNESS_DAYS(3), so a stall shows up as output rather than as absence of it. It does not fail the job — diagnosing whether this stall is Play-side or permissions-side needs the service account, which I don't have.Notes
by-versionprefers the API'svalueLabelwhen it adds information, so rows read1000 (0.14.0b2)rather than a bare code.crash-rate/anr-rate/summarycommands or the collected CSV format.--dry-runand the parsing/warning logic by offline tests.Verification
uv run ruff check .,uv run mypy *.py, anduv run pytest -q(11 passed) all green locally — 9 new tests intest_vitals.pycovering the dimensioned query body, per-version row parsing, and the freshness warning.