Como usar o Pyroscope para profiling contínuo de aplicações em produção

1. Introdução ao profiling contínuo e ao Pyroscope

O profiling contínuo é uma técnica de observabilidade que coleta dados de desempenho de aplicações em produção de forma permanente, ao contrário do profiling tradicional on-demand, que é executado apenas quando há suspeita de problemas. Enquanto o profiling tradicional captura instantâneos isolados, o contínuo oferece uma visão temporal completa, permitindo detectar regressões sutis, vazamentos graduais e padrões de uso que só aparecem sob carga real.

O Pyroscope é uma plataforma open-source de profiling contínuo que se destaca por sua arquitetura leve e integração nativa com o ecossistema Grafana. Ele opera em dois modos principais: standalone (servidor próprio com interface web) ou como plugin do Grafana, consolidando perfis com métricas e logs. Suporta múltiplas linguagens: Java (via async-profiler), Python (via py-spy), Go, Ruby, Node.js e Rust.

2. Instalação e configuração do Pyroscope

A instalação mais rápida é via Docker para o servidor standalone:

docker run -d -p 4040:4040 \
  --name pyroscope \
  -v /data/pyroscope:/var/lib/pyroscope \
  pyroscope/pyroscope:latest server

Para configuração básica com armazenamento local e autenticação:

# docker-compose.yml
services:
  pyroscope:
    image: pyroscope/pyroscope:latest
    ports:
      - "4040:4040"
    environment:
      - PYROSCOPE_STORAGE_PATH=/var/lib/pyroscope
      - PYROSCOPE_BASIC_AUTH_USER=admin
      - PYROSCOPE_BASIC_AUTH_PASS=secret123
    volumes:
      - ./data:/var/lib/pyroscope
    command:
      - server
      - --retention=720h
      - --max-nodes-serialization=4096

A retenção de 720h (30 dias) é adequada para ambientes de produção. O parâmetro max-nodes-serialization controla a granularidade dos perfis armazenados.

3. Instrumentação de aplicações com agentes Pyroscope

Java (usando async-profiler)

Adicione a dependência Maven:

<dependency>
    <groupId>io.pyroscope</groupId>
    <artifactId>agent</artifactId>
    <version>0.13.0</version>
</dependency>

Configuração na inicialização da aplicação:

import io.pyroscope.javaagent.PyroscopeAgent;

public class App {
    public static void main(String[] args) {
        PyroscopeAgent.start(new PyroscopeAgent.Options.Builder()
            .setApplicationName("meu-servico-java")
            .setServerAddress("http://localhost:4040")
            .setProfilingEvent(ProfilingEvent.ITIMER)
            .setSampleRate(100)
            .addTag("versao", "1.2.3")
            .addTag("ambiente", "producao")
            .build()
        );
        // Restante da aplicação
    }
}

Python (usando py-spy)

Instale o agente:

pip install pyroscope-io

Instrumentação em uma aplicação Flask:

import pyroscope

pyroscope.configure(
    app_name="meu-servico-python",
    server_address="http://localhost:4040",
    tags={
        "versao": "2.0.1",
        "ambiente": "producao"
    }
)

from flask import Flask
app = Flask(__name__)

@app.route('/api/processar')
def processar():
    # Lógica da aplicação
    return "OK"

4. Tipos de perfil suportados e análise de dados

CPU profiling

Identifica funções que consomem mais processamento. No Pyroscope, visualize o flame graph e filtre por intervalo de tempo:

# Consulta PyroscopeQL para top funções por CPU
pyroscope.cpu{app="meu-servico-java"} | top(10)

Heap profiling

Detecta vazamentos de memória e alocação excessiva. Em Java, ative o profiling de heap:

PyroscopeAgent.start(new PyroscopeAgent.Options.Builder()
    .setProfilingEvent(ProfilingEvent.ALLOC)
    .setAllocLive(true)
    .build()
);

Perfis adicionais

Em Go, o Pyroscope captura goroutines:

import "github.com/pyroscope-io/client/pyroscope"

pyroscope.Start(pyroscope.Config{
    ApplicationName: "meu-servico-go",
    ServerAddress:   "http://localhost:4040",
    ProfileTypes: []pyroscope.ProfileType{
        pyroscope.ProfileCPU,
        pyroscope.ProfileGoroutines,
        pyroscope.ProfileMutex,
    },
})

5. Criação de dashboards e alertas no Pyroscope

Dashboard no Grafana

Após integrar o Pyroscope como datasource, crie um painel com a consulta:

# Top 5 funções por CPU nos últimos 30 minutos
topk(5, avg_over_time(
    pyroscope.cpu{app=~"meu-servico.*", ambiente="producao"}[5m]
))

Alertas baseados em perfis

Configure um alerta no Grafana para picos de alocação de heap:

# Alerta: heap allocation > 500MB/s
avg_over_time(
    pyroscope.alloc_objects{app="meu-servico-java"}[1m]
) > 500000000

Combinando métricas com profiling

No mesmo dashboard, adicione métricas tradicionais (Prometheus) lado a lado com perfis Pyroscope para correlação visual.

6. Casos de uso práticos em produção

Debugging de regressões após deploy

Compare duas versões lado a lado:

# Comparação entre versão atual e anterior
pyroscope.cpu{app="meu-servico", versao="2.0.0"} 
vs 
pyroscope.cpu{app="meu-servico", versao="1.9.9"}

Otimização de custos

Identifique funções que aumentam latência em endpoints críticos:

# Funções mais lentas no endpoint /api/pagamento
pyroscope.cpu{app="meu-servico", endpoint="/api/pagamento"} | top(5)

Investigação de incidentes

Correlacione picos de CPU com perfis específicos:

# Perfil durante pico às 14:30
pyroscope.cpu{app="meu-servico"} 
  | time_range("2024-01-15T14:25:00Z", "2024-01-15T14:35:00Z")

7. Boas práticas e limitações

Overhead do profiling contínuo

O Pyroscope adiciona entre 1-5% de overhead de CPU em modo padrão (100 amostras/segundo). Para minimizar:

# Reduza a taxa de amostragem para aplicações sensíveis
.setSampleRate(50)  # 50 amostras/segundo

Estratégias de amostragem

  • Use ITIMER (wall-clock) para aplicações com I/O intenso
  • Use CPU para aplicações CPU-bound
  • Em Java, prefira async-profiler para menor overhead

Limitações

  • Linguagens interpretadas: Python e Ruby têm maior overhead de profiling
  • Serverless: Funções efêmeras (AWS Lambda) exigem adaptações com envio assíncrono
  • Armazenamento: Perfis de alta resolução consomem ~1GB/dia por serviço com 100 amostras/s

Referências