tracemalloc — Трассировка выделения памяти

Добавлено в версии 3.4.


Модуль tracemalloc — это инструмент отладки для трассировки блоков памяти, выделенных Python. Он предоставляет следующую информацию:

  • Трассировка, где был выделен (allocated) объект
  • Статистика по выделенным блокам памяти по имени файла и по номеру строки: общий размер, количество и средний размер выделенных блоков памяти
  • Вычислите различия между двумя снимками для обнаружения утечек памяти

Чтобы отследить большинство блоков памяти, выделенных Python, модуль следует запустить как можно раньше, установив для переменной среды PYTHONTRACEMALLOC значение 1 или используя параметр командной строки -X tracemalloc. Функцию tracemalloc.start() можно вызвать во время выполнения, чтобы начать отслеживание выделения памяти Python.

По умолчанию трассировка выделенного блока памяти сохраняет только самый последний фрейм (1 фрейм). Чтобы сохранить 25 фреймов при запуске: устанавливает для переменной среды PYTHONTRACEMALLOC значение 25 или используйте параметр командной строки -X tracemalloc=25.

Примеры

Показать верхнюю десятку

Показать 10 файлов, занимающих больше всего памяти:

import tracemalloc

tracemalloc.start()

# ... работа приложения ...

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

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

Пример вывода набора тестов 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

Мы видим, что Python загрузил данных на 4855 KiB (байт-код и константы) из модулей и что модуль collections выделил 244 KiB для построения типов namedtuple.

Дополнительные параметры см. в Snapshot.statistics().

Вычислить различия

Сделать два снимка и показать различия:

import tracemalloc
tracemalloc.start()
# ... работа приложения ...

snapshot1 = tracemalloc.take_snapshot()
# ... вызвать функцию с утечкой памяти ...
snapshot2 = tracemalloc.take_snapshot()

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

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

Пример вывода до/после выполнения некоторых тестов набора тестов 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

Мы видим, что Python загрузил 8173 KiB данных модуля (байт-код и константы), и это на 4428 KiB больше, чем было загружено до тестов, когда был сделан предыдущий снимок. Точно так же модуль linecache кэшировал 940 KiB исходного кода Python для форматирования обратной трассировки, начиная с предыдущего снимка.

Если в системе мало свободной памяти, снимки можно записать на диск с помощью метода Snapshot.dump() для анализа снимка в автономном режиме. Затем используйте метод Snapshot.load(), чтобы перезагрузить снимок.

Получить трассировку блока памяти

Код для отображения трассировки самого большого блока памяти:

import tracemalloc

# Сохранить 25 фреймов
tracemalloc.start(25)

# ... работа приложения ...

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

# выберать самый большой блок памяти
stat = top_stats[0]
print("%s memory blocks: %.1f KiB" % (stat.count, stat.size / 1024))
for line in stat.traceback.format():
    print(line)

Пример вывода набора тестов Python (трассировка ограничена 25 фреймами):

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)

Мы видим, что больше всего памяти было выделено в модуле importlib для загрузки данных (байт-кода и констант) из модулей: 870.1 KiB. Трассировка — это место, где importlib загрузил данные в последний раз: в строке import pdb модуля doctest. Трассировка может измениться, если загружается новый модуль.

Симпатичная вершина

Код для отображения 10 строк, выделяющих больше всего памяти, с красивым выводом, игнорируя файлы <frozen importlib._bootstrap> и <unknown>:

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()

# ... работа приложения ...

snapshot = tracemalloc.take_snapshot()
display_top(snapshot)

Пример вывода набора тестов 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

Дополнительные параметры см. в Snapshot.statistics().

API

Функции

tracemalloc.clear_traces()

Очистить следы блоков памяти, выделенных Python.

См. также stop().

tracemalloc.get_object_traceback(obj)

Получить трассировку, где был выделен Python объект obj. Возвращает экземпляр Traceback или None, если модуль tracemalloc не отслеживает выделение памяти или не отслеживает выделение объекта.

См. также функции gc.get_referrers() и sys.getsizeof().

tracemalloc.get_traceback_limit()

Получить максимальное количество фреймов, хранящихся в обратной трассировке трассировки.

Модуль tracemalloc должен отслеживать выделение памяти, чтобы получить ограничение, в противном случае возникает исключение.

Ограничение задаётся функцией start().

tracemalloc.get_traced_memory()

Получить текущий размер и пиковый размер блоков памяти, отслеживаемых модулем tracemalloc, в виде кортежа: (current: int, peak: int).

tracemalloc.get_tracemalloc_memory()

Получить использование памяти в байтах модуля tracemalloc, используемого для хранения следов блоков памяти. Возвращает int.

tracemalloc.is_tracing()

