MÓDULO 10 · CONCEITO 10 DE 12

Profiling Contínuo

Flame graphs, sampling profilers, Pyroscope, Parca, eBPF profiling e o quarto pilar de observabilidade

Tempo de leitura ~30 min Pré-requisito 04 · Distributed Tracing Próximo 11 · Observabilidade em Kubernetes

Os três pilares tradicionais de observabilidade são logs, métricas e traces. O profiling contínuo emerge como um quarto pilar — e é o único sinal que responde à pergunta "qual linha de código está consumindo CPU ou memória em produção, agora?". Métricas dizem "CPU está alta". Traces dizem "este request demorou 800ms". Profiling diz "68% do CPU está sendo consumido pela função serialize_response na linha 142 do arquivo handlers.go". A diferença entre profiling ad-hoc e profiling contínuo é temporal: profiling ad-hoc é executado manualmente quando você suspeita de um problema; profiling contínuo coleta dados permanentemente em produção, com histórico — você pode responder "a regressão que apareceu após o deploy das 14:30 está em qual função?" sem precisar reproduzir o problema.

Como Funciona o Sampling Profiler

Um sampling profiler interrompe a execução do programa em intervalos regulares (tipicamente 100-500 vezes por segundo), captura o stack trace do thread ou goroutine naquele instante, e agrega essas amostras ao longo do tempo. Funções que aparecem com mais frequência nos stack traces consomem mais CPU — a frequência de aparição é o estimador de custo.

Tipos de profiling

overhead

Um sampling profiler bem implementado tem overhead de 1-3% de CPU — aceitável em produção para a maioria dos workloads. Profilers instrumentados (que injetam código em cada entrada/saída de função) têm overhead de 5-30% e não são adequados para produção contínua. A escolha de sampling rate importa: 100 amostras/s é padrão; aumentar para 500 captura mais granularidade mas aumenta o overhead linearmente.

Flame Graphs

Flame graphs são a visualização padrão de profiling, criada por Brendan Gregg em 2011. Cada caixa representa uma função na call stack. A largura é proporcional ao número de vezes que a função apareceu nas amostras. As caixas são empilhadas: a função mais abaixo é o ponto de entrada (ex: main), as funções acima são chamadas subsequentes.

Como ler um flame graph

# Exemplo de análise de flame graph — identificando hot path
#
# processRequest [████████████████████████████████████████] 100%
# ├─ parseJSON   [██████████████████] 45%
# │  └─ unmarshal [████████████████] 40%   ← PLATÔ — hot function
# ├─ queryDB     [████████████] 30%
# │  └─ sql.Query [███████████] 28%        ← gasta tempo em I/O (wall-clock)
# └─ renderHTML  [████████] 20%
#    └─ template.Execute [███████] 18%     ← platô secundário
#
# Ação: unmarshal() consome 40% do CPU — candidato a otimização.
# Opções: JSON parsing customizado, caching de schemas parseados,
# ou substituir JSON por protobuf para reduzir deserialização.

Differential flame graphs

Um differential flame graph compara dois profiles — tipicamente antes e depois de um deploy. Caixas vermelhas: mais CPU no profile novo (regressão). Caixas azuis: menos CPU (melhoria). Permite identificar regressões introduzidas por uma mudança de código sem análise manual comparativa — você vê imediatamente qual função ficou mais cara após o deploy das 14:30.

Pyroscope (Grafana)

Pyroscope (adquirido pela Grafana em 2023) é o backend open source de continuous profiling mais adotado. Integra-se nativamente ao stack Grafana, suporta múltiplas linguagens via SDKs ou eBPF, e armazena profiles em object storage (S3/GCS) — custo de storage muito baixo.

# docker-compose.yml — Pyroscope standalone
services:
  pyroscope:
    image: grafana/pyroscope:latest
    ports:
      - "4040:4040"
    command:
      - "-config.file=/etc/pyroscope/config.yaml"
    volumes:
      - ./pyroscope.yaml:/etc/pyroscope/config.yaml

# pyroscope.yaml
storage:
  backend: s3
  s3:
    bucket_name: my-profiles
    region: us-east-1

# Grafana datasource: adicione Pyroscope como datasource
# No Grafana Explore: selecione Pyroscope, query por service_name
# + time range → flame graph interativo do período
# Com Grafana 10+: correlação spans (Tempo) → profiles (Pyroscope)
# via service.name como chave de correlação

