tracemalloc— Rastrea la asignación de memoria

Nuevo en la versión 3.4.

Código fuente: Lib/tracemalloc.py


El módulo tracemalloc es una herramienta de depuración para rastrear los espacios de memoria asignados por Python. Este proporciona la siguiente información:

  • El rastreo al lugar de origen del objeto asignado

  • Las estadísticas en los espacios de memoria asignados por nombre de archivo y por número de línea: tamaño total, número y tamaño promedio de los espacios de memoria asignados

  • Calcula las diferencias entre dos informes instantáneos para detectar alguna filtración en la memoria

Para rastrear la mayoría de los espacios de memoria asignados por Python; el módulo debe empezar tan pronto como sea posible configurando la variable del entorno PYTHONTRACEMALLOC a 1, o usando la opción del comando de línea -X tracemalloc. La función tracemalloc.start() puede ser llamada en tiempo de ejecución para empezar a rastrear las asignaciones de memoria de Python.

Por defecto, el rastreo de un espacio de memoria asignado solo guarda el cuadro mas reciente (1 cuadro). Para guardar 25 cuadros desde el PYTHONTRACEMALLOC`comienzo, configura la variable del entorno a ``25`, o usa -X tracemalloc=25 en la opción de línea de comando.

Ejemplos

Mostrar los 10 principales

Mostrar los 10 archivos asignando la mayor cantidad de memoria:

import tracemalloc

tracemalloc.start()

# ... run your application ...

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')

print("[ Top 10 ]")
for stat in top_stats[:10]:
    print(stat)

Ejemplo de la salida del conjunto de pruebas de Python:

[ Top 10 ]
<frozen importlib._bootstrap>:716: size=4855 KiB, count=39328, average=126 B
<frozen importlib._bootstrap>:284: size=521 KiB, count=3199, average=167 B
/usr/lib/python3.4/collections/__init__.py:368: size=244 KiB, count=2315, average=108 B
/usr/lib/python3.4/unittest/case.py:381: size=185 KiB, count=779, average=243 B
/usr/lib/python3.4/unittest/case.py:402: size=154 KiB, count=378, average=416 B
/usr/lib/python3.4/abc.py:133: size=88.7 KiB, count=347, average=262 B
<frozen importlib._bootstrap>:1446: size=70.4 KiB, count=911, average=79 B
<frozen importlib._bootstrap>:1454: size=52.0 KiB, count=25, average=2131 B
<string>:5: size=49.7 KiB, count=148, average=344 B
/usr/lib/python3.4/sysconfig.py:411: size=48.0 KiB, count=1, average=48.0 KiB

Se puede ver que Python ha cargado 4855 KiB de data (código de bytes y constantes) desde los módulos y que el modulo collections asigno 24KiB para crear tipos namedtuple.

Mira Snapshot.statistics() para más opciones.

Calcula las diferencias

Toma dos capturas instantáneas y muestra las diferencias:

import tracemalloc
tracemalloc.start()
# ... start your application ...

snapshot1 = tracemalloc.take_snapshot()
# ... call the function leaking memory ...
snapshot2 = tracemalloc.take_snapshot()

top_stats = snapshot2.compare_to(snapshot1, 'lineno')

print("[ Top 10 differences ]")
for stat in top_stats[:10]:
    print(stat)

Ejemplo de la salida antes y después de probar el conjunto de pruebas de Python:

[ Top 10 differences ]
<frozen importlib._bootstrap>:716: size=8173 KiB (+4428 KiB), count=71332 (+39369), average=117 B
/usr/lib/python3.4/linecache.py:127: size=940 KiB (+940 KiB), count=8106 (+8106), average=119 B
/usr/lib/python3.4/unittest/case.py:571: size=298 KiB (+298 KiB), count=589 (+589), average=519 B
<frozen importlib._bootstrap>:284: size=1005 KiB (+166 KiB), count=7423 (+1526), average=139 B
/usr/lib/python3.4/mimetypes.py:217: size=112 KiB (+112 KiB), count=1334 (+1334), average=86 B
/usr/lib/python3.4/http/server.py:848: size=96.0 KiB (+96.0 KiB), count=1 (+1), average=96.0 KiB
/usr/lib/python3.4/inspect.py:1465: size=83.5 KiB (+83.5 KiB), count=109 (+109), average=784 B
/usr/lib/python3.4/unittest/mock.py:491: size=77.7 KiB (+77.7 KiB), count=143 (+143), average=557 B
/usr/lib/python3.4/urllib/parse.py:476: size=71.8 KiB (+71.8 KiB), count=969 (+969), average=76 B
/usr/lib/python3.4/contextlib.py:38: size=67.2 KiB (+67.2 KiB), count=126 (+126), average=546 B