True, если модуль tracemalloc отслеживает выделение памяти Python, False в противном случае.

См. также функции start() и stop().

tracemalloc.start(nframe: int=1)

Начать отслеживать выделение памяти Python: устанавливает хуки на распределители памяти Python. Собранные трассировки трассировок будут ограничены фреймами nframe. По умолчанию трассировка блока памяти хранит только самый последний фрейм: ограничение составляет 1. nframe должен быть больше или равен 1.

Сохранение фрейма, превышающего 1, полезно только для вычисления статистики, сгруппированной по 'traceback', или для вычисления совокупной статистики: см. методы Snapshot.compare_to() и Snapshot.statistics().

Хранение большего количества фреймов увеличивает нагрузку на память и ЦП модуля tracemalloc. Используйте функцию get_tracemalloc_memory(), чтобы измерить, сколько памяти используется модулем tracemalloc.

Переменная среды PYTHONTRACEMALLOC (PYTHONTRACEMALLOC=NFRAME) и параметр командной строки -X tracemalloc=NFRAME могут использоваться для запуска трассировки при запуске.

См. также функции stop(), is_tracing() и get_traceback_limit().

tracemalloc.stop()

Прекратить отслеживать выделение памяти Python: удалить перехватчики на распределителях памяти Python. Также очищает все ранее собранные следы блоков памяти, выделенных Python.

Вызвать функцию take_snapshot(), чтобы сделать снимок трассировки перед её очисткой.

См. также функции start(), is_tracing() и clear_traces().

tracemalloc.take_snapshot()

Сделать снимок следов блоков памяти, выделенных Python. Возвращает новый экземпляр Snapshot.

Снимок не включает блоки памяти, выделенные до того, как модуль tracemalloc начал отслеживать выделение памяти.

Трассировки трассировок ограничены фреймами get_traceback_limit(). Используйте параметр nframe функции start() для хранения большего количества фреймов.

Модуль tracemalloc должен отслеживать выделение памяти, чтобы сделать моментальный снимок, см. функцию start().

См. также функцию get_object_traceback().

DomainFilter

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

Фильтрация следов блоков памяти по их адресному пространству (домену).

Добавлено в версии 3.6.

inclusive

Если inclusive равен True (включить), сопоставляет блоки памяти, выделенные в адресном пространстве domain.

Если inclusive равен False (исключить), сопоставить блоки памяти, не выделенные в адресном пространстве domain.

domain

Адресное пространство блока памяти (int). Свойство только для чтения.

Filter

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

Фильтр по следам блоков памяти.

См. функцию fnmatch.fnmatch() для синтаксиса filename_pattern. Расширение файла '.pyc' заменено на '.py'.

Примеры:

  • Filter(True, subprocess.__file__) включает только следы модуля subprocess
  • Filter(False, tracemalloc.__file__) исключает следы модуля tracemalloc
  • Filter(False, "<unknown>") исключает пустые трассировки

Изменено в версии 3.5: Расширение файла '.pyo' больше не заменяется на '.py'.

Изменено в версии 3.6: Добавлен атрибут domain.

domain

Адресное пространство блока памяти (int или None).

tracemalloc использует домен 0 для отслеживания выделения памяти, сделанного Python. Расширения C могут использовать другие домены для отслеживания других ресурсов.

inclusive

Если inclusive равно True (включить), сопоставляются только блоки памяти, выделенные в файле с именем, совпадающим с filename_pattern в строке с номером lineno.

Если inclusive равен False (исключить), игнорировать блоки памяти, выделенные в файле с именем, соответствующим filename_pattern в строке с номером lineno.

lineno

Номер строки (int) фильтра. Если lineno равен None, фильтр соответствует любому номеру строки.

filename_pattern

Шаблон имени файла фильтра (str). Свойство только для чтения.

all_frames

Если all_frames равен True, проверяются все фреймы трассировки. Если all_frames равен False, проверяется только самый последний фрейм.

Данный атрибут не действует, если предел трассировки равен 1. См. функцию get_traceback_limit() и атрибут Snapshot.traceback_limit.

Frame

class tracemalloc.Frame

Фрейм трассировки.

Класс Traceback представляет собой последовательность экземпляров Frame.

filename

Имя файла (str).

lineno

Номер строки (int).

Snapshot

class tracemalloc.Snapshot

Снимок следов блоков памяти, выделенных Python.

Функция take_snapshot() создаёт экземпляр моментального снимка.

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

Вычисляет различия со старым снимком. Получить статистику в виде отсортированного списка экземпляров StatisticDiff, сгруппированных по key_type.

См. метод Snapshot.statistics() для параметров key_type и cumulative.