eBPF Profiling — Sem Modificar a Aplicação

eBPF permite executar programas sandboxed no kernel Linux. Para profiling, isso significa capturar stack traces de qualquer processo sem modificar código, sem SDK, e sem reiniciar o processo — o profiler opera no nível do kernel observando todos os processos simultaneamente.

limitações do eBPF

Requer kernel Linux 4.9+ (preferencialmente 5.8+). Para linguagens com JIT (Java, .NET, Node.js): stack traces podem ser incompletos sem integração com o runtime para resolver símbolos JIT — o profiler vê endereços de memória, não nomes de função. Para Python/Ruby: interpretadores gerenciados precisam de suporte extra (frame pointer restoration). Não funciona em Windows. Ferramentas: Grafana Beyla (eBPF auto-instrumentação), Parca (CNCF, eBPF-first), Cilium/Pixie (K8s networking + profiling).

Profiling por Linguagem

C# — Pyroscope SDK + pprof nativo
// Package: Pyroscope.Profiler

// Program.cs — profiling contínuo com Pyroscope
using Pyroscope;

PyroscopeAgent.Start(new PyroscopeConfig
{
    ApplicationName = "order-service",
    ServerAddress = "http://pyroscope:4040",
    Tags = new Dictionary<string, string>
    {
        { "environment", "production" },
        { "version", "1.2.0" },
        { "region", "us-east-1" },
    },
    ProfilingTypes = new[]
    {
        ProfileType.Cpu,
        ProfileType.Alloc,       // alocações — auxilia GC tuning
        ProfileType.Exception,   // exceções como eventos de profiling
    },
    SampleRate = 100,            // 100 amostras por segundo
});

// Anotar spans de negócio para segmentar o flame graph
// Permite ver o perfil apenas durante uma operação específica
using (Profiler.NewSpan("process-order"))
{
    await ProcessOrderAsync(request);
}

// Analisar heap manualmente (dev/staging):
// dotnet-dump collect --process-id $(pgrep dotnet)
// dotnet-dump analyze <dumpfile>
// > dumpheap -stat   ← objetos por tipo com tamanho total
// > gcroot <address> ← por que este objeto não foi coletado?

O Pyroscope .NET SDK usa o profiler nativo do CLR com baixo overhead. Tags permitem differential profiling entre versões — compare o flame graph de "version=1.1.0" com "version=1.2.0" diretamente no Grafana para identificar regressões.

Python — Pyroscope SDK + py-spy
# Package: pyroscope-io

import pyroscope

pyroscope.configure(
    application_name="order-service",
    server_address="http://pyroscope:4040",
    tags={
        "environment": "production",
        "version": "1.2.0",
    },
    oncpu=True,   # CPU profiling
    native=False, # True = profila extensões C nativas também
)

# Context manager para anotar seções de código
with pyroscope.tag_wrapper({"endpoint": "/api/orders"}):
    result = process_order(request)

# py-spy — profiling sem modificar código (externo ao processo)
# Útil para debugging ad-hoc em produção sem redeploy:
#
# py-spy top --pid 12345
#   → top-like de funções (atualiza em tempo real)
#
# py-spy record -o profile.svg --pid 12345 --duration 30
#   → grava 30s e exporta flame graph SVG interativo
#
# Em Kubernetes (requer shareProcessNamespace: true):
# kubectl exec -it <pod> -- py-spy top --pid 1
#
# Diagnosticar deadlock ou hang:
# py-spy dump --pid 12345
#   → dump de todos os threads com stack trace atual

py-spy não requer modificação do código e pode profilear qualquer processo Python em execução — útil para incidentes em produção sem redeploy. O Pyroscope SDK é para profiling contínuo automatizado.

Go — pprof nativo + Pyroscope SDK
import (
    _ "net/http/pprof" // registra handlers /debug/pprof/*
    "github.com/grafana/pyroscope-go"
)

