---
title: "go tool trace: Latência, Scheduler e Goroutines em Go"
url: "https://golang.com.br/blog/go-tool-trace-runtime-trace-latencia-goroutines/"
markdown_url: "https://golang.com.br/blog/go-tool-trace-runtime-trace-latencia-goroutines.MD"
description: "Aprenda go tool trace e runtime/trace para investigar picos de latência, scheduler, goroutines, GC, syscalls, regiões, tarefas, testes e produção em Go."
date: "2026-07-26"
author: "Golang Brasil"
---

# go tool trace: Latência, Scheduler e Goroutines em Go

Aprenda go tool trace e runtime/trace para investigar picos de latência, scheduler, goroutines, GC, syscalls, regiões, tarefas, testes e produção em Go.


Se uma aplicação Go apresenta **picos de latência, goroutines paradas, CPU ociosa apesar de fila crescente ou pausas difíceis de explicar**, use `go tool trace` quando a pergunta depender da ordem dos eventos. O execution trace registra uma linha do tempo do runtime: execução e bloqueio de goroutines, decisões do scheduler, garbage collection, syscalls, rede e tarefas instrumentadas pela aplicação.

A recomendação direta é: comece pelas métricas para delimitar o sintoma, use [pprof](/blog/pprof-go-producao/) para localizar onde os recursos se concentram e recorra ao trace para entender **quando e por que o fluxo deixou de avançar**. Para inspecionar variáveis e caminhar pelo código de uma execução específica, [Delve](/blog/delve-debug-go-vscode-docker/) continua sendo a ferramenta certa.

Este guia mostra como gerar traces em testes e serviços, abrir a interface, interpretar scheduler e goroutines, instrumentar operações com `runtime/trace` e usar o Flight Recorder com segurança.

## O que o execution trace responde

Um perfil de CPU agrega amostras por função. Isso é excelente para descobrir que uma função consumiu 35% do processador, mas não preserva necessariamente a história completa de uma requisição. O trace mantém a dimensão temporal e pode revelar situações como:

- dezenas de goroutines prontas, mas sem tempo de CPU suficiente;
- uma operação serializando atrás de um mutex;
- workers ociosos porque a fila não entrega trabalho;
- goroutines bloqueadas em channel, rede ou syscall;
- períodos de garbage collection coincidindo com uma cauda de latência;
- baixa utilização dos processadores lógicos apesar de trabalho pendente;
- uma tarefa lógica migrando por várias goroutines;
- uma requisição rápida na média, mas lenta quando disputa um recurso específico.

| Ferramenta | Melhor pergunta |
|---|---|
| Métricas | O problema existe, quando começou e qual dimensão foi afetada? |
| `pprof` | Onde CPU, heap, bloqueio ou contenção se concentram? |
| `go tool trace` | Como os eventos se encadearam ao longo do tempo? |
| Delve | Qual é o valor desta variável neste ponto da execução? |
| Race detector | Houve acesso concorrente inseguro à memória? |
| OpenTelemetry | Como uma requisição atravessou serviços e dependências? |

O execution trace não substitui tracing distribuído. O [OpenTelemetry em Go](/blog/go-opentelemetry-observabilidade-tracing-metricas/) acompanha operações entre processos e serviços; `runtime/trace` detalha o comportamento do runtime **dentro de um processo Go**. Os dois podem investigar a mesma latência em escalas diferentes.

## Como gerar um trace com go test

A forma mais simples de aprender é capturar um teste ou benchmark reproduzível. O suporte já faz parte de `go test`:

```bash
go test -trace=trace.out ./internal/checkout
```

Depois abra o arquivo:

```bash
go tool trace trace.out
```

O comando inicia um servidor local e exibe a URL da interface. Em uma máquina sem navegador automático, copie o endereço informado para o seu navegador.

Para reduzir ruído, execute somente o teste relevante:

```bash
go test \
  -run '^TestCheckout_ReservaConcorrente$' \
  -count=1 \
  -trace=trace.out \
  ./internal/checkout

go tool trace trace.out
```

