mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 16:53:20 -04:00
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>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user