Google Play Developer Reporting
Export Android vitals (crash rate, ANR rate, slow start, LMK) from the Google Play Developer Reporting API to Last9 as OpenTelemetry metrics.
Export Android vitals from the Google Play Developer Reporting API to Last9 as OpenTelemetry metrics. Covers crash rate, ANR rate, slow start, excessive wakeups, stuck wakelocks, Low Memory Kill rate, and hourly error counts.
Data freshness: Daily metrics lag 24–48 hours; hourly error counts lag ~2–4 hours. These are Google-side constraints.
Prerequisites
- Python 3.13+ or Docker
- Google Play Console service account with View app information and download bulk reports (read-only) permission
- Last9 account with OTLP ingestion enabled
Google Play Console Setup
- Go to Play Console → Setup → API access
- Link or create a Google Cloud project
- Create a service account (or use an existing one)
- Grant the service account View app information and download bulk reports (read-only)
- Download the JSON key file — store it securely, never commit it
Quick Start
git clone https://github.com/last9/opentelemetry-examplescd opentelemetry-examples/python/google-play-reporting
cp .env.example .env# Edit .env with your values
export SERVICE_ACCOUNT_JSON_PATH=/path/to/your/service-account.jsondocker compose upConfiguration
| Variable | Description | Default |
|---|---|---|
GOOGLE_APPLICATION_CREDENTIALS | Path to service account JSON inside container | /run/secrets/service-account.json |
ANDROID_PACKAGE_NAMES | Comma-separated package names to monitor | com.example.app |
POLL_INTERVAL_SECONDS | Poll frequency in seconds | 1800 |
POLL_DAYS_BACK | Days of history to fetch each poll | 30 |
ENABLE_HOURLY | Also poll crash/ANR at hourly granularity (~2–4h lag) | false |
ANDROID_DIMENSIONS | Comma-separated dimensions (e.g. versionCode,apiLevel) | (aggregate) |
OTLP_ENDPOINT | Last9 OTLP endpoint | https://otlp.last9.io |
OTLP_HEADERS | Auth headers (Authorization=Basic <token>) | — |
OTEL_SERVICE_NAME | Service name shown in Last9 | google-play-reporting |
Dimension note: Google may return empty rows for per-dimension queries on low-traffic apps. Start with
ANDROID_DIMENSIONS=(empty) and only enable dimensions once you confirm aggregate data flows.
Metrics Exported
Crash rate
| Metric | Description |
|---|---|
android.vitals.crash_rate | Daily crash rate |
android.vitals.crash_rate_7d | 7-day user-weighted crash rate |
android.vitals.crash_rate_28d | 28-day user-weighted crash rate |
android.vitals.user_perceived_crash_rate | Foreground crash rate |
android.vitals.crash_distinct_users | Users in the crash rate denominator |
ANR rate
| Metric | Description |
|---|---|
android.vitals.anr_rate | Daily ANR rate |
android.vitals.anr_rate_7d | 7-day user-weighted ANR rate |
android.vitals.anr_rate_28d | 28-day user-weighted ANR rate |
android.vitals.user_perceived_anr_rate | Foreground ANR rate |
android.vitals.anr_distinct_users | Users in the ANR rate denominator |
Slow start
| Metric | Description |
|---|---|
android.vitals.slow_start_rate | Fraction of slow app starts |
android.vitals.slow_start_rate_7d | 7-day slow start rate |
android.vitals.slow_start_rate_28d | 28-day slow start rate |
Slow rendering
| Metric | Description |
|---|---|
android.vitals.slow_rendering_rate_20fps | Sessions rendering below 20 FPS |
android.vitals.slow_rendering_rate_30fps | Sessions rendering below 30 FPS |
Battery & memory
| Metric | Description |
|---|---|
android.vitals.excessive_wakeup_rate | Sessions with >10 AlarmManager wakeups/hour |
android.vitals.stuck_background_wakelock_rate | Sessions with a background wakelock held >1 hour |
android.vitals.lmk_rate | Active-use Low Memory Kill rate |
Error counts (hourly)
| Metric | Description |
|---|---|
android.vitals.error_report_count | Hourly error count (~2–4h lag) |
android.vitals.error_distinct_users | Distinct users with errors |
Labels
Every data point carries these labels:
| Label | Present on | Example |
|---|---|---|
android_package | all metrics | com.example.app |
android_date | all metrics | 2026-02-24 |
android_report_type | error count only | CRASH or ANR |
android_start_type | slow start only | COLD, WARM, or HOT |
android_version_code | when ANDROID_DIMENSIONS includes it | 142 |
android_api_level | when ANDROID_DIMENSIONS includes it | 34 |
Dashboard Queries
All metrics carry historical timestamps (the measurement date), not the current time. Set your Last9 dashboard time range to Last 30 days.
Stat panels (current value)
Use last_over_time to avoid staleness returning 0 — each android_date series has one historical data point that goes stale after ~5 minutes:
# Crash rate (7d rolling)max by (android_package) (last_over_time(android_vitals_crash_rate_7d_ratio{android_package="$package"}[30d]))
# ANR rate (7d rolling)max by (android_package) (last_over_time(android_vitals_anr_rate_7d_ratio{android_package="$package"}[30d]))
# User-perceived crash ratemax by (android_package) (last_over_time(android_vitals_user_perceived_crash_rate_ratio{android_package="$package"}[30d]))
# Total error countsum by (android_package) (last_over_time(android_vitals_error_report_count{android_package="$package"}[30d]))Time series panels (trends)
Use max by to collapse the android_date dimension — without it, each date creates a separate series in the legend:
# Daily crash rate trendmax by (android_package) (android_vitals_crash_rate_ratio{android_package="$package"})
# Daily ANR rate trendmax by (android_package) (android_vitals_anr_rate_ratio{android_package="$package"})
# Hourly error count by type (CRASH vs ANR)sum by (android_package, android_report_type) (android_vitals_error_report_count{android_package="$package"})
# Slow start ratemax by (android_package) (android_vitals_slow_start_rate_ratio{android_package="$package"})
# Battery & memorymax by (android_package) (android_vitals_excessive_wakeup_rate_ratio{android_package="$package"})max by (android_package) (android_vitals_stuck_background_wakelock_rate_ratio{android_package="$package"})max by (android_package) (android_vitals_lmk_rate_ratio{android_package="$package"})Dashboard variable: Add $package as a label values variable:
label_values(android_vitals_crash_rate_ratio, android_package)Verification
After starting the collector, check the logs for a successful export line:
INFO Exported 582 data points (32 metrics) to Last9Then check Last9 for metrics with the prefix android_vitals_*.
Troubleshooting
slowRenderingRateMetricSet returns 403
The service account is missing the slow rendering permission. Go to Play Console → Setup → API access, find the service account, and verify View app information and download bulk reports (read-only) is explicitly checked. The collector will automatically start exporting slow rendering metrics on the next poll after the permission is granted.
Stat panels show 0
The metrics carry historical timestamps. At “now”, all data points are stale. Use last_over_time(...[30d]) as shown in the query examples above.
400 Bad Request on startup with POLL_DAYS_BACK=1
Google’s daily API requires start_time < end_time. With 1 day back, start_time == end_time when the freshness date is today. Use POLL_DAYS_BACK=2 or higher.
No data for dimensions
Google may return empty rows for per-dimension queries on low-traffic apps. Start with ANDROID_DIMENSIONS= (empty) to confirm aggregate data flows first.