Skip to content
Last9
Book demo

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

  1. Go to Play Console → Setup → API access
  2. Link or create a Google Cloud project
  3. Create a service account (or use an existing one)
  4. Grant the service account View app information and download bulk reports (read-only)
  5. Download the JSON key file — store it securely, never commit it

Quick Start

git clone https://github.com/last9/opentelemetry-examples
cd 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.json
docker compose up

Configuration

VariableDescriptionDefault
GOOGLE_APPLICATION_CREDENTIALSPath to service account JSON inside container/run/secrets/service-account.json
ANDROID_PACKAGE_NAMESComma-separated package names to monitorcom.example.app
POLL_INTERVAL_SECONDSPoll frequency in seconds1800
POLL_DAYS_BACKDays of history to fetch each poll30
ENABLE_HOURLYAlso poll crash/ANR at hourly granularity (~2–4h lag)false
ANDROID_DIMENSIONSComma-separated dimensions (e.g. versionCode,apiLevel)(aggregate)
OTLP_ENDPOINTLast9 OTLP endpointhttps://otlp.last9.io
OTLP_HEADERSAuth headers (Authorization=Basic <token>)
OTEL_SERVICE_NAMEService name shown in Last9google-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

MetricDescription
android.vitals.crash_rateDaily crash rate
android.vitals.crash_rate_7d7-day user-weighted crash rate
android.vitals.crash_rate_28d28-day user-weighted crash rate
android.vitals.user_perceived_crash_rateForeground crash rate
android.vitals.crash_distinct_usersUsers in the crash rate denominator

ANR rate

MetricDescription
android.vitals.anr_rateDaily ANR rate
android.vitals.anr_rate_7d7-day user-weighted ANR rate
android.vitals.anr_rate_28d28-day user-weighted ANR rate
android.vitals.user_perceived_anr_rateForeground ANR rate
android.vitals.anr_distinct_usersUsers in the ANR rate denominator

Slow start

MetricDescription
android.vitals.slow_start_rateFraction of slow app starts
android.vitals.slow_start_rate_7d7-day slow start rate
android.vitals.slow_start_rate_28d28-day slow start rate

Slow rendering

MetricDescription
android.vitals.slow_rendering_rate_20fpsSessions rendering below 20 FPS
android.vitals.slow_rendering_rate_30fpsSessions rendering below 30 FPS

Battery & memory

MetricDescription
android.vitals.excessive_wakeup_rateSessions with >10 AlarmManager wakeups/hour
android.vitals.stuck_background_wakelock_rateSessions with a background wakelock held >1 hour
android.vitals.lmk_rateActive-use Low Memory Kill rate

Error counts (hourly)

MetricDescription
android.vitals.error_report_countHourly error count (~2–4h lag)
android.vitals.error_distinct_usersDistinct users with errors

Labels

Every data point carries these labels:

LabelPresent onExample
android_packageall metricscom.example.app
android_dateall metrics2026-02-24
android_report_typeerror count onlyCRASH or ANR
android_start_typeslow start onlyCOLD, WARM, or HOT
android_version_codewhen ANDROID_DIMENSIONS includes it142
android_api_levelwhen ANDROID_DIMENSIONS includes it34

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 rate
max by (android_package) (last_over_time(android_vitals_user_perceived_crash_rate_ratio{android_package="$package"}[30d]))
# Total error count
sum by (android_package) (last_over_time(android_vitals_error_report_count{android_package="$package"}[30d]))

Use max by to collapse the android_date dimension — without it, each date creates a separate series in the legend:

# Daily crash rate trend
max by (android_package) (android_vitals_crash_rate_ratio{android_package="$package"})
# Daily ANR rate trend
max 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 rate
max by (android_package) (android_vitals_slow_start_rate_ratio{android_package="$package"})
# Battery & memory
max 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 Last9

Then 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.


For questions, reach out on Discord or Email.