A user opens a ticket: the mobile app is sluggish. You check your dashboards. CPU utilization is flat. Error rates are zero. Your APM lights are a reassuring green. The backend, by every visible measure, is healthy. But the user is not wrong. The slowness is real, and it is happening somewhere in the long, shadowy corridor between an Android device and your server.

The problem is that most observability tools stop at the application boundary. They measure what happens after your framework parses a request. They track database queries, cache hits, and downstream service calls. What they miss is the mechanics of the request itself: the time an OkHttp call spends inside the Android HTTP stack, the transit across a volatile mobile network, the TLS handshake negotiation, and the silent waiting that happens inside kernel queues before your code ever runs. These gaps swallow milliseconds—or entire seconds—while your APM remains silent.

Encryption makes the blindness complete. Modern Android apps route everything through BoringSSL. By the time a packet hits the kernel network stack, the HTTP headers are encrypted. A standard tcpdump or network hook sees only opaque TLS records. You can observe that traffic is flowing, but you cannot read it. You certainly cannot tie a specific kernel-level TCP segment back to a specific user's API call. The trace context you need is trapped inside the ciphertext.

eBPF changes the equation because it lets you instrument the system from the inside out without altering your application code. Instead of asking your app to report its own latency, you attach small programs directly to the kernel and to critical userspace libraries. These programs observe events as they happen, extract what you need, and ship it to a ring buffer. There is no SDK bloat inside your Android APK beyond a lightweight header, and there is no instrumentation agent rewriting your backend classes.

The Four-Part Setup

Building this pipeline involves four distinct layers of observation.

1. The traceparent anchor. On the Android device, you add an OkHttp interceptor that injects a W3C traceparent header into every outbound request. This is the only change required on the mobile side, and it is minimal. The header travels inside the encrypted payload all the way to your backend. Because it sits inside the HTTP layer, it survives inside the plaintext that the TLS engine eventually exposes.

2. TCP arrival timing. On the backend host, you use eBPF Traffic Control hooks attached to the network interface. These programs fire as individual TCP segments arrive. They capture sequence numbers and timestamps at the edge. You now know precisely when bits left the wire and entered your machine, long before your application reads a single byte.

3. Decryption probes. Your backend terminates TLS using either OpenSSL or BoringSSL. Here is where the architecture gets interesting. Using uprobes—dynamic userspace probes—you attach to SSL_write and SSL_read inside the TLS library. These functions fire the instant plaintext data passes through the encryption engine. Your eBPF program reads that decrypted buffer, scans for the traceparent header, and extracts it. The kernel now possesses a direct mapping between a raw TCP flow and a specific mobile request, without you ever handling certificates or keys in a custom tool.

4. Kernel queueing. Even after the data is decrypted and ready, your application may not consume it immediately. You attach kprobes to relevant kernel functions handling socket buffers and scheduling events. This measures queueing latency: the time requests spend waiting in kernel land because your process is contending for CPU or simply has not called read() yet.

All four signal sources write events into an eBPF ring buffer. A sidecar process running in userspace drains this buffer, correlates events by traceparent ID, and reconstructs a single, coherent timeline for every request. What was previously a scattering of disconnected kernel noise becomes a structured trace.

Reading the Full Path

The assembled output is typically rendered as a flame graph or a structured span tree that splits latency into four concrete parts:

  • Network transit time: The duration from the Android radio sending the last byte of the request to the backend NIC receiving it. This is where cellular volatility lives.
  • TLS handshake duration: The time spent negotiating the encrypted tunnel. On spotty networks, this can dwarf actual data transfer.
  • Kernel queueing latency: Time spent in kernel buffers and scheduler queues after the segment arrives but before userspace consumes it.
  • Application processing time: The slice your backend framework and business logic actually consume.

You might discover that an 800ms mobile request spends only 40ms inside your JSON parser. Another 200ms vanishes into a stalled TLS handshake across a lossy connection. Another 300ms disappears into kernel backlog on an oversubscribed host. Your APM was only reporting the 40ms. Without kernel-level visibility, you would have optimized the wrong thing entirely.

Overhead That Actually Scales

Traditional APM agents achieve their visibility by intercepting calls inside your language runtime. They wrap methods, allocate span objects, and serialize telemetry data inside your process heap. Under load, that overhead compounds quickly. Serialization costs rise. Garbage collection pressure increases. You are paying for visibility with your application's own resources.

eBPF programs run inside a kernel virtual machine and are JIT-compiled to native machine instructions. A verifier checks them for safety before they load. Each probe adds microseconds to the path, not milliseconds. The heavy work of matching events and rendering graphs happens in the sidecar, outside your service's hot path. You are not inflating heap allocations. You are not adding serialization taxes inside request handling. For services pushing thousands of requests per second, that distinction matters.

What It Takes to Run

This is not a magic bullet, and it is not a managed SaaS you can toggle on. You need a backend kernel with modern eBPF support, including BTF type information so your probes can safely traverse kernel structures. Your TLS library must expose symbols that uprobes can target; if you ship a statically linked binary with a stripped or heavily customized OpenSSL build, you will need to account for that. You also need to verify that your traceparent header survives any proxies or edge gateways between the mobile client and the TLS termination point.

But for teams exhausted by "mobile is slow" tickets that defy root-cause analysis, this architecture replaces ritual guessing with hard signal. You stop speculating about network weather and start measuring specific requests through specific pipes.

The Real Takeaway

You do not have to treat encrypted mobile traffic as an opaque stream that magically becomes visible once it hits your framework. By combining a simple OkHttp header with strategically placed eBPF probes on the backend, you can follow a single request from an Android device through TLS decryption, kernel queues, and into your application logic—without rewriting your mobile app and without instrumenting your backend code the way traditional APM demands. That is not just incremental improvement. It is end-to-end observability across