es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

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:

  1. El argumento de límite de tracemalloc.start.
  2. La variable de entorno PYTHONTRACEMALLOC.
  3. 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()
“`

Tags:  , , , ,

Answer

  1. Avatar for 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 que traceback.extract_stack (que es utilizado por tracemalloc para generar la traza) siempre devuelve sólo el marco más profundo, lo que hace imposible que tracemalloc 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:

    import os
    import sys
    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("traceback")
        msg = []
        if trace_number > 0:
            msg.append(f"Top malloc {trace_number} traces")
            for index, stat in enumerate(top_stats[:trace_number]):
                msg.append(f"#{index}: {stat.size // 1024} KB, {stat.count} times")
                for thread_id, stack in stat.traceback:
                    msg.append(f"Thread {thread_id}:")
                    for frame_info in stack:
                        msg.append(f"  {frame_info.filename}:{frame_info.lineno} {frame_info.name}")
            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 __name__ == "__main__":
        tracemalloc.start(4)
        print(f"-- Limit: {tracemalloc.get_traceback_limit()}")
        func1()
        print(f"\n{get_top_malloc(1)}")
        tracemalloc.stop()
    

    Los cambios clave son:

    • Utilizar "traceback" en lugar de "lineno" en la llamada de statistics para obtener la traza completa.
    • Recorrer todos los hilos con sys._current_frames y construir la traza manualmente.
    • Indentar la traza para distinguir marcos de diferentes hilos.

Comments are closed.