Em outubro de 2011, Brendan Gregg — então engenheiro de performance no Joyent, antes de Netflix e Intel — publicou um post no blog dele com um diagrama estranho: retângulos coloridos empilhados em camadas, formando uma silhueta irregular que lembrava chamas. O nome stick: flame graph. A ideia era simples e transformadora: visualizar dados de profiling de sampling de forma que a hierarquia de chamadas ficasse imediatamente legível, sem precisar ler tabelas de mil linhas. Em uma década, flame graph virou padrão visual da indústria — Netflix, Google, Facebook, Cloudflare, todos os grandes players usam.
Profiling é a prática que faz o "performance como disciplina" do conceito 01 ser possível. Sem profiler, otimização vira chute; com profiler, vira diagnóstico. As ferramentas modernas — pprof do Go, dotnet-trace do .NET, py-spy do Python, perf e eBPF do Linux — capturam o que acontece em produção com overhead tipicamente abaixo de 2%, e expõem o resultado em flame graphs ou equivalentes interativos. A barreira de entrada baixou tanto que sêniores que ainda otimizam "por instinto" estão usando ferramenta errada para o problema.
Este conceito articula a distinção entre profilers sampling e instrumentation, mostra a anatomia do flame graph (e como lê-lo), apresenta as ferramentas idiomáticas em três ecossistemas, e fecha com continuous profiling — a prática moderna de ter profiling ligado em produção sempre, capturando dados reais, não sintéticos. O conceito 06 (benchmarking) complementa com a outra metade da disciplina: medição controlada, não observacional.
A premissa: profiling é habilidade prática que se aprende fazendo. A teoria sem captura real é academia. Os exemplos do conceito todos rodam localmente em uma tarde — vale fazer cada um na linguagem onde você passa mais tempo, e capturar os flame graphs com seus próprios olhos.
Sampling vs instrumentation — duas filosofias
Profilers se dividem em duas filosofias com trade-offs distintos. Saber qual ferramenta é qual define se você consegue usar em produção ou não.
Profilers de instrumentation
Inserem código em cada função (no compile, no runtime,
ou via bytecode rewrite) que registra entrada, saída, e
tempo. Resultado: trace completo do que aconteceu,
contagem exata de chamadas, tempo exato em cada função.
Custos: overhead alto (10–500% dependendo do
granularidade), distorção do comportamento (funções
pequenas viram desproporcionalmente caras pelo overhead),
tamanho de arquivo gerado grande. Usar em produção
tipicamente é inviável. Examples: cProfile
(Python, builtin), dotnet-trace com
--clrevents Method, JetBrains dotTrace
"tracing mode", line_profiler (Python).
Profilers de sampling
Capturam stack trace periódica (tipicamente 99 ou 100
vezes por segundo) e contam quantas vezes cada função
apareceu no stack. Não medem tempo absoluto — medem
frequência relativa de aparição, que é
proporcional ao tempo de CPU gasto. Custos: overhead
tipicamente < 2% (na frequência padrão); imprecisão
em funções rápidas que podem aparecer ou não no sample;
sem captura de chamadas individuais. Vantagens: baixo
overhead permite uso em produção, robusto a sistemas
complexos. Examples: perf (Linux),
pprof (Go), dotnet-trace com
sample profiler, py-spy, async-profiler
(Java).
A regra prática: sampling para produção, instrumentation para microbenchmarks ou bugs específicos. Sampling te diz onde o tempo é gasto em sistema real; instrumentation responde "exatamente quantas vezes essa função foi chamada", que raramente é a pergunta certa.
Anatomia do flame graph
Flame graph é a forma visual canônica de apresentar dados de sampling. Vale entender exatamente como ler um, porque a leitura é contraintuitiva no início.
Eixo vertical (Y): profundidade do
stack. A função no topo do retângulo é a função
chamadora; a função abaixo é a chamada.
Em alguns flame graphs (icicle graph), a orientação é
invertida — chamadora embaixo. A barra base do gráfico
é tipicamente main ou entry point.
Eixo horizontal (X): alfabético ou por agrupamento, não temporal. Esse é o erro mais comum de leitura: pensar que o eixo X é tempo. Não é. Cada retângulo agrega todas as amostras onde aquela função apareceu naquela posição do stack, independente de quando ocorreram.
Largura do retângulo: proporcional ao tempo de CPU gasto naquela função (e descendentes). Uma função que ocupa metade da largura do gráfico ocupou metade do tempo de CPU.
Cor: tipicamente sem significado (gerada para diferenciar visualmente). Algumas variantes usam cor para indicar tipo (vermelho = user code, azul = libraries, etc.). Sempre vale ler a legenda.
A leitura é portanto: "olhar o que é mais largo no meio/topo". Funções largas no topo do gráfico são folhas caras — código onde o tempo é gasto diretamente. Funções largas no meio são chamadores que delegam para várias coisas caras. A pergunta diagnóstica é "qual função folha está consumindo mais tempo?" — essa é onde otimizar.
Profiling em três linguagens
Cada linguagem tem ferramentas idiomáticas. Vale conhecer as três principais.
# instalação (uma vez, global)
dotnet tool install -g dotnet-trace
dotnet tool install -g dotnet-counters
dotnet tool install -g dotnet-dump
# captura sample profile (10 segundos)
dotnet-trace collect \
--process-id $PID \
--duration 00:00:10 \
--providers Microsoft-DotNETCore-SampleProfiler \
--output cpu.nettrace
# converte para speedscope (flame graph interativo)
dotnet-trace convert cpu.nettrace --format Speedscope
# abrir https://speedscope.app e arrastar o arquivo .speedscope.json
# counters em tempo real (tipo top mas com métricas .NET)
dotnet-counters monitor \
--process-id $PID \
System.Runtime \
Microsoft.AspNetCore.Hosting
# captura heap (memory leak)
dotnet-dump collect --process-id $PID
dotnet-dump analyze core_*
# análise gráfica completa: PerfView (Windows-first, mas roda em Linux)
PerfView /OnlyProviders=*Microsoft-DotNETCore-SampleProfiler collect
dotnet-trace + speedscope é o caminho
padrão em 2026 para flame graph rápido. Para
análise mais profunda (alocação por tipo, GC
stats), PerfView ainda é a ferramenta canônica.
Para produção contínua, Pyroscope agent .NET.
# py-spy: profiler sampling sem mexer no código
pip install py-spy
# top ao vivo
py-spy top --pid $PID
# captura flame graph (30 segundos)
py-spy record -o profile.svg --pid $PID --duration 30
# abre profile.svg no browser — flame graph nativo
# inclui modo "native" para C extensions
py-spy record -o profile.svg --pid $PID --duration 30 --native
# scalene: profiling com info de alocação por linha
pip install scalene
scalene minha_app.py
# gera HTML com tempo CPU + tempo GPU + memória por linha
# cProfile (instrumentation, builtin)
python -m cProfile -o profile.prof minha_app.py
python -c "import pstats; p = pstats.Stats('profile.prof'); \
p.sort_stats('cumulative').print_stats(20)"
# converte para flame graph
pip install snakeviz
snakeviz profile.prof # abre browser interativo
py-spy (Ben Frederickson, 2018) é
revolucionário: profiler sampling em Rust que pega
stacks de processo Python rodando, sem patch no
código, sem precisar rodar como root em
modo apropriado. Para produção, Pyroscope tem
py-spy embutido.
// no código: importar net/http/pprof expõe endpoints
package main
import (
_ "net/http/pprof"
"net/http"
"runtime"
)
func main() {
runtime.SetMutexProfileFraction(1) // habilita mutex profile
runtime.SetBlockProfileRate(1) // habilita block profile
go http.ListenAndServe(":6060", nil) // /debug/pprof/* exposto
// ... resto da aplicação
}
// captura CPU profile (30s) e abre browser interativo
go tool pprof -http=:8080 \
http://localhost:6060/debug/pprof/profile?seconds=30
// captura heap (snapshot atual)
go tool pprof -http=:8080 \
http://localhost:6060/debug/pprof/heap
// outros profiles disponíveis:
// /debug/pprof/goroutine - todas as goroutines + stacks
// /debug/pprof/block - operações que bloquearam
// /debug/pprof/mutex - contenção em mutex
// trace completo de execução (não é sample, é instrumentação leve)
curl http://localhost:6060/debug/pprof/trace?seconds=5 -o trace.out
go tool trace trace.out # interface web mostrando goroutines/GC/eventos
// continuous profiling em produção: Pyroscope, Parca, Datadog
Go tem a melhor experiência out-of-the-box. Profile
sempre disponível em endpoint HTTP, ferramenta
oficial gera flame graph navegável.
go tool trace é único — mostra
goroutines individuais ao longo do tempo, eventos de
GC, network, syscalls.
Os tipos de profile que importam
Profile não é uma coisa só. Há vários tipos, cada um respondendo pergunta diferente.
CPU profile — onde o tempo de CPU é gasto. Responde "qual função consome ciclo?". É o profile padrão. Sampling, baixo overhead.
Heap / allocation profile — onde
memória é alocada. Responde "quem está enchendo o
heap?". Útil para GC pressure, memory leak. Em Go
/debug/pprof/heap; em .NET via
dotnet-trace com providers de GC; em Python
via scalene ou tracemalloc.
Block profile — onde goroutines /
threads bloqueiam (channel, mutex, network). Responde
"estou esperando o quê?". Em Go,
/debug/pprof/block; em .NET, exemplificado
por análise de wait events em PerfView.
Mutex profile — onde há contenção em
mutex. Em Go, /debug/pprof/mutex. Em outras
linguagens, frequentemente parte do block profile.
Goroutine / thread dump — snapshot do
estado de todas as threads. Útil para diagnóstico de
"sistema travou": qual thread está fazendo o quê
agora? Em Go, /debug/pprof/goroutine?debug=2;
em .NET, dotnet-dump; em Java,
jstack.
Off-CPU profile — Brendan Gregg formalizou: profile que mostra onde o programa está esperando (não rodando CPU). Captura via eBPF, expõe esperas em I/O, sleep, locks. Em Linux moderno, é uma das ferramentas mais poderosas para diagnóstico de "está lento mas CPU está ociosa".
Continuous profiling — profiling em produção sempre
A grande mudança da década 2014–2024 em profiling foi a adoção de continuous profiling: profile capturado periodicamente (a cada 10s, por exemplo) em todas as instâncias de produção, agregado em backend especializado, navegável por timeline. A proposta foi articulada por Google em 2010 internamente ("Google-Wide Profiling", paper IEEE 2010), aberta com Pyroscope em 2020 (depois adquirida pela Grafana Labs em 2023), e por Parca (Polar Signals, 2022).
A diferença qualitativa: você não precisa esperar um problema para capturar. Quando algo dá errado às 2 da manhã de quinta-feira, você navega o profile daquele momento exato. Compara com profile de quinta passada às 2 da manhã. Vê a regressão diretamente. Sem continuous profiling, você está captando profile depois do incidente e tentando reproduzir o problema; com continuous profiling, você tem a captura do momento da falha já na mão.
# exemplo: instalando Pyroscope agent em Go
import "github.com/grafana/pyroscope-go"
func main() {
pyroscope.Start(pyroscope.Config{
ApplicationName: "catalog-api",
ServerAddress: "http://pyroscope:4040",
ProfileTypes: []pyroscope.ProfileType{
pyroscope.ProfileCPU,
pyroscope.ProfileAllocObjects,
pyroscope.ProfileAllocSpace,
pyroscope.ProfileInuseObjects,
pyroscope.ProfileInuseSpace,
},
})
// ... resto da aplicação
}
Em .NET: Pyroscope.NET nuget, similar
configuração. Em Python: pyroscope-io. Em
Java: async-profiler + Pyroscope agent. O
overhead em todos os casos é abaixo de 2%, e o ganho
em diagnóstico é qualitativo.
Datadog Continuous Profiler é a alternativa proprietária com integração ampla e visualização rica; Parca é a alternativa open-source agnóstica de stack; Pyroscope é o caminho default em ecossistemas Grafana. Para sistemas críticos em produção, ter um deles configurado é higiene de senior.
Lendo flame graph — o workflow real
Captura o profile é o passo fácil. Ler o flame graph e tirar conclusão útil é onde o senior agrega valor. O workflow típico:
1. Olhe o todo primeiro. Antes de mergulhar em detalhe, identifique as três ou quatro funções mais largas no gráfico. Tipicamente, 80% do tempo está em ~5 funções. Essas são candidatas iniciais.
2. Navegue até as folhas. Para cada função larga, clique para fazer zoom (em ferramenta interativa). Veja o que ela está chamando. Continue até atingir folhas — funções que não chamam nada relevante. As folhas são o que de fato consome tempo.
3. Identifique o tipo de hot path. A folha cara é: (a) código seu? (b) library/framework? (c) syscall? (d) GC? Cada categoria tem padrão distinto de otimização. Código seu — ataca diretamente. Library — considera substituir, ou parametrizar (ex.: ORM gerando query lenta). Syscall — geralmente I/O excessivo, considera batching. GC — pressão de alocação, conceito 08.
4. Quantifique antes de mexer. Função que ocupa 50% do CPU profile vale a pena otimizar; função que ocupa 2% raramente vale, mesmo se fácil. 80/20: foca nos 20% que respondem por 80% do tempo.
5. Mexa, capture de novo, compare. Após mudança, novo profile. A região antes dominante diminuiu? Apareceu algo novo? Iteração.
Exemplos de padrões reconhecíveis em flame graph
Com tempo, sêniores reconhecem assinaturas visuais comuns que apontam direto para a causa. Os três padrões mais frequentes:
Pico em função pequena chamada milhões de vezes. Função aparece como retângulo médio-grande, mas as descendentes dela são pequenas e diversificadas. Sintoma: chamada em loop apertado. Solução típica: batching, ou eliminação da chamada em loop.
Pico em GC ou allocation. Funções
como System.GC, runtime.gcBgMarkWorker,
PyMem_Alloc aparecem desproporcionalmente
grandes. Sintoma: pressão de GC. Solução: reduzir
alocações (conceito 08).
Pico em syscall ou network. Funções
como read, write,
recv, ou no .NET
Socket.ReceiveAsync. Sintoma: I/O
dominante, comum em sistemas que poderiam
paralelizar/batch I/O.
Profiling em CI — perfomance regression detection
Profiling tradicionalmente foi atividade ad-hoc — você captura quando precisa. A prática emergente em times maduros é integrar profiling em CI: cada PR que toca em hot path conhecido captura profile sob carga sintética, compara com baseline, falha o build se regrediu.
Ferramentas modernas tornam isso possível. Codspeed integra com benchmarks existentes (cargo bench, pytest-benchmark, BenchmarkDotNet) e roda em CI com instrumentation determinística (sem ruído de máquina). Bencher e Touca oferecem alternativas. A ideia: mensurar performance é parte de "passar nos testes".
Profile capturado em ambiente de teste com tráfego sintético, conclusões aplicadas em produção. Cenário recorrente: equipe captura profile sob load test que usa endpoint padrão com payload pequeno. Identifica função "hot" e otimiza. Em produção, payload é diversificado, alguns muito maiores, e a função otimizada não era o hot path real — outras eram. O esforço foi gasto otimizando 5% e ignorando os 50%. Defesa: continuous profiling em produção. Profile sob carga real revela o que sintético não — e otimização baseada em produção converge mais rápido.
Diferencial profiling — comparando dois states
Uma das técnicas mais úteis e menos conhecidas é differential flame graph: dois profiles comparados visualmente, mostrando o delta de tempo. Útil para diagnosticar regressões: "essa versão ficou mais lenta — onde?".
Em pprof: go tool pprof -base before.pprof
after.pprof. Ferramentas como Pyroscope têm
"compare view" entre dois períodos de tempo. Em
Speedscope (para .NET), modo de comparação está
disponível. A leitura é: vermelho = ficou mais lento,
verde = ficou mais rápido. Uma única imagem responde
"onde a regressão aconteceu".
Antes de propor otimização, capture profile da
situação real e mostre na revisão. "Esse
endpoint tem hot path em X.SerializeJSON
que ocupa 40% do CPU profile sob carga típica" é
argumento defensável. "Acho que esse endpoint está
lento por causa da serialização" não é. Diferença
entre as duas afirmações é se você pegou o profile.
E se a otimização proposta vale o ônus, capture
profile de novo após a mudança e mostre o delta —
em mesma escala, mesmo cenário. A disciplina de
"profile + delta" é o que separa arguments
defensáveis de instinto.
Por que importa para a sua carreira
Profiling é habilidade prática que diferencia juniores de sêniores em qualquer time que toca produção. Em entrevistas de design para sistemas com escala, "como você descobriria onde o tempo está sendo gasto?" é pergunta direta — a resposta forte cita ferramentas por linguagem (pprof, dotnet-trace, py-spy, perf+eBPF), distingue sampling de instrumentation, menciona continuous profiling como prática moderna. Em revisão de código, exigir "qual profile suporta essa otimização?" é serviço ao time. Em pos-mortem, apresentar profiles capturados durante o incidente é argumento canônico — guia para a causa raiz, não especulação. E em onboarding de sistema novo, capturar profile é uma das formas mais rápidas de entender o que ele faz na prática.
Como praticar
- Flame graph do seu sistema. Em um projeto seu (mesmo simples), instale a ferramenta de profiling apropriada (dotnet-trace, py-spy, ou pprof). Aplique carga sintética simples (k6, Apache Bench, ou requests em loop). Capture profile de 30s. Visualize o flame graph. Identifique as três funções que mais consomem tempo. Faça hipótese de otimização para a primeira. Esse exercício, repetido em três projetos, calibra leitura de flame graph permanentemente.
- Continuous profiling local. Suba Pyroscope em docker localmente. Configure agent na sua aplicação. Rode workload variado por 20 minutos. Navegue na UI: timeline, comparação entre períodos, filtros por endpoint. Esse é o padrão moderno de operação de produção; sentir uma vez fixa o vocabulário.
-
Differential profile. Pegue dois
commits do seu projeto — antes e depois de uma
otimização (ou regressão). Capture profile do mesmo
endpoint sob mesma carga em cada um. Use ferramenta
de comparação (pprof
-base, ou Speedscope diff, ou Pyroscope compare). Identifique onde houve mudança. Esse é o tipo de evidência que torna PRs de performance defensáveis.
Referências para aprofundar
- artigo Flame Graphs — Brendan Gregg (blog post fundador, 2011).
- paper The Flame Graph — Brendan Gregg (CACM, 2016).
- paper Google-Wide Profiling: A Continuous Profiling Infrastructure for Data Centers — Ren et al. (IEEE Micro, 2010).
- livro Systems Performance: Enterprise and the Cloud (2ª ed.) — Brendan Gregg (Pearson, 2020).
- livro BPF Performance Tools — Brendan Gregg (Pearson, 2019).
- livro Pro .NET Performance — Sasha Goldshtein, Dima Zurbalev, Ido Flatow (Apress, 2012).
- docs dotnet-trace + speedscope.
- docs pprof.
- docs py-spy.
- docs Grafana Pyroscope.
- docs Parca.
- vídeo Brendan Gregg — Performance Tuning (várias palestras LISA, USENIX, 2014–2024).