Een gebruiker opent een ticket: de mobiele app is traag. Je controleert je dashboards. Het CPU-gebruik is stabiel. De foutpercentages zijn nul. De lampjes van je APM geven geruststellend groen licht. De backend is, naar alle zichtbare maten, gezond. Maar de gebruiker heeft gelijk. De traagheid is echt, en het gebeurt ergens in de lange, schaduwrijke corridor tussen een Android-toestel en je server.
Het probleem is dat de meeste observability-tools stoppen bij de applicatiegrens. Ze meten wat er gebeurt nadat je framework een verzoek heeft geparsed. Ze houden databasequeries, cache-hits en downstream service-aanroepen bij. Wat ze missen, is de mechanica van het verzoek zelf: de tijd die een OkHttp-aanroep doorbrengt in de Android HTTP-stack, de transit over een instabiel mobiel netwerk, de TLS-handshake-onderhandeling en het stille wachten dat plaatsvindt in kernel-queues voordat je code überhaupt wordt uitgevoerd. Deze hiaten slikken milliseconden — of zelfs hele seconden — terwijl je APM zwijgt.
Encryptie maakt de blindheid compleet. Moderne Android-apps routeren alles via BoringSSL. Tegen de tijd dat een pakket de kernel network stack bereikt, zijn de HTTP-headers versleuteld. Een standaard tcpdump of network hook ziet alleen ondoorzichtige TLS-records. Je kunt zien dat er verkeer stroomt, maar je kunt het niet lezen. Je kunt zeker geen specifiek TCP-segment op kernelniveau koppelen aan de API-aanroep van een specifieke gebruiker. De trace-context die je nodig hebt, zit gevangen in de ciphertext.
eBPF verandert de formule, omdat het je in staat stelt het systeem van binnenuit te instrumenteren zonder je applicatiecode aan te passen. In plaats van je app te vragen om zijn eigen latentie te rapporteren, koppel je kleine programma's rechtstreeks aan de kernel en aan kritieke userspace-libraries. Deze programma's observeren gebeurtenissen terwijl ze plaatsvinden, extraheren wat je nodig hebt en sturen het naar een ring buffer. Er is geen SDK-bloat in je Android APK buiten een lichtgewicht header, en er is geen instrumentation agent die je backend-classes herschrijft.
De vierdelige opstelling
Het bouwen van deze pipeline omvat vier verschillende lagen van observatie.
1. Het traceparent-anker. Op het Android-toestel voeg je een OkHttp-interceptor toe die een W3C traceparent-header injecteert in elk uitgaand verzoek. Dit is de enige vereiste wijziging aan de mobiele zijde, en het is minimaal. De header reist binnen de versleutelde payload helemaal naar je backend. Omdat deze zich binnen de HTTP-laag bevindt, blijft hij behouden in de plaintext die de TLS-engine uiteindelijk ontsluit.
2. TCP-aankomsttijd. Op de backend-host gebruik je eBPF Traffic Control-hooks die aan de netwerkinterface zijn gekoppeld. Deze programma's worden geactiveerd zodra individuele TCP-segmenten arriveren. Ze leggen sequentienummers en tijdstempels vast aan de rand. Je weet nu precies wanneer bits de kabel verlieten en je machine binnenkwamen, lang voordat je applicatie een enkele byte leest.
3. Decryptie-probes. Je backend beëindigt TLS met behulp van ofwel OpenSSL of BoringSSL. Hier wordt de architectuur interessant. Met behulp van uprobes — dynamische userspace-probes — koppel je je aan SSL_write en SSL_read binnen de TLS-library. Deze functies worden geactiveerd op het moment dat plaintext-data door de encryptiemotor gaat. Je eBPF-programma leest die gedecrypteerde buffer, scant op de traceparent-header en extraheert deze. De kernel beschikt nu over een directe koppeling tussen een ruwe TCP-flow en een specifiek mobiel verzoek, zonder dat je ooit certificaten of sleutels hoeft te verwerken in een aangepaste tool.
4. Kernel-queuing. Zelfs nadat de data is gedecrypteerd en klaar is, hoeft je applicatie deze niet onmiddellijk te consumeren. Je koppelt kprobes aan relevante kernel-functies die socketbuffers en scheduling-events afhandelen. Dit meet de queuing-latentie: de tijd die verzoeken doorbrengen wachtend in het kernel-gebied omdat je proces strijdt om CPU-tijd of simpelweg nog geen read() heeft aangeroepen.
Alle vier de signaalbronnen schrijven gebeurtenissen naar een eBPF ring buffer. Een sidecar-proces dat in de userspace draait, leegt deze buffer, correleert gebeurtenissen op basis van de traceparent-ID en reconstrueert een enkele, coherente tijdlijn voor elk verzoek. Wat voorheen een versnippering van losstaande kernelruis was, wordt een gestructureerde trace.
Het volledige pad lezen
De samengestelde output wordt doorgaans weergegeven als een flame graph of een gestructureerde span tree die de latentie opdeelt in vier concrete delen:
- 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
