Um usuário abre um ticket: o aplicativo móvel está lento. Você verifica seus dashboards. A utilização da CPU está estável. As taxas de erro são zero. As luzes do seu APM estão em um verde tranquilizador. O backend, por todas as medidas visíveis, está saudável. Mas o usuário não está errado. A lentidão é real e está acontecendo em algum lugar no longo e sombrio corredor entre um dispositivo Android e o seu servidor.

O problema é que a maioria das ferramentas de observabilidade para na fronteira da aplicação. Elas medem o que acontece depois que o seu framework faz o parse de uma requisição. Elas rastreiam consultas ao banco de dados, cache hits e chamadas de serviços downstream. O que elas perdem é a mecânica da própria requisição: o tempo que uma chamada OkHttp passa dentro da stack HTTP do Android, o trânsito através de uma rede móvel instável, a negociação do handshake TLS e a espera silenciosa que ocorre dentro das filas do kernel antes mesmo do seu código ser executado. Essas lacunas engolem milissegundos — ou segundos inteiros — enquanto o seu APM permanece em silêncio.

A criptografia torna essa cegueira completa. Aplicativos Android modernos roteiam tudo através do BoringSSL. No momento em que um pacote atinge a stack de rede do kernel, os cabeçalhos HTTP já estão criptografados. Um tcpdump padrão ou um network hook vê apenas registros TLS opacos. Você pode observar que o tráfego está fluindo, mas não consegue lê-lo. Certamente, você não consegue vincular um segmento TCP específico em nível de kernel a uma chamada de API de um usuário específico. O contexto de rastreamento (trace context) que você precisa está preso dentro do texto cifrado (ciphertext).

O eBPF muda a equação porque permite instrumentar o sistema de dentro para fora sem alterar o código da sua aplicação. Em vez de pedir que seu app reporte sua própria latência, você anexa pequenos programas diretamente ao kernel e a bibliotecas críticas do userspace. Esses programas observam os eventos conforme eles acontecem, extraem o que você precisa e o enviam para um ring buffer. Não há bloat de SDK dentro do seu APK Android além de um header leve, e não há um agente de instrumentação reescrevendo suas classes de backend.

A Configuração em Quatro Partes

Construir este pipeline envolve quatro camadas distintas de observação.

1. A âncora traceparent. No dispositivo Android, você adiciona um interceptor OkHttp que injeta um cabeçalho W3C traceparent em cada requisição de saída. Esta é a única alteração necessária no lado móvel, e é mínima. O cabeçalho viaja dentro do payload criptografado até o seu backend. Como ele reside dentro da camada HTTP, ele sobrevive dentro do texto simples (plaintext) que o mecanismo TLS eventualmente expõe.

2. Temporização de chegada do TCP. No host do backend, você usa hooks de eBPF Traffic Control anexados à interface de rede. Esses programas são disparados conforme os segmentos TCP individuais chegam. Eles capturam números de sequência e timestamps na borda. Agora você sabe precisamente quando os bits saíram do cabo e entraram na sua máquina, muito antes de sua aplicação ler um único byte.

3. Sondas de descriptografia. Seu backend encerra o TLS usando OpenSSL ou BoringSSL. É aqui que a arquitetura se torna interessante. Usando uprobes — sondas dinâmicas de userspace — você se anexa ao SSL_write e SSL_read dentro da biblioteca TLS. Essas funções são disparadas no instante em que os dados em texto simples passam pelo mecanismo de criptografia. Seu programa eBPF lê esse buffer descriptografado, procura pelo cabeçalho traceparent e o extrai. O kernel agora possui um mapeamento direto entre um fluxo TCP bruto e uma requisição móvel específica, sem que você precise manipular certificados ou chaves em uma ferramenta personalizada.

4. Enfileiramento no kernel. Mesmo após os dados serem descriptografados e estarem prontos, sua aplicação pode não consumi-los imediatamente. Você anexa kprobes a funções relevantes do kernel que lidam com buffers de socket e eventos de agendamento (scheduling). Isso mede a latência de enfileiramento: o tempo que as requisições passam esperando no kernel porque seu processo está disputando a CPU ou simplesmente ainda não chamou read().

Todas as quatro fontes de sinal escrevem eventos em um ring buffer do eBPF. Um processo sidecar executado no userspace esvazia esse buffer, correlaciona os eventos pelo ID do traceparent e reconstrói uma linha do tempo única e coerente para cada requisição. O que antes era uma dispersão de ruído de kernel desconectado torna-se um trace estruturado.

Lendo o Caminho Completo

O resultado montado é normalmente renderizado como um flame graph ou uma árvore de spans estruturada que divide a latência em quatro partes concretas:

  • 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