`-count=1` evita reutilizar resultado do cache de testes. Se o problema aparece apenas com corrida ou carga, não assuma que um único caso pequeno reproduzirá o comportamento. Construa um teste que preserve a concorrência necessária, mas mantenha o período curto.

Para benchmark:

```bash
go test \
  -run '^$' \
  -bench '^BenchmarkDispatcher$' \
  -benchtime=3s \
  -trace=trace.out \
  ./internal/worker
```

A página de [testes em Go](/aprenda/testes-go/) explica seleção de casos, benchmarks e ferramentas auxiliares. O trace deve complementar um teste que já representa o defeito, não esconder uma reprodução instável.

## Como coletar trace de um serviço

Se o serviço importa `net/http/pprof` em um servidor administrativo, o endpoint `/debug/pprof/trace` disponibiliza uma coleta ao vivo. Exemplo de captura curta:

```bash
curl --fail --show-error \
  -o trace.out \
  "http://127.0.0.1:6060/debug/pprof/trace?seconds=5"

go tool trace trace.out
```

Cinco segundos sob carga representativa costumam ensinar mais do que um arquivo enorme coletado fora da janela do problema. Se a ocorrência dura pouco, sincronize a captura com um teste de carga, um alerta ou uma operação conhecida.

A porta administrativa não deve compartilhar exposição com a API pública. Use `127.0.0.1`, rede privada, VPN, túnel ou `port-forward` autenticado. O mesmo cuidado vale para endpoints de CPU, heap, mutex e goroutines descritos no guia de [pprof seguro em produção](/blog/pprof-go-producao/).

Quando não houver HTTP administrativo, uma aplicação standalone pode iniciar e encerrar a coleta pelo pacote `runtime/trace`:

```go
package main

import (
    "log"
    "os"
    "runtime/trace"
)

func main() {
    f, err := os.Create("trace.out")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    if err := trace.Start(f); err != nil {
        log.Fatal(err)
    }
    defer trace.Stop()

    runWorkload()
}

func runWorkload() {
    // Execute aqui somente a janela que precisa ser investigada.
}
```

Não envolva toda a vida de um servidor nesse `Start` por padrão. Traces longos aumentam custo, tamanho do arquivo e dificuldade de análise.

## Como ler a interface do go tool trace

A interface pode variar entre versões do toolchain, mas o raciocínio central permanece. Comece pelas visões de alto nível e só depois amplie uma faixa temporal específica.

### Visão por processador lógico

A timeline por processador mostra quando cada P do scheduler executou trabalho. Procure:

- longos espaços vazios enquanto existe fila de trabalho;
- execução concentrada em poucos Ps;
- alternância excessiva entre goroutines;
- uma fase paralela que se torna serial;
- intervalos dominados por GC ou syscalls.

Um espaço vazio não prova defeito. Talvez o serviço estivesse realmente sem trabalho. Compare a timeline com throughput, tamanho da fila, latência e o gerador de carga.

### Goroutines

A visão de goroutines permite acompanhar estados como execução, runnable e bloqueio. Uma goroutine **runnable** está pronta, mas aguarda escalonamento. Muitas goroutines runnable por tempo prolongado podem indicar saturação de CPU ou competição excessiva.

Já uma goroutine bloqueada pode estar esperando:

- recebimento ou envio em channel;
- `sync.Mutex` ou outra sincronização;
- timer;
- syscall;
- rede;
- conclusão de outra tarefa.

O estado sozinho não basta. Um worker esperar no channel quando não há jobs é saudável; centenas de produtores bloqueados porque nenhum consumidor avança podem indicar backpressure ausente, deadlock ou capacidade insuficiente.

### GC e heap

Eventos de garbage collection ajudam a correlacionar alocação, trabalho do coletor e pausas com a latência. Não conclua que “o GC é o problema” apenas porque ele aparece. Pergunte:

