Сообщить об ошибке.

Функция monitoring модуля sys в Python

Инструмент мониторинга программ, работающих на Python >= 3.12

Содержание:

Использование профайлера или отладчика в CPython может серьезно повлиять на производительность. Замедления на порядок - обычное дело.

Python 3.12 представил новую структуру sys.monitoring, которая позволит осуществлять мониторинг при низких затратах. Это пространство имен обеспечивает доступ к функциям и константам, необходимым для активации и управления мониторингом событий. Обеспечивает детальный контроль, так что инструменты могут прослушивать только определенные события в определенных строках кода.

Мониторинг программ Python осуществляется путем регистрации функций обратного вызова для событий и активации набора событий. Активация событий и регистрация функций обратного вызова независимы друг от друга.

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

Обратите внимание, что в отличие от предшественника sys.settrace(), события и обратные вызовы относятся к интерпретатору, а не к потоку.

Регистрация и использование инструментов

sys.monitoring.use_tool_id(tool_id: int, name: str, /) -> None:

Необходимо вызвать функцию до того, как tool_id можно будет использовать. tool_id должен находиться в диапазоне от 0 до 5 включительно. Вызывает исключение ValueError, если tool_id используется.

sys.monitoring.clear_tool_id(tool_id: int, /) -> None:

Отменяет регистрацию всех событий и функций обратного вызова, связанных с tool_id.

sys.monitoring.free_tool_id(tool_id: int, /) -> None:

Следует вызывать, когда инструмент больше не нуждается в tool_id. Вызовет sys.monitoring.clear_tool_id() перед освобождением tool_id.

sys.monitoring.get_tool(tool_id: int, /) -> str | None:

Возвращает название инструмента, если tool_id используется, в противном случае возвращает None. tool_id должен находиться в диапазоне от 0 до 5 включительно.

Все идентификаторы обрабатываются виртуальной машиной одинаково в отношении событий, но следующие идентификаторы предварительно определены, чтобы упростить взаимодействие инструментов:

sys.monitoring.DEBUGGER_ID = 0
sys.monitoring.COVERAGE_ID = 1
sys.monitoring.PROFILER_ID = 2
sys.monitoring.OPTIMIZER_ID = 5

sys.monitoring.register_callback(tool_id: int, event: int, func: Callable | None, /) → Callable | None:

Функция sys.monitoring.register_callback() регистрирует вызов func для события event с данным tool_id.

Если для данного tool_id и события event был зарегистрирован другой обратный вызов, то он отменяется и возвращается. В противном случае register_callback() возвращает None.

Функции могут быть сняты с регистрации путем вызова sys.monitoring.register_callback(tool_id, event, None). Функции обратного вызова могут быть зарегистрированы или сняты с регистрации в любое время.

Поддерживаются следующие события:

sys.monitoring.events.BRANCH:

Берется (или нет) условная ветвь.

sys.monitoring.events.CALL:

Вызов в коде Python (событие происходит перед вызовом).

sys.monitoring.events.C_RAISE:

Исключение, возникающее при вызове любого объекта, кроме функций Python (событие происходит после завершения работы).

sys.monitoring.events.C_RETURN:

Возврат из любого вызываемого объекта, кроме функций Python (событие происходит после возврата).

sys.monitoring.events.EXCEPTION_HANDLED:

Обрабатывается исключение.

sys.monitoring.events.INSTRUCTION:

Инструкция виртуальной машины будет выполнена.

sys.monitoring.events.JUMP:

Выполнен безусловный скачок в графике потока управления

sys.monitoring.events.LINE:

Будет выполнена инструкция, которая имеет другой номер строки из предыдущей инструкции.

sys.monitoring.events.PY_RESUME:

Возобновление работы функции Python (для функций-генераторов и сопрограмм), за исключением вызовов throw().

sys.monitoring.events.PY_RETURN:

Возврат из функции Python (происходит непосредственно перед возвратом, кадр вызываемой функции будет находиться в стеке).

sys.monitoring.events.PY_START:

Начало функции Python (происходит сразу после вызова, кадр вызываемой функции будет находиться в стеке)

sys.monitoring.events.PY_THROW:

Функция Python возобновляется вызовом throw().

sys.monitoring.events.PY_UNWIND:

Выход из функции Python во время разматывания исключения.

sys.monitoring.events.PY_YIELD:

Возврат из функции Python (происходит непосредственно перед возвратом, кадр вызываемой функции будет находиться в стеке).

sys.monitoring.events.RAISE:

Возникает исключение, за исключением тех, которые вызывают событие STOP_ITERATION.

sys.monitoring.events.RERAISE:

Исключение возникает повторно, например, в конце блока finally.

sys.monitoring.events.STOP_ITERATION:

Вызывается искусственный StopIteration (см. событие STOP_ITERATION).

В будущем могут быть добавлены дополнительные события.

Эти события являются атрибутами пространства имён sys.monitoring.events. Каждое событие представлено в виде целочисленной константы, степень двойки которой равна 2. Чтобы определить набор событий, просто побитно объедините отдельные события. Например, чтобы указать события PY_RETURN и PY_START, используйте выражение PY_RETURN | PY_START.

*`sys.monitoring.events.NO_EVENTS

An alias for 0 so users can do explicit comparisons like:

Copyif get_events(DEBUGGER_ID) == NO_EVENTS:...

Небольшой пример техники мониторинга

Небольшой пример техники мониторинга за глобальным списком sys.path, который может изменился в процессе выполнения программы.

# example.py
import sys

# Контрольное значение длины списка `sys.path`
path_length = len(sys.path)

def monitor(code, line_number):
    """Функция мониторинга для выполнения каждой инструкции"""
    global path_length
    if len(sys.path) != path_length:
        print("🛎️ В `sys.path` изменилась длина")
        path_length = len(sys.path)

        breakpoint()
    else:
        # Отключим для данной инструкции, предполагая, что она 
        # не изменит sys.path в случае повторного выполнения.
        return sys.monitoring.DISABLE


# Регистрация отладчика
sys.monitoring.use_tool_id(sys.monitoring.DEBUGGER_ID, "debugging")
# Включим отладчик для учебных событий
sys.monitoring.set_events(
    sys.monitoring.DEBUGGER_ID,
    sys.monitoring.events.INSTRUCTION,
)
# запускаем `monitor()` для каждой инструкции
sys.monitoring.register_callback(
    sys.monitoring.DEBUGGER_ID,
    sys.monitoring.events.INSTRUCTION,
    monitor,
)

Разберем представленный код:

  • monitor() - функция для мониторинга sys.path. Она работает с каждой инструкцией с отдельными этапами выполнения кода Python.
  • функция monitor() проверяет, изменилась ли длина sys.path для обнаружения мутаций. Функция не позволит обнаружить изменение элементов на месте, но для примера этого достаточно.
  • при обнаружении изменений monitor() выводит эмодзи колокольчика и открывает отладчик через breakpoint().
  • если длина sys.path не изменялась, то monitor() возвращается sys.monitoring.DISABLE. Эта константа указывает sys.monitoring избегать повторного вызова этой инструкции. Такое поведение открывает одно из ключевых преимуществ производительности sys.monitoring: это позволяет избежать повторных ненужных обращений к инструментам мониторинга. Без этого шага накладные расходы на функцию мониторинга значительно замедлили бы работу программы, возможно, до такой степени, что стали бы непрактичными.
  • sys.monitoring.use_tool_id() включает инструмент с идентификатором DEBUGGER_ID и произвольным названием "debugging". sys.monitoring поддерживает только шесть активных инструментов, которые должны использовать уникальные идентификаторы. В примере используем идентификатор DEBUGGER_ID.
  • sys.monitoring.set_events() указывает sys.monitoring на включение инструмента отладки для событий INSTRUCTION. Это означает, что инструмент включён только для этих событий.
  • наконец, sys.monitoring.register_callback() сообщает sys.monitoring о вызове monitor() при каждом событии инструкции.

Теперь посмотрим, как это работает. Ниже приведён небольшой пример, который изменяет sys.path. Код ниже необходимо вставить в конец предыдущего кода.

print("=> Старт")
sys.path.insert(0, "animals")
print("=> Стоп")

После вызова sys.path.insert() сразу откроется отладчик pdb:

$ python example.py
=> Старт
🛎️ В `sys.path` изменилась длина
> /.../example.py(46)<module>()
-> print("=> Стоп")
(Pdb)

Обратите внимание, что отладчик pdb открывается на инструкции после произошедшей мутации, в следующей строке с print(). Это происходит потому, что sys.monitoring запускает функцию мониторинга перед каждой инструкцией.

Чтобы найти виновника, нужно вернуться на шаг назад. В данном случае это означает предыдущую строку, но после большого блока if/else или возврата функции, это может быть сложнее.

При открытом pdb можно использовать любые команды отладки. Например, pp для вывода sys.path в удобном для чтения виде.