func main() {
    // pprof em porta interna — nunca expor publicamente
    go http.ListenAndServe("localhost:6060", nil)

    profiler, _ := pyroscope.Start(pyroscope.Config{
        ApplicationName: "order-service",
        ServerAddress:   "http://pyroscope:4040",
        Tags: map[string]string{
            "environment": os.Getenv("ENV"),
            "version":     version,
        },
        ProfileTypes: []pyroscope.ProfileType{
            pyroscope.ProfileCPU,
            pyroscope.ProfileAllocObjects,  // objetos alocados
            pyroscope.ProfileAllocSpace,    // bytes alocados
            pyroscope.ProfileInuseObjects,  // objetos vivos no heap
            pyroscope.ProfileInuseSpace,    // bytes vivos no heap
            pyroscope.ProfileGoroutines,
            pyroscope.ProfileMutexCount,
            pyroscope.ProfileMutexDuration,
            pyroscope.ProfileBlockCount,
            pyroscope.ProfileBlockDuration,
        },
    })
    defer profiler.Stop()

    // Anotar spans de negócio
    pyroscope.TagWrapper(ctx,
        pyroscope.Labels("operation", "process-order"),
        func() { processOrder(ctx, req) },
    )
}

// Profiling manual via CLI:
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
//   → CPU profile 30s, abre UI interativa
//
// go tool pprof -http=:8080 profile.pb.gz
//   → UI web com flame graph, top, tree, source view
//
// Diagnosticar goroutine leak:
// curl localhost:6060/debug/pprof/goroutine?debug=2
//   → dump de todas as goroutines com stack trace

Go tem o pprof mais maduro de qualquer linguagem mainstream. ProfileInuseSpace vs ProfileAllocSpace: InuseSpace mostra o heap atual (útil para memory leaks), AllocSpace mostra alocações totais (útil para otimizar pressão de GC).

Casos de Uso Reais

Detectar regressão de performance após deploy

Com profiling contínuo, você compara o differential flame graph antes e depois de um deploy. Sem profiling contínuo, você detecta a regressão via métricas (CPU aumentou 30%) mas não sabe onde no código — precisa reproduzir localmente com profiling ad-hoc, o que raramente replica o workload de produção.

Memory leaks em produção

Memory profiling captura quais funções estão alocando mais memória ao longo do tempo. Com profiling contínuo histórico, você identifica quando o leak começou (após qual deploy o heap cresceu linearmente) e quais allocators são responsáveis — a correlação temporal com deploys é a chave do diagnóstico.

Otimização de custo de infraestrutura

Profiling em toda a frota revela ineficiências de CPU que se multiplicam por centenas de instâncias. Exemplo real: função de serialização JSON desnecessariamente cara em Go consumindo 15% de CPU em todos os pods. Substituindo por uma biblioteca mais eficiente, a frota precisou de 15% menos CPUs — economia identificada por profiling, não por revisão de código especulativa.

GC tuning

Em linguagens com GC (Java, .NET, Go), o profiling de alocação revela padrões que causam pressão de GC: alocações frequentes de objetos de curta vida, boxing de value types desnecessários, slices crescendo com realloc frequente. Reduzir alocações é frequentemente mais eficaz que ajustar parâmetros do GC.

Decisões de engenharia

Pyroscope vs Parca vs eBPF direto?

Pyroscope: melhor integração com Grafana stack (Tempo, Loki, Mimir), SDKs maduros para Go, Python e .NET, UI com differential flame graphs. Parca (CNCF): eBPF-first, sem SDK necessário, armazena em Parquet — boa opção para profiling de infra sem tocar código. eBPF direto (perf, bpftrace): para debugging avançado de kernel e situações onde SDKs não atendem. Para a maioria dos times com stack Grafana: Pyroscope. Para Kubernetes sem mudança de código: Parca ou Beyla.

Quando habilitar profiling contínuo?

Não é para todos os sistemas. Faz sentido quando: CPU ou memória é custo significativo de infra (profiling pode identificar 10-20% de economia), você tem problemas de performance recorrentes difíceis de reproduzir, ou o workload de produção é muito diferente do de staging. Para sistemas pequenos com CPU idle > 50%, o custo de setup supera o benefício imediato. Comece habilitando apenas CPU profiling — é o mais fácil de interpretar. Adicione heap profiling quando investigar memory leaks ou pressão de GC.

pprof endpoint deve ser exposto em produção?

Sim, mas protegido. Exponha em porta interna (localhost:6060) ou behind authn middleware — nunca publicamente. O pprof endpoint em si não é perigoso, mas expõe nomes de funções e estrutura interna do código (information disclosure) e pode ser usado para consumir CPU ao disparar um profile de 60 segundos. Em Kubernetes, acesse via kubectl port-forward para profiling manual. O Pyroscope faz pull do pprof endpoint internamente na rede do cluster.

Como correlacionar profiling com traces?