1. a aplicação passou a alocar mais antes da cauda de latência?
2. o heap cresceu por retenção ou por volume temporário?
3. a CPU já estava saturada?
4. o evento coincide repetidamente com o sintoma?
5. um perfil de heap ou `alloc_space` confirma a hipótese?

O trace mostra o encadeamento; o pprof mostra quais funções geraram ou retiveram alocações. Use ambos para evitar uma otimização baseada em coincidência visual.

### Syscalls e rede

Uma goroutine pode liberar o processador enquanto espera I/O, mas uma sequência de chamadas lentas ainda afeta a operação lógica. Quando a timeline aponta rede ou syscall, investigue timeout, DNS, pool de conexões, servidor remoto e cancelamento. Para detalhar DNS, conexão, TLS e obtenção de conexão em um cliente HTTP, use [httptrace](/blog/httptrace-go-debug-http-client/).

## Instrumentando tarefas, regiões e logs

O runtime já produz muitos eventos, mas nomes de domínio tornam o trace mais fácil de interpretar. O pacote `runtime/trace` oferece tarefas, regiões e logs associados a `context.Context`.

Uma **task** representa uma operação lógica que pode atravessar várias goroutines. Uma **region** marca uma etapa dentro dessa operação. Como `trace.WithRegion` não retorna erro, capture explicitamente o resultado da função instrumentada:

```go
package checkout

import (
    "context"
    "runtime/trace"
)

func Process(ctx context.Context, orderID string) error {
    ctx, task := trace.NewTask(ctx, "checkout")
    defer task.End()

    trace.Log(ctx, "order_id", orderID)

    var reserveErr error
    trace.WithRegion(ctx, "reserve-stock", func() {
        reserveErr = reserveStock(ctx, orderID)
    })
    if reserveErr != nil {
        return reserveErr
    }

    var paymentErr error
    trace.WithRegion(ctx, "authorize-payment", func() {
        paymentErr = authorizePayment(ctx, orderID)
    })
    return paymentErr
}
```

A instrumentação não deve alterar o contrato da função nem esconder erros. Mantenha poucos nomes de tipo, como `checkout`, `reserve-stock` e `authorize-payment`. Não gere `regionType` ou `taskType` com ID de pedido; use `trace.Log` para o identificador. Tipos com cardinalidade ilimitada dificultam agregação e análise.

Para uma região cujo início e fim ficam no mesmo fluxo, também existe `StartRegion`:

```go
region := trace.StartRegion(ctx, "encode-response")
defer region.End()
```

A região deve terminar na mesma goroutine em que começou, e regiões aninhadas precisam encerrar na ordem correta.

## Estudo de caso: CPU ociosa e fila crescendo

Imagine um worker pool com 32 consumidores. O dashboard mostra fila crescente, mas CPU abaixo de 30%. Um perfil de CPU não apresenta função dominante. A timeline revela:

1. os workers recebem jobs;
2. todos chegam a uma seção protegida pelo mesmo mutex;
3. uma goroutine faz I/O de rede enquanto segura o lock;
4. as outras permanecem bloqueadas;
5. os processadores ficam ociosos porque o trabalho foi serializado.

A hipótese deixa de ser “precisamos de mais workers”. Adicionar consumidores só aumentaria a fila no mutex. A correção provável é mover o I/O para fora da seção crítica, reduzir o estado protegido ou dividir o lock por chave.

Antes do deploy:

```bash
go test ./...
go test -race ./...
go test -bench '^BenchmarkDispatcher$' -benchmem ./internal/worker
```

Depois, repita a carga e gere outro trace com duração comparável. A validação não é “a timeline ficou bonita”; é throughput maior, latência menor e ausência de regressão funcional.

## Estudo de caso: goroutine leak ou operação legítima?

Um contador crescente de goroutines pode significar leak, mas o trace ajuda a separar causas. Considere chamadas a uma dependência sem timeout. Cada requisição inicia uma goroutine que fica aguardando resposta. Se o cliente não propaga cancelamento, o número cresce durante a degradação remota.

