Introdução
O profiling em produção sempre foi domínio dos corajosos. Durante anos, os SREs enfrentaram um dilema desconfortável: ou anexar ferramentas de profiling pesadas que introduziam latência mensurável e arriscavam desestabilizar as cargas de trabalho em produção, ou voar às cegas e depender de dashboards de métricas que mostravam sintomas mas nunca as causas raiz. O surgimento do eBPF como uma tecnologia mainstream do kernel Linux mudou fundamentalmente esse cálculo. Com o eBPF, você pode instrumentar praticamente todas as camadas do kernel e do espaço do usuário com overhead desprezível, gerando o tipo de dados de observabilidade profunda que anteriormente só estava disponível em ambientes de staging.
O eBPF, que significa extended Berkeley Packet Filter, evoluiu de suas origens como um mecanismo de filtragem de pacotes de rede para uma máquina virtual de uso geral dentro do kernel. Os programas escritos para eBPF são verificados pelo kernel antes da execução, garantindo que não possam travar o sistema, entrar em loops infinitos ou acessar memória não autorizada. Essa garantia de segurança é o que torna o eBPF especialmente adequado para profiling em produção. Você pode anexar probes a funções do kernel, tracepoints, entradas de funções em espaço do usuário e contadores de desempenho de hardware sem recompilar o kernel ou reiniciar serviços.
Este guia cobre os workflows práticos que equipes de SRE usam diariamente para diagnosticar problemas de desempenho em sistemas Linux em produção. Vamos percorrer profiling de CPU, análise de latência, detecção de vazamentos de memória, rastreamento de rede e profiling de I/O usando a cadeia de ferramentas eBPF padrão. Todos os comandos e scripts mostrados aqui foram usados em ambientes de produção executando kernel 5.15 e mais recentes.
Por Que o eBPF Muda o Profiling em Produção
As ferramentas tradicionais de profiling impõem custos que as tornam impraticáveis em produção. Executar strace em um serviço ocupado pode desacelerá-lo por um fator de 100x ou mais, porque o strace usa ptrace para interceptar cada syscall, fazendo troca de contexto entre o processo rastreado e o processo de rastreamento para cada invocação. Mesmo o perf, que usa contadores de desempenho de hardware e é muito mais leve que o strace, ainda requer escrever dados de amostragem em disco e pode gerar pressão substancial de I/O sob altas taxas de amostragem.
O eBPF muda a equação de três maneiras importantes. Primeiro, os programas eBPF executam dentro do kernel, eliminando o overhead de troca de contexto das ferramentas baseadas em ptrace. Quando você anexa um kprobe ou tracepoint, o programa eBPF executa inline com o caminho de código do kernel, tipicamente adicionando apenas dezenas de nanossegundos por invocação. Segundo, os programas eBPF podem agregar dados dentro do kernel usando maps, o que significa que você pode computar histogramas, contagens e resumos sem copiar cada evento para o espaço do usuário. Um histograma de latência que geraria milhões de eventos por segundo com strace produz uma única leitura de map por intervalo com eBPF. Terceiro, o verificador eBPF garante a segurança: seu programa não pode desreferenciar ponteiros nulos, acessar memória fora dos limites ou entrar em loop indefinidamente.
O impacto prático é dramático. Ferramentas como biolatency podem rastrear cada requisição de I/O de bloco em um sistema lidando com centenas de milhares de IOPS, produzindo um histograma de latência com menos de 1% de overhead de CPU. Você pode executar funclatency contra uma função quente no seu servidor de aplicação enquanto serve tráfego de pico. Isso simplesmente não era possível com gerações anteriores de ferramentas de rastreamento.
Configurando a Cadeia de Ferramentas eBPF
O ecossistema eBPF se consolidou em torno de três conjuntos de ferramentas principais: bcc-tools, bpftrace e programas CO-RE baseados em libbpf. Cada um serve um caso de uso diferente, e uma estação de trabalho SRE bem equipada deve ter todos os três disponíveis.
Em sistemas Ubuntu e Debian, instale a cadeia de ferramentas completa:
sudo apt-get update
sudo apt-get install -y bpfcc-tools bpftrace linux-headers-$(uname -r)
sudo apt-get install -y libbpf-dev bpftool
Em sistemas RHEL e Fedora:
sudo dnf install -y bcc-tools bpftrace kernel-devel-$(uname -r)
sudo dnf install -y libbpf-devel bpftool
Verifique se seu kernel suporta BTF, que é necessário para recursos modernos do bpftrace e programas CO-RE:
ls /sys/kernel/btf/vmlinux
bpftool btf dump file /sys/kernel/btf/vmlinux format raw | head -c 100
Se o BTF não estiver disponível, você precisará garantir que seu kernel foi compilado com CONFIG_DEBUG_INFO_BTF=y. A maioria dos kernels de distribuição a partir de 2022 inclui suporte a BTF.
O pacote bcc-tools fornece dezenas de ferramentas prontas que cobrem os cenários de profiling mais comuns. Elas são tipicamente instaladas como executáveis com o sufixo -bpfcc em sistemas baseados em Debian ou diretamente em /usr/share/bcc/tools/ no RHEL. O bpftrace fornece uma linguagem de script de alto nível para escrever one-liners e scripts curtos personalizados. O libbpf e os programas CO-RE (Compile Once, Run Everywhere) são usados para construir ferramentas eBPF portáteis e de grau de produção que são distribuídas como binários autônomos.
Workflows de Profiling de CPU
O profiling de CPU é o ponto de partida mais comum ao investigar problemas de desempenho. O objetivo é identificar quais funções consomem mais tempo de CPU, seja no espaço do kernel ou do usuário, e gerar flame graphs que tornem os hotspots visualmente óbvios.
A abordagem mais simples usa a ferramenta profile do bcc para amostrar stack traces em uma frequência fixa:
sudo profile-bpfcc -F 99 -a --stack-storage-size 16384 30 > /tmp/cpu-stacks.txt
Isso amostra todas as CPUs a 99 Hz por 30 segundos. A frequência de 99 Hz evita aliasing com atividades baseadas em timer que frequentemente executam a 100 Hz. A saída contém stack traces dobrados que podem ser alimentados diretamente nas ferramentas FlameGraph de Brendan Gregg:
git clone https://github.com/brendangregg/FlameGraph.git
cat /tmp/cpu-stacks.txt | FlameGraph/stackcollapse-bpf.pl | FlameGraph/flamegraph.pl > cpu-flame.svg
Para profiling mais direcionado, o bpftrace permite que você faça profile de um processo específico e filtre por CPU:
sudo bpftrace -e 'profile:hz:99 /pid == 12345/ { @[ustack(perf), comm] = count(); }' > stacks.bt
Quando você precisa entender o comportamento de agendamento da CPU em vez do tempo de execução, a ferramenta cpudist mostra por quanto tempo as threads executam on-CPU antes de serem desagendadas:
sudo cpudist-bpfcc -p 12345 10 1
Isso imprime um histograma de durações on-CPU para o processo 12345 em um intervalo de 10 segundos. Tempos curtos on-CPU combinados com altas trocas de contexto sugerem contenção de lock. Tempos longos on-CPU com baixo throughput sugerem gargalos computacionais.
Para investigar problemas de migração de CPU em sistemas NUMA, você pode rastrear eventos do agendador:
sudo bpftrace -e 'tracepoint:sched:sched_migrate_task {
printf("pid=%d comm=%s from_cpu=%d to_cpu=%d\n",
args->pid, args->comm, args->orig_cpu, args->dest_cpu);
}'
Análise de Latência
A análise de latência é onde o eBPF realmente brilha porque pode medir o tempo entre eventos arbitrários sem perturbar o caminho de código medido. A coleção bcc-tools inclui várias ferramentas de latência especializadas.
A latência de I/O de bloco é medida com biolatency, que rastreia o tempo desde a requisição de I/O de bloco até a conclusão:
sudo biolatency-bpfcc -D 10 1
A flag -D divide a latência por dispositivo de disco, tornando fácil identificar quais discos estão lentos. A saída é um histograma de potência de dois mostrando a distribuição das latências em microssegundos.
A latência da fila de execução, que mede quanto tempo as threads esperam na fila do agendador antes de obter tempo de CPU, é medida com runqlat:
sudo runqlat-bpfcc -p 12345 10 1
Alta latência na fila de execução significa que seus processos estão esperando por CPU, o que indica saturação de CPU. Se você vê latências acima de 10 milissegundos durante operação normal, você precisa de mais capacidade de CPU ou precisa investigar o que está consumindo CPU.
A latência de função mede o tempo de execução de uma função específica do kernel ou do espaço do usuário:
sudo funclatency-bpfcc -p 12345 'c:malloc' 10 1
Isso rastreia chamadas malloc na libc do processo 12345 e mostra um histograma de latência. Você pode rastrear qualquer função que tenha um símbolo no binário ou biblioteca compartilhada. Para funções do kernel:
sudo funclatency-bpfcc 'vfs_read' 10 1
Para rastreamento de latência em nível de aplicação com bpftrace, você pode medir o tempo entre dois pontos de probe:
sudo bpftrace -e '
uprobe:/usr/bin/myapp:process_request { @start[tid] = nsecs; }
uretprobe:/usr/bin/myapp:process_request /@start[tid]/ {
@latency_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}
END { clear(@start); }
'
Análise de Memória
Problemas de memória em produção variam de vazamentos graduais que causam OOM kills ao longo de dias a ineficiências de cache que degradam o desempenho. O eBPF fornece várias ferramentas para cada categoria.
A ferramenta memleak rastreia chamadas de alocação e liberação de memória, acompanhando alocações pendentes para identificar vazamentos:
sudo memleak-bpfcc -p 12345 --combined-only 30
Isso se anexa ao processo 12345 e após 30 segundos imprime stack traces de alocações que não foram liberadas, ordenadas por total de bytes pendentes. Isso é inestimável para capturar vazamentos em serviços de longa execução sem reiniciá-los com depuração de alocador especializada habilitada.
Para rastreamento de alocação de memória do kernel:
sudo memleak-bpfcc --combined-only 30
Sem a flag de pid, o memleak rastreia alocações do kernel via kmalloc e kfree, que podem identificar vazamentos de memória do kernel causados por drivers ou módulos do kernel.
O comportamento do cache tem um impacto enorme no desempenho da aplicação. A ferramenta cachestat fornece estatísticas por segundo sobre o cache de páginas:
sudo cachestat-bpfcc 5
A saída inclui acertos, erros, páginas sujas e proporções de leitura/escrita. Uma alta taxa de erros indica que seu conjunto de trabalho excede a memória disponível, e você deve considerar se sua aplicação precisa de mais RAM ou melhores padrões de acesso.
Os OOM kills podem ser rastreados em tempo real para entender quais processos estão sendo encerrados e por quê:
sudo bpftrace -e 'kprobe:oom_kill_process {
printf("OOM kill: pid=%d comm=%s pages=%d\n",
((struct task_struct *)arg1)->pid,
((struct task_struct *)arg1)->comm,
arg0);
}'
Para uma abordagem mais simples, a ferramenta oomkill do bcc captura isso automaticamente:
sudo oomkill-bpfcc
Isso imprime uma linha cada vez que o OOM killer é invocado, incluindo os detalhes do processo que disparou e do processo encerrado.
Rastreamento de Rede
Problemas de desempenho de rede são notoriamente difíceis de diagnosticar porque envolvem interações entre a aplicação, a pilha TCP do kernel e a infraestrutura de rede. O eBPF fornece ferramentas cirúrgicas para cada camada.
A ferramenta tcplife rastreia o tempo de vida de sessões TCP, mostrando quando conexões são estabelecidas e fechadas junto com os bytes transferidos:
sudo tcplife-bpfcc -D
A flag -D inclui timestamps. Cada linha mostra o PID, nome do processo, endereços local e remoto, portas, duração e bytes enviados/recebidos. Isso é essencial para identificar rotatividade de conexões, conexões de curta duração inesperadas ou serviços que mantêm conexões abertas por mais tempo do que o esperado.
As retransmissões TCP são um indicador crítico da saúde da rede:
sudo tcpretrans-bpfcc -l
A flag -l inclui tail loss probes. Cada evento de retransmissão é impresso com os endereços de origem e destino, estado e a função do kernel que disparou a retransmissão. Clusters de retransmissões para um destino específico indicam problemas no caminho da rede.
Pacotes descartados na camada TCP podem ser rastreados com tcpdrop:
sudo tcpdrop-bpfcc
Cada pacote descartado é impresso com seu stack trace do kernel, que diz exatamente por que o kernel descartou o pacote. Causas comuns incluem estouro de buffer de socket, resets de conexão e pressão de memória.
Para análise detalhada do estado de conexão TCP, você pode escrever um script bpftrace que rastreia transições de estado:
sudo bpftrace -e '
tracepoint:tcp:tcp_set_state {
printf("pid=%d sport=%d dport=%d oldstate=%d newstate=%d\n",
pid, args->sport, args->dport, args->oldstate, args->newstate);
}
'
A latência de resolução DNS é frequentemente uma fonte negligenciada de latência de aplicação. Você pode rastrear consultas DNS no nível do resolver:
sudo bpftrace -e '
uprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo { @start[tid] = nsecs; }
uretprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo /@start[tid]/ {
@dns_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}
'
Profiling de I/O
O I/O de armazenamento é uma das fontes mais comuns de latência em produção, e o eBPF fornece ferramentas que rastreiam I/O em múltiplos níveis da pilha de armazenamento.
A ferramenta biosnoop rastreia operações individuais de I/O de bloco com timestamps, latências e atribuição de processo:
sudo biosnoop-bpfcc -d sda 10
Isso rastreia todo o I/O de bloco para o dispositivo sda por 10 segundos, mostrando o PID, nome do processo, disco, tipo de operação, setor, bytes e latência de cada operação. Esta é a ferramenta preferida quando você precisa entender exatamente o que está gerando I/O em um disco específico.
O rastreamento em nível de sistema de arquivos fornece contexto de nível mais alto. A ferramenta ext4slower rastreia operações ext4 que excedem um limite de latência:
sudo ext4slower-bpfcc 10
Isso imprime todas as operações ext4 mais lentas que 10 milissegundos, incluindo leituras, escritas, aberturas e syncs. Isso identifica imediatamente operações lentas do sistema de arquivos sem exigir que você correlacione rastreamentos em nível de bloco com metadados do sistema de arquivos.
Para rastreamento geral de sistema de arquivos em todos os tipos de sistema de arquivos, o fileslower serve ao mesmo propósito:
sudo fileslower-bpfcc 10
Padrões de escrita são críticos para entender como as aplicações interagem com o armazenamento. O equivalente filetop do bpftrace mostra quais arquivos estão sendo lidos e escritos com mais frequência:
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_write {
@writes[comm, pid] = count();
}
interval:s:5 { print(@writes); clear(@writes); }
'
Para cargas de trabalho pesadas em fsync como bancos de dados, rastrear operações de sync pode revelar padrões de flush inesperados:
sudo bpftrace -e '
kprobe:vfs_fsync_range {
@fsync_latency[comm] = count();
}
kretprobe:vfs_fsync_range {
printf("fsync completed: comm=%s\n", comm);
}
'
Construindo One-Liners Personalizados com bpftrace
O verdadeiro poder do eBPF vem da capacidade de escrever programas de rastreamento personalizados adaptados à sua pilha específica. A linguagem de script do bpftrace torna isso acessível a qualquer pessoa que saiba escrever um shell script.
A estrutura básica de um one-liner do bpftrace é uma especificação de probe seguida por um bloco de ação. Probes podem se anexar a kprobes (funções do kernel), uprobes (funções em espaço do usuário), tracepoints (eventos estáveis do kernel) e eventos de software.
Contar syscalls por processo:
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
Histograma de tamanhos de leitura por processo:
sudo bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret > 0/ {
@read_bytes[comm] = hist(args->ret);
}'
Rastrear criação de novos processos com linhas de comando completas:
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
printf("exec: pid=%d ppid=%d %s\n", pid, curtask->real_parent->pid, str(args->filename));
}'
Rastrear aberturas de arquivo com caminhos completos:
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
printf("open: pid=%d comm=%s file=%s\n", pid, comm, str(args->filename));
}'
Medir o tempo que sua aplicação gasta em uma função específica, agregado por call stack:
sudo bpftrace -e '
uprobe:/opt/myapp/bin/server:DatabaseQuery { @start[tid] = nsecs; }
uretprobe:/opt/myapp/bin/server:DatabaseQuery /@start[tid]/ {
@query_ns[ustack(5)] = hist(nsecs - @start[tid]);
delete(@start[tid]);
}
'
Para eventos de alta frequência, use maps para agregar dentro do kernel e evitar sobrecarregar o buffer de saída:
sudo bpftrace -e '
tracepoint:syscalls:sys_exit_read /args->ret > 0/ {
@total_bytes[comm] = sum(args->ret);
@total_calls[comm] = count();
}
interval:s:10 {
printf("\n--- Top readers (10s window) ---\n");
print(@total_bytes, 10);
print(@total_calls, 10);
clear(@total_bytes);
clear(@total_calls);
}
'
Integração de Profiling Contínuo
Enquanto o profiling ad-hoc com bpftrace e bcc-tools é essencial para resposta a incidentes, o profiling contínuo fornece visibilidade de desempenho sempre ativa. Dois projetos open-source lideram este espaço: Parca e Pyroscope.
O Parca usa eBPF para amostrar continuamente stack traces em todos os processos de um host com overhead mínimo. O agente Parca executa como um DaemonSet no Kubernetes ou um serviço systemd em bare metal:
sudo parca-agent --remote-store-address=parca-server:7070 \
--node=production-host-01 \
--sampling-ratio=1.0 \
--http-address=:7071
O agente descobre automaticamente todos os processos em execução, resolve símbolos a partir de informações de depuração e dados BTF, e transmite dados de profiling para o servidor Parca. O servidor armazena perfis em um formato colunar otimizado para consultas de séries temporais e fornece uma UI para explorar flame graphs, comparar perfis entre janelas de tempo e identificar regressões.
O Pyroscope adota uma abordagem similar mas suporta modos adicionais de profiling além de CPU, incluindo profiling de alocação de memória, profiling de contenção de lock e profiling de goroutine para aplicações Go:
# pyroscope-agent.yaml
server-address: http://pyroscope-server:4040
log-level: info
targets:
- service-name: api-server
spy-name: ebpfspy
application-name: api-server
- service-name: worker
spy-name: ebpfspy
application-name: worker-pool
Ambas as ferramentas se integram com Grafana para criação de dashboards e alertas. Uma configuração típica de profiling contínuo inclui:
# Grafana data source configuration for Parca
curl -X POST http://grafana:3000/api/datasources \
-H "Content-Type: application/json" \
-d '{
"name": "Parca",
"type": "parca",
"url": "http://parca-server:7070",
"access": "proxy"
}'
O verdadeiro valor do profiling contínuo emerge ao longo do tempo. Quando uma regressão de desempenho é implantada, você pode comparar o perfil atual com uma linha de base de antes da implantação e ver imediatamente quais funções estão consumindo mais CPU. Isso transforma a depuração de desempenho de uma investigação reativa em um sistema de detecção proativo.
Melhores Práticas e Segurança em Produção
As garantias de segurança do eBPF não significam que você pode executar qualquer programa eBPF em qualquer sistema de produção sem pensar. Embora o verificador previna travamentos do kernel, programas eBPF mal escritos ainda podem consumir CPU excessiva, gerar saída esmagadora ou interferir no desempenho do sistema.
Sempre defina limites de tempo no bpftrace e bcc-tools. Cada sessão de profiling deve ter uma duração explícita:
# Good: explicit 30-second duration
sudo biolatency-bpfcc 30 1
# Dangerous: runs until manually stopped
sudo biolatency-bpfcc
Tenha cautela com probes de alta frequência. Anexar um kprobe a uma função que dispara milhões de vezes por segundo adicionará overhead mensurável mesmo se o programa eBPF em si for trivial. Antes de anexar um probe em produção, estime a frequência:
sudo bpftrace -e 'kprobe:vfs_read { @count = count(); } interval:s:1 { print(@count); clear(@count); }'
Se a função dispara mais de algumas centenas de milhares de vezes por segundo, considere se você pode usar um tracepoint em vez de um kprobe, adicionar um filtro para reduzir o número de eventos processados ou amostrar em vez de rastrear cada evento.
Use a flag --dry-run do bpftrace para validar scripts antes da execução:
sudo bpftrace --dry-run -e 'kprobe:vfs_read { @[comm] = count(); }'
Limite os tamanhos de map para prevenir crescimento ilimitado de memória:
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' --map-max-entries=10000
Para sistemas de produção, estabeleça um runbook de ferramentas e scripts eBPF pré-aprovados. A coleção bcc-tools é bem testada e segura para uso em produção. Scripts bpftrace personalizados devem ser revisados pela equipe e testados em staging antes do uso em produção.
Considere executar ferramentas eBPF dentro de contêineres com as capacidades apropriadas em vez de com acesso root completo:
docker run --rm -it --privileged \
-v /sys/kernel/debug:/sys/kernel/debug:ro \
-v /sys/kernel/btf:/sys/kernel/btf:ro \
-v /proc:/proc:ro \
quay.io/iovisor/bpftrace:latest \
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
Finalmente, documente seus workflows de profiling. Quando um incidente ocorre às 3 da manhã, você não quer estar escrevendo scripts bpftrace do zero. Mantenha um repositório de scripts de profiling testados organizados por sintoma: alta CPU, alta latência, crescimento de memória, erros de rede e saturação de I/O. Cada script deve incluir comentários explicando o que mede, overhead esperado e como interpretar a saída. O eBPF lhe dá superpoderes em produção, mas apenas se você tiver praticado usá-los antes da emergência chegar.