Se puede ver que Python cargó 8173 KiB de información del modulo (código de bytes y constantes), y que eso es `4428KiB más de lo que ha sido cargado antes de los test, cuando la anterior captura de pantalla fue tomada. De manera similar, el modulo linecache ha almacenado en caché``940 KiB`` del código fuente de Python para formatear los seguimientos, todo desde la captura instántanea.

Si el sistema tiene poca memoria libre, los informes instantáneos pueden ser escritos en el disco usando el método Snapshot.dump() para analizar el informe instantáneo offline. Después usa el método Snapshot.load() para actualizar el informe.

Consigue el seguimiento del bloque de memoria

Código para configurar el seguimiento del bloque de memoria más grande:

import tracemalloc

# Store 25 frames
tracemalloc.start(25)

# ... run your application ...

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('traceback')

# pick the biggest memory block
stat = top_stats[0]
print("%s memory blocks: %.1f KiB" % (stat.count, stat.size / 1024))
for line in stat.traceback.format():
    print(line)

Ejemplo de la salida del conjunto de pruebas de Python (rastreo limitado a 25 cuadros):

903 memory blocks: 870.1 KiB
  File "<frozen importlib._bootstrap>", line 716
  File "<frozen importlib._bootstrap>", line 1036
  File "<frozen importlib._bootstrap>", line 934
  File "<frozen importlib._bootstrap>", line 1068
  File "<frozen importlib._bootstrap>", line 619
  File "<frozen importlib._bootstrap>", line 1581
  File "<frozen importlib._bootstrap>", line 1614
  File "/usr/lib/python3.4/doctest.py", line 101
    import pdb
  File "<frozen importlib._bootstrap>", line 284
  File "<frozen importlib._bootstrap>", line 938
  File "<frozen importlib._bootstrap>", line 1068
  File "<frozen importlib._bootstrap>", line 619
  File "<frozen importlib._bootstrap>", line 1581
  File "<frozen importlib._bootstrap>", line 1614
  File "/usr/lib/python3.4/test/support/__init__.py", line 1728
    import doctest
  File "/usr/lib/python3.4/test/test_pickletools.py", line 21
    support.run_doctest(pickletools)
  File "/usr/lib/python3.4/test/regrtest.py", line 1276
    test_runner()
  File "/usr/lib/python3.4/test/regrtest.py", line 976
    display_failure=not verbose)
  File "/usr/lib/python3.4/test/regrtest.py", line 761
    match_tests=ns.match_tests)
  File "/usr/lib/python3.4/test/regrtest.py", line 1563
    main()
  File "/usr/lib/python3.4/test/__main__.py", line 3
    regrtest.main_in_temp_cwd()
  File "/usr/lib/python3.4/runpy.py", line 73
    exec(code, run_globals)
  File "/usr/lib/python3.4/runpy.py", line 160
    "__main__", fname, loader, pkg_name)

Se puede ver que la mayor parte de la memoria fue asignada en el módulo importlib para cargar datos (códigos de bytes y constantes) desde módulos 870.1 KiB. El rastreo esta donde el módulo importlib cargó datos más recientemente: en la linea import pdb el módulo doctest.

Los 10 más bonitos

Codifica para configurar las 10 líneas que asignan gran parte de la memoria con una salida bonita, ignorando los archivos``<frozen importlib._bootstap>`` y <unkownn>:

import linecache
import os
import tracemalloc

def display_top(snapshot, key_type='lineno', limit=10):
    snapshot = snapshot.filter_traces((
        tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
        tracemalloc.Filter(False, "<unknown>"),
    ))
    top_stats = snapshot.statistics(key_type)

    print("Top %s lines" % limit)
    for index, stat in enumerate(top_stats[:limit], 1):
        frame = stat.traceback[0]
        print("#%s: %s:%s: %.1f KiB"
              % (index, frame.filename, frame.lineno, stat.size / 1024))
        line = linecache.getline(frame.filename, frame.lineno).strip()
        if line:
            print('    %s' % line)

    other = top_stats[limit:]
    if other:
        size = sum(stat.size for stat in other)
        print("%s other: %.1f KiB" % (len(other), size / 1024))
    total = sum(stat.size for stat in top_stats)
    print("Total allocated size: %.1f KiB" % (total / 1024))

tracemalloc.start()

# ... run your application ...

snapshot = tracemalloc.take_snapshot()
display_top(snapshot)

Ejemplo de la salida del conjunto de pruebas de Python:

Top 10 lines
#1: Lib/base64.py:414: 419.8 KiB
    _b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
#2: Lib/base64.py:306: 419.8 KiB
    _a85chars2 = [(a + b) for a in _a85chars for b in _a85chars]
#3: collections/__init__.py:368: 293.6 KiB
    exec(class_definition, namespace)
#4: Lib/abc.py:133: 115.2 KiB
    cls = super().__new__(mcls, name, bases, namespace)
#5: unittest/case.py:574: 103.1 KiB
    testMethod()
#6: Lib/linecache.py:127: 95.4 KiB
    lines = fp.readlines()
#7: urllib/parse.py:476: 71.8 KiB
    for a in _hexdig for b in _hexdig}
#8: <string>:5: 62.0 KiB
#9: Lib/_weakrefset.py:37: 60.0 KiB
    self.data = set()
#10: Lib/base64.py:142: 59.8 KiB
    _b32tab2 = [a + b for a in _b32tab for b in _b32tab]
6220 other: 3602.8 KiB
Total allocated size: 5303.1 KiB

Mira Snapshot.statistics() para más opciones.

API

Funciones

tracemalloc.clear_traces()

Limpia los rastros de los bloques de memoria asignados por Python.

Mira también la función stop().

tracemalloc.get_object_traceback(obj)

Obtén el rastreo de donde el objeto de Python fue asignado. Retorna una instancia Traceback o None si el módulo tracemalloc no esta rastreando ninguna asignación de memoria o no rastreó la asignación del objeto.

Mira también las funciones gc.get_referrers() y sys.getsizeof().

tracemalloc.get_traceback_limit()

Obtén el número máximo de cuadros guardados en el seguimiento de un rastro.

El módulo tracemalloc debe rastrear las asignaciones de memoria para obtener el límite, de otra manera se inicia una excepción.

El limite es establecido por la función start().

tracemalloc.get_traced_memory()

Obtén el tamaño actual y tamaño pico de los bloques de memorias rastreados por el módulo tracemalloc como una tupla: (current: int, peak: int).

tracemalloc.get_tracemalloc_memory()

Obtén el uso de la memoria en bytes desde el modulo tracemalloc usado para guardar rastreos de bloques de memoria. Retorna una clase int.

tracemalloc.is_tracing()

Si el módulo tracemalloc esta rastreando asignaciones de memoria de Python retorna True sino retorna False.

También mira las funciones start() y stop().

tracemalloc.start(nframe: int=1)

Empieza a rastrear las asignaciones de memoria de Python: instala hooks en las asignaciones de memoria de Python. Los rastreos coleccionados van a estar limitados a nframe. Por defecto, un rastro de un bloque de memoria solo guarda el cuadro mas reciente: el limite es 1. nframe debe ser mayor o igual a 1.

Guardar mas de 1 cuadro es solo útil para calcular estadísticas agrupadas por seguimiento o para calcular estadísticas acumulativa: mira las funciones Snapshot.compare_to() y Snapshot.statistics().

Guardar mas cuadros aumenta la memoria y la sobrecargar de la CPU del modulo tracemalloc. Usa la función get_tracemalloc_memory() para medir cuanta memoria se usa por el módulo tracemalloc.

La variable del entorno PYTHONTRACEMALLOC (PYTHONTRACEMALLOC=NFRAME) y la opción de comando de linea -X tracemalloc=NFRAME se puede usar para empezar a rastrear desde el inicio.

También mira las funciones stop(), is_tracing() y get_traceback_limit().

tracemalloc.stop()

Stop tracing Python memory allocations: uninstall hooks on Python memory allocators. Also clears all previously collected traces of memory blocks allocated by Python.

Llama a la función take_snapshot() para tomar una captura instantánea de los rastreos, antes de limpiarlos.

También mira las funciones start(), is_tracing() y clear_traces().

tracemalloc.take_snapshot()

Toma una captura instantánea de los bloques de memoria asignados por Python. Retorna una nueva instancia de la clase Snapshot.

La captura instantánea no incluye ningún bloque de memoria asignado antes de que el módulo tracemalloc haya empezado a rastrear asignaciones de memoria.

Los seguimientos de los rastros son limitados por la función get_traceback_limit(). Usa el parámetro nframe de la función start() para guardar mas cuadros.

El módulo tracemalloc debe empezar a rastrear las asignaciones de memoria para tomar una captura instantánea. Mira la función start().

También mira la función get_object_traceback().

Filtro de dominio

class tracemalloc.DomainFilter(inclusive: bool, domain: int)

Filtra los rastros de los bloques de memoria por su espacio de dirección (dominio)

Nuevo en la versión 3.6.

inclusive

Si inclusive es True (incluye), relaciona los bloques de memoria asignados en el espacio de dirección domain.

Si inclusive es False (excluye), relaciona los bloques de memoria no asignados en el espacio de dirección domain.

domain

Espacio de dirección de un bloque de memoria (int). Propiedad solo-lectura.

Filtro

class tracemalloc.Filter(inclusive: bool, filename_pattern: str, lineno: int=None, all_frames: bool=False, domain: int=None)

Filtra los rastros de los bloques de memoria.

También mira la función fnmatch.fnmatch() para la sintaxis de filename_pattern. La extensión '.pyc' es remplazada por '.py'.

Ejemplos:

  • Filter(True, subprocess.__file__) solo incluye los rastros de el módulo subprocess

  • Filter(False, tracemalloc.__file__) excluye los rastros de memoria del módulo tracemalloc

  • Filter(False, "<unknown>") excluye los seguimientos vacíos

Distinto en la versión 3.5: La extensión '.pyo' ya no se remplaza con '.py'.

Distinto en la versión 3.6: Agregado el atributo domain .

domain

El espacio de dirección de un bloque de memoria (int o None).

tracemalloc usa el dominio 0 para rastrear las asignaciones de memoria hechas por Python. Las extensiones C pueden usar otros dominios para rastrear otros recursos.

inclusive

Si inclusive es True (incluye), solo relaciona los bloques de memoria asignados en un archivo con el nombre coincidiendo con el atributo filename_pattern en el número de línea del atributo lineno.

Si inclusive es False (excluye), ignora los bloques de memoria asignados en un archivo con el nombre coincidiendo con el atributo filename_pattern en el número de línea del atributo lineno.

lineno

El número de linea (int) del filtro. Si lineno es None, el filtro se relaciona con cualquier número de linea.

filename_pattern

El patrón del nombre de archivo del filtro (str). Propiedad solo-lectura.

all_frames

Si all_frames es True, todos los cuadros de los rastreos son chequeados. Si all_frames es False, solo el cuadro mas reciente es chequeado.

El atributo no tiene efecto si el limite de rastreo es 1. Mira la función get_traceback_limit() y el atributo Snapshot.traceback_limit.

Cuadro

class tracemalloc.Frame

Cuadro de un rastreo.

La clase Traceback es una secuencia de las instancias de la clase Frame.

filename

Nombre de archivo (str).

lineno

Número de línea (int).

Captura instantánea

class tracemalloc.Snapshot

Captura instantánea de los rastros de los bloques de memoria asignados por Python.

La función take_snapshot() crea una instancia de captura instantánea.

compare_to(old_snapshot: Snapshot, key_type: str, cumulative: bool=False)

Calcula las diferencias con una vieja captura instantánea. Obtiene las estadísticas en una lista ordenada de instancias de la clase StatisticDiff agrupadas por key_type.

Mira el método Snapshot.statistics() para los parámetros key_type y cumulative.

El resultado esta guardado desde el más grande al más pequeño por los valores absolutos de StatisticDiff.size_diff(), StatisticDiff.size(), el valor absoluto de StatisticDiff.count_diff(), Statistic.count() y después por el atributo StatisticDiff.traceback().

dump(filename)

Escribe la captura instantánea en un archivo.

Usa el método load() para recargar la captura instantánea.

filter_traces(filters)

Crea una nueva instancia de clase Snapshot con una secuencia de traces filtrados, filters es una lista de las instancias de DomainFilter y Filter. Si filters es una lista vacia, retorna una nueva instancia de clase Snapshot con una copia de los rastreos.

Los filtros todo incluido se aplican de a uno, si los filtros no incluidos coinciden. Si al menos un filtro exclusivo coincide, se ignora un rastro.

Distinto en la versión 3.6: Las instancias de clase DomainFilter ahora también son aceptadas en filters.

classmethod load(filename)

Carga la captura instantánea desde un archivo.

También mira el método dump().

statistics(key_type: str, cumulative: bool=False)

Obtiene estadísticas como una lista ordenada, de instancias de Statistic agrupadas por key_type:

key_type

descripción

'filename'

nombre del archivo

'lineno'

nombre del archivo y número de línea

'traceback'

seguimiento

Si cumulative es True, acumula el tamaño y cuenta los bloques de memoria de todos los cuadros del seguimiento de un rastro, no solo el cuadro mas reciente. El modo acumulativo solo puede ser usado cuando key_type se iguala a 'filename' y 'lineno'.

El resultado se organiza desde el más grande hasta el más pequeño por el atributo Statistic.size, Statistic.count y después por Statistic.traceback.

traceback_limit

El número máximo de cuadros organizados en el rastreo del atributo traces: resulta de la función get_traceback_limit cuando la captura instantánea fue tomada.

traces

Rastros de todos los bloques de memoria asignados por Python: secuencia de instancias de Trace.

La secuencia tiene un orden que no esta definido. Usa el método Snapshot.statistics() para obtener una lista ordenada de estadísticas.

Estadística

class tracemalloc.Statistic

Estadística de las asignaciones de memoria.

Snapshot.statistics() retorna una lista de instancias de la clase Statistic.

También mira la clase StatisticDiff.

count

Número de bloques de memoria (int).

size

El tamaño total de los bloques de memoria en bytes (int).

traceback

Rastrea donde un bloque de memoria fue asignado, la instancia de la clase Traceback.

StatisticDiff

class tracemalloc.StatisticDiff

La diferencia de estadística en las asignaciones de memoria entre una vieja y una nueva instancia de clase Snapshot.

Snapshot.compare_to() retorna una lista de instancias de la clase StatisticDiff. Mira también la clase Statistic.

count

El número de bloques de memoria en la nueva captura de pantalla (int): 0 si los bloques de memoria han sido liberados en la nueva captura instantánea.

count_diff

La diferencia de los números de los bloques de memoria entre las capturas viejas y nuevas (int): 0 si el bloque de memoria ha sido asignado en la nueva captura instantánea.

size

El tamaño total de los bloques de memoria en bytes en la nueva captura instantánea (int): 0 si el bloque de memoria ha sido liberado en la nueva captura instantánea.

size_diff

La diferencia de el tamaño total de un bloque de memoria en bytes entre las capturas instantáneas viejas y nuevas (int): 0 si el bloque de memoria ha sido asignado en la nueva captura instantánea.

traceback

Rastrea donde los bloques de memoria han sido asignados, instancia de la clase Traceback.

Rastro

class tracemalloc.Trace

Rastro de un bloque de memoria.

El atributo Snapshot.traces es una secuencia de las instancias de la clase Trace.

Distinto en la versión 3.6: Agregado el atributo domain .

domain

Espacio de dirección de un bloque de memoria (int). Propiedad solo-lectura.

tracemalloc usa el dominio 0 para rastrear las asignaciones de memoria hechas por Python. Las extensiones C pueden usar otros dominios para rastrear otros recursos.

size

Tamaño de un bloque de memoria en bytes (int).

traceback

Rastrea donde un bloque de memoria fue asignado, la instancia de la clase Traceback.

Seguimiento

class tracemalloc.Traceback

La secuencia de las instancias de la clase Frame organizadas desde el cuadro mas antiguo al más reciente.

Un seguimiento contiene por lo menos 1 cuadro. Si el módulo tracemalloc falla al traer un cuadro, se usa el nombre del archivo "<unknown>" en el número de linea 0.

Cuando se toma una captura instantánea, los seguimientos de los rastros son limitados a get_traceback_limit() cuadros. Mira la función take_snapshot().

El atributo Trace.traceback es una instancia de la clase Traceback.

Distinto en la versión 3.7: Los cuadros están organizados desde el mas antiguo hasta el más reciente, en vez de el más reciente al más antiguo.

format(limit=None, most_recent_first=False)

Formatea el seguimiento como una lista de líneas con nuevas líneas. Usa el módulo linecache para obtener líneas del código fuente. Si se establece limit, si limit es positivo: formatea el cuadro mas reciente. Si no, formatea los abs(limit) cuadros más antiguos. Si most_recent_first es True, el orden de los cuadros formateados es invertido, retornando primero el cuadro más reciente en vez del último.

Similar a la función traceback.format_tb(), excepto por el método format() que no incluye nuevas líneas.

Ejemplo:

print("Traceback (most recent call first):")
for line in traceback:
    print(line)

Salida:

Traceback (most recent call first):
  File "test.py", line 9
    obj = Object()
  File "test.py", line 12
    tb = tracemalloc.get_object_traceback(f())