mirror of
https://github.com/python/cpython.git
synced 2025-07-16 07:45:20 +00:00
gh-96143: Allow Linux perf profiler to see Python calls (GH-96123)
⚠️ ⚠️ Note for reviewers, hackers and fellow systems/low-level/compiler engineers ⚠️ ⚠️ If you have a lot of experience with this kind of shenanigans and want to improve the **first** version, **please make a PR against my branch** or **reach out by email** or **suggest code changes directly on GitHub**. If you have any **refinements or optimizations** please, wait until the first version is merged before starting hacking or proposing those so we can keep this PR productive.
This commit is contained in:
parent
0f733fffe8
commit
6d791a9736
24 changed files with 1412 additions and 2 deletions
75
Python/clinic/sysmodule.c.h
generated
75
Python/clinic/sysmodule.c.h
generated
|
@ -1151,6 +1151,79 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored))
|
|||
|
||||
#endif /* defined(ANDROID_API_LEVEL) */
|
||||
|
||||
PyDoc_STRVAR(sys_activate_stack_trampoline__doc__,
|
||||
"activate_stack_trampoline($module, backend, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Activate the perf profiler trampoline.");
|
||||
|
||||
#define SYS_ACTIVATE_STACK_TRAMPOLINE_METHODDEF \
|
||||
{"activate_stack_trampoline", (PyCFunction)sys_activate_stack_trampoline, METH_O, sys_activate_stack_trampoline__doc__},
|
||||
|
||||
static PyObject *
|
||||
sys_activate_stack_trampoline_impl(PyObject *module, const char *backend);
|
||||
|
||||
static PyObject *
|
||||
sys_activate_stack_trampoline(PyObject *module, PyObject *arg)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
const char *backend;
|
||||
|
||||
if (!PyUnicode_Check(arg)) {
|
||||
_PyArg_BadArgument("activate_stack_trampoline", "argument", "str", arg);
|
||||
goto exit;
|
||||
}
|
||||
Py_ssize_t backend_length;
|
||||
backend = PyUnicode_AsUTF8AndSize(arg, &backend_length);
|
||||
if (backend == NULL) {
|
||||
goto exit;
|
||||
}
|
||||
if (strlen(backend) != (size_t)backend_length) {
|
||||
PyErr_SetString(PyExc_ValueError, "embedded null character");
|
||||
goto exit;
|
||||
}
|
||||
return_value = sys_activate_stack_trampoline_impl(module, backend);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(sys_deactivate_stack_trampoline__doc__,
|
||||
"deactivate_stack_trampoline($module, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Dectivate the perf profiler trampoline.");
|
||||
|
||||
#define SYS_DEACTIVATE_STACK_TRAMPOLINE_METHODDEF \
|
||||
{"deactivate_stack_trampoline", (PyCFunction)sys_deactivate_stack_trampoline, METH_NOARGS, sys_deactivate_stack_trampoline__doc__},
|
||||
|
||||
static PyObject *
|
||||
sys_deactivate_stack_trampoline_impl(PyObject *module);
|
||||
|
||||
static PyObject *
|
||||
sys_deactivate_stack_trampoline(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
return sys_deactivate_stack_trampoline_impl(module);
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(sys_is_stack_trampoline_active__doc__,
|
||||
"is_stack_trampoline_active($module, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Returns *True* if the perf profiler trampoline is active.");
|
||||
|
||||
#define SYS_IS_STACK_TRAMPOLINE_ACTIVE_METHODDEF \
|
||||
{"is_stack_trampoline_active", (PyCFunction)sys_is_stack_trampoline_active, METH_NOARGS, sys_is_stack_trampoline_active__doc__},
|
||||
|
||||
static PyObject *
|
||||
sys_is_stack_trampoline_active_impl(PyObject *module);
|
||||
|
||||
static PyObject *
|
||||
sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
return sys_is_stack_trampoline_active_impl(module);
|
||||
}
|
||||
|
||||
#ifndef SYS_GETWINDOWSVERSION_METHODDEF
|
||||
#define SYS_GETWINDOWSVERSION_METHODDEF
|
||||
#endif /* !defined(SYS_GETWINDOWSVERSION_METHODDEF) */
|
||||
|
@ -1194,4 +1267,4 @@ sys_getandroidapilevel(PyObject *module, PyObject *Py_UNUSED(ignored))
|
|||
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
|
||||
#define SYS_GETANDROIDAPILEVEL_METHODDEF
|
||||
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
|
||||
/*[clinic end generated code: output=322fb0409e376ad4 input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=43b44240211afe95 input=a9049054013a1b77]*/
|
||||
|
|
|
@ -118,6 +118,11 @@ The following implementation-specific options are available:\n\
|
|||
files are desired as well as suppressing the extra visual location indicators \n\
|
||||
when the interpreter displays tracebacks.\n\
|
||||
\n\
|
||||
-X perf: activate support for the Linux \"perf\" profiler by activating the \"perf\"\n\
|
||||
trampoline. When this option is activated, the Linux \"perf\" profiler will be \n\
|
||||
able to report Python calls. This option is only available on some platforms and will \n\
|
||||
do nothing if is not supported on the current system. The default value is \"off\".\n\
|
||||
\n\
|
||||
-X frozen_modules=[on|off]: whether or not frozen modules should be used.\n\
|
||||
The default is \"on\" (or \"off\" if you are running a local build).";
|
||||
|
||||
|
@ -745,6 +750,7 @@ _PyConfig_InitCompatConfig(PyConfig *config)
|
|||
config->use_hash_seed = -1;
|
||||
config->faulthandler = -1;
|
||||
config->tracemalloc = -1;
|
||||
config->perf_profiling = -1;
|
||||
config->module_search_paths_set = 0;
|
||||
config->parse_argv = 0;
|
||||
config->site_import = -1;
|
||||
|
@ -829,6 +835,7 @@ PyConfig_InitIsolatedConfig(PyConfig *config)
|
|||
config->use_hash_seed = 0;
|
||||
config->faulthandler = 0;
|
||||
config->tracemalloc = 0;
|
||||
config->perf_profiling = 0;
|
||||
config->safe_path = 1;
|
||||
config->pathconfig_warnings = 0;
|
||||
#ifdef MS_WINDOWS
|
||||
|
@ -940,6 +947,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
|
|||
COPY_ATTR(_install_importlib);
|
||||
COPY_ATTR(faulthandler);
|
||||
COPY_ATTR(tracemalloc);
|
||||
COPY_ATTR(perf_profiling);
|
||||
COPY_ATTR(import_time);
|
||||
COPY_ATTR(code_debug_ranges);
|
||||
COPY_ATTR(show_ref_count);
|
||||
|
@ -1050,6 +1058,7 @@ _PyConfig_AsDict(const PyConfig *config)
|
|||
SET_ITEM_UINT(hash_seed);
|
||||
SET_ITEM_INT(faulthandler);
|
||||
SET_ITEM_INT(tracemalloc);
|
||||
SET_ITEM_INT(perf_profiling);
|
||||
SET_ITEM_INT(import_time);
|
||||
SET_ITEM_INT(code_debug_ranges);
|
||||
SET_ITEM_INT(show_ref_count);
|
||||
|
@ -1331,6 +1340,7 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict)
|
|||
CHECK_VALUE("hash_seed", config->hash_seed <= MAX_HASH_SEED);
|
||||
GET_UINT(faulthandler);
|
||||
GET_UINT(tracemalloc);
|
||||
GET_UINT(perf_profiling);
|
||||
GET_UINT(import_time);
|
||||
GET_UINT(code_debug_ranges);
|
||||
GET_UINT(show_ref_count);
|
||||
|
@ -1687,6 +1697,26 @@ config_read_env_vars(PyConfig *config)
|
|||
return _PyStatus_OK();
|
||||
}
|
||||
|
||||
static PyStatus
|
||||
config_init_perf_profiling(PyConfig *config)
|
||||
{
|
||||
int active = 0;
|
||||
const char *env = config_get_env(config, "PYTHONPERFSUPPORT");
|
||||
if (env) {
|
||||
if (_Py_str_to_int(env, &active) != 0) {
|
||||
active = 0;
|
||||
}
|
||||
if (active) {
|
||||
config->perf_profiling = 1;
|
||||
}
|
||||
}
|
||||
const wchar_t *xoption = config_get_xoption(config, L"perf");
|
||||
if (xoption) {
|
||||
config->perf_profiling = 1;
|
||||
}
|
||||
return _PyStatus_OK();
|
||||
|
||||
}
|
||||
|
||||
static PyStatus
|
||||
config_init_tracemalloc(PyConfig *config)
|
||||
|
@ -1788,6 +1818,12 @@ config_read_complex_options(PyConfig *config)
|
|||
return status;
|
||||
}
|
||||
}
|
||||
if (config->perf_profiling < 0) {
|
||||
status = config_init_perf_profiling(config);
|
||||
if (_PyStatus_EXCEPTION(status)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
if (config->pycache_prefix == NULL) {
|
||||
status = config_init_pycache_prefix(config);
|
||||
|
@ -2104,6 +2140,9 @@ config_read(PyConfig *config, int compute_path_config)
|
|||
if (config->tracemalloc < 0) {
|
||||
config->tracemalloc = 0;
|
||||
}
|
||||
if (config->perf_profiling < 0) {
|
||||
config->perf_profiling = 0;
|
||||
}
|
||||
if (config->use_hash_seed < 0) {
|
||||
config->use_hash_seed = 0;
|
||||
config->hash_seed = 0;
|
||||
|
|
|
@ -1149,6 +1149,16 @@ init_interp_main(PyThreadState *tstate)
|
|||
if (_PyTraceMalloc_Init(config->tracemalloc) < 0) {
|
||||
return _PyStatus_ERR("can't initialize tracemalloc");
|
||||
}
|
||||
|
||||
|
||||
#ifdef PY_HAVE_PERF_TRAMPOLINE
|
||||
if (config->perf_profiling) {
|
||||
if (_PyPerfTrampoline_SetCallbacks(&_Py_perfmap_callbacks) < 0 ||
|
||||
_PyPerfTrampoline_Init(config->perf_profiling) < 0) {
|
||||
return _PyStatus_ERR("can't initialize the perf trampoline");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
status = init_sys_streams(tstate);
|
||||
|
@ -1723,6 +1733,7 @@ finalize_interp_clear(PyThreadState *tstate)
|
|||
_PyArg_Fini();
|
||||
_Py_ClearFileSystemEncoding();
|
||||
_Py_Deepfreeze_Fini();
|
||||
_PyPerfTrampoline_Fini();
|
||||
}
|
||||
|
||||
finalize_interp_types(tstate->interp);
|
||||
|
|
|
@ -2053,6 +2053,80 @@ sys_getandroidapilevel_impl(PyObject *module)
|
|||
}
|
||||
#endif /* ANDROID_API_LEVEL */
|
||||
|
||||
/*[clinic input]
|
||||
sys.activate_stack_trampoline
|
||||
|
||||
backend: str
|
||||
/
|
||||
|
||||
Activate the perf profiler trampoline.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
sys_activate_stack_trampoline_impl(PyObject *module, const char *backend)
|
||||
/*[clinic end generated code: output=5783cdeb51874b43 input=b09020e3a17c78c5]*/
|
||||
{
|
||||
#ifdef PY_HAVE_PERF_TRAMPOLINE
|
||||
if (strcmp(backend, "perf") == 0) {
|
||||
_PyPerf_Callbacks cur_cb;
|
||||
_PyPerfTrampoline_GetCallbacks(&cur_cb);
|
||||
if (cur_cb.init_state != _Py_perfmap_callbacks.init_state) {
|
||||
if (_PyPerfTrampoline_SetCallbacks(&_Py_perfmap_callbacks) < 0 ) {
|
||||
PyErr_SetString(PyExc_ValueError, "can't activate perf trampoline");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
PyErr_Format(PyExc_ValueError, "invalid backend: %s", backend);
|
||||
return NULL;
|
||||
}
|
||||
if (_PyPerfTrampoline_Init(1) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
PyErr_SetString(PyExc_ValueError, "perf trampoline not available");
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
sys.deactivate_stack_trampoline
|
||||
|
||||
Dectivate the perf profiler trampoline.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
sys_deactivate_stack_trampoline_impl(PyObject *module)
|
||||
/*[clinic end generated code: output=b50da25465df0ef1 input=491f4fc1ed615736]*/
|
||||
{
|
||||
if (_PyPerfTrampoline_Init(0) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
sys.is_stack_trampoline_active
|
||||
|
||||
Returns *True* if the perf profiler trampoline is active.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
sys_is_stack_trampoline_active_impl(PyObject *module)
|
||||
/*[clinic end generated code: output=ab2746de0ad9d293 input=061fa5776ac9dd59]*/
|
||||
{
|
||||
#ifdef PY_HAVE_PERF_TRAMPOLINE
|
||||
if (_PyIsPerfTrampolineActive()) {
|
||||
Py_RETURN_TRUE;
|
||||
}
|
||||
#endif
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef sys_methods[] = {
|
||||
/* Might as well keep this in alphabetic order */
|
||||
SYS_ADDAUDITHOOK_METHODDEF
|
||||
|
@ -2108,6 +2182,9 @@ static PyMethodDef sys_methods[] = {
|
|||
METH_VARARGS | METH_KEYWORDS, set_asyncgen_hooks_doc},
|
||||
SYS_GET_ASYNCGEN_HOOKS_METHODDEF
|
||||
SYS_GETANDROIDAPILEVEL_METHODDEF
|
||||
SYS_ACTIVATE_STACK_TRAMPOLINE_METHODDEF
|
||||
SYS_DEACTIVATE_STACK_TRAMPOLINE_METHODDEF
|
||||
SYS_IS_STACK_TRAMPOLINE_ACTIVE_METHODDEF
|
||||
SYS_UNRAISABLEHOOK_METHODDEF
|
||||
#ifdef Py_STATS
|
||||
SYS__STATS_ON_METHODDEF
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue