Un utente apre un ticket: l'app mobile è lenta. Controlli le tue dashboard. L'utilizzo della CPU è piatto. I tassi di errore sono pari a zero. Le luci del tuo APM sono di un rassicurante verde. Il backend, secondo ogni misura visibile, è in salute. Ma l'utente non ha torto. La lentezza è reale, e sta accadendo da qualche parte nel lungo e oscuro corridoio tra un dispositivo Android e il tuo server.
Il problema è che la maggior parte degli strumenti di osservabilità si ferma al confine dell'applicazione. Misurano ciò che accade dopo che il tuo framework ha analizzato una richiesta. Monitorano le query al database, le cache hit e le chiamate ai servizi a valle. Ciò che sfugge loro è la meccanica della richiesta stessa: il tempo che una chiamata OkHttp trascorre all'interno dello stack HTTP di Android, il transito attraverso una rete mobile instabile, la negoziazione del TLS handshake e l'attesa silenziosa che avviene nelle code del kernel prima ancora che il tuo codice venga eseguito. Questi vuoti inghiottono millisecondi — o interi secondi — mentre il tuo APM rimane in silenzio.
La crittografia rende la cecità totale. Le moderne app Android instradano tutto attraverso BoringSSL. Nel momento in cui un pacchetto raggiunge lo stack di rete del kernel, gli header HTTP sono criptati. Un normale tcpdump o un hook di rete vedono solo record TLS opachi. Puoi osservare che il traffico scorre, ma non puoi leggerlo. Certamente non puoi collegare un segmento TCP specifico a livello di kernel a una specifica chiamata API di un utente. Il contesto di tracciamento di cui hai bisogno è intrappolato all'interno del testo cifrato.
eBPF cambia le carte in tavola perché ti permette di strumentare il sistema dall'interno verso l'esterno senza alterare il codice della tua applicazione. Invece di chiedere alla tua app di riportare la propria latenza, attacchi piccoli programmi direttamente al kernel e alle librerie critiche dello userspace. Questi programmi osservano gli eventi man mano che accadono, estraggono ciò di cui hai bisogno e lo inviano a un ring buffer. Non c'è alcun bloat dell'SDK all'interno della tua APK Android oltre a un header leggero, e non c'è alcun agente di strumentazione che riscrive le classi del tuo backend.
La configurazione in quattro parti
La costruzione di questa pipeline prevede quattro distinti livelli di osservazione.
1. L'ancora traceparent. Sul dispositivo Android, aggiungi un interceptor OkHttp che inserisce un header W3C traceparent in ogni richiesta in uscita. Questa è l'unica modifica richiesta sul lato mobile, ed è minima. L'header viaggia all'interno del payload criptato fino al tuo backend. Poiché si trova all'interno dello strato HTTP, sopravvive all'interno del testo in chiaro che il motore TLS espone infine.
2. Tempistica di arrivo TCP. Sull'host del backend, utilizzi hook di eBPF Traffic Control collegati all'interfaccia di rete. Questi programmi vengono attivati man mano che arrivano i singoli segmenti TCP. Catturano i numeri di sequenza e i timestamp all'estremità. Ora sai precisamente quando i bit hanno lasciato il cavo ed è entrati nella tua macchina, molto prima che la tua applicazione legga un singolo byte.
3. Sonde di decrittazione. Il tuo backend termina il TLS utilizzando OpenSSL o BoringSSL. È qui che l'architettura diventa interessante. Utilizzando le uprobes — sonde dinamiche dello userspace — ti colleghi a SSL_write e SSL_read all'interno della libreria TLS. Queste funzioni vengono attivate nell'istante in cui i dati in chiaro passano attraverso il motore di crittografia. Il tuo programma eBPF legge quel buffer decriptato, scansiona l'header traceparent ed lo estrae. Il kernel possiede ora una mappatura diretta tra un flusso TCP grezzo e una specifica richiesta mobile, senza che tu debba mai gestire certificati o chiavi in uno strumento personalizzato.
4. Code del kernel. Anche dopo che i dati sono stati decriptati e sono pronti, la tua applicazione potrebbe non consumarli immediatamente. Colleghi le kprobes alle funzioni rilevanti del kernel che gestiscono i buffer dei socket e gli eventi di scheduling. Questo misura la latenza di accodamento: il tempo che le richieste trascorrono in attesa nel kernel perché il tuo processo sta contendendo la CPU o semplicemente non ha ancora chiamato read().
Tutte e quattro le sorgenti di segnale scrivono eventi in un ring buffer eBPF. Un processo sidecar in esecuzione nello userspace svuota questo buffer, correla gli eventi tramite l'ID traceparent e ricostruisce una timeline singola e coerente per ogni richiesta. Ciò che prima era una dispersione di rumore del kernel disconnesso diventa una traccia strutturata.
Leggere l'intero percorso
L'output assemblato viene tipicamente visualizzato come un flame graph o un albero di span strutturato che suddivide la latenza in quattro parti concrete:
- 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
