tracemalloc — Отслеживание выделения памяти

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


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

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

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

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

Примеры

Показать топ-10

Отобразить 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 (include), сопоставьте блоки памяти, выделенные в адресном пространстве domain.

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

domain

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

Фильтр

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

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

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

Examples:

  • 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 атрибут.

Структура

class tracemalloc.Frame

Фрейм трейсбэка.

Класс Traceback представляет собой последовательность Frame сущности.

filename

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

lineno

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

Снимок

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 сущность с копией трассировки.

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

Изменено в версии 3.6: DomainFilter сущности в настоящее время также принимаются в filters.

classmethod load(filename)

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

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

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

Получение статистики в виде отсортированного списка Statistic сущности, сгруппированных по key_type:

key_type описание
'filename' filename
'lineno' filename и line number
'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() для получения отсортированного списка статистики.

Статистическая величина

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 сущность.

Трассировка

class tracemalloc.Trace

Трассировка блока памяти.

Snapshot.traces атрибут представляет собой последовательность Trace сущности.

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

domain

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

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

size

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

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