Результат сортируется от большего к меньшему по: абсолютному значению StatisticDiff.size_diff, StatisticDiff.size, абсолютному значению StatisticDiff.count_diff, Statistic.count, а затем по StatisticDiff.traceback.

dump(filename)

Записать снимок в файл.

Используйте load(), чтобы перезагрузить снимок.

filter_traces(filters)

Создать новый экземпляр Snapshot с отфильтрованной последовательностью traces, filters — это список экземпляров DomainFilter и Filter. Если filters — пустой список, возвращает новый экземпляр Snapshot с копией трассировок.

Все включающие фильтры применяются сразу, трасса игнорируется, если ни один из включающих фильтров не соответствует ей. Трассировка игнорируется, если ей соответствует хотя бы один эксклюзивный фильтр.

Изменено в версии 3.6: Экземпляры DomainFilter теперь также принимаются в filters.

classmethod load(filename)

Загрузить снимок из файла.

См. также dump().

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

Получить статистику в виде отсортированного списка экземпляров Statistic, сгруппированных по key_type:

key_type описание
'filename' имя файла
'lineno' имя файла и номер строки
'traceback' traceback

Если cumulative равен True, суммирует размер и количество блоков памяти всех фреймов обратной трассировки, а не только самого последнего фрейма. Кумулятивный режим можно использовать только с key_type равным 'filename' и 'lineno'.

Результат сортируется от большего к меньшему по: Statistic.size, Statistic.count и затем по Statistic.traceback.

traceback_limit

Максимальное количество фреймов, сохранённых в трассировке traces: результат get_traceback_limit() на момент создания моментального снимка.

traces

Следы всех блоков памяти, выделенных Python: последовательность экземпляров Trace.

Последовательность имеет неопределенный порядок. Используйте метод Snapshot.statistics(), чтобы получить отсортированный список статистики.

Statistic

class tracemalloc.Statistic

Статистика по выделению памяти.

Snapshot.statistics() возвращает список экземпляров Statistic.

См. также класс StatisticDiff.

count

Количество блоков памяти (int).

size

Общий размер блоков памяти в байтах (int).

traceback

Трассировка, где был выделен блок памяти, экземпляр Traceback.

StatisticDiff

class tracemalloc.StatisticDiff

Статистическая разница в выделении памяти между старым и новым экземпляром Snapshot.

Snapshot.compare_to() возвращает список экземпляров StatisticDiff. См. также класс Statistic.

count

Количество блоков памяти в новом снимке (int): 0, если блоки памяти были освобождены в новом снимке.

count_diff

Разница в количестве блоков памяти между старым и новым снимками (int): 0, если блоки памяти были выделены в новом снимке.

size

Общий размер блоков памяти в байтах в новом снимке (int): 0, если блоки памяти были освобождены в новом снимке.

size_diff

Разница общего размера блоков памяти в байтах между старым и новым снимками (int): 0, если блоки памяти были выделены в новом снимке.

traceback

Трассировка, где были выделены блоки памяти, экземпляр Traceback.

Trace

class tracemalloc.Trace

След блока памяти.

Атрибут Snapshot.traces представляет собой последовательность экземпляров Trace.

Изменено в версии 3.6: Добавлен атрибут domain.

domain

Адресное пространство блока памяти (int). Свойство только для чтения.

tracemalloc использует домен 0 для отслеживания выделения памяти, сделанного Python. Расширения C могут использовать другие домены для отслеживания других ресурсов.

size

Размер блока памяти в байтах (int).

traceback

Трассировка, где был выделен блок памяти, экземпляр Traceback.

Traceback

class tracemalloc.Traceback

Последовательность экземпляров Frame, отсортированных от самого старого до самого последнего фрейма.

Трассировка содержит как минимум фрейм 1. Если модулю tracemalloc не удалось получить фрейм, используется имя файла "<unknown>" в строке с номером 0.

При создании моментального снимка обратная трассировка трассировок ограничена фреймами get_traceback_limit(). См. функцию take_snapshot().

Атрибут Trace.traceback является экземпляром экземпляра Traceback.

Изменено в версии 3.7: Фреймы теперь сортируются от самого старого к самому последнему, а не от самого последнего к самому старому.

format(limit=None, most_recent_first=False)

Отформатировать трассировку как список строк с новыми строками. Используйте модуль linecache для извлечения строк из исходного кода. Если установлено значение limit, отформатировать самые последние фреймы limit, если значение limit положительное. В противном случае отформатировать самые старые фреймы abs(limit). Если most_recent_first равен True, порядок отформатированных фреймов меняется на противоположный, и вместо последнего возвращается самый последний фрейм.

Аналогична функции traceback.format_tb(), за исключением того, что format() не включает новые строки.

Пример:

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

Выход:

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