diff options
| author | Ian Rogers <irogers@google.com> | 2026-06-15 18:15:32 -0700 |
|---|---|---|
| committer | Namhyung Kim <namhyung@kernel.org> | 2026-06-30 10:17:13 -0700 |
| commit | b4e38080066d3010488e89f19e57fce38746cb51 (patch) | |
| tree | 3f11f6ccbf4ebef5d32d11aa1a58d191eeb340fd /tools/perf/util/python.c | |
| parent | 27e59401c78c4d8ebad4eb233b5b987fa47a8e21 (diff) | |
perf python: Add python session abstraction wrapping perf's session
Sessions are necessary to be able to use perf.data files within a
tool. Add a wrapper python type that incorporates the tool. Allow a
sample callback to be passed when creating the session. When
process_events is run this callback will be called, if supplied, for
sample events.
An example use looks like:
```
$ perf record -e cycles,instructions -a sleep 3
$ PYTHONPATH=..../perf/python python3
Python 3.13.7 (main, Aug 20 2025, 22:17:40) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import perf
>>> count=0
... def handle_sample(x):
... global count
... if count < 3:
... print(dir(x))
... count = count + 1
... perf.session(perf.data("perf.data"),sample=handle_sample).process_events()
...
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'sample_addr', 'sample_cpu', 'sample_id', 'sample_ip', 'sample_period', 'sample_pid', 'sample_stream_id', 'sample_tid', 'sample_time', 'type']
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'sample_addr', 'sample_cpu', 'sample_id', 'sample_ip', 'sample_period', 'sample_pid', 'sample_stream_id', 'sample_tid', 'sample_time', 'type']
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'sample_addr', 'sample_cpu', 'sample_id', 'sample_ip', 'sample_period', 'sample_pid', 'sample_stream_id', 'sample_tid', 'sample_time', 'type']
```
Also, add the ability to get the thread associated with a session. For
threads, allow the comm string to be retrieved. This can be useful for
filtering threads. Connect up some of the standard event handling in
psession->tool to better support queries of the machine. Also connect
up the symbols.
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Gemini:gemini-3.1-pro-preview
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alice Rogers <alice.mei.rogers@gmail.com>
Cc: Dapeng Mi <dapeng1.mi@linux.intel.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Leo Yan <leo.yan@linux.dev>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Richter <tmricht@linux.ibm.com>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
Diffstat (limited to 'tools/perf/util/python.c')
| -rw-r--r-- | tools/perf/util/python.c | 357 |
1 files changed, 351 insertions, 6 deletions
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index 31da1283fc15..dc71fe7e3787 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -14,8 +14,10 @@ #include <structmember.h> #include "callchain.h" +#include "comm.h" #include "counts.h" #include "data.h" +#include "debug.h" #include "event.h" #include "evlist.h" #include "evsel.h" @@ -25,8 +27,14 @@ #include "pmus.h" #include "print_binary.h" #include "record.h" +#include "session.h" #include "strbuf.h" +#include "symbol.h" +#include "stat.h" +#include "header.h" +#include "thread.h" #include "thread_map.h" +#include "tool.h" #include "tp_pmu.h" #include "trace-event.h" #include "util/sample.h" @@ -2579,6 +2587,332 @@ static int pyrf_data__setup_types(void) return PyType_Ready(&pyrf_data__type); } +struct pyrf_thread { + PyObject_HEAD + + struct thread *thread; +}; + +static void pyrf_thread__delete(struct pyrf_thread *pthread) +{ + thread__put(pthread->thread); + Py_TYPE(pthread)->tp_free((PyObject *)pthread); +} + +static PyObject *pyrf_thread__comm(PyObject *obj) +{ + struct pyrf_thread *pthread = (void *)obj; + const char *str; + + CHECK_INITIALIZED(pthread->thread, "perf.thread"); + + str = thread__comm_str(pthread->thread); + + if (!str) + Py_RETURN_NONE; + + return PyUnicode_FromString(str); +} + +static PyMethodDef pyrf_thread__methods[] = { + { + .ml_name = "comm", + .ml_meth = (PyCFunction)pyrf_thread__comm, + .ml_flags = METH_NOARGS, + .ml_doc = PyDoc_STR("Comm(and) associated with this thread.") + }, + { .ml_name = NULL, } +}; + +static PyObject *pyrf_thread__get_pid(struct pyrf_thread *pthread, void *closure __maybe_unused) +{ + return PyLong_FromLong(thread__pid(pthread->thread)); +} + +static PyObject *pyrf_thread__get_tid(struct pyrf_thread *pthread, void *closure __maybe_unused) +{ + return PyLong_FromLong(thread__tid(pthread->thread)); +} + +static PyObject *pyrf_thread__get_ppid(struct pyrf_thread *pthread, void *closure __maybe_unused) +{ + return PyLong_FromLong(thread__ppid(pthread->thread)); +} + +static PyObject *pyrf_thread__get_cpu(struct pyrf_thread *pthread, void *closure __maybe_unused) +{ + return PyLong_FromLong(thread__cpu(pthread->thread)); +} + +static PyGetSetDef pyrf_thread__getset[] = { + { .name = "pid", .get = (getter)pyrf_thread__get_pid, .doc = "process ID" }, + { .name = "tid", .get = (getter)pyrf_thread__get_tid, .doc = "thread ID" }, + { .name = "ppid", .get = (getter)pyrf_thread__get_ppid, .doc = "parent process ID" }, + { .name = "cpu", .get = (getter)pyrf_thread__get_cpu, .doc = "cpu number" }, + { .name = NULL } +}; + +static const char pyrf_thread__doc[] = PyDoc_STR("perf thread object."); + +static PyTypeObject pyrf_thread__type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "perf.thread", + .tp_basicsize = sizeof(struct pyrf_thread), + .tp_dealloc = (destructor)pyrf_thread__delete, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + .tp_methods = pyrf_thread__methods, + .tp_getset = pyrf_thread__getset, + .tp_doc = pyrf_thread__doc, +}; + +static int pyrf_thread__setup_types(void) +{ + return PyType_Ready(&pyrf_thread__type); +} + +static PyObject *pyrf_thread__from_thread(struct thread *thread) +{ + struct pyrf_thread *pthread = PyObject_New(struct pyrf_thread, &pyrf_thread__type); + + if (!pthread) + return NULL; + + pthread->thread = thread__get(thread); + return (PyObject *)pthread; +} + +struct pyrf_session { + PyObject_HEAD + + struct perf_session *session; + struct perf_tool tool; + struct pyrf_data *pdata; + PyObject *sample; +}; + +static int pyrf_session_tool__sample(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine __maybe_unused) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyObject *pyevent = pyrf_event__new(event); + struct pyrf_event *pevent = (struct pyrf_event *)pyevent; + PyObject *ret; + bool needs_swap; + + if (pyevent == NULL) + return -ENOMEM; + + needs_swap = sample->evsel->needs_swap; + + memcpy(&pevent->event, event, event->header.size); + /* The event is already swapped, disable swapping temporarily */ + sample->evsel->needs_swap = false; + if (evsel__parse_sample(sample->evsel, &pevent->event, &pevent->sample) < 0) { + sample->evsel->needs_swap = needs_swap; + Py_DECREF(pyevent); + return -1; + } + sample->evsel->needs_swap = needs_swap; + + ret = PyObject_CallFunction(psession->sample, "O", pyevent); + if (!ret) { + Py_DECREF(pyevent); + return -1; + } + Py_DECREF(ret); + Py_DECREF(pyevent); + return 0; +} + +static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObject *args) +{ + struct machine *machine; + struct thread *thread = NULL; + PyObject *result; + int pid; + + CHECK_INITIALIZED(psession->session, "session"); + + if (!PyArg_ParseTuple(args, "i", &pid)) + return NULL; + + machine = &psession->session->machines.host; + thread = machine__find_thread(machine, pid, pid); + + if (!thread) { + machine = perf_session__find_machine(psession->session, pid); + if (machine) + thread = machine__find_thread(machine, pid, pid); + } + + if (!thread) { + PyErr_Format(PyExc_TypeError, "Failed to find thread %d", pid); + return NULL; + } + result = pyrf_thread__from_thread(thread); + thread__put(thread); + return result; +} + +static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + struct pyrf_data *pdata; + PyObject *sample = NULL; + static char *kwlist[] = { "data", "sample", NULL }; + struct pyrf_session *psession; + struct perf_session *session; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|O", kwlist, &pyrf_data__type, &pdata, + &sample)) + return NULL; + + psession = PyObject_New(struct pyrf_session, type); + if (!psession) + return NULL; + + psession->session = NULL; + psession->sample = NULL; + psession->pdata = NULL; + + Py_INCREF(pdata); + psession->pdata = pdata; + + perf_tool__init(&psession->tool, /*ordered_events=*/true); + psession->tool.ordering_requires_timestamps = true; + + #define ADD_TOOL(name) \ + do { \ + if (name) { \ + if (!PyCallable_Check(name)) { \ + PyErr_SetString(PyExc_TypeError, #name " must be callable"); \ + goto err_out; \ + } \ + psession->tool.name = pyrf_session_tool__##name; \ + Py_INCREF(name); \ + psession->name = name; \ + } \ + } while (0) + + ADD_TOOL(sample); + #undef ADD_TOOL + + psession->tool.comm = perf_event__process_comm; + psession->tool.mmap = perf_event__process_mmap; + psession->tool.mmap2 = perf_event__process_mmap2; + psession->tool.namespaces = perf_event__process_namespaces; + psession->tool.cgroup = perf_event__process_cgroup; + psession->tool.exit = perf_event__process_exit; + psession->tool.fork = perf_event__process_fork; + psession->tool.ksymbol = perf_event__process_ksymbol; + psession->tool.text_poke = perf_event__process_text_poke; + psession->tool.build_id = perf_event__process_build_id; + psession->tool.attr = perf_event__process_attr; + psession->tool.feature = perf_event__process_feature; + psession->tool.stat = perf_event__process_stat_event; + session = perf_session__new(&pdata->data, &psession->tool); + if (IS_ERR(session)) { + PyErr_Format(PyExc_IOError, "failed to create session: %ld", PTR_ERR(session)); + goto err_out; + } + psession->session = session; + + if (symbol__init(perf_session__env(session)) < 0) { + PyErr_SetString(PyExc_OSError, "perf: symbol__init failed"); + goto err_out; + } + + + + return (PyObject *)psession; +err_out: + Py_DECREF(psession); + return NULL; +} + +static void pyrf_session__delete(struct pyrf_session *psession) +{ + perf_session__delete(psession->session); + Py_XDECREF(psession->pdata); + Py_XDECREF(psession->sample); + Py_TYPE(psession)->tp_free((PyObject *)psession); +} + +static PyObject *pyrf_session__find_thread_events(struct pyrf_session *psession) +{ + int err; + + CHECK_INITIALIZED(psession->session, "session"); + + err = perf_session__process_events(psession->session); + + if (PyErr_Occurred()) + return NULL; + + if (err < 0) { + PyErr_Format(PyExc_OSError, "Process events failed: %d", err); + return NULL; + } + + Py_RETURN_NONE; +} + +static PyMethodDef pyrf_session__methods[] = { + { + .ml_name = "process_events", + .ml_meth = (PyCFunction)pyrf_session__find_thread_events, + .ml_flags = METH_NOARGS, + .ml_doc = PyDoc_STR("Iterate and process events.") + }, + { + .ml_name = "find_thread", + .ml_meth = (PyCFunction)pyrf_session__find_thread, + .ml_flags = METH_VARARGS, + .ml_doc = PyDoc_STR("Returns the thread associated with a pid.") + }, + { .ml_name = NULL, } +}; + +static const char pyrf_session__doc[] = PyDoc_STR("perf session object."); + +static PyObject *pyrf_session__getattro(struct pyrf_session *psession, PyObject *attr_name) +{ + if (!psession->session) { + PyErr_SetString(PyExc_ValueError, "session not initialized"); + return NULL; + } + return PyObject_GenericGetAttr((PyObject *) psession, attr_name); +} + +static int pyrf_session__setattro(struct pyrf_session *psession, PyObject *attr_name, + PyObject *value) +{ + if (!psession->session) { + PyErr_SetString(PyExc_ValueError, "session not initialized"); + return -1; + } + return PyObject_GenericSetAttr((PyObject *) psession, attr_name, value); +} + +static PyTypeObject pyrf_session__type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "perf.session", + .tp_basicsize = sizeof(struct pyrf_session), + .tp_dealloc = (destructor)pyrf_session__delete, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + .tp_methods = pyrf_session__methods, + .tp_doc = pyrf_session__doc, + .tp_new = pyrf_session__new, + .tp_getattro = (getattrofunc) pyrf_session__getattro, + .tp_setattro = (setattrofunc) pyrf_session__setattro, +}; + +static int pyrf_session__setup_types(void) +{ + return PyType_Ready(&pyrf_session__type); +} + static PyMethodDef perf__methods[] = { { .ml_name = "metrics", @@ -2633,8 +2967,10 @@ PyMODINIT_FUNC PyInit_perf(void) }; PyObject *module = PyModule_Create(&moduledef); - if (module == NULL || - pyrf_event__setup_types() < 0 || + if (module == NULL) + return NULL; + + if (pyrf_event__setup_types() < 0 || pyrf_evlist__setup_types() < 0 || pyrf_evsel__setup_types() < 0 || pyrf_thread_map__setup_types() < 0 || @@ -2642,8 +2978,12 @@ PyMODINIT_FUNC PyInit_perf(void) pyrf_pmu_iterator__setup_types() < 0 || pyrf_pmu__setup_types() < 0 || pyrf_counts_values__setup_types() < 0 || - pyrf_data__setup_types() < 0) - return module; + pyrf_data__setup_types() < 0 || + pyrf_session__setup_types() < 0 || + pyrf_thread__setup_types() < 0) { + Py_DECREF(module); + return NULL; + } /* The page_size is placed in util object. */ page_size = sysconf(_SC_PAGE_SIZE); @@ -2693,6 +3033,9 @@ PyMODINIT_FUNC PyInit_perf(void) Py_INCREF(&pyrf_data__type); PyModule_AddObject(module, "data", (PyObject *)&pyrf_data__type); + Py_INCREF(&pyrf_session__type); + PyModule_AddObject(module, "session", (PyObject *)&pyrf_session__type); + dict = PyModule_GetDict(module); if (dict == NULL) goto error; @@ -2706,7 +3049,9 @@ PyMODINIT_FUNC PyInit_perf(void) } error: - if (PyErr_Occurred()) - PyErr_SetString(PyExc_ImportError, "perf: Init failed!"); + if (PyErr_Occurred()) { + Py_XDECREF(module); + return NULL; + } return module; } |