Pyroscope suporta span-level profiling: passe o trace context para o profiler (via TagWrapper em Go, NewSpan em .NET) e ele associa amostras ao trace ID ativo no momento. No Grafana, o datasource Pyroscope está integrado ao datasource Tempo — você vê o flame graph do processo durante um span específico clicando em "Profiles for this span" na UI do trace. A correlação usa service.name como chave entre os dois datastores.

Como praticar

  1. Configure profiling contínuo com Pyroscope em um serviço Go ou Python: instale o SDK, configure push para um Pyroscope local (Docker), e visualize o flame graph no Grafana. Crie uma função artificialmente cara (loop desnecessário, serialização ineficiente) e confirme que ela aparece como platô no flame graph após alguns segundos de tráfego.
    Critério: o flame graph aparece no Grafana com dados reais do serviço; a função cara é identificável como platô com largura proporcional ao overhead introduzido; o setup está em docker-compose reproduzível; a função cara foi otimizada e o differential flame graph mostra a melhora (caixas azuis na função modificada).
  2. Diagnostique um memory leak simulado usando heap profiling: crie um leak intencional (acumular objetos em uma cache global sem limite de tamanho), execute por alguns minutos, e use memory profiling para identificar exatamente qual função está alocando os objetos que não são coletados. Compare o heap antes e depois do leak ser introduzido.
    Critério: o profiling de heap mostra crescimento linear de InuseSpace ao longo do tempo; a função responsável pelo acúmulo é identificada pelo profiling (não por revisão de código especulativa); após o fix (adicionar limite de tamanho à cache), o InuseSpace estabiliza; o exercício documenta o processo de diagnóstico como seria feito em produção.
  3. Use go tool pprof (ou py-spy para Python) para analisar um processo ao vivo: capture um CPU profile de 30 segundos de uma aplicação sob carga (use um gerador de carga simples — hey ou wrk), abra a UI web interativa (-http=:8080), e identifique os 3 maiores consumidores de CPU usando as views "Top", "Flame Graph" e "Source".
    Critério: o profile foi capturado com carga real no serviço; as 3 views (Top, Flame Graph, Source) foram exploradas e os 3 maiores consumidores documentados com % de CPU; a view Source mostra as linhas de código específicas onde o CPU é consumido; pelo menos uma função identificada é um candidato óbvio de otimização (ex: alocação desnecessária, I/O síncrono em hot path).
  4. Compare CPU profiling vs wall-clock profiling no mesmo serviço: escolha um endpoint que faz uma query de banco de dados e mede ambos os tipos de profile. Documente a diferença — quantos % do CPU profile mostra código de aplicação vs quantos % do wall-clock profile está em espera de I/O.
    Critério: o CPU profile mostra os hotspots de processamento (parse, serialização, cálculos); o wall-clock profile mostra que a maior parte do tempo está em sql.Query ou equivalente (I/O wait); a documentação explica por que wall-clock é mais útil para diagnosticar latência de endpoint do que CPU profiling; a diferença entre os dois tipos é clara o suficiente para explicar a um colega.
  5. Gere e analise um differential flame graph entre duas versões do mesmo código: crie uma regressão de performance deliberada em uma função (ex: mudar de map lookup para linear search), profile a versão antes e depois da regressão, e use o Pyroscope ou Grafana para gerar o differential. Identifique e corrija a regressão usando apenas o differential como guia, sem ler o código.
    Critério: o differential flame graph mostra claramente caixas vermelhas na função regressada; a regressão é identificada e corrigida usando apenas o visual do differential (sem revisão de código); um segundo differential (antes da regressão vs após o fix) confirma que a melhora é visível; o exercício documenta o processo como seria realizado em um incidente real pós-deploy.

