tracemalloc registra onde os blocos de memória controlados pelo alocador do Python foram criados. Ele responde a uma pergunta muito prática: quais linhas passaram a reter mais memória entre dois pontos de uma execução? Isso é mais útil do que observar apenas que o processo cresceu, pois oferece uma pista concreta no código.
O módulo faz parte da biblioteca padrão e não exige dependência. Ainda assim, ele não mede tudo. O RSS mostrado pelo sistema operacional inclui o interpretador, bibliotecas nativas, páginas compartilhadas e memória que o alocador manteve para reutilização. Portanto, um snapshot do tracemalloc e o RSS podem variar de maneiras diferentes sem que nenhum deles esteja errado.
Comece o rastreamento no momento certo
Ative o rastreamento antes de importar ou executar o trecho que deseja investigar. Quando ele começa tarde, as alocações anteriores não têm traceback e ficam fora da comparação. Em um serviço, uma alternativa é iniciar o processo com PYTHONTRACEMALLOC=10 ou com a opção -X tracemalloc=10. Em um script ou teste, a API é mais direta:
import tracemalloc
tracemalloc.start(10)
antes = tracemalloc.take_snapshot()
dados = [{"id": i, "valor": str(i)} for i in range(50_000)]
depois = tracemalloc.take_snapshot()
for estatistica in depois.compare_to(antes, "lineno")[:10]:
print(estatistica)
O argumento de start() é a profundidade máxima do traceback guardado para cada bloco. Uma profundidade maior ajuda quando a linha alocadora é genérica e a origem relevante está alguns frames acima, mas também aumenta o custo de memória e CPU do próprio diagnóstico. Comece com 10 em uma reprodução curta e meça o impacto antes de usar em produção.
take_snapshot() captura apenas os blocos que continuam alocados naquele instante. A variável dados foi mantida de propósito, então o segundo snapshot deve mostrar crescimento. Em um caso real, tire snapshots depois de etapas equivalentes, por exemplo após processar o primeiro, o décimo e o centésimo lote.
Leia estatísticas e diferenças
Um snapshot pode agrupar blocos por lineno, filename ou traceback. lineno costuma ser o primeiro filtro útil porque aponta arquivo e linha. traceback separa a mesma linha conforme a pilha que levou até ela, o que ajuda em funções reutilizadas por vários fluxos.
O resultado de compare_to() contém size_diff, count_diff, size e count. size_diff mostra a variação líquida de bytes; count_diff, a variação no número de blocos. Uma linha com milhares de blocos pequenos pode indicar uma coleção crescente. Uma linha com poucos blocos grandes pode apontar buffers ou respostas mantidas. Analise os dois números, não apenas a primeira posição da lista.
Para transformar a saída em dados que um teste possa avaliar:
import tracemalloc
def executar_ciclos(quantidade: int) -> None:
for _ in range(quantidade):
processar_lote()
tracemalloc.start(15)
executar_ciclos(5) # aquecimento
base = tracemalloc.take_snapshot()
executar_ciclos(50)
final = tracemalloc.take_snapshot()
for item in final.compare_to(base, "filename"):
if item.size_diff > 100_000:
print(item.traceback, item.size_diff, item.count_diff)
O aquecimento reduz ruído de imports tardios, criação de pools e preenchimento inicial de caches. O limite de 100.000 bytes é apenas um critério de triagem, não uma definição universal de vazamento. Ajuste-o ao volume de trabalho e confirme se o crescimento continua em novas rodadas.
Filtre ruído sem apagar evidências
Snapshot.filter_traces() aceita objetos Filter e DomainFilter. Um filtro pode excluir frames internos do Python e testes auxiliares para destacar a aplicação:
filtros = [
tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
tracemalloc.Filter(False, "*/site-packages/*"),
tracemalloc.Filter(True, "*/minha_aplicacao/*"),
]
snapshot_filtrado = final.filter_traces(filtros)
for item in snapshot_filtrado.statistics("lineno")[:10]:
print(item)
Use exclusões com cautela. Se uma biblioteca retém objetos por causa da forma como sua aplicação a chama, remover site-packages pode esconder parte importante da investigação. Primeiro guarde uma análise completa; depois crie uma visão filtrada para navegar melhor.
Meça o consumo atual e o pico
get_traced_memory() retorna dois valores: bytes rastreados atualmente e o pico desde o início. reset_peak() redefine apenas o pico, sem interromper o rastreamento. Isso permite medir uma operação isolada:
tracemalloc.start()
carregar_configuracao()
tracemalloc.reset_peak()
processar_relatorio()
atual, pico = tracemalloc.get_traced_memory()
print(f"atual={atual / 1024:.1f} KiB")
print(f"pico={pico / 1024:.1f} KiB")
O pico ajuda a diagnosticar tarefas que liberam memória ao terminar, mas ultrapassam o limite durante o processamento. Já snapshots são melhores para descobrir o que permaneceu alocado. As duas leituras se complementam.
Reproduza antes de concluir que há vazamento
Uma comparação isolada não prova vazamento. Imports, caches limitados, conexões, compilação de expressões regulares e estruturas internas podem crescer uma vez e estabilizar. Faça várias rodadas idênticas, descarte o aquecimento e observe a tendência. Se necessário, execute gc.collect() antes de cada snapshot diagnóstico para reduzir diferenças de objetos coletáveis, sabendo que isso altera o comportamento normal e não deve virar uma correção artificial.
Também confirme se referências legítimas ainda existem. Uma lista global, um callback registrado, uma tarefa assíncrona não finalizada ou um cache sem limite pode manter objetos alcançáveis. O tracemalloc mostra a linha de alocação, não a cadeia de referências que os mantém vivos. Para essa segunda pergunta, use inspeção de referências, o módulo gc ou um profiler de objetos apropriado.
Evite afirmar que a memória “vazou” apenas porque o RSS não caiu após del e coleta de lixo. O Python pode liberar objetos para seus próprios pools sem devolver imediatamente as páginas ao sistema operacional. Da mesma forma, extensões em C podem aumentar o RSS sem aparecer de modo completo nos snapshots.
Salve snapshots para comparar execuções
Snapshots podem ser persistidos com dump() e carregados com Snapshot.load(). Isso é útil quando o ambiente que apresenta o problema não possui ferramentas interativas:
snapshot = tracemalloc.take_snapshot()
snapshot.dump("/tmp/apos-carga.snapshot")
## Em outra execução de análise:
carregado = tracemalloc.Snapshot.load("/tmp/apos-carga.snapshot")
for item in carregado.statistics("traceback")[:5]:
print(item)
O arquivo pode revelar caminhos, nomes de módulos e detalhes da aplicação. Trate-o como artefato de diagnóstico, controle acesso e não o publique. Para uma comparação válida entre execuções, mantenha a mesma versão do código, do Python, das dependências e uma carga semelhante.
Um roteiro prático de investigação
Primeiro, reproduza o crescimento com entradas controladas. Depois, ative o rastreamento antes do aquecimento, capture uma linha de base e repita unidades equivalentes de trabalho. Compare por lineno e por traceback, investigue as maiores diferenças positivas e verifique se elas continuam crescendo. Por fim, confronte o resultado com RSS, contadores de negócio e limites de caches.
Cuidados ao usar em testes e produção
Um teste de memória precisa tolerar pequenas variações do interpretador. Em vez de exigir um número exato de bytes, repita a operação, estabeleça uma margem baseada em medições e verifique uma tendência claramente anormal. Isole o teste de plugins, logs e processos paralelos. Resultados de versões diferentes do Python não devem ser comparados como se o alocador fosse idêntico.
Em produção, prefira uma janela curta e controlada. Ativar muitos frames durante horas pode consumir recursos justamente em um processo já pressionado. Registre a versão da aplicação, o volume processado e os horários dos snapshots; sem esse contexto, uma diferença grande pode refletir apenas cargas diferentes.
Chame tracemalloc.stop() ao terminar a coleta. clear_traces() apaga os rastros atuais e permite uma nova linha de base, mas invalida comparações com snapshots anteriores. Antes de limpar, salve apenas o artefato necessário em local protegido.
Ao comunicar o resultado, informe se o número representa tamanho atual, pico, diferença entre snapshots ou RSS. Cite as linhas suspeitas como pontos de investigação, não como culpa comprovada. A validação final deve mostrar que, após corrigir a retenção, a mesma carga estabiliza em rodadas sucessivas sem prejudicar o comportamento funcional.
Combine o diagnóstico com cProfile para analisar CPU somente quando também houver uma pergunta de tempo de execução. CPU e memória são dimensões diferentes. Para retenção intencional, revise limites e TTL com cachetools.
A documentação oficial do tracemalloc, consultada em 28 de julho de 2026, detalha inicialização, snapshots, filtros, domínios e medição de pico. Use-a como referência da versão do Python executada pelo projeto. Com medições em pontos equivalentes e uma distinção clara entre heap rastreado e memória do processo, o módulo transforma um sintoma vago em hipóteses verificáveis.