tracemalloc: no se pueden capturar marcos superiores
Estoy intentando encontrar fugas de memoria en un proyecto enorme usando tracemalloc
para extraer marcos de código. Sin embargo, no puedo obtener más de un marco más profundo, lo cual es inútil para proyectos grandes con muchas dependencias y miríadas de llamadas anidadas. De acuerdo con la documentación de Python, intenté usar:
- El argumento de límite de
tracemalloc.start
. - La variable de entorno
PYTHONTRACEMALLOC
. - El argumento de línea de comando
-X tracemalloc
.
Y tracemalloc.get_traceback_limit()
muestra el número correcto que he establecido. Sin embargo, todavía obtengo solo un marco por objeto Traceback. Funciona de la misma manera en las versiones de Python 3.8.5 y 3.9.7 en diferentes máquinas. ¿Qué está mal aquí? ¿Cómo puedo solucionar esto?
Aquí hay un ejemplo mínimo:
“`py
import os
import tracemalloc
def get_top_malloc(trace_number):
snapshot = tracemalloc.take_snapshot()
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False, "<frozen importlib.bootstrap="">"),
tracemalloc.Filter(False, "<unknown>"),
))
top_stats = snapshot.statistics("lineno")
msg = []
if trace_number > 0:
msg.append(f"Top malloc {trace_number} lines")
for index, stat in enumerate(top_stats[:trace_number]):
msg.append(f"#{index}: {stat.size // 1024} KB, {stat.count} times")
print(stat.traceback.frames) # ¡Aquí solo hay un solo marco!
print(dir(stat.traceback))
msg.extend(line for line in stat.traceback.format(limit=16))
other = top_stats[trace_number:]
if other:
size = sum(stat.size for stat in other)
msg.append(f"{len(other)} other: {size // 1024} KB")
total = sum(stat.size for stat in top_stats)
msg.append(f"Total allocated size: {total // 1024 // 1024} MB")
return "\n".join(msg)
storage = {}
def func2():
def func3():
value = '3.1415926535897932384626433832795028841971'
value = value * 4096
storage['pi'] = value
func3()
def func1():
func2()
if <strong>name</strong> == "<strong>main</strong>":
tracemalloc.start(4)
print(f"\n– Limit: {tracemalloc.get_traceback_limit()}")
func1()
print(f"\n– {get_top_malloc(1)}")
tracemalloc.stop()
“`
davy.ai
Parece que la limitación no está relacionada con
tracemalloc
en sí, sino más bien con cómo se genera la traza. El problema es quetraceback.extract_stack
(que es utilizado portracemalloc
para generar la traza) siempre devuelve sólo el marco más profundo, lo que hace imposible quetracemalloc
vuelva más atrás en la traza.Una forma de solucionar esta limitación es utilizar un método alternativo para generar la traza, como
sys._current_frames
, que devuelve el marco actual para cada hilo. Al recorrer todos los marcos en todos los hilos, es posible construir una traza completa que se remonta hasta la raíz de cada hilo.Aquí hay una implementación de ejemplo:
Los cambios clave son:
"traceback"
en lugar de"lineno"
en la llamada destatistics
para obtener la traza completa.sys._current_frames
y construir la traza manualmente.