summaryrefslogtreecommitdiffstats
path: root/Python
diff options
context:
space:
mode:
authorPablo Galindo Salgado <Pablogsal@gmail.com>2022-08-30 17:11:18 (GMT)
committerGitHub <noreply@github.com>2022-08-30 17:11:18 (GMT)
commit6d791a97364b68d5f9c3514a0470aac487fc538d (patch)
tree745205d7e8698ea7398eb353311f55dc973507bf /Python
parent0f733fffe8f4caaac3ce1b5306af86b42fb0c7fa (diff)
downloadcpython-6d791a97364b68d5f9c3514a0470aac487fc538d.zip
cpython-6d791a97364b68d5f9c3514a0470aac487fc538d.tar.gz
cpython-6d791a97364b68d5f9c3514a0470aac487fc538d.tar.bz2
gh-96143: Allow Linux perf profiler to see Python calls (GH-96123)
:warning: :warning: Note for reviewers, hackers and fellow systems/low-level/compiler engineers :warning: :warning: 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.
Diffstat (limited to 'Python')
-rw-r--r--Python/clinic/sysmodule.c.h75
-rw-r--r--Python/initconfig.c39
-rw-r--r--Python/pylifecycle.c11
-rw-r--r--Python/sysmodule.c77
4 files changed, 201 insertions, 1 deletions
diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
index 0f96366..ddf01a7 100644
--- a/Python/clinic/sysmodule.c.h
+++ b/Python/clinic/sysmodule.c.h
@@ -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]*/
diff --git a/Python/initconfig.c b/Python/initconfig.c
index 70f0363..33a8f27 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -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;
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index bb646f1..8ce6d71 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -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);
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index c286438..75e6455 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -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