O caminho de correção inclui:

- propagar `context.Context`;
- definir timeout no ponto que conhece o orçamento;
- interromper trabalho quando `ctx.Done()` fechar;
- limitar concorrência;
- evitar goroutine “fire and forget” sem proprietário;
- acompanhar quantidade e idade das operações.

Veja [context, timeout e cancelamento em Go](/blog/context-timeout-cancelamento-go/) e o guia de [concorrência com goroutines e channels](/aprenda/concorrencia-go/) para os padrões de ciclo de vida.

## Flight Recorder: capturando o passado

Desde Go 1.25, `runtime/trace` inclui o Flight Recorder. Em vez de iniciar a coleta somente depois do alerta, ele mantém uma janela móvel dos eventos recentes e permite salvar um snapshot quando um gatilho dispara.

A API atual usa `trace.NewFlightRecorder` com `trace.FlightRecorderConfig`, `Start`, `WriteTo` e `Stop`:

```go
package diagnostics

import (
    "log"
    "os"
    "runtime/trace"
    "sync"
    "time"
)

var snapshotOnce sync.Once

func StartFlightRecorder() *trace.FlightRecorder {
    fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{
        MinAge:   2 * time.Second,
        MaxBytes: 16 << 20,
    })
    if err := fr.Start(); err != nil {
        log.Printf("flight recorder indisponível: %v", err)
    }
    return fr
}

func CaptureSlowRequest(fr *trace.FlightRecorder, elapsed time.Duration) {
    if !fr.Enabled() || elapsed < 500*time.Millisecond {
        return
    }

    snapshotOnce.Do(func() {
        f, err := os.Create("slow-request.trace")
        if err != nil {
            log.Printf("criando snapshot: %v", err)
            return
        }
        defer f.Close()

        if _, err := fr.WriteTo(f); err != nil {
            log.Printf("gravando snapshot: %v", err)
        }
    })
}
```

`MinAge` define uma retenção mínima desejada; `MaxBytes` limita a janela por tamanho e pode prevalecer sobre a idade. Ajuste os valores com teste de carga, porque um serviço movimentado produz dados mais rapidamente. Somente uma goroutine pode executar `WriteTo` por vez, e atualmente apenas um Flight Recorder pode estar ativo no processo.

Não crie um arquivo novo para toda requisição lenta. Use cooldown, amostragem, limite de snapshots, armazenamento controlado e rotação. O artigo sobre [Flight Recorder no Go 1.25](/blog/flight-recorder-go-1-25/) apresenta o conceito; para detalhes de API e mudanças, consulte sempre a documentação da versão do seu toolchain.

## Trace, pprof ou tracing distribuído: como escolher

Use este roteiro:

1. **Existe uma regressão mensurável?** Comece por métricas e logs.
2. **CPU ou memória está alta?** Colete pprof primeiro.
3. **A latência cresce sem função dominante?** Trace pode revelar espera e serialização.
4. **O problema atravessa serviços?** Use OpenTelemetry para localizar o trecho e runtime trace no processo suspeito.
5. **Precisa examinar valor e fluxo de uma execução reproduzível?** Use Delve.
6. **O evento raro já terminou quando o alerta chega?** Considere Flight Recorder.

A comparação com outras linguagens também ajuda a enxergar o método, não apenas a ferramenta. O guia de <a href="https://rustlang.com.br/blog/rust-profiling-performance-producao-2026/?utm_source=golang.com.br&utm_medium=referral&utm_campaign=portfolio_crosslink&utm_content=go-tool-trace-runtime-trace-latencia-goroutines" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">profiling de Rust em produção</a> mostra como separar CPU, memória e latência em outro ecossistema de sistemas.

## Erros comuns ao usar go tool trace

### Coletar tempo demais

Arquivo maior não significa diagnóstico melhor. Capture uma janela curta e relevante. Traces extensos consomem mais recursos e dificultam encontrar o evento útil.

