instrumentation.md · Markdown – Документация Python
Документация Python неофициальный перевод

instrumentation.md

425 строк · 21.7 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.15/howto/instrumentation.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Инструментирование CPython с помощью DTrace и SystemTap89DTrace и SystemTap – это инструменты мониторинга, каждый из которых предоставляет способ проверки того, что делают процессы в компьютерной системе. Оба используют предметно-ориентированные языки, позволяющие пользователю писать сценарии, которые:1011- фильтровать, какие процессы наблюдать12- собирать данные из интересующих процессов13- создавать отчёты по данным1415Начиная с Python 3.6, CPython можно собрать со встроенными «маркерами», также известными как «зонды», которые могут наблюдаться скриптом DTrace или SystemTap, что упрощает мониторинг того, что делают процессы CPython в системе.1617**Особенность реализации CPython:** Маркеры DTrace являются деталями реализации интерпретатора CPython. Никаких гарантий совместимости зондов между версиями CPython не даётся. Скрипты DTrace могут перестать работать или работать некорректно без предупреждения при смене версий CPython.1819## Включение статических маркеров2021macOS имеет встроенную поддержку DTrace. В Linux для сборки CPython со встроенными маркерами для SystemTap необходимо установить средства разработки SystemTap.2223На машине с Linux это можно сделать с помощью:2425```console26$ yum install systemtap-sdt-devel27```2829или:3031```console32$ sudo apt-get install systemtap-sdt-dev33```3435Затем CPython должен быть [`configured with the --with-dtrace option`](https://python-all.ru/3.15/using/configure.html#cmdoption-with-dtrace):3637```text38checking for --with-dtrace... yes39```4041В macOS можно вывести список доступных зондов DTrace, запустив процесс Python в фоне и перечислив все зонды, предоставляемые провайдером Python:4243```console44$ python3.6 -q &45$ sudo dtrace -l -P python$!  # или: dtrace -l -m python3.64647   ID   PROVIDER            MODULE                          FUNCTION NAME4829564 python18035        python3.6          _PyEval_EvalFrameDefault function-entry4929565 python18035        python3.6             dtrace_function_entry function-entry5029566 python18035        python3.6          _PyEval_EvalFrameDefault function-return5129567 python18035        python3.6            dtrace_function_return function-return5229568 python18035        python3.6                           collect gc-done5329569 python18035        python3.6                           collect gc-start5429570 python18035        python3.6          _PyEval_EvalFrameDefault line5529571 python18035        python3.6                 maybe_dtrace_line line56```5758В Linux можно проверить, присутствуют ли статические маркеры SystemTap в собранном бинарном файле, посмотрев, содержит ли он секцию «.note.stapsdt».5960```console61$ readelf -S ./python | grep .note.stapsdt62[30] .note.stapsdt        NOTE         0000000000000000 00308d7863```6465Если Python собран как разделяемая библиотека (с опцией конфигурации [`--enable-shared`](https://python-all.ru/3.15/using/configure.html#cmdoption-enable-shared)), то искать нужно в разделяемой библиотеке. Например:6667```console68$ readelf -S libpython3.3dm.so.1.0 | grep .note.stapsdt69[29] .note.stapsdt        NOTE         0000000000000000 00365b6870```7172Достаточно современный readelf может вывести метаданные:7374```console75$ readelf -n ./python7677Displaying notes found at file offset 0x00000254 with length 0x00000020:78    Owner                 Data size          Description79    GNU                  0x00000010          NT_GNU_ABI_TAG (ABI version tag)80        OS: Linux, ABI: 2.6.328182Displaying notes found at file offset 0x00000274 with length 0x00000024:83    Owner                 Data size          Description84    GNU                  0x00000014          NT_GNU_BUILD_ID (unique build ID bitstring)85        Build ID: df924a2b08a7e89f6e11251d4602022977af26708687Displaying notes found at file offset 0x002d6c30 with length 0x00000144:88    Owner                 Data size          Description89    stapsdt              0x00000031          NT_STAPSDT (SystemTap probe descriptors)90        Provider: python91        Name: gc__start92        Location: 0x00000000004371c3, Base: 0x0000000000630ce2, Semaphore: 0x00000000008d6bf693        Arguments: -4@%ebx94    stapsdt              0x00000030          NT_STAPSDT (SystemTap probe descriptors)95        Provider: python96        Name: gc__done97        Location: 0x00000000004374e1, Base: 0x0000000000630ce2, Semaphore: 0x00000000008d6bf898        Arguments: -8@%rax99    stapsdt              0x00000045          NT_STAPSDT (SystemTap probe descriptors)100        Provider: python101        Name: function__entry102        Location: 0x000000000053db6c, Base: 0x0000000000630ce2, Semaphore: 0x00000000008d6be8103        Arguments: 8@%rbp 8@%r12 -4@%eax104    stapsdt              0x00000046          NT_STAPSDT (SystemTap probe descriptors)105        Provider: python106        Name: function__return107        Location: 0x000000000053dba8, Base: 0x0000000000630ce2, Semaphore: 0x00000000008d6bea108        Arguments: 8@%rbp 8@%r12 -4@%eax109```110111Приведённые выше метаданные содержат информацию для SystemTap, описывающую, как он может модифицировать стратегически размещённые инструкции машинного кода, чтобы включить хуки трассировки, используемые скриптом SystemTap.112113## Статические зонды DTrace114115Следующий пример скрипта DTrace можно использовать для отображения иерархии вызовов/возвратов скрипта Python, трассируя только внутри вызова функции с именем «start». Иными словами, вызовы функций во время импорта отображаться не будут:116117```text118self int indent;119120python$target:::function-entry121/copyinstr(arg1) == "start"/122{123        self->trace = 1;124}125126python$target:::function-entry127/self->trace/128{129        printf("%d\t%*s:", timestamp, 15, probename);130        printf("%*s", self->indent, "");131        printf("%s:%s:%d\n", basename(copyinstr(arg0)), copyinstr(arg1), arg2);132        self->indent++;133}134135python$target:::function-return136/self->trace/137{138        self->indent--;139        printf("%d\t%*s:", timestamp, 15, probename);140        printf("%*s", self->indent, "");141        printf("%s:%s:%d\n", basename(copyinstr(arg0)), copyinstr(arg1), arg2);142}143144python$target:::function-return145/copyinstr(arg1) == "start"/146{147        self->trace = 0;148}149```150151Его можно вызвать так:152153```console154$ sudo dtrace -q -s call_stack.d -c "python3.6 script.py"155```156157Вывод выглядит так:158159```text160156641360502280  function-entry:call_stack.py:start:23161156641360518804  function-entry: call_stack.py:function_1:1162156641360532797  function-entry:  call_stack.py:function_3:9163156641360546807 function-return:  call_stack.py:function_3:10164156641360563367 function-return: call_stack.py:function_1:2165156641360578365  function-entry: call_stack.py:function_2:5166156641360591757  function-entry:  call_stack.py:function_1:1167156641360605556  function-entry:   call_stack.py:function_3:9168156641360617482 function-return:   call_stack.py:function_3:10169156641360629814 function-return:  call_stack.py:function_1:2170156641360642285 function-return: call_stack.py:function_2:6171156641360656770  function-entry: call_stack.py:function_3:9172156641360669707 function-return: call_stack.py:function_3:10173156641360687853  function-entry: call_stack.py:function_4:13174156641360700719 function-return: call_stack.py:function_4:14175156641360719640  function-entry: call_stack.py:function_5:18176156641360732567 function-return: call_stack.py:function_5:21177156641360747370 function-return:call_stack.py:start:28178```179180## Статические маркеры SystemTap181182Низкоуровневый способ использования интеграции с SystemTap заключается в прямом использовании статических маркеров. Для этого необходимо явно указать бинарный файл, содержащий их.183184Например, следующий скрипт SystemTap можно использовать для отображения иерархии вызовов/возвратов скрипта Python:185186```text187probe process("python").mark("function__entry") {188     filename = user_string($arg1);189     funcname = user_string($arg2);190     lineno = $arg3;191192     printf("%s => %s in %s:%d\\n",193            thread_indent(1), funcname, filename, lineno);194}195196probe process("python").mark("function__return") {197    filename = user_string($arg1);198    funcname = user_string($arg2);199    lineno = $arg3;200201    printf("%s <= %s in %s:%d\\n",202           thread_indent(-1), funcname, filename, lineno);203}204```205206Его можно вызвать так:207208```console209$ stap \210  show-call-hierarchy.stp \211  -c "./python test.py"212```213214Вывод выглядит так:215216```text21711408 python(8274):        => __contains__ in Lib/_abcoll.py:36221811414 python(8274):         => __getitem__ in Lib/os.py:42521911418 python(8274):          => encode in Lib/os.py:49022011424 python(8274):          <= encode in Lib/os.py:49322111428 python(8274):         <= __getitem__ in Lib/os.py:42622211433 python(8274):        <= __contains__ in Lib/_abcoll.py:366223```224225где столбцы означают:226227- время в микросекундах с момента запуска скрипта228- имя исполняемого файла229- PID процесса230231а остальная часть показывает иерархию вызовов/возвратов по мере выполнения скрипта.232233Для сборки CPython [`--enable-shared`](https://python-all.ru/3.15/using/configure.html#cmdoption-enable-shared) маркеры находятся внутри разделяемой библиотеки libpython, и точечный путь зонда должен это отражать. Например, следующая строка из приведённого выше примера:234235```text236probe process("python").mark("function__entry") {237```238239должна выглядеть так:240241```text242probe process("python").library("libpython3.6dm.so.1.0").mark("function__entry") {243```244245(предполагается [отладочная сборка](https://python-all.ru/3.15/using/configure.html#debug-build) CPython 3.6)246247## Доступные статические маркеры248249**function\_\_entry(str filename, str funcname, int lineno)**250251Этот маркер указывает, что началось выполнение функции Python. Он срабатывает только для чистых Python-функций (байткод).252253Имя файла, имя функции и номер строки передаются обратно скрипту трассировки в качестве позиционных аргументов, к которым необходимо обращаться через `$arg1`, `$arg2`, `$arg3`:254255> - `$arg1` : `(const char *)` filename, доступно через `user_string($arg1)`256> - `$arg2` : `(const char *)` имя функции, доступно через `user_string($arg2)`257> - `$arg3` : `int` номер строки258259**function\_\_return(str filename, str funcname, int lineno)**260261Этот маркер противоположен `function__entry()` и указывает, что выполнение функции Python завершилось (либо через `return`, либо через исключение). Он срабатывает только для чистых Python-функций (байткод).262263Аргументы такие же, как для `function__entry()`264265**line(str filename, str funcname, int lineno)**266267Этот маркер указывает, что строка Python скоро будет выполнена. Это эквивалент построчной трассировки с помощью профилировщика Python. Он не срабатывает внутри C-функций.268269Аргументы такие же, как для `function__entry()`.270271**gc\_\_start(int generation)**272273Срабатывает, когда интерпретатор Python запускает цикл сборки мусора. `arg0` – поколение для сканирования, например [`gc.collect()`](https://python-all.ru/3.15/library/gc.html#gc.collect).274275**gc\_\_done(long collected)**276277Срабатывает, когда интерпретатор Python завершает цикл сборки мусора. `arg0` – количество собранных объектов.278279**import\_\_find\_\_load\_\_start(str modulename)**280281Срабатывает до того, как [`importlib`](https://python-all.ru/3.15/library/importlib.html#module-importlib) попытается найти и загрузить модуль. `arg0` – имя модуля.282283Добавлено в версии 3.7.284285**import\_\_find\_\_load\_\_done(str modulename, int found)**286287Срабатывает после вызова функции find\_and\_load объекта [`importlib`](https://python-all.ru/3.15/library/importlib.html#module-importlib). `arg0` – имя модуля, `arg1` указывает, был ли модуль успешно загружен.288289Добавлено в версии 3.7.290291**audit(str event, void \*tuple)**292293Срабатывает при вызове [`sys.audit()`](https://python-all.ru/3.15/library/sys.html#sys.audit) или [`PySys_Audit()`](https://python-all.ru/3.15/c-api/sys.html#c.PySys_Audit). `arg0` – имя события в виде строки C, `arg1` – указатель [`PyObject`](https://python-all.ru/3.15/c-api/structures.html#c.PyObject) на объект кортежа.294295Добавлено в версии 3.8.296297### Точки входа в C298299Чтобы упростить запуск маркеров DTrace, C API Python содержит ряд вспомогательных функций, повторяющих каждый статический маркер. В сборках Python без поддержки DTrace эти функции ничего не делают.300301В общем случае вызывать их вручную не требуется – Python сделает это сам.302303| Функция C API | Статический маркер | Примечания |304| --- | --- | --- |305| void PyDTrace\_LINE(const char \*arg0, const char \*arg1, int arg2) | `line()` |  |306| void PyDTrace\_FUNCTION\_ENTRY(const char \*arg0, const char \*arg1, int arg2) | `function__entry()` |  |307| void PyDTrace\_FUNCTION\_RETURN(const char \*arg0, const char \*arg1, int arg2) | `function__return()` |  |308| void PyDTrace\_GC\_START(int arg0) | `gc__start()` |  |309| void PyDTrace\_GC\_DONE([Py\_ssize\_t](https://python-all.ru/3.15/c-api/intro.html#c.Py_ssize_t) arg0) | `gc__done()` |  |310| void PyDTrace\_INSTANCE\_NEW\_START(int arg0) | `instance__new__start()` | Не используется в Python |311| void PyDTrace\_INSTANCE\_NEW\_DONE(int arg0) | `instance__new__done()` | Не используется в Python |312| void PyDTrace\_INSTANCE\_DELETE\_START(int arg0) | `instance__delete__start()` | Не используется в Python |313| void PyDTrace\_INSTANCE\_DELETE\_DONE(int arg0) | `instance__delete__done()` | Не используется в Python |314| void PyDTrace\_IMPORT\_FIND\_LOAD\_START(const char \*arg0) | `import__find__load__start()` |  |315| void PyDTrace\_IMPORT\_FIND\_LOAD\_DONE(const char \*arg0, int arg1) | `import__find__load__done()` |  |316| void PyDTrace\_AUDIT(const char \*arg0, void \*arg1) | `audit()` |  |317318### C-проверки зондов319320#### `int PyDTrace_LINE_ENABLED(void)`321322#### `int PyDTrace_FUNCTION_ENTRY_ENABLED(void)`323324#### `int PyDTrace_FUNCTION_RETURN_ENABLED(void)`325326#### `int PyDTrace_GC_START_ENABLED(void)`327328#### `int PyDTrace_GC_DONE_ENABLED(void)`329330#### `int PyDTrace_INSTANCE_NEW_START_ENABLED(void)`331332#### `int PyDTrace_INSTANCE_NEW_DONE_ENABLED(void)`333334#### `int PyDTrace_INSTANCE_DELETE_START_ENABLED(void)`335336#### `int PyDTrace_INSTANCE_DELETE_DONE_ENABLED(void)`337338#### `int PyDTrace_IMPORT_FIND_LOAD_START_ENABLED(void)`339340#### `int PyDTrace_IMPORT_FIND_LOAD_DONE_ENABLED(void)`341342#### `int PyDTrace_AUDIT_ENABLED(void)`343344Все вызовы функций `PyDTrace` должны быть защищены вызовом одной из этих функций. Это позволяет Python минимизировать влияние на производительность, когда зондирование отключено.345346В сборках без включённого DTrace эти функции ничего не делают и возвращают `0`.347348## Тэпсеты SystemTap349350Более высокоуровневый способ использования интеграции SystemTap – это использование «тэпсета»: аналога библиотеки в SystemTap, который скрывает некоторые низкоуровневые детали статических маркеров.351352Вот файл тэпсета, основанный на неразделяемой сборке CPython:353354```text355/*356   Provide a higher-level wrapping around the function__entry and357   function__return markers:358 \*/359probe python.function.entry = process("python").mark("function__entry")360{361    filename = user_string($arg1);362    funcname = user_string($arg2);363    lineno = $arg3;364    frameptr = $arg4365}366probe python.function.return = process("python").mark("function__return")367{368    filename = user_string($arg1);369    funcname = user_string($arg2);370    lineno = $arg3;371    frameptr = $arg4372}373```374375Если этот файл установлен в каталог тэпсетов SystemTap (например, `/usr/share/systemtap/tapset`), то становятся доступны следующие дополнительные точки зондирования:376377**python.function.entry(str filename, str funcname, int lineno, frameptr)**378379Эта точка зондирования указывает, что выполнение функции Python началось. Она срабатывает только для чистых Python-функций (байт-код).380381**python.function.return(str filename, str funcname, int lineno, frameptr)**382383Эта точка зондирования является обратной к `python.function.return` и указывает, что выполнение функции Python завершено (либо через `return`, либо через исключение). Она срабатывает только для чистых Python (байт-код) функций.384385## Примеры386387Этот скрипт SystemTap использует приведённый выше тэпсет для более чистой реализации примера трассировки иерархии вызовов функций Python, без необходимости называть статические маркеры напрямую:388389```text390probe python.function.entry391{392  printf("%s => %s in %s:%d\n",393         thread_indent(1), funcname, filename, lineno);394}395396probe python.function.return397{398  printf("%s <= %s in %s:%d\n",399         thread_indent(-1), funcname, filename, lineno);400}401```402403Следующий скрипт использует тэпсет выше, чтобы предоставить представление типа top для всего выполняющегося кода CPython, показывая 20 наиболее часто входящих байт-код фреймов каждую секунду во всей системе:404405```text406global fn_calls;407408probe python.function.entry409{410    fn_calls[pid(), filename, funcname, lineno] += 1;411}412413probe timer.ms(1000) {414    printf("\033[2J\033[1;1H") /* clear screen \*/415    printf("%6s %80s %6s %30s %6s\n",416           "PID", "FILENAME", "LINE", "FUNCTION", "CALLS")417    foreach ([pid, filename, funcname, lineno] in fn_calls- limit 20) {418        printf("%6d %80s %6d %30s %6d\n",419            pid, filename, lineno, funcname,420            fn_calls[pid, filename, funcname, lineno]);421    }422    delete fn_calls;423}424```425