Perguntas de entrevista

    Como você lê um flame graph? O que é um "platô" e o que ele indica?

    No flame graph, o eixo Y representa profundidade da call stack (mais alto = mais profundo na cadeia de chamadas), e o eixo X representa proporção de CPU — não é temporal, é proporção de amostras coletadas. A largura de cada caixa indica quanto CPU aquela função consumiu, incluindo todas as funções que ela chamou.

    Um platô é uma caixa larga que não tem filhos de largura significativa — ela é a função que efetivamente está consumindo CPU, não delegando para outras. Exemplo: se processRequest ocupa 80% da largura mas chama parseJSON que ocupa 70%, e parseJSON chama unmarshal que ocupa 60% sem filhos de destaque — então unmarshal é o platô e é o candidato primário de otimização. O erro comum ao ler flame graphs é focar em funções largas no meio do stack que têm filhos igualmente largos — essas são passagem, não o problema.

    Qual a diferença entre CPU profiling e wall-clock profiling?

    CPU profiling captura amostras apenas quando o thread está em execução ativa (user-space ou kernel-space em nome do thread). Tempo bloqueado em I/O, sleep, ou esperando lock não aparece. Ideal para identificar hotspots de processamento puro — parse, cálculos, serialização.

    Wall-clock profiling captura amostras a cada intervalo de tempo real, independente de o thread estar executando ou bloqueado. Um thread bloqueado em I/O por 2 segundos aparece como 2 segundos no wall-clock profile. Ideal para identificar onde o programa está realmente demorando do ponto de vista do usuário — incluindo latência de banco de dados, espera em locks, e chamadas de rede.

    Exemplo prático: um endpoint HTTP que demora 500ms total, sendo 50ms de CPU e 450ms de espera pelo banco de dados. CPU profiling mostra 50ms de processamento — não revela o bottleneck real. Wall-clock profiling mostra que 90% do tempo está em sql.Query — o bottleneck é imediatamente evidente.

    Por que profiling contínuo é o "quarto pilar" de observabilidade? O que ele revela que logs, métricas e traces não revelam?

    Logs, métricas e traces respondem ao "quê" e "quando": "o request X falhou às 14:30 com timeout" (log), "a latência P99 subiu 40%" (métrica), "o span de banco de dados demorou 800ms" (trace). Eles não respondem ao "onde no código" — qual função, qual linha, qual hot path está causando o problema.

    Profiling contínuo responde ao "onde": dado que a latência P99 subiu 40%, o profiling diz "a função deserialize_payload que antes consumia 5% de CPU agora consome 35% — o commit de 14:18 adicionou uma chamada de regex desnecessária nessa função". Sem profiling, você teria que fazer revisão de código especulativa, profiling manual em staging (que não replica o workload de produção), ou adicionar logs em cada função suspeita e redeployar. Com profiling contínuo histórico e differential flame graphs, a causa raiz é encontrada em minutos.

    Como você diagnosticaria um memory leak em produção usando profiling?

    O processo em etapas: (1) Confirmar o leak via métricas: crescimento linear de uso de memória ao longo do tempo, sem platôs, que não é explicado por crescimento de tráfego. Se o uso de memória cresce mesmo com tráfego estável, é leak. (2) Correlacionar com deploys: usando o profiling contínuo histórico, identificar após qual deploy o crescimento começou — isso aponta para o commit responsável. (3) Analisar heap profiling: comparar o InuseSpace profile (objetos vivos no heap) de uma hora atrás com o atual. A diferença são os objetos que não estão sendo coletados. A view "Top" por InuseSpace mostra quais tipos de objetos ocupam mais memória. (4) Rastrear a alocação: AllocObjects profile mostra quais funções estão alocando mais objetos — frequentemente a função que aloca não é a que retém. Ferramentas como dotnet-dump (GC roots), go tool pprof heap, ou jmap/jstack (Java) mostram por que os objetos não estão sendo coletados (qual referência os está retendo). (5) Validar o fix: após o fix, o InuseSpace deve estabilizar — monitorar por algumas horas para confirmar que não volta a crescer linearmente.

    O que é um differential flame graph e como você o usaria para investigar uma regressão de performance após um deploy?

    Um differential flame graph compara dois profiles coletados em momentos diferentes — tipicamente antes e depois de um deploy, ou com e sem uma funcionalidade específica. A diferença de largura de cada função é codificada em cor: vermelho significa que a função ficou mais cara no profile novo (mais amostras, mais CPU), azul significa que ficou mais barata. A intensidade da cor é proporcional à magnitude da diferença.

    Workflow de investigação de regressão: (1) o alerta de CPU alta ou P99 elevado dispara após o deploy das 14:30; (2) no Grafana/Pyroscope, selecione "Diff" e compare o período antes do deploy (ex: 13:00-14:00) com o período depois (14:30-15:30); (3) o differential flame graph mostra imediatamente quais funções ficaram mais caras em vermelho — essas são as candidatas; (4) a função com mais vermelho no nível mais profundo da stack (sem filhos vermelhos expressivos) é o platô da regressão; (5) inspecione a função no código — frequentemente é uma mudança de algoritmo O(n) para O(n²), alocação desnecessária adicionada, ou lock de escopo ampliado. O processo inteiro tipicamente leva 5-15 minutos, vs horas de revisão de código especulativa sem profiling.

Referências para aprofundar

  1. artigo Flame Graphs — Brendan Gregg (brendangregg.com, 2011). brendangregg.com/flamegraphs.html — O artigo original de Brendan Gregg que criou os flame graphs: motivação, como a visualização funciona, como gerar com perf + FlameGraph scripts, e variantes (icicle charts, differential, off-CPU). Leitura obrigatória antes de qualquer outro material sobre o tema.
  2. artigo The Flame Graph — Brendan Gregg (ACM Queue, 2016). queue.acm.org — Versão peer-reviewed do paper original sobre flame graphs na ACM Queue. Mais aprofundado que o post do blog: inclui análise de casos, variantes de flame graph (heat maps, differential), e como interpretar artefatos comuns (stack frame truncation, JIT frames).
  3. livro Systems Performance: Enterprise and the Cloud — Brendan Gregg (Prentice Hall, 2ª ed. 2020). Capítulos 5 (Applications) e 6 (CPUs) cobrem profiling em profundidade: sampling profilers, instrucao-level profiling, off-CPU analysis, e como usar perf, bpftrace e flame graphs para diagnóstico de performance em produção.
  4. docs Grafana Pyroscope — Documentação oficial — Grafana Labs (2024). grafana.com/docs/pyroscope — Referência completa de instalação, SDKs (Go, Python, .NET, Java, Ruby), configuração de storage em S3, e integração com o Grafana para flame graphs e correlação com Tempo. Inclui guia de profiling eBPF via Grafana Alloy.
  5. docs Parca — Continuous Profiling (CNCF) — Parca Project (2024). parca.dev — Documentação do Parca: profiling eBPF sem SDK, deployment no Kubernetes, storage em Parquet, e API de query. Alternativa ao Pyroscope para ambientes onde modificar código das aplicações não é viável.
  6. docs Go — net/http/pprof — Go Project (2024). pkg.go.dev/net/http/pprof — Documentação do pprof integrado ao Go: endpoints disponíveis (/profile, /heap, /goroutine, /mutex, /block), como usar com go tool pprof, e a UI web interativa. O sistema de profiling mais maduro de qualquer linguagem mainstream.
  7. docs py-spy — Sampling Profiler for Python — Ben Frederickson (2024). github.com/benfred/py-spy — Documentação do py-spy: profiling de processos Python sem modificação de código, subcomandos (top, record, dump), uso em Kubernetes com shareProcessNamespace, e exportação de flame graphs. A ferramenta go-to para debugging ad-hoc de performance em Python.
  8. docs Profiling C# .NET Applications — Microsoft Learn (2024). learn.microsoft.com/dotnet/core/diagnostics — Guia oficial de diagnóstico .NET: dotnet-trace para CPU profiling, dotnet-dump para análise de heap, dotnet-counters para métricas em tempo real. Inclui como identificar memory leaks com GC root analysis.
  9. artigo Continuous Profiling for Production Go Applications — Grafana Labs Blog (2022). grafana.com/blog — Como a Grafana Labs usa Pyroscope internamente para profiling contínuo de seus próprios serviços Go: casos reais de otimização identificados via profiling, redução de custo de infra, e workflow de investigação de regressões com differential flame graphs.
  10. artigo eBPF — What is eBPF? — eBPF Foundation (2024). ebpf.io/what-is-ebpf — Introdução técnica ao eBPF: como programas sandboxed executam no kernel Linux, o verifier de segurança, e aplicações em observabilidade, segurança e networking. Contexto fundamental para entender eBPF profiling e ferramentas como Pixie, Parca e Beyla.
  11. artigo Off-CPU Analysis — Brendan Gregg (2015). brendangregg.com/offcpuanalysis.html — Técnica para analisar tempo gasto fora da CPU: espera em I/O, locks, sleep. O complemento do CPU profiling para entender latência total. Inclui como usar perf e bpftrace para off-CPU flame graphs — revelam bottlenecks invisíveis ao CPU profiler.
  12. docs OpenTelemetry Profiling Signal — OpenTelemetry Project (2024). opentelemetry.io/docs/specs/otel/profiles — Especificação do quarto sinal OTel (em desenvolvimento): formato padronizado para profiles, correlação com trace context, e protocolo de transporte OTLP para profiling. Define como o profiling se integrará ao ecossistema OTel quando estabilizado.