### Interpretar correlação como causa

GC, syscall ou troca de goroutine perto de uma requisição lenta não prova causalidade. Repita a coleta, compare casos rápidos e lentos e confirme com pprof, métricas ou teste controlado.

### Instrumentar IDs como tipos

`trace.NewTask(ctx, "checkout-12345")` cria cardinalidade desnecessária. Use tipo estável `checkout` e registre o ID com `trace.Log`.

### Expor o endpoint administrativo

`/debug/pprof/trace` não deve ficar aberto na internet. O risco não é apenas consumo de recursos: artefatos de diagnóstico podem revelar estrutura interna e dados adicionados pela instrumentação.

### Otimizar antes de reproduzir

Uma timeline complexa convida a mudanças intuitivas. Primeiro formule uma hipótese, depois altere uma variável e repita a medição sob cenário comparável.

### Confundir trace do runtime com trace distribuído

`runtime/trace` não substitui propagação de spans entre serviços. Ele explica o processo Go; OpenTelemetry explica a jornada entre componentes.

## Checklist de investigação

Antes da coleta:

- defina o sintoma e a métrica afetada;
- registre versão do Go, commit e configuração relevante;
- escolha uma janela curta;
- proteja o endpoint ou mecanismo de captura;
- reproduza carga representativa.

Durante a análise:

- compare timeline com métricas;
- diferencie runnable de bloqueada;
- procure transição de paralelo para serial;
- correlacione GC com perfil de alocação;
- identifique channels, locks, rede e syscalls;
- verifique tarefas e regiões instrumentadas.

Depois da hipótese:

- adicione ou preserve um teste de regressão;
- rode `go test ./...` e `go test -race ./...` quando aplicável;
- faça benchmark antes e depois;
- gere um segundo trace comparável;
- remova acessos temporários;
- armazene ou descarte o artefato conforme a política de segurança.

## Perguntas frequentes

### Para que serve o go tool trace?

Ele abre um execution trace do runtime e mostra eventos ao longo do tempo. É especialmente útil para latência, scheduler, goroutines, GC, syscalls e operações que atravessam várias goroutines.

### Qual é a diferença entre go tool trace e pprof?

`pprof` agrega amostras para mostrar onde os recursos se concentram. O trace preserva a linha do tempo para mostrar como e quando os eventos se relacionaram. Muitas investigações usam os dois.

### Como gerar um trace de um teste Go?

Use `go test -trace=trace.out ./pacote` e abra com `go tool trace trace.out`. Se possível, selecione um teste com `-run` e desative o cache com `-count=1`.

### runtime/trace pode ser usado em produção?

Sim, com coleta curta, acesso protegido, gatilho controlado e validação de overhead. Trate o arquivo como potencialmente sensível e evite exposição pública do endpoint administrativo.

### Quando usar o Flight Recorder?

Quando o problema é raro e só pode ser detectado depois que a janela crítica começou. O Flight Recorder mantém os eventos recentes e permite salvar o passado imediato quando um gatilho confiável dispara.

## Referências oficiais

- [Diagnostics — Go Documentation](https://go.dev/doc/diagnostics)
- [Pacote runtime/trace](https://pkg.go.dev/runtime/trace)
- [More powerful Go execution traces](https://go.dev/blog/execution-traces-2024)
- [Flight Recorder in Go 1.25](https://go.dev/blog/flight-recorder)

## Conclusão

`go tool trace` é a ferramenta certa quando a média e os perfis agregados não contam a história inteira. Ele mostra como goroutines, scheduler, GC, syscalls e regiões da aplicação se organizam no tempo — justamente a dimensão que desaparece quando você olha apenas para totais.

Use-o como parte de um método: métricas para detectar, pprof para localizar, trace para ordenar os eventos, testes e benchmarks para validar. Com coletas curtas, instrumentação de baixa cardinalidade e endpoints protegidos, problemas de latência deixam de ser uma sequência de palpites e viram hipóteses verificáveis.
