Mobile application performance monitoring (MAPM) is the practice of collecting telemetry directly from your app as it runs on real users' devices, so you can see how it actually behaves in the field instead of guessing from a handful of one-star reviews. It exists because the phone is the one place your existing observability stack can't reach: your backend traces stop at the network boundary, and everything after that (the cold start, the janky scroll, the request that timed out on a train) happens on a device you don't control.
The hard part is that mobile runs in an environment you can't reproduce locally. A user is on a three-year-old Android phone, on a flaky cellular connection, with battery saver throttling the CPU, while your simulator on a fast laptop shows everything working perfectly. This article explains what MAPM actually measures, how the instrumentation works, and where teams get it wrong.
What mobile performance monitoring actually collects
At its core, MAPM means running an SDK inside your app that captures signals as the user interacts with it, buffers them on the device, and exports them to a backend for analysis. The signals that matter most on mobile are different from the ones you watch on a server:
- App startup time. Both cold start (process launched from scratch) and warm start (process alive, activity recreated). This is the first thing a user experiences, and slow cold starts are one of the top drivers of abandonment.
- Crashes. Unhandled exceptions with full stack traces, ideally correlated with the OS version, device model, and app build that produced them.
- ANRs (Application Not Responding). On Android, the UI thread being blocked for roughly 5 seconds triggers the system's "app isn't responding" dialog. The iOS equivalent is a watchdog termination. These are often more damaging than crashes because the app appears frozen rather than gone.
- Slow and frozen frames. Renders that miss the frame budget (over 16ms at 60fps) cause visible jank; frames that take hundreds of milliseconds freeze the UI. This is what users mean when they say an app "feels slow."
- Network performance. Latency, error rates, and payload sizes for the HTTP calls the app makes, captured on the client where real-world radio conditions apply, not from the server's point of view.
- Session data. The sequence of screens, taps, and events leading up to an error, grouped into a session so you can reconstruct what the user was doing when things broke.
Crash reporting alone is not performance monitoring. A crash-free rate of 99.9% tells you nothing about the users who didn't crash but sat staring at a spinner for eight seconds and then closed the app. MAPM covers the crashes and the slow degradations that never throw an exception.
How the instrumentation works
The SDK does most of the work automatically. Taking OpenTelemetry Android as the concrete example, once you add the agent to your build.gradle.kts it wires up automatic instrumentation for the common cases without you writing span code by hand:
1234dependencies {implementation(platform("io.opentelemetry.android:opentelemetry-android-bom:1.4.0"))implementation("io.opentelemetry.android:android-agent")}
Out of the box, that captures Activity and Fragment lifecycle events as spans, detects ANRs and reports them as spans against the UI thread, records unhandled exceptions with stack traces, tags telemetry with the current network state, and flags slow (>16ms) and frozen (>700ms) frames. Network calls made through OkHttp can be instrumented so each request becomes a span you can measure and correlate.
Two design choices make this work on a constrained device rather than draining the battery. Telemetry is buffered and exported in batches instead of one network call per event, which keeps the radio from waking up constantly. And when the device has no connectivity, data is persisted to disk and flushed once the connection returns, so you don't lose the telemetry from exactly the poor-network conditions you most want to see.
The telemetry itself is tagged with resource attributes describing the environment: device.model.identifier, os.name, os.version, and the network connection type. A session.id attribute ties events from the same session together. That consistent tagging is what lets you later ask "is this crash specific to one OS version?" instead of eyeballing individual reports.
Why client-side data alone isn't enough
The single biggest reason to instrument mobile with an open standard rather than a closed vendor SDK is correlation. A checkout that feels slow to the user might be slow because of the network, the client rendering, or a backend service three hops deep. If your mobile telemetry lives in one silo and your backend traces live in another, you're stuck arguing about whether it's "a client issue" or "an API issue."
When the mobile client and the backend share a trace context, the client span that starts on a button tap and the backend spans it triggers become one distributed trace. The tap, the network request, the API gateway, and the database query all show up on a single timeline. Everyone looks at the same data, and the finger-pointing stops. This end-to-end view is the payoff of treating mobile as another source of logs, metrics, and traces rather than a separate product category. It's the same principle behind real user monitoring on the web, extended to the native app.
Common pitfalls
Treating the crash-free rate as your only KPI. It's easy to measure and it hides the slow, non-crashing failures that quietly bleed users. Watch startup time, ANR rate, and slow-frame rate alongside it.
Over-instrumenting and paying for it in battery and data. Creating a span for every tiny UI event, or exporting on every event without batching, turns your monitoring into a performance problem of its own. Start with the automatic instrumentation, add manual spans only for the flows that matter (login, checkout, search, sync), and use sampling as volume grows.
Shipping personally identifiable information (PII) in your telemetry. Screen names, span attributes, and URLs with query strings can carry personal data straight into your observability backend. Redact attributes before export and avoid putting identifiers in span names. Treat this as a compliance requirement, since telemetry backends are rarely scoped for personal data.
Ambiguous session boundaries. Unlike a backend request, a mobile session has no obvious start and end. Decide explicitly how inactivity ends a session and how app backgrounding is handled, or your session-scoped metrics will be inconsistent across releases.
Final thoughts
Once you're collecting mobile telemetry, the value compounds when it sits next to everything else you run, so a slow screen can be traced through to the backend service that caused it rather than investigated in isolation. Building on OpenTelemetry keeps that data portable and vendor-neutral from the start.
Dash0 is OpenTelemetry-native, so any OpenTelemetry data your mobile apps export via OTLP (the OpenTelemetry Protocol) lands alongside your backend distributed traces and logs in one place, correlated by trace context. You get the full journey from a user's tap to the database query behind it, without stitching together separate tools.
Start a free trial to see your mobile and backend telemetry in a single view. No credit card required.