diff options
Diffstat (limited to 'tools/perf/util/python.c')
| -rw-r--r-- | tools/perf/util/python.c | 2005 |
1 files changed, 1645 insertions, 360 deletions
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index d8a842621cda..60dbe2dad5eb 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -2,11 +2,18 @@ #define PY_SSIZE_T_CLEAN #include <Python.h> +#include <elf.h> +#include <errno.h> #include <inttypes.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> #include <string.h> +#include <stdlib.h> #include <linux/err.h> #include <poll.h> +#include <sys/stat.h> #include <unistd.h> #include <internal/lib.h> @@ -22,12 +29,17 @@ #include "counts.h" #include "data.h" #include "debug.h" +#include "call-path.h" +#include "db-export.h" +#include "thread-stack.h" #include "dso.h" #include "dwarf-regs.h" #include "event.h" +#include "env.h" #include "branch.h" #include "evlist.h" #include "evsel.h" +#include "symbol.h" #include "expr.h" #include "map.h" #include "metricgroup.h" @@ -40,6 +52,7 @@ #include "srccode.h" #include "srcline.h" #include "strbuf.h" +#include "strlist.h" #include "symbol.h" #include "stat.h" #include "header.h" @@ -58,16 +71,6 @@ PyMODINIT_FUNC PyInit_perf(void); static PyObject *pyrf_evsel__from_evsel(struct evsel *evsel); -#define member_def(type, member, ptype, help) \ - { #member, ptype, \ - offsetof(struct pyrf_event, event) + offsetof(struct type, member), \ - 0, help } - -#define sample_member_def(name, member, ptype, help) \ - { #name, ptype, \ - offsetof(struct pyrf_event, sample) + offsetof(struct perf_sample, member), \ - 0, help } - #define CHECK_INITIALIZED(ptr, msg) \ do { \ if (!(ptr)) { \ @@ -86,48 +89,110 @@ static PyObject *pyrf_evsel__from_evsel(struct evsel *evsel); struct pyrf_event { PyObject_HEAD - /** @sample: The parsed sample from the event. */ - struct perf_sample sample; + /** @sample: The parsed sample, pointing to @sample_storage or a borrowed sample. */ + struct perf_sample *sample; + /** @sample_storage: Storage for @sample when owned by this object. */ + struct perf_sample sample_storage; + /** @evsel: The evsel associated with the event. */ + struct evsel *evsel; + /** @machine: The machine associated with the sample/event. */ + struct machine *machine; /** @al: The address location from machine__resolve, lazily computed. */ struct addr_location al; /** @al_resolved: True when machine__resolve been called. */ bool al_resolved; - /** @callchain: Resolved callchain, eagerly computed if requested. */ + /** @callchain: Resolved callchain, lazily computed. */ PyObject *callchain; /** @brstack: Resolved branch stack, eagerly computed if requested. */ PyObject *brstack; - /** @event: The underlying perf_event that may be in a file or ring buffer. */ - union perf_event event; + /** @event: The underlying perf_event, pointing to @event_copy or a borrowed event. */ + const union perf_event *event; + /** @event_copy: Copy of @event when owned by this object. */ + union perf_event *event_copy; }; -#define sample_members \ - sample_member_def(sample_pid, pid, T_INT, "event pid"), \ - sample_member_def(sample_tid, tid, T_INT, "event tid"), \ - sample_member_def(sample_time, time, T_ULONGLONG, "event timestamp"), \ - sample_member_def(sample_id, id, T_ULONGLONG, "event id"), \ - sample_member_def(sample_stream_id, stream_id, T_ULONGLONG, "event stream id"), \ - sample_member_def(sample_period, period, T_ULONGLONG, "event period"), \ - sample_member_def(sample_cpu, cpu, T_UINT, "event cpu"), +static const union perf_event zero_event; + +static PyObject *pyrf_event__get_member(PyObject *self, void *closure) +{ + struct pyrf_event *pevent = (void *)self; + const union perf_event *event = pevent->event ?: &zero_event; + + return PyMember_GetOne((const char *)event, closure); +} + +static PyObject *pyrf_event__get_sample_member(PyObject *self, void *closure) +{ + struct pyrf_event *pevent = (void *)self; + const struct perf_sample *sample = pevent->sample ?: &pevent->sample_storage; + + return PyMember_GetOne((const char *)sample, closure); +} + +#define named_member_def(field, type, member, ptype, help) \ + { .name = #field, .get = pyrf_event__get_member, .doc = help, \ + .closure = &(struct PyMemberDef){ #field, ptype, \ + offsetof(struct type, member), READONLY, help } } + +#define member_def(type, member, ptype, help) \ + named_member_def(member, type, member, ptype, help) + +struct pyrf_string_member { + size_t offset; + size_t maxlen; +}; + +static size_t pyrf_event__str_len(const struct pyrf_event *pevent, size_t offset, size_t maxlen) +{ + const char *base = (const char *)(pevent->event ?: &zero_event); + size_t event_size = pevent->event ? pevent->event->header.size : 0; + + if (event_size <= offset) + return 0; + return strnlen(base + offset, min(event_size - offset, maxlen)); +} + +static PyObject *pyrf_event__get_string_member(PyObject *self, void *closure) +{ + struct pyrf_event *pevent = (void *)self; + const struct pyrf_string_member *sm = closure; + const char *base = (const char *)(pevent->event ?: &zero_event); + + return PyUnicode_FromStringAndSize(base + sm->offset, + pyrf_event__str_len(pevent, sm->offset, sm->maxlen)); +} + +#define string_member_def(type, member, help) \ + { .name = #member, .get = pyrf_event__get_string_member, .doc = help, \ + .closure = &(struct pyrf_string_member){ offsetof(struct type, member), \ + sizeof(((struct type *)0)->member) } } + +#define sample_member_def(field, member, ptype, help) \ + { .name = #field, .get = pyrf_event__get_sample_member, .doc = help, \ + .closure = &(struct PyMemberDef){ #field, ptype, \ + offsetof(struct perf_sample, member), READONLY, help } } static PyObject *pyrf_event__get_evsel(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (!pevent->sample.evsel) + if (!pevent->sample || !pevent->sample->evsel) Py_RETURN_NONE; - return pyrf_evsel__from_evsel(pevent->sample.evsel); + return pyrf_evsel__from_evsel(pevent->sample->evsel); } -static PyGetSetDef pyrf_event__getset[] = { - { - .name = "evsel", - .get = pyrf_event__get_evsel, - .set = NULL, - .doc = "tracking event.", - }, - { .name = NULL, }, -}; +#define sample_members \ + { .name = "evsel", .get = pyrf_event__get_evsel, .doc = "tracking event." }, \ + sample_member_def(sample_pid, pid, T_INT, "event pid"), \ + sample_member_def(sample_tid, tid, T_INT, "event tid"), \ + sample_member_def(sample_time, time, T_ULONGLONG, "event timestamp"), \ + sample_member_def(sample_id, id, T_ULONGLONG, "event id"), \ + sample_member_def(sample_stream_id, stream_id, T_ULONGLONG, "event stream id"), \ + sample_member_def(machine_pid, machine_pid, T_UINT, "event machine pid"), \ + sample_member_def(vcpu, vcpu, T_UINT, "event vcpu"), \ + sample_member_def(sample_period, period, T_ULONGLONG, "event period"), \ + sample_member_def(sample_cpu, cpu, T_UINT, "event cpu"), static void pyrf_event__delete(struct pyrf_event *pevent) { @@ -135,13 +200,15 @@ static void pyrf_event__delete(struct pyrf_event *pevent) addr_location__exit(&pevent->al); Py_XDECREF(pevent->callchain); Py_XDECREF(pevent->brstack); - perf_sample__exit(&pevent->sample); + perf_sample__exit(&pevent->sample_storage); + evsel__put(pevent->evsel); + free(pevent->event_copy); Py_TYPE(pevent)->tp_free((PyObject *)pevent); } static const char pyrf_mmap_event__doc[] = PyDoc_STR("perf mmap event object."); -static PyMemberDef pyrf_mmap_event__members[] = { +static PyGetSetDef pyrf_mmap_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_event_header, misc, T_USHORT, "event misc"), @@ -150,7 +217,7 @@ static PyMemberDef pyrf_mmap_event__members[] = { member_def(perf_record_mmap, start, T_ULONGLONG, "start of the map"), member_def(perf_record_mmap, len, T_ULONGLONG, "map length"), member_def(perf_record_mmap, pgoff, T_ULONGLONG, "page offset"), - member_def(perf_record_mmap, filename, T_STRING_INPLACE, "backing store"), + string_member_def(perf_record_mmap, filename, "backing store"), { .name = NULL, }, }; @@ -158,13 +225,15 @@ static PyObject *pyrf_mmap_event__repr(const struct pyrf_event *pevent) { PyObject *ret; char *s; + int fn_len = pyrf_event__str_len(pevent, offsetof(struct perf_record_mmap, filename), + sizeof(pevent->event->mmap.filename)); if (asprintf(&s, "{ type: mmap, pid: %u, tid: %u, start: %#" PRI_lx64 ", " "length: %#" PRI_lx64 ", offset: %#" PRI_lx64 ", " - "filename: %s }", - pevent->event.mmap.pid, pevent->event.mmap.tid, - pevent->event.mmap.start, pevent->event.mmap.len, - pevent->event.mmap.pgoff, pevent->event.mmap.filename) < 0) { + "filename: %.*s }", + pevent->event->mmap.pid, pevent->event->mmap.tid, + pevent->event->mmap.start, pevent->event->mmap.len, + pevent->event->mmap.pgoff, fn_len, pevent->event->mmap.filename) < 0) { ret = PyErr_NoMemory(); } else { ret = PyUnicode_FromString(s); @@ -180,8 +249,7 @@ static PyTypeObject pyrf_mmap_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_mmap_event__doc, - .tp_members = pyrf_mmap_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_mmap_event__getset, .tp_repr = (reprfunc)pyrf_mmap_event__repr, }; @@ -191,64 +259,69 @@ static PyObject *pyrf_mmap2_event__get_maj(PyObject *self, void *closure __maybe { struct pyrf_event *pevent = (void *)self; - if (pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) + if (pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) Py_RETURN_NONE; - return PyLong_FromUnsignedLong(pevent->event.mmap2.maj); + return PyLong_FromUnsignedLong(pevent->event->mmap2.maj); } static PyObject *pyrf_mmap2_event__get_min(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) + if (pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) Py_RETURN_NONE; - return PyLong_FromUnsignedLong(pevent->event.mmap2.min); + return PyLong_FromUnsignedLong(pevent->event->mmap2.min); } static PyObject *pyrf_mmap2_event__get_ino(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) + if (pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) Py_RETURN_NONE; - return PyLong_FromUnsignedLongLong(pevent->event.mmap2.ino); + return PyLong_FromUnsignedLongLong(pevent->event->mmap2.ino); } static PyObject *pyrf_mmap2_event__get_ino_generation(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) + if (pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID) Py_RETURN_NONE; - return PyLong_FromUnsignedLongLong(pevent->event.mmap2.ino_generation); + return PyLong_FromUnsignedLongLong(pevent->event->mmap2.ino_generation); } static PyObject *pyrf_mmap2_event__get_build_id(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; - if (!(pevent->event.header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID)) + if (!(pevent->event->header.misc & PERF_RECORD_MISC_MMAP_BUILD_ID)) Py_RETURN_NONE; - int size = pevent->event.mmap2.build_id_size; + int size = pevent->event->mmap2.build_id_size; if (size > 20) size = 20; - return PyBytes_FromStringAndSize((const char *)pevent->event.mmap2.build_id, size); + return PyBytes_FromStringAndSize((const char *)pevent->event->mmap2.build_id, size); } static PyGetSetDef pyrf_mmap2_event__getset[] = { - { - .name = "evsel", - .get = pyrf_event__get_evsel, - .set = NULL, - .doc = "tracking event.", - }, + sample_members + member_def(perf_event_header, type, T_UINT, "event type"), + member_def(perf_event_header, misc, T_USHORT, "event misc"), + member_def(perf_record_mmap2, pid, T_UINT, "event pid"), + member_def(perf_record_mmap2, tid, T_UINT, "event tid"), + member_def(perf_record_mmap2, start, T_ULONGLONG, "start of the map"), + member_def(perf_record_mmap2, len, T_ULONGLONG, "map length"), + member_def(perf_record_mmap2, pgoff, T_ULONGLONG, "page offset"), + member_def(perf_record_mmap2, prot, T_UINT, "protection"), + member_def(perf_record_mmap2, flags, T_UINT, "flags"), + string_member_def(perf_record_mmap2, filename, "backing store"), { .name = "maj", .get = pyrf_mmap2_event__get_maj, @@ -282,31 +355,18 @@ static PyGetSetDef pyrf_mmap2_event__getset[] = { { .name = NULL, }, }; -static PyMemberDef pyrf_mmap2_event__members[] = { - sample_members - member_def(perf_event_header, type, T_UINT, "event type"), - member_def(perf_event_header, misc, T_USHORT, "event misc"), - member_def(perf_record_mmap2, pid, T_UINT, "event pid"), - member_def(perf_record_mmap2, tid, T_UINT, "event tid"), - member_def(perf_record_mmap2, start, T_ULONGLONG, "start of the map"), - member_def(perf_record_mmap2, len, T_ULONGLONG, "map length"), - member_def(perf_record_mmap2, pgoff, T_ULONGLONG, "page offset"), - member_def(perf_record_mmap2, prot, T_UINT, "protection"), - member_def(perf_record_mmap2, flags, T_UINT, "flags"), - member_def(perf_record_mmap2, filename, T_STRING_INPLACE, "backing store"), - { .name = NULL, }, -}; - static PyObject *pyrf_mmap2_event__repr(const struct pyrf_event *pevent) { PyObject *ret; char *s; - - if (asprintf(&s, "{ type: mmap2, pid: %u, tid: %u, start: %#" PRI_lx64 ", length: %#" PRI_lx64 ", offset: %#" PRI_lx64 ", flags: %#x, prot: %#x, filename: %s }", - pevent->event.mmap2.pid, pevent->event.mmap2.tid, - pevent->event.mmap2.start, pevent->event.mmap2.len, - pevent->event.mmap2.pgoff, pevent->event.mmap2.flags, - pevent->event.mmap2.prot, pevent->event.mmap2.filename) < 0) + int fn_len = pyrf_event__str_len(pevent, offsetof(struct perf_record_mmap2, filename), + sizeof(pevent->event->mmap2.filename)); + + if (asprintf(&s, "{ type: mmap2, pid: %u, tid: %u, start: %#" PRI_lx64 ", length: %#" PRI_lx64 ", offset: %#" PRI_lx64 ", flags: %#x, prot: %#x, filename: %.*s }", + pevent->event->mmap2.pid, pevent->event->mmap2.tid, + pevent->event->mmap2.start, pevent->event->mmap2.len, + pevent->event->mmap2.pgoff, pevent->event->mmap2.flags, + pevent->event->mmap2.prot, fn_len, pevent->event->mmap2.filename) < 0) return PyErr_NoMemory(); ret = PyUnicode_FromString(s); @@ -321,14 +381,13 @@ static PyTypeObject pyrf_mmap2_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_mmap2_event__doc, - .tp_members = pyrf_mmap2_event__members, .tp_getset = pyrf_mmap2_event__getset, .tp_repr = (reprfunc)pyrf_mmap2_event__repr, }; static const char pyrf_task_event__doc[] = PyDoc_STR("perf task (fork/exit) event object."); -static PyMemberDef pyrf_task_event__members[] = { +static PyGetSetDef pyrf_task_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_fork, pid, T_UINT, "event pid"), @@ -343,12 +402,12 @@ static PyObject *pyrf_task_event__repr(const struct pyrf_event *pevent) { return PyUnicode_FromFormat("{ type: %s, pid: %u, ppid: %u, tid: %u, " "ptid: %u, time: %" PRI_lu64 "}", - pevent->event.header.type == PERF_RECORD_FORK ? "fork" : "exit", - pevent->event.fork.pid, - pevent->event.fork.ppid, - pevent->event.fork.tid, - pevent->event.fork.ptid, - pevent->event.fork.time); + pevent->event->header.type == PERF_RECORD_FORK ? "fork" : "exit", + pevent->event->fork.pid, + pevent->event->fork.ppid, + pevent->event->fork.tid, + pevent->event->fork.ptid, + pevent->event->fork.time); } static PyTypeObject pyrf_task_event__type = { @@ -358,28 +417,36 @@ static PyTypeObject pyrf_task_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_task_event__doc, - .tp_members = pyrf_task_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_task_event__getset, .tp_repr = (reprfunc)pyrf_task_event__repr, }; static const char pyrf_comm_event__doc[] = PyDoc_STR("perf comm event object."); -static PyMemberDef pyrf_comm_event__members[] = { +static PyGetSetDef pyrf_comm_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_comm, pid, T_UINT, "event pid"), member_def(perf_record_comm, tid, T_UINT, "event tid"), - member_def(perf_record_comm, comm, T_STRING_INPLACE, "process name"), + string_member_def(perf_record_comm, comm, "process name"), { .name = NULL, }, }; static PyObject *pyrf_comm_event__repr(const struct pyrf_event *pevent) { - return PyUnicode_FromFormat("{ type: comm, pid: %u, tid: %u, comm: %s }", - pevent->event.comm.pid, - pevent->event.comm.tid, - pevent->event.comm.comm); + PyObject *ret; + char *s; + int comm_len = pyrf_event__str_len(pevent, offsetof(struct perf_record_comm, comm), + sizeof(pevent->event->comm.comm)); + + if (asprintf(&s, "{ type: comm, pid: %u, tid: %u, comm: %.*s }", + pevent->event->comm.pid, pevent->event->comm.tid, + comm_len, pevent->event->comm.comm) < 0) + return PyErr_NoMemory(); + + ret = PyUnicode_FromString(s); + free(s); + return ret; } static PyTypeObject pyrf_comm_event__type = { @@ -389,14 +456,13 @@ static PyTypeObject pyrf_comm_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_comm_event__doc, - .tp_members = pyrf_comm_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_comm_event__getset, .tp_repr = (reprfunc)pyrf_comm_event__repr, }; static const char pyrf_throttle_event__doc[] = PyDoc_STR("perf throttle event object."); -static PyMemberDef pyrf_throttle_event__members[] = { +static PyGetSetDef pyrf_throttle_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_throttle, time, T_ULONGLONG, "timestamp"), @@ -408,11 +474,11 @@ static PyMemberDef pyrf_throttle_event__members[] = { static PyObject *pyrf_throttle_event__repr(const struct pyrf_event *pevent) { const struct perf_record_throttle *te = (const struct perf_record_throttle *) - (&pevent->event.header + 1); + (&pevent->event->header + 1); return PyUnicode_FromFormat("{ type: %sthrottle, time: %" PRI_lu64 ", id: %" PRI_lu64 ", stream_id: %" PRI_lu64 " }", - pevent->event.header.type == PERF_RECORD_THROTTLE ? "" : "un", + pevent->event->header.type == PERF_RECORD_THROTTLE ? "" : "un", te->time, te->id, te->stream_id); } @@ -423,14 +489,13 @@ static PyTypeObject pyrf_throttle_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_throttle_event__doc, - .tp_members = pyrf_throttle_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_throttle_event__getset, .tp_repr = (reprfunc)pyrf_throttle_event__repr, }; static const char pyrf_lost_event__doc[] = PyDoc_STR("perf lost event object."); -static PyMemberDef pyrf_lost_event__members[] = { +static PyGetSetDef pyrf_lost_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_lost, id, T_ULONGLONG, "event id"), @@ -445,7 +510,7 @@ static PyObject *pyrf_lost_event__repr(const struct pyrf_event *pevent) if (asprintf(&s, "{ type: lost, id: %#" PRI_lx64 ", " "lost: %#" PRI_lx64 " }", - pevent->event.lost.id, pevent->event.lost.lost) < 0) { + pevent->event->lost.id, pevent->event->lost.lost) < 0) { ret = PyErr_NoMemory(); } else { ret = PyUnicode_FromString(s); @@ -461,14 +526,13 @@ static PyTypeObject pyrf_lost_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_lost_event__doc, - .tp_members = pyrf_lost_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_lost_event__getset, .tp_repr = (reprfunc)pyrf_lost_event__repr, }; static const char pyrf_stat_event__doc[] = PyDoc_STR("perf stat event object."); -static PyMemberDef pyrf_stat_event__members[] = { +static PyGetSetDef pyrf_stat_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_stat, id, T_ULONGLONG, "event id"), @@ -482,14 +546,16 @@ static PyMemberDef pyrf_stat_event__members[] = { static PyObject *pyrf_stat_event__repr(const struct pyrf_event *pevent) { + const union perf_event *event = pevent->event ?: &zero_event; + return PyUnicode_FromFormat( "{ type: stat, id: %llu, cpu: %u, thread: %u, val: %llu, ena: %llu, run: %llu }", - pevent->event.stat.id, - pevent->event.stat.cpu, - pevent->event.stat.thread, - pevent->event.stat.val, - pevent->event.stat.ena, - pevent->event.stat.run); + event->stat.id, + event->stat.cpu, + event->stat.thread, + event->stat.val, + event->stat.ena, + event->stat.run); } static PyTypeObject pyrf_stat_event__type = { @@ -500,28 +566,27 @@ static PyTypeObject pyrf_stat_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_stat_event__doc, - .tp_members = pyrf_stat_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_stat_event__getset, .tp_repr = (reprfunc)pyrf_stat_event__repr, }; static const char pyrf_stat_round_event__doc[] = PyDoc_STR("perf stat round event object."); -static PyMemberDef pyrf_stat_round_event__members[] = { +static PyGetSetDef pyrf_stat_round_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), - { .name = "stat_round_type", .type = T_ULONGLONG, - .offset = offsetof(struct pyrf_event, event) + offsetof(struct perf_record_stat_round, type), - .doc = "round type" }, + named_member_def(stat_round_type, perf_record_stat_round, type, T_ULONGLONG, "round type"), member_def(perf_record_stat_round, time, T_ULONGLONG, "round time"), { .name = NULL, }, }; static PyObject *pyrf_stat_round_event__repr(const struct pyrf_event *pevent) { + const union perf_event *event = pevent->event ?: &zero_event; + return PyUnicode_FromFormat("{ type: stat_round, type: %llu, time: %llu }", - pevent->event.stat_round.type, - pevent->event.stat_round.time); + event->stat_round.type, + event->stat_round.time); } static PyTypeObject pyrf_stat_round_event__type = { @@ -532,14 +597,13 @@ static PyTypeObject pyrf_stat_round_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_stat_round_event__doc, - .tp_members = pyrf_stat_round_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_stat_round_event__getset, .tp_repr = (reprfunc)pyrf_stat_round_event__repr, }; static const char pyrf_read_event__doc[] = PyDoc_STR("perf read event object."); -static PyMemberDef pyrf_read_event__members[] = { +static PyGetSetDef pyrf_read_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), member_def(perf_record_read, pid, T_UINT, "event pid"), @@ -550,8 +614,8 @@ static PyMemberDef pyrf_read_event__members[] = { static PyObject *pyrf_read_event__repr(const struct pyrf_event *pevent) { return PyUnicode_FromFormat("{ type: read, pid: %u, tid: %u }", - pevent->event.read.pid, - pevent->event.read.tid); + pevent->event->read.pid, + pevent->event->read.tid); /* * FIXME: return the array of read values, * making this method useful ;-) @@ -565,26 +629,12 @@ static PyTypeObject pyrf_read_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_read_event__doc, - .tp_members = pyrf_read_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_read_event__getset, .tp_repr = (reprfunc)pyrf_read_event__repr, }; static const char pyrf_sample_event__doc[] = PyDoc_STR("perf sample event object."); -static PyMemberDef pyrf_sample_event__members[] = { - sample_members - sample_member_def(sample_ip, ip, T_ULONGLONG, "event ip"), - sample_member_def(sample_addr, addr, T_ULONGLONG, "event addr"), - sample_member_def(sample_phys_addr, phys_addr, T_ULONGLONG, "event physical addr"), - sample_member_def(sample_weight, weight, T_ULONGLONG, "event weight"), - sample_member_def(sample_data_src, data_src, T_ULONGLONG, "event data source"), - sample_member_def(sample_insn_count, insn_cnt, T_ULONGLONG, "event instruction count"), - sample_member_def(sample_cyc_count, cyc_cnt, T_ULONGLONG, "event cycle count"), - member_def(perf_event_header, type, T_UINT, "event type"), - { .name = NULL, }, -}; - static PyObject *pyrf_sample_event__repr(const struct pyrf_event *pevent) { PyObject *ret; @@ -602,16 +652,18 @@ static PyObject *pyrf_sample_event__repr(const struct pyrf_event *pevent) #ifdef HAVE_LIBTRACEEVENT static bool is_tracepoint(const struct pyrf_event *pevent) { - if (!pevent->sample.evsel) + struct evsel *evsel = pevent->sample->evsel ?: pevent->evsel; + + if (!evsel) return false; - return pevent->sample.evsel->core.attr.type == PERF_TYPE_TRACEPOINT; + return evsel->core.attr.type == PERF_TYPE_TRACEPOINT; } static PyObject* tracepoint_field(const struct pyrf_event *pe, struct tep_format_field *field) { struct tep_handle *pevent = field->event->tep; - void *data = pe->sample.raw_data; + void *data = pe->sample->raw_data; PyObject *ret = NULL; unsigned long long val; unsigned int offset, len; @@ -651,7 +703,7 @@ tracepoint_field(const struct pyrf_event *pe, struct tep_format_field *field) static PyObject* get_tracepoint_field(struct pyrf_event *pevent, PyObject *attr_name) { - struct evsel *evsel = pevent->sample.evsel; + struct evsel *evsel = pevent->sample->evsel ?: pevent->evsel; struct tep_event *tp_format = evsel__tp_format(evsel); struct tep_format_field *field; @@ -676,18 +728,19 @@ get_tracepoint_field(struct pyrf_event *pevent, PyObject *attr_name) static int pyrf_sample_event__resolve_al(struct pyrf_event *pevent) { - struct evsel *evsel = pevent->sample.evsel; - struct evlist *evlist = evsel ? evsel->evlist : NULL; - struct perf_session *session = evlist ? evlist__session(evlist) : NULL; - if (pevent->al_resolved) return 0; - if (!session) + /* + * Use pevent->machine, which pyrf_event__new() initializes while the + * session callback is active and pyrf_event__copy() clears after + * resolving pevent->al if a reference is retained beyond the callback. + */ + if (!pevent->machine) return -1; addr_location__init(&pevent->al); - if (machine__resolve(&session->machines.host, &pevent->al, &pevent->sample) < 0) { + if (machine__resolve(pevent->machine, &pevent->al, pevent->sample) < 0) { addr_location__exit(&pevent->al); return -1; } @@ -771,6 +824,15 @@ static PyObject *pyrf_sample_event__get_sym_start(struct pyrf_event *pevent, return PyLong_FromUnsignedLongLong(pevent->al.sym->start); } +static PyObject *pyrf_sample_event__get_sym_offset(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.sym) + Py_RETURN_NONE; + + return PyLong_FromUnsignedLongLong(pevent->al.addr - pevent->al.sym->start); +} + static PyObject *pyrf_sample_event__get_sym_end(struct pyrf_event *pevent, void *closure __maybe_unused) { @@ -783,17 +845,17 @@ static PyObject *pyrf_sample_event__get_sym_end(struct pyrf_event *pevent, static PyObject *pyrf_sample_event__get_raw_buf(struct pyrf_event *pevent, void *closure __maybe_unused) { - if (pevent->event.header.type != PERF_RECORD_SAMPLE) + if (pevent->event->header.type != PERF_RECORD_SAMPLE) Py_RETURN_NONE; - return PyBytes_FromStringAndSize((const char *)pevent->sample.raw_data, - pevent->sample.raw_size); + return PyBytes_FromStringAndSize((const char *)pevent->sample->raw_data, + pevent->sample->raw_size); } static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args) { struct pyrf_event *pevent = (void *)self; - u64 addr = pevent->sample.ip; + u64 addr = pevent->sample->ip; char *srcfile = NULL; char *srccode = NULL; unsigned int line = 0; @@ -801,15 +863,21 @@ static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args) PyObject *result; struct addr_location al; + /* + * addr defaults to pevent->sample->ip when omitted, and when + * addr != pevent->sample->ip a local addr_location is resolved so + * callers can inspect callchain/branch addresses without mutating + * the sample's cached pevent->al. + */ if (!PyArg_ParseTuple(args, "|K", &addr)) return NULL; if (pyrf_sample_event__resolve_al(pevent) < 0) Py_RETURN_NONE; - if (addr != pevent->sample.ip) { + if (addr != pevent->sample->ip) { addr_location__init(&al); - thread__find_symbol_fb(pevent->al.thread, pevent->sample.cpumode, addr, &al); + thread__find_symbol_fb(pevent->al.thread, pevent->sample->cpumode, addr, &al); } else { addr_location__init(&al); al.thread = thread__get(pevent->al.thread); @@ -829,6 +897,12 @@ static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args) addr_location__exit(&al); if (srcfile) { + /* + * PyUnicode_FromStringAndSize(line, len) (via Py_BuildValue "s#") + * must copy the source line before free_srccode() frees the + * buffer, and because the line returned by find_sourceline() is + * bounded by len rather than NUL-terminated. + */ srccode = find_sourceline(srcfile, line, &len); result = Py_BuildValue("(sIs#)", srcfile, line, srccode, (Py_ssize_t)len); free(srcfile); @@ -842,29 +916,21 @@ static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args) static PyObject *pyrf_sample_event__insn(PyObject *self, PyObject *args __maybe_unused) { struct pyrf_event *pevent = (void *)self; - struct thread *thread; - struct machine *machine; - if (pyrf_sample_event__resolve_al(pevent) < 0) - Py_RETURN_NONE; - - thread = pevent->al.thread; - - if (!thread || !thread__maps(thread)) - Py_RETURN_NONE; - - machine = maps__machine(thread__maps(thread)); - if (!machine) - Py_RETURN_NONE; + if (pevent->sample->ip && !pevent->sample->insn_len) { + if (pyrf_sample_event__resolve_al(pevent) < 0 || + !pevent->al.thread || !pevent->machine) + Py_RETURN_NONE; - if (pevent->sample.ip && !pevent->sample.insn_len) - perf_sample__fetch_insn(&pevent->sample, thread, machine); + perf_sample__fetch_insn(pevent->sample, pevent->al.thread, + pevent->machine); + } - if (!pevent->sample.insn_len) + if (!pevent->sample->insn_len) Py_RETURN_NONE; - return PyBytes_FromStringAndSize((const char *)pevent->sample.insn, - pevent->sample.insn_len); + return PyBytes_FromStringAndSize((const char *)pevent->sample->insn, + pevent->sample->insn_len); } struct pyrf_callchain_node { @@ -872,11 +938,13 @@ struct pyrf_callchain_node { u64 ip; struct map *map; struct symbol *sym; + char *sym_name; }; static void pyrf_callchain_node__delete(struct pyrf_callchain_node *pnode) { map__put(pnode->map); + free(pnode->sym_name); Py_TYPE(pnode)->tp_free((PyObject *)pnode); } @@ -889,6 +957,8 @@ static PyObject *pyrf_callchain_node__get_ip(struct pyrf_callchain_node *pnode, static PyObject *pyrf_callchain_node__get_symbol(struct pyrf_callchain_node *pnode, void *closure __maybe_unused) { + if (pnode->sym_name) + return PyUnicode_FromString(pnode->sym_name); if (pnode->sym) return PyUnicode_FromString(pnode->sym->name); return PyUnicode_FromString("[unknown]"); @@ -912,10 +982,23 @@ static PyObject *pyrf_callchain_node__get_dso(struct pyrf_callchain_node *pnode, return PyUnicode_FromString(dsoname); } +static PyObject *pyrf_callchain_node__get_sym_offset(struct pyrf_callchain_node *pnode, + void *closure __maybe_unused) +{ + u64 addr; + + if (!pnode->sym) + Py_RETURN_NONE; + + addr = pnode->map ? map__map_ip(pnode->map, pnode->ip) : pnode->ip; + return PyLong_FromUnsignedLongLong(addr - pnode->sym->start); +} + static PyGetSetDef pyrf_callchain_node__getset[] = { - { .name = "ip", .get = (getter)pyrf_callchain_node__get_ip, }, - { .name = "symbol", .get = (getter)pyrf_callchain_node__get_symbol, }, - { .name = "dso", .get = (getter)pyrf_callchain_node__get_dso, }, + { .name = "ip", .get = (getter)pyrf_callchain_node__get_ip, }, + { .name = "symbol", .get = (getter)pyrf_callchain_node__get_symbol, }, + { .name = "dso", .get = (getter)pyrf_callchain_node__get_dso, }, + { .name = "sym_offset", .get = (getter)pyrf_callchain_node__get_sym_offset, }, { .name = NULL, }, }; @@ -933,6 +1016,7 @@ struct pyrf_callchain_frame { u64 ip; struct map *map; struct symbol *sym; + char *sym_name; }; struct pyrf_callchain { @@ -944,8 +1028,10 @@ struct pyrf_callchain { static void pyrf_callchain__delete(struct pyrf_callchain *pchain) { if (pchain->frames) { - for (u64 i = 0; i < pchain->nr_frames; i++) + for (u64 i = 0; i < pchain->nr_frames; i++) { map__put(pchain->frames[i].map); + free(pchain->frames[i].sym_name); + } free(pchain->frames); } Py_TYPE(pchain)->tp_free((PyObject *)pchain); @@ -975,6 +1061,7 @@ static PyObject *pyrf_callchain__item(PyObject *obj, Py_ssize_t i) pnode->ip = pchain->frames[i].ip; pnode->map = map__get(pchain->frames[i].map); pnode->sym = pchain->frames[i].sym; + pnode->sym_name = pchain->frames[i].sym_name ? strdup(pchain->frames[i].sym_name) : NULL; return (PyObject *)pnode; } @@ -994,10 +1081,53 @@ static PyTypeObject pyrf_callchain__type = { .tp_as_sequence = &pyrf_callchain__sequence_methods, }; +static int pyrf_sample_event__resolve_callchain(struct pyrf_event *pevent) +{ + struct callchain_cursor *cursor; + struct pyrf_callchain *pchain; + struct callchain_cursor_node *node; + + if (pevent->callchain || !pevent->sample->callchain || !pevent->machine) + return 0; + + if (pyrf_sample_event__resolve_al(pevent) < 0) + return 0; + + cursor = get_tls_callchain_cursor(); + if (thread__resolve_callchain(pevent->al.thread, cursor, pevent->sample, + NULL, NULL, PERF_MAX_STACK_DEPTH) != 0) + return 0; + + callchain_cursor_commit(cursor); + pchain = PyObject_New(struct pyrf_callchain, &pyrf_callchain__type); + if (!pchain) + return -ENOMEM; + + pchain->nr_frames = cursor->nr; + pchain->frames = calloc(pchain->nr_frames, sizeof(*pchain->frames)); + if (!pchain->frames) { + Py_DECREF(pchain); + PyErr_NoMemory(); + return -ENOMEM; + } + for (u64 i = 0; i < pchain->nr_frames; i++) { + node = callchain_cursor_current(cursor); + pchain->frames[i].ip = node->ip; + pchain->frames[i].map = map__get(node->ms.map); + pchain->frames[i].sym = node->ms.sym; + callchain_cursor_advance(cursor); + } + pevent->callchain = (PyObject *)pchain; + return 0; +} + static PyObject *pyrf_sample_event__get_callchain(PyObject *self, void *closure __maybe_unused) { struct pyrf_event *pevent = (void *)self; + if (pyrf_sample_event__resolve_callchain(pevent) < 0) + return NULL; + if (!pevent->callchain) Py_RETURN_NONE; @@ -1166,7 +1296,123 @@ pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name) return obj ?: PyObject_GenericGetAttr((PyObject *) pevent, attr_name); } +static int pyrf_sample_event__resolve_addr_al(struct pyrf_event *pevent, + struct addr_location *addr_al) +{ + addr_location__init(addr_al); + if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.thread) + return -1; + + thread__find_symbol_fb(pevent->al.thread, pevent->sample->cpumode, + pevent->sample->addr, addr_al); + return 0; +} + +static PyObject *pyrf_sample_event__get_addr_dso(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + struct addr_location addr_al; + PyObject *ret = Py_None; + + if (pyrf_sample_event__resolve_addr_al(pevent, &addr_al) == 0 && addr_al.map) + ret = PyUnicode_FromString(dso__name(map__dso(addr_al.map))); + else + Py_INCREF(Py_None); + + addr_location__exit(&addr_al); + return ret; +} + +static PyObject *pyrf_sample_event__get_addr_symbol(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + struct addr_location addr_al; + PyObject *ret = Py_None; + + if (pyrf_sample_event__resolve_addr_al(pevent, &addr_al) == 0 && addr_al.sym) + ret = PyUnicode_FromString(addr_al.sym->name); + else + Py_INCREF(Py_None); + + addr_location__exit(&addr_al); + return ret; +} + +static PyObject *pyrf_sample_event__get_addr_sym_offset(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + struct addr_location addr_al; + PyObject *ret = Py_None; + + if (pyrf_sample_event__resolve_addr_al(pevent, &addr_al) == 0 && addr_al.sym) + ret = PyLong_FromUnsignedLongLong(addr_al.addr - addr_al.sym->start); + else + Py_INCREF(Py_None); + + addr_location__exit(&addr_al); + return ret; +} + +static PyObject *pyrf_sample_event__get_branch_type(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLong(pevent->sample->flags & PERF_BRANCH_MASK); +} + +static PyObject *pyrf_sample_event__get_in_tx(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLong(!!(pevent->sample->flags & PERF_IP_FLAG_IN_TX)); +} + +static PyObject *pyrf_sample_event__get_transaction(struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pevent->sample->transaction); +} + static PyGetSetDef pyrf_sample_event__getset[] = { + sample_members + sample_member_def(sample_ip, ip, T_ULONGLONG, "event ip"), + sample_member_def(sample_addr, addr, T_ULONGLONG, "event addr"), + sample_member_def(sample_phys_addr, phys_addr, T_ULONGLONG, "event physical addr"), + sample_member_def(sample_weight, weight, T_ULONGLONG, "event weight"), + sample_member_def(sample_data_src, data_src, T_ULONGLONG, "event data source"), + sample_member_def(sample_insn_count, insn_cnt, T_ULONGLONG, "event instruction count"), + sample_member_def(sample_cyc_count, cyc_cnt, T_ULONGLONG, "event cycle count"), + sample_member_def(flags, flags, T_UINT, "event flags"), + member_def(perf_event_header, type, T_UINT, "event type"), + { + .name = "addr_dso", + .get = (getter)pyrf_sample_event__get_addr_dso, + .doc = "event destination dso.", + }, + { + .name = "addr_symbol", + .get = (getter)pyrf_sample_event__get_addr_symbol, + .doc = "event destination symbol.", + }, + { + .name = "addr_sym_offset", + .get = (getter)pyrf_sample_event__get_addr_sym_offset, + .doc = "event destination symbol offset.", + }, + { + .name = "branch_type", + .get = (getter)pyrf_sample_event__get_branch_type, + .doc = "branch type.", + }, + { + .name = "in_tx", + .get = (getter)pyrf_sample_event__get_in_tx, + .doc = "in transaction flag.", + }, + { + .name = "transaction", + .get = (getter)pyrf_sample_event__get_transaction, + .doc = "transaction execution.", + }, + { .name = "callchain", .get = pyrf_sample_event__get_callchain, @@ -1186,12 +1432,6 @@ static PyGetSetDef pyrf_sample_event__getset[] = { .doc = "event raw buffer.", }, { - .name = "evsel", - .get = pyrf_event__get_evsel, - .set = NULL, - .doc = "tracking event.", - }, - { .name = "dso", .get = (getter)pyrf_sample_event__get_dso, .set = NULL, @@ -1228,6 +1468,12 @@ static PyGetSetDef pyrf_sample_event__getset[] = { .doc = "event map page offset.", }, { + .name = "sym_offset", + .get = (getter)pyrf_sample_event__get_sym_offset, + .set = NULL, + .doc = "event symbol offset.", + }, + { .name = "symbol", .get = (getter)pyrf_sample_event__get_symbol, .set = NULL, @@ -1271,7 +1517,6 @@ static PyTypeObject pyrf_sample_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_sample_event__doc, - .tp_members = pyrf_sample_event__members, .tp_getset = pyrf_sample_event__getset, .tp_methods = pyrf_sample_event__methods, .tp_repr = (reprfunc)pyrf_sample_event__repr, @@ -1280,11 +1525,38 @@ static PyTypeObject pyrf_sample_event__type = { static const char pyrf_context_switch_event__doc[] = PyDoc_STR("perf context_switch event object."); -static PyMemberDef pyrf_context_switch_event__members[] = { +static PyObject *pyrf_context_switch_event__get_next_prev_pid(const struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + if (pevent->event->header.type == PERF_RECORD_SWITCH_CPU_WIDE) + return PyLong_FromUnsignedLong(pevent->event->context_switch.next_prev_pid); + Py_RETURN_NONE; +} + +static PyObject *pyrf_context_switch_event__get_next_prev_tid(const struct pyrf_event *pevent, + void *closure __maybe_unused) +{ + if (pevent->event->header.type == PERF_RECORD_SWITCH_CPU_WIDE) + return PyLong_FromUnsignedLong(pevent->event->context_switch.next_prev_tid); + Py_RETURN_NONE; +} + +static PyGetSetDef pyrf_context_switch_event__getset[] = { sample_members member_def(perf_event_header, type, T_UINT, "event type"), - member_def(perf_record_switch, next_prev_pid, T_UINT, "next/prev pid"), - member_def(perf_record_switch, next_prev_tid, T_UINT, "next/prev tid"), + member_def(perf_event_header, misc, T_USHORT, "event misc"), + { + .name = "next_prev_pid", + .get = (getter)pyrf_context_switch_event__get_next_prev_pid, + .set = NULL, + .doc = "next/prev pid for CPU-wide switch, or None.", + }, + { + .name = "next_prev_tid", + .get = (getter)pyrf_context_switch_event__get_next_prev_tid, + .set = NULL, + .doc = "next/prev tid for CPU-wide switch, or None.", + }, { .name = NULL, }, }; @@ -1292,11 +1564,19 @@ static PyObject *pyrf_context_switch_event__repr(const struct pyrf_event *pevent { PyObject *ret; char *s; - - if (asprintf(&s, "{ type: context_switch, next_prev_pid: %u, next_prev_tid: %u, switch_out: %u }", - pevent->event.context_switch.next_prev_pid, - pevent->event.context_switch.next_prev_tid, - !!(pevent->event.header.misc & PERF_RECORD_MISC_SWITCH_OUT)) < 0) { + int res; + + if (pevent->event->header.type == PERF_RECORD_SWITCH_CPU_WIDE) { + res = asprintf(&s, + "{ type: context_switch, next_prev_pid: %u, next_prev_tid: %u, switch_out: %u }", + pevent->event->context_switch.next_prev_pid, + pevent->event->context_switch.next_prev_tid, + !!(pevent->event->header.misc & PERF_RECORD_MISC_SWITCH_OUT)); + } else { + res = asprintf(&s, "{ type: context_switch, switch_out: %u }", + !!(pevent->event->header.misc & PERF_RECORD_MISC_SWITCH_OUT)); + } + if (res < 0) { ret = PyErr_NoMemory(); } else { ret = PyUnicode_FromString(s); @@ -1312,8 +1592,7 @@ static PyTypeObject pyrf_context_switch_event__type = { .tp_dealloc = (destructor)pyrf_event__delete, .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, .tp_doc = pyrf_context_switch_event__doc, - .tp_members = pyrf_context_switch_event__members, - .tp_getset = pyrf_event__getset, + .tp_getset = pyrf_context_switch_event__getset, .tp_repr = (reprfunc)pyrf_context_switch_event__repr, }; @@ -1387,18 +1666,120 @@ static PyTypeObject *pyrf_event__type[] = { [PERF_RECORD_STAT_ROUND] = &pyrf_stat_round_event__type, }; +static int pyrf_event__copy(struct pyrf_event *pevent) +{ + const union perf_event *orig_event = pevent->event; + const struct perf_sample *orig_sample = pevent->sample; + size_t copy_size = orig_event->header.size; + int err = -EINVAL; + + /* + * If a session callback retained a reference to pevent, resolve its + * address location and callchain now while pevent->machine is still + * live and thread->maps reflects the point in time of the sample. + */ + if (pevent->machine && pevent->evsel) { + pyrf_sample_event__resolve_al(pevent); + if (pyrf_sample_event__resolve_callchain(pevent) < 0) { + pevent->machine = NULL; + if (!pevent->event_copy) { + pevent->event = &zero_event; + pevent->sample = &pevent->sample_storage; + } + return -ENOMEM; + } + } + pevent->machine = NULL; + + if (pevent->event_copy) + return 0; + + /* + * Clear borrowed pointers immediately so that even if copying fails, + * pevent does not retain dangling pointers to the caller's stack, + * ring buffer, or session. + */ + pevent->event = &zero_event; + pevent->sample = &pevent->sample_storage; + + /* + * Allocate an extra zero byte so string fields such as mmap/mmap2 + * filenames or comm strings are always NUL-terminated. + */ + pevent->event_copy = calloc(1, copy_size + 1); + if (!pevent->event_copy) { + PyErr_NoMemory(); + return -ENOMEM; + } + + memcpy(pevent->event_copy, orig_event, copy_size); + pevent->event = pevent->event_copy; + + if (pevent->evsel) { + bool needs_swap = pevent->evsel->needs_swap; + + pevent->evsel->needs_swap = false; + err = evsel__parse_sample(pevent->evsel, pevent->event_copy, + &pevent->sample_storage); + pevent->evsel->needs_swap = needs_swap; + } + + if (err == 0) { + if (orig_sample != &pevent->sample_storage) { + if (orig_sample->id) + pevent->sample_storage.id = orig_sample->id; + if (orig_sample->period) + pevent->sample_storage.period = orig_sample->period; + if (orig_sample->machine_pid) + pevent->sample_storage.machine_pid = + orig_sample->machine_pid; + if (orig_sample->vcpu != (u32)-1) + pevent->sample_storage.vcpu = orig_sample->vcpu; + if (orig_sample->insn_len && !pevent->sample_storage.insn_len) { + memcpy(pevent->sample_storage.insn, orig_sample->insn, + sizeof(pevent->sample_storage.insn)); + pevent->sample_storage.insn_len = orig_sample->insn_len; + } + } + pevent->sample = &pevent->sample_storage; + } else if (orig_sample != &pevent->sample_storage) { + /* + * Synthesized events (e.g. Intel PT itrace) or events without + * an evsel may not have raw sample buffers for + * evsel__parse_sample(); copy the scalar fields from the + * borrowed sample. + */ + perf_sample__exit(&pevent->sample_storage); + pevent->sample_storage = *orig_sample; + if (pevent->sample_storage.evsel) + pevent->sample_storage.evsel = + evsel__get(pevent->sample_storage.evsel); + pevent->sample_storage.callchain = NULL; + pevent->sample_storage.merged_callchain = false; + pevent->sample_storage.raw_data = NULL; + pevent->sample_storage.raw_size = 0; + pevent->sample_storage.branch_stack = NULL; + pevent->sample_storage.user_regs = NULL; + pevent->sample_storage.intr_regs = NULL; + pevent->sample_storage.user_stack.size = 0; + pevent->sample_storage.user_stack.data = NULL; + pevent->sample_storage.aux_sample.size = 0; + pevent->sample_storage.aux_sample.data = NULL; + pevent->sample = &pevent->sample_storage; + } else if (pevent->evsel) { + PyErr_Format(PyExc_OSError, "perf: can't parse sample, err=%d", err); + return err; + } + return 0; +} + static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *evsel, - struct perf_session *session, - struct machine *machine) + struct perf_session *session, struct machine *machine, + struct perf_sample *sample_arg) { struct pyrf_event *pevent; struct perf_sample *sample; - int err; u32 min_size; - bool needs_swap; - - if (!machine) - machine = session ? &session->machines.host : NULL; if (event->header.type >= ARRAY_SIZE(pyrf_event__type) || pyrf_event__type[event->header.type] == NULL) { @@ -1411,89 +1792,58 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev event->header.size, event->header.type); } - size_t copy_size = event->header.size; - - if (copy_size > sizeof(pevent->event)) { - return PyErr_Format(PyExc_TypeError, "Unexpected event size: %zd < %zu", - sizeof(pevent->event), copy_size); + if (event->header.size > sizeof(union perf_event)) { + return PyErr_Format(PyExc_TypeError, "Unexpected event size: %zu < %u", + sizeof(union perf_event), event->header.size); } pevent = PyObject_New(struct pyrf_event, pyrf_event__type[event->header.type]); if (pevent == NULL) return PyErr_NoMemory(); - /* Copy the event for memory safety and initialize variables. */ - memcpy(&pevent->event, event, copy_size); - if (copy_size < sizeof(pevent->event)) - memset((char *)&pevent->event + copy_size, 0, sizeof(pevent->event) - copy_size); - - if (event->header.type == PERF_RECORD_MMAP2) - pevent->event.mmap2.filename[sizeof(pevent->event.mmap2.filename) - 1] = '\0'; - - perf_sample__init(&pevent->sample, /*all=*/true); + perf_sample__init(&pevent->sample_storage, /*all=*/true); + pevent->sample = sample_arg ?: &pevent->sample_storage; + pevent->evsel = evsel ? evsel__get(evsel) : NULL; + pevent->machine = NULL; pevent->callchain = NULL; pevent->brstack = NULL; pevent->al_resolved = false; addr_location__init(&pevent->al); + pevent->event = event; + pevent->event_copy = NULL; + + /* + * When called outside a session callback (e.g. pyrf_evlist__read_on_cpu) + * or without a pre-parsed sample for an evsel, copy the event and parse + * its sample into sample_storage immediately. + */ + if ((!session || (evsel && !sample_arg)) && pyrf_event__copy(pevent) < 0) { + Py_DECREF(pevent); + return NULL; + } if (!evsel) return (PyObject *)pevent; - /* Parse the sample again so that pointers are within the copied event. */ - needs_swap = evsel->needs_swap; + sample = pevent->sample; + if (session && session->evlist && perf_guest && sample->id) { + struct perf_sample_id *sid = evlist__id2sid(session->evlist, sample->id); - evsel->needs_swap = false; - err = evsel__parse_sample(evsel, &pevent->event, &pevent->sample); - evsel->needs_swap = needs_swap; - if (err < 0) { - Py_DECREF(pevent); - return PyErr_Format(PyExc_OSError, - "perf: can't parse sample, err=%d", err); - } - sample = &pevent->sample; - if (machine && sample->callchain) { - struct addr_location al; - struct callchain_cursor *cursor; - u64 i; - struct pyrf_callchain *pchain; - - addr_location__init(&al); - if (machine__resolve(machine, &al, sample) >= 0) { - cursor = get_tls_callchain_cursor(); - if (thread__resolve_callchain(al.thread, cursor, sample, - NULL, NULL, PERF_MAX_STACK_DEPTH) == 0) { - callchain_cursor_commit(cursor); - - pchain = PyObject_New(struct pyrf_callchain, &pyrf_callchain__type); - if (!pchain) { - addr_location__exit(&al); - Py_DECREF(pevent); - return NULL; - } - pchain->nr_frames = cursor->nr; - pchain->frames = calloc(pchain->nr_frames, - sizeof(*pchain->frames)); - if (!pchain->frames) { - Py_DECREF(pchain); - addr_location__exit(&al); - Py_DECREF(pevent); - return PyErr_NoMemory(); - } - struct callchain_cursor_node *node; - - for (i = 0; i < pchain->nr_frames; i++) { - node = callchain_cursor_current(cursor); - pchain->frames[i].ip = node->ip; - pchain->frames[i].map = - map__get(node->ms.map); - pchain->frames[i].sym = node->ms.sym; - callchain_cursor_advance(cursor); - } - pevent->callchain = (PyObject *)pchain; - } - addr_location__exit(&al); + if (sid) { + sample->machine_pid = sid->machine_pid; + sample->vcpu = sid->vcpu.cpu; } } + if (!machine && session) { + machine = sample->machine_pid ? + machines__find(&session->machines, sample->machine_pid) : + &session->machines.host; + if (!machine) + machine = &session->machines.host; + } + pevent->machine = machine; + if (machine && machine->pid > 0 && !sample->machine_pid) + sample->machine_pid = machine->pid; if (sample->branch_stack) { struct branch_stack *bs = sample->branch_stack; struct branch_entry *entries = perf_sample__branch_entries(sample); @@ -1795,7 +2145,6 @@ static int pyrf_pmu__setup_types(void) return PyType_Ready(&pyrf_pmu__type); } - /** A python iterator for pmus that has no equivalent in the C code. */ struct pyrf_pmu_iterator { PyObject_HEAD @@ -2206,12 +2555,13 @@ static PyObject *pyrf_evsel__read(struct pyrf_evsel *pevsel, { struct evsel *evsel = pevsel->evsel; int cpu = 0, cpu_idx, thread = 0, thread_idx; + static char *kwlist[] = { "cpu", "thread", NULL }; struct perf_counts_values *old_count, *new_count; struct pyrf_counts_values *count_values; CHECK_INITIALIZED(evsel, "evsel"); - if (!PyArg_ParseTuple(args, "ii", &cpu, &thread)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", kwlist, &cpu, &thread)) return NULL; cpu_idx = perf_cpu_map__idx(evsel->core.cpus, (struct perf_cpu){.cpu = cpu}); @@ -2736,10 +3086,6 @@ static int prepare_metric(const struct metric_expr *mexp, double val, ena, run; int ret, source_count = 0; struct perf_counts_values *old_count, *new_count; - char *n = strdup(evsel__metric_id(cur)); - - if (!n) - return -ENOMEM; /* * If there are multiple uncore PMUs and we're not reading the @@ -2781,9 +3127,15 @@ static int prepare_metric(const struct metric_expr *mexp, if (ena != run && run != 0) val = val * ena / run; + char *n = strdup(evsel__metric_id(cur)); + + if (!n) + return -ENOMEM; ret = expr__add_id_val_source_count(pctx, n, val, source_count); - if (ret) + if (ret) { + free(n); return ret; + } } for (int i = 0; metric_refs && metric_refs[i].metric_name; i++) { @@ -2801,6 +3153,7 @@ static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist, { int ret, cpu = 0, cpu_idx = 0, thread = 0, thread_idx = 0; const char *metric; + static char *kwlist[] = { "metric", "cpu", "thread", NULL }; struct rb_node *node; struct metric_expr *mexp = NULL; struct expr_parse_ctx *pctx; @@ -2809,7 +3162,7 @@ static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist, CHECK_INITIALIZED(pevlist->evlist, "evlist"); - if (!PyArg_ParseTuple(args, "sii", &metric, &cpu, &thread)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sii", kwlist, &metric, &cpu, &thread)) return NULL; for (node = rb_first_cached(&evlist__metric_events(pevlist->evlist)->entries); @@ -2951,10 +3304,7 @@ free_list: return PyErr_NoMemory(); } - -static PyObject *pyrf_evlist__add(struct pyrf_evlist *pevlist, - PyObject *args, - PyObject *kwargs __maybe_unused) +static PyObject *pyrf_evlist__add(struct pyrf_evlist *pevlist, PyObject *args) { struct evlist *evlist; PyObject *pevsel; @@ -3032,7 +3382,7 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist, perf_mmap__consume(&md->core); Py_RETURN_NONE; } - pyevent = pyrf_event__new(event, evsel, evlist__session(evlist), /*machine=*/NULL); + pyevent = pyrf_event__new(event, evsel, evlist__session(evlist), /*machine=*/NULL, NULL); perf_mmap__consume(&md->core); if (pyevent == NULL) return PyErr_Occurred() ? NULL : PyErr_NoMemory(); @@ -3166,7 +3516,7 @@ static PyMethodDef pyrf_evlist__methods[] = { { .ml_name = "add", .ml_meth = (PyCFunction)pyrf_evlist__add, - .ml_flags = METH_VARARGS | METH_KEYWORDS, + .ml_flags = METH_VARARGS, .ml_doc = PyDoc_STR("adds an event selector to the list.") }, { @@ -3400,6 +3750,11 @@ static const struct perf_constant perf__constants[] = { PERF_CONST(RECORD_STAT_ROUND), PERF_CONST(RECORD_MISC_SWITCH_OUT), + + /* Values for the flags of a perf.call_return, see thread-stack.h. */ + { "CALL_RETURN_NO_CALL", CALL_RETURN_NO_CALL }, + { "CALL_RETURN_NO_RETURN", CALL_RETURN_NO_RETURN }, + { "CALL_RETURN_NON_CALL", CALL_RETURN_NON_CALL }, { .name = NULL, }, }; @@ -3410,7 +3765,7 @@ static PyObject *pyrf__tracepoint(struct pyrf_evsel *pevsel, char *sys = NULL; char *name = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ss", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss", kwlist, &sys, &name)) return NULL; @@ -3469,10 +3824,12 @@ static PyObject *pyrf__parse_events(PyObject *self, PyObject *args) perf_evlist__set_maps(evlist__core(evlist), cpus, threads); if (parse_events(evlist, input, &err)) { parse_events_error__print(&err, input); + parse_events_error__exit(&err); PyErr_SetFromErrno(PyExc_OSError); evlist__put(evlist); return NULL; } + parse_events_error__exit(&err); result = pyrf_evlist__from_evlist(evlist); evlist__put(evlist); return result; @@ -3678,6 +4035,10 @@ static int pyrf_data__init(struct pyrf_data *pdata, PyObject *args, PyObject *kw pdata->data.mode = PERF_DATA_MODE_READ; pdata->data.file.fd = fd; if (perf_data__open(&pdata->data) < 0) { + if (fd >= 0) { + close(fd); + pdata->data.file.fd = -1; + } PyErr_Format(PyExc_IOError, "Failed to open perf data: %s", pdata->data.path ? pdata->data.path : "perf.data"); return -1; @@ -3746,16 +4107,38 @@ static void pyrf_thread__delete(struct pyrf_thread *pthread) static PyObject *pyrf_thread__comm(PyObject *obj) { struct pyrf_thread *pthread = (void *)obj; - const char *str; + struct thread *thread = pthread->thread; + bool has_comm = false; + char *str = NULL; + PyObject *ret; - CHECK_INITIALIZED(pthread->thread, "perf.thread"); + CHECK_INITIALIZED(thread, "perf.thread"); - str = thread__comm_str(pthread->thread); + /* + * Copy the comm string under comm_lock so a concurrent PERF_RECORD_COMM + * in perf_session__process_events() (which runs with the GIL released) + * cannot free the old comm_str via comm__override() while it is being + * read, and drop comm_lock before calling into the Python allocator. + */ + down_read(thread__comm_lock(thread)); + if (!list_empty(thread__comm_list(thread))) { + const struct comm *comm; + + comm = list_first_entry(thread__comm_list(thread), struct comm, list); + if (comm && comm__str(comm)) { + has_comm = true; + str = strdup(comm__str(comm)); + } + } + up_read(thread__comm_lock(thread)); - if (!str) + if (!has_comm) Py_RETURN_NONE; - - return PyUnicode_FromString(str); + if (!str) + return PyErr_NoMemory(); + ret = PyUnicode_FromString(str); + free(str); + return ret; } static PyMethodDef pyrf_thread__methods[] = { @@ -3834,77 +4217,699 @@ struct pyrf_session { struct perf_session *session; struct perf_tool tool; + struct itrace_synth_opts itrace_opts; struct pyrf_data *pdata; PyObject *sample; PyObject *stat; + PyObject *context_switch; + PyObject *call_return; + struct call_return_processor *crp; + /** + * @call_return_last_db_id: Counter used to give each perf.call_return a + * unique db_id. A call's db_id may be allocated before the call + * returns, so the identifiers cannot be generated by the Python script. + */ + u64 call_return_last_db_id; + u64 sample_last_db_id; + /** + * @callback_raised: A python callback raised an exception and requested + * that event processing stops, see pyrf_session__callback_raised(). + */ + bool callback_raised; + unsigned int callback_depth; + unsigned long callback_thread; + char *vmlinux_name; + char *kallsyms_name; + char *symfs; }; -static int pyrf_session_tool__sample(const struct perf_tool *tool, - union perf_event *event, - struct perf_sample *sample, - struct machine *machine) +struct pyrf_call_return { + PyObject_HEAD + struct call_return cr; + /** + * @call_path: perf.callchain of the call path, ordered from the + * outermost caller to the callee. Resolved eagerly as the underlying + * struct call_path is owned by the call return processor. + */ + PyObject *call_path; + char *comm; + pid_t machine_pid; + pid_t pid; + pid_t tid; +}; + +static PyObject *pyrf_call_return__repr(struct pyrf_call_return *pcr) { - struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); - PyObject *pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine); - PyObject *ret; + return PyUnicode_FromString("{ type: call_return }"); +} - if (pyevent == NULL) - return -ENOMEM; +static PyObject *pyrf_call_return__get_comm(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + if (pcr->comm) + return PyUnicode_FromString(pcr->comm); + Py_RETURN_NONE; +} - ret = PyObject_CallFunction(psession->sample, "O", pyevent); - if (!ret) { - Py_DECREF(pyevent); - return -1; +static PyObject *pyrf_call_return__get_machine_pid(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromLong(pcr->machine_pid); +} + +static PyObject *pyrf_call_return__get_pid(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + if (pcr->pid != -1) + return PyLong_FromLong(pcr->pid); + Py_RETURN_NONE; +} + +static PyObject *pyrf_call_return__get_tid(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + if (pcr->tid != -1) + return PyLong_FromLong(pcr->tid); + Py_RETURN_NONE; +} + +static PyObject *pyrf_call_return__get_call_time(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->call_time); +} + +static PyObject *pyrf_call_return__get_return_time(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->return_time); +} + +static PyObject *pyrf_call_return__get_branch_count(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->branch_count); +} + +static PyObject *pyrf_call_return__get_call_ref(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->call_ref); +} + +static PyObject *pyrf_call_return__get_return_ref(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromUnsignedLongLong(cr->return_ref); +} + +static PyObject *pyrf_call_return__get_flags(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + struct call_return *cr = &pcr->cr; + + return PyLong_FromLong(cr->flags); +} + +static PyObject *pyrf_call_return__get_call_path(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + PyObject *call_path = pcr->call_path ?: Py_None; + + Py_INCREF(call_path); + return call_path; +} + +static PyObject *pyrf_call_return__get_parent_id(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pcr->cr.parent_db_id); +} + +static PyObject *pyrf_call_return__get_insn_count(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pcr->cr.insn_count); +} + +static PyObject *pyrf_call_return__get_cyc_count(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pcr->cr.cyc_count); +} + +static PyObject *pyrf_call_return__get_db_id(struct pyrf_call_return *pcr, + void *closure __maybe_unused) +{ + return PyLong_FromUnsignedLongLong(pcr->cr.db_id); +} + +static PyGetSetDef pyrf_call_return__getset[] = { + { + .name = "db_id", + .get = (getter)pyrf_call_return__get_db_id, + .doc = "Unique identifier of this call return.", + }, + { + .name = "parent_id", + .get = (getter)pyrf_call_return__get_parent_id, + .doc = "db_id of the calling call return, 0 if there is none.", + }, + { + .name = "insn_count", + .get = (getter)pyrf_call_return__get_insn_count, + .doc = "Instructions executed between the call and the return.", + }, + { + .name = "cyc_count", + .get = (getter)pyrf_call_return__get_cyc_count, + .doc = "Cycles elapsed between the call and the return.", + }, + { + .name = "comm", + .get = (getter)pyrf_call_return__get_comm, + .doc = "Thread comm at the time of the call/return, or None.", + }, + { + .name = "machine_pid", + .get = (getter)pyrf_call_return__get_machine_pid, + .doc = "Machine PID (0 for host).", + }, + { + .name = "pid", + .get = (getter)pyrf_call_return__get_pid, + .doc = "Process ID of the thread, or None.", + }, + { + .name = "tid", + .get = (getter)pyrf_call_return__get_tid, + .doc = "Thread identifier, None if unknown.", + }, + { + .name = "call_time", + .get = (getter)pyrf_call_return__get_call_time, + .doc = "Timestamp of the call.", + }, + { + .name = "return_time", + .get = (getter)pyrf_call_return__get_return_time, + .doc = "Timestamp of the return.", + }, + { + .name = "branch_count", + .get = (getter)pyrf_call_return__get_branch_count, + .doc = "Branches taken between the call and the return.", + }, + { + .name = "call_ref", + .get = (getter)pyrf_call_return__get_call_ref, + .doc = "Reference to the sample of the call.", + }, + { + .name = "return_ref", + .get = (getter)pyrf_call_return__get_return_ref, + .doc = "Reference to the sample of the return.", + }, + { + .name = "flags", + .get = (getter)pyrf_call_return__get_flags, + .doc = "Bitmask of perf.CALL_RETURN_* values.", + }, + { + .name = "call_path", + .get = (getter)pyrf_call_return__get_call_path, + .doc = "perf.callchain from outermost caller to callee.", + }, + { .name = NULL, }, +}; + +static void pyrf_call_return__delete(struct pyrf_call_return *pevent) +{ + Py_XDECREF(pevent->call_path); + free(pevent->comm); + Py_TYPE(pevent)->tp_free((PyObject *)pevent); +} + +static PyTypeObject pyrf_call_return__type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "perf.call_return", + .tp_basicsize = sizeof(struct pyrf_call_return), + .tp_dealloc = (destructor)pyrf_call_return__delete, + .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, + .tp_doc = "perf call_return object.", + .tp_getset = pyrf_call_return__getset, + .tp_repr = (reprfunc)pyrf_call_return__repr, +}; + +/* + * Turn a struct call_path, that is a leaf with parent pointers up to the + * outermost caller, into a perf.callchain ordered from the outermost caller to + * the leaf. The struct call_path is owned by the call return processor and so + * must be resolved before returning to Python. + */ +static PyObject *pyrf_call_path__to_callchain(const struct call_path *cp, + struct thread *thread) +{ + struct pyrf_callchain *pchain; + const struct call_path *pos; + u64 nr_frames = 0; + + /* The root call path is a sentinel with no symbol, skip it. */ + for (pos = cp; pos && pos->parent; pos = pos->parent) + nr_frames++; + + pchain = PyObject_New(struct pyrf_callchain, &pyrf_callchain__type); + if (!pchain) + return NULL; + + pchain->nr_frames = nr_frames; + pchain->frames = nr_frames ? calloc(nr_frames, sizeof(*pchain->frames)) : NULL; + if (nr_frames && !pchain->frames) { + pchain->nr_frames = 0; + Py_DECREF(pchain); + return PyErr_NoMemory(); + } + /* Fill in reverse so that the outermost caller is first. */ + for (pos = cp; nr_frames > 0; pos = pos->parent) { + struct pyrf_callchain_frame *frame = &pchain->frames[--nr_frames]; + + frame->ip = pos->ip; + frame->sym = NULL; + frame->sym_name = pos->sym ? strdup(pos->sym->name) : NULL; + frame->map = NULL; + if (thread) { + struct addr_location al; + u8 cpumode = pos->in_kernel ? PERF_RECORD_MISC_KERNEL + : PERF_RECORD_MISC_USER; + + addr_location__init(&al); + thread__find_map(thread, cpumode, pos->ip, &al); + frame->map = map__get(al.map); + if (frame->map) + frame->sym = pos->sym; + addr_location__exit(&al); + } } + return (PyObject *)pchain; +} + +static PyObject *pyrf_call_return__new(struct call_return *cr) +{ + struct pyrf_call_return *pevent; + + pevent = PyObject_New(struct pyrf_call_return, &pyrf_call_return__type); + if (!pevent) + return NULL; + + pevent->cr = *cr; + /* Avoid lifetime issues with thread by caching tid/pid/machine_pid */ + if (cr->thread) { + struct maps *maps = thread__maps(cr->thread); + + pevent->machine_pid = maps && maps__machine(maps) ? maps__machine(maps)->pid : 0; + pevent->pid = thread__pid(cr->thread); + pevent->tid = thread__tid(cr->thread); + } else { + pevent->machine_pid = 0; + pevent->pid = -1; + pevent->tid = -1; + } + pevent->cr.thread = NULL; + pevent->comm = cr->comm ? strdup(comm__str(cr->comm)) : NULL; + pevent->call_path = NULL; + if (cr->cp) { + pevent->call_path = pyrf_call_path__to_callchain(cr->cp, cr->thread); + if (!pevent->call_path) { + Py_DECREF(pevent); + return NULL; + } + } + return (PyObject *)pevent; +} + +/* + * Number of times a python callback has requested that event processing + * stops, see pyrf_session__callback_raised(). + */ +static unsigned int pyrf_session__stop_requests; + +/* + * A python callback raised an exception. + * + * Returning an error to the session code would cause reader__read_event() to + * print a "processing failed for event of type: ..." error to stderr. This is + * noise, and in the case of a TUI corrupts the display, when a callback raises + * to deliberately stop processing (e.g. cancelling a background load) and the + * error message is redundant with the python exception that is raised anyway. + * + * Instead request that event processing stops by setting session_done and + * return success. The exception remains pending (PyErr_Occurred()) and + * pyrf_session__find_thread_events() returns NULL so python raises it. + * + * Note, session_done is a process-global rather than per-session. Were + * multiple sessions processing events concurrently in different python + * threads then an exception in one would stop all of them. Only the session + * whose callback raised clears session_done, and the other sessions detect + * the stop request, using pyrf_session__stop_requests, and raise an error + * rather than silently dropping events. + */ +static int pyrf_session__callback_raised(struct pyrf_session *psession) +{ + psession->callback_raised = true; + pyrf_session__stop_requests++; + session_done = 1; + return 0; +} + +/* + * session_done stops the reader loops, the ordered_events and deferred sample + * flushes, and auxtrace__flush_events(), but some callbacks may still happen + * after a callback has raised. For example, a single sample may generate + * multiple call_return callbacks, and perf_session__flush_thread_stacks() + * delivers call returns for the remaining stack entries (skipping it would + * only defer those callbacks until the threads are deleted). Calling into + * python with an exception pending is an error, and the events would be + * discarded anyway, so callbacks check this and skip calling python. + */ +static bool pyrf_session__exception_pending(void) +{ + return PyErr_Occurred() != NULL; +} + +static int __pyrf_session__call_return_process(struct call_return *cr, + u64 *parent_db_id, + struct pyrf_session *psession) +{ + PyObject *pyevent, *ret; + + if (!psession->call_return || pyrf_session__exception_pending()) + return 0; + + if (!cr->db_id) + cr->db_id = ++psession->call_return_last_db_id; + + if (parent_db_id) { + if (!*parent_db_id) + *parent_db_id = ++psession->call_return_last_db_id; + cr->parent_db_id = *parent_db_id; + } + + pyevent = pyrf_call_return__new(cr); + if (!pyevent) + return -1; + + ret = PyObject_CallFunctionObjArgs(psession->call_return, pyevent, NULL); + Py_DECREF(pyevent); + if (!ret) + return pyrf_session__callback_raised(psession); Py_DECREF(ret); + return 0; +} + +static PyGILState_STATE pyrf_session__callback_enter(struct pyrf_session *psession) +{ + PyGILState_STATE gstate = PyGILState_Ensure(); + + psession->callback_thread = PyThread_get_thread_ident(); + psession->callback_depth++; + return gstate; +} + +static void pyrf_session__callback_leave(struct pyrf_session *psession, + PyGILState_STATE gstate) +{ + psession->callback_depth--; + PyGILState_Release(gstate); +} + +/* + * Call returns may be processed during a sample callback, or when thread + * stacks are flushed, so acquire the GIL, see pyrf_session_tool__sample(). + */ +static int pyrf_session__call_return_process(struct call_return *cr, + u64 *parent_db_id, + void *data) +{ + struct pyrf_session *psession = data; + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session__call_return_process(cr, parent_db_id, psession); + + pyrf_session__callback_leave(psession, gstate); + return ret; +} + +static int pyrf_session__call_event(struct pyrf_session *psession, PyObject *callback, + PyObject *pyevent, const char *extra_arg, bool has_extra_arg) +{ + PyObject *ret; + int err = 0; + + if (has_extra_arg) + ret = PyObject_CallFunction(callback, "Oz", pyevent, extra_arg); + else + ret = PyObject_CallFunctionObjArgs(callback, pyevent, NULL); + + Py_XDECREF(ret); + if (Py_REFCNT(pyevent) > 1) { + PyObject *exc_type = NULL, *exc_value = NULL, *exc_tb = NULL; + + /* + * The Python callback retained a reference to the event + * beyond the callback's return (or in an exception frame); + * copy the underlying event and sample before process_events + * reuses or unmaps its buffer. + */ + if (!ret) + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + err = pyrf_event__copy((struct pyrf_event *)pyevent); + if (!ret) { + PyErr_Clear(); + PyErr_Restore(exc_type, exc_value, exc_tb); + } + } Py_DECREF(pyevent); + if (!ret || err < 0) + return pyrf_session__callback_raised(psession); return 0; } -static int pyrf_session_tool__stat(const struct perf_tool *tool, - struct perf_session *session, - union perf_event *event) +static int __pyrf_session_tool__sample(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + u64 sample_db_id = ++psession->sample_last_db_id; + + if (pyrf_session__exception_pending()) + return 0; + + if (psession->crp) { + struct addr_location al, addr_al; + struct thread *thread = NULL; + + addr_location__init(&al); + addr_location__init(&addr_al); + + if (machine__resolve(machine, &al, sample) >= 0) { + thread = al.thread; + if (sample->flags & (PERF_IP_FLAG_CALL | + PERF_IP_FLAG_TRACE_BEGIN | + PERF_IP_FLAG_RETURN | + PERF_IP_FLAG_BRANCH | + PERF_IP_FLAG_TRACE_END)) { + thread__resolve(thread, &addr_al, sample); + } + + int err; + + err = thread_stack__process(thread, thread__comm(thread), sample, + &al, &addr_al, sample_db_id, psession->crp); + if (err) { + addr_location__exit(&addr_al); + addr_location__exit(&al); + return err; + } + } + + addr_location__exit(&addr_al); + addr_location__exit(&al); + + /* A call_return callback may have raised. */ + if (pyrf_session__exception_pending()) + return 0; + } + + if (psession->sample) { + PyObject *pyevent = pyrf_event__new(event, sample->evsel, + psession->session, + machine, sample); + + if (pyevent == NULL) + return -ENOMEM; + + return pyrf_session__call_event(psession, psession->sample, pyevent, + /*extra_arg=*/NULL, /*has_extra_arg=*/false); + } + return 0; +} + +static int __pyrf_session_tool__context_switch(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyObject *pyevent; + + if (pyrf_session__exception_pending()) { + /* + * Skip calling python, but keep the machine's thread state + * consistent as perf_event__process_switch() would have been + * called had no python callback been registered. + */ + return perf_event__process_switch(tool, event, sample, machine); + } + + pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine, sample); + if (perf_event__process_switch(tool, event, sample, machine) < 0) { + Py_XDECREF(pyevent); + return -1; + } + + if (pyevent == NULL) + return -ENOMEM; + + return pyrf_session__call_event(psession, psession->context_switch, pyevent, + /*extra_arg=*/NULL, /*has_extra_arg=*/false); +} + +static int __pyrf_session_tool__stat(const struct perf_tool *tool, + struct perf_session *session, + union perf_event *event) { struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); struct evsel *evsel = evlist__id2evsel(session->evlist, event->stat.id); - PyObject *pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session, - /*machine=*/NULL); const char *name = evsel ? evsel__name(evsel) : "unknown"; - PyObject *ret; + PyObject *pyevent; + if (pyrf_session__exception_pending()) + return 0; + + pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session, + /*machine=*/NULL, NULL); if (pyevent == NULL) return -ENOMEM; - ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, name); - if (!ret) { - Py_DECREF(pyevent); - return -1; - } - Py_DECREF(ret); - Py_DECREF(pyevent); - return 0; + return pyrf_session__call_event(psession, psession->stat, pyevent, + name, /*has_extra_arg=*/true); } -static int pyrf_session_tool__stat_round(const struct perf_tool *tool, - struct perf_session *session __maybe_unused, - union perf_event *event) +static int __pyrf_session_tool__stat_round(const struct perf_tool *tool, + struct perf_session *session __maybe_unused, + union perf_event *event) { struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); - PyObject *pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session, - /*machine=*/NULL); - PyObject *ret; + PyObject *pyevent; + if (pyrf_session__exception_pending()) + return 0; + + pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session, + /*machine=*/NULL, NULL); if (pyevent == NULL) return -ENOMEM; - ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, NULL); - if (!ret) { - Py_DECREF(pyevent); - return -1; + return pyrf_session__call_event(psession, psession->stat, pyevent, + /*extra_arg=*/NULL, /*has_extra_arg=*/true); +} + +/* + * The session currently inside perf_session__new() or + * perf_session__process_events() with the GIL released. Because symbol_conf + * and session_done are process-globals and perf_session is not thread-safe, + * only one session may be active at a time. Modified with the GIL held. + */ +static struct pyrf_session *pyrf_session__active; + +static bool pyrf_session__check_not_busy(struct pyrf_session *psession) +{ + if (pyrf_session__active == psession && + (!psession->callback_depth || + psession->callback_thread != PyThread_get_thread_ident())) { + PyErr_SetString(PyExc_RuntimeError, + "perf.session is busy processing events in another thread"); + return false; } - Py_DECREF(ret); - Py_DECREF(pyevent); - return 0; + return true; +} + +/* + * The GIL is released while events are processed, see + * pyrf_session__find_thread_events(), so that other python threads can run, + * such as a UI thread while a session reads from a pipe that may block. The + * tool callbacks that use python acquire the GIL, PyGILState_Ensure() is + * reentrant so nested callbacks, like call returns during a sample, work. + */ + +static int pyrf_session_tool__sample(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session_tool__sample(tool, event, sample, machine); + + pyrf_session__callback_leave(psession, gstate); + return ret; +} + +static int pyrf_session_tool__context_switch(const struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session_tool__context_switch(tool, event, sample, machine); + + pyrf_session__callback_leave(psession, gstate); + return ret; +} + +static int pyrf_session_tool__stat(const struct perf_tool *tool, + struct perf_session *session, + union perf_event *event) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session_tool__stat(tool, session, event); + + pyrf_session__callback_leave(psession, gstate); + return ret; +} + +static int pyrf_session_tool__stat_round(const struct perf_tool *tool, + struct perf_session *session, + union perf_event *event) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyGILState_STATE gstate = pyrf_session__callback_enter(psession); + int ret = __pyrf_session_tool__stat_round(tool, session, event); + + pyrf_session__callback_leave(psession, gstate); + return ret; } static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObject *args) @@ -3912,26 +4917,42 @@ static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObje struct machine *machine; struct thread *thread = NULL; PyObject *result; - int pid; + int pid, tid = -1; CHECK_INITIALIZED(psession->session, "session"); + if (!pyrf_session__check_not_busy(psession)) + return NULL; - if (!PyArg_ParseTuple(args, "i", &pid)) + if (!PyArg_ParseTuple(args, "i|i", &pid, &tid)) return NULL; + if (tid == -1) { + tid = pid; + pid = -1; + } + + /* + * When host lookup fails, guest machines (default_guest and the guests + * rb_root) are searched. + */ machine = &psession->session->machines.host; - thread = machine__find_thread(machine, pid, pid); + thread = machine__find_thread(machine, pid, tid); if (!thread) { - machine = perf_session__find_machine(psession->session, pid); + machine = perf_session__find_machine(psession->session, pid != -1 ? pid : tid); if (machine) - thread = machine__find_thread(machine, pid, pid); + thread = machine__find_thread(machine, pid, tid); + if (!thread) { + machine = machines__find(&psession->session->machines, + DEFAULT_GUEST_KERNEL_ID); + if (machine) + thread = machine__find_thread(machine, pid, tid); + } } - if (!thread) { - PyErr_Format(PyExc_TypeError, "Failed to find thread %d", pid); - return NULL; - } + /* Return None rather than raising TypeError when a PID/TID is not known. */ + if (!thread) + Py_RETURN_NONE; result = pyrf_thread__from_thread(thread); thread__put(thread); return result; @@ -3940,23 +4961,44 @@ static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObje static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { struct pyrf_data *pdata; - PyObject *sample = NULL, *stat = NULL; - static char *kwlist[] = { "data", "sample", "stat", NULL }; + PyObject *sample = NULL, *stat = NULL, *context_switch = NULL; + PyObject *call_return = NULL; + char *itrace_str = NULL; + char *vmlinux_str = NULL, *kallsyms_str = NULL, *symfs_str = NULL; + static char *kwlist[] = { "data", "sample", "stat", "context_switch", + "call_return", "itrace", "vmlinux", "kallsyms", + "symfs", NULL }; struct pyrf_session *psession; struct perf_session *session; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|OO", kwlist, &pyrf_data__type, &pdata, - &sample, &stat)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|OOOOzzzz", kwlist, + &pyrf_data__type, &pdata, + &sample, &stat, &context_switch, + &call_return, &itrace_str, + &vmlinux_str, &kallsyms_str, &symfs_str)) return NULL; psession = PyObject_New(struct pyrf_session, type); if (!psession) return NULL; + /* PyObject_New doesn't zero the memory, initialize every member. */ psession->session = NULL; psession->sample = NULL; psession->stat = NULL; + psession->context_switch = NULL; + psession->call_return = NULL; + psession->crp = NULL; + psession->call_return_last_db_id = 0; + psession->sample_last_db_id = 0; + psession->callback_raised = false; + psession->callback_depth = 0; + psession->callback_thread = 0; + psession->vmlinux_name = NULL; + psession->kallsyms_name = NULL; + psession->symfs = NULL; psession->pdata = NULL; + memset(&psession->itrace_opts, 0, sizeof(psession->itrace_opts)); Py_INCREF(pdata); psession->pdata = pdata; @@ -3966,7 +5008,7 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject #define ADD_TOOL(name) \ do { \ - if (name) { \ + if (name && name != Py_None) { \ if (!PyCallable_Check(name)) { \ PyErr_SetString(PyExc_TypeError, #name " must be callable"); \ goto err_out; \ @@ -3978,14 +5020,38 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject } while (0) ADD_TOOL(sample); + if (call_return && call_return != Py_None && (!sample || sample == Py_None)) + psession->tool.sample = pyrf_session_tool__sample; ADD_TOOL(stat); + ADD_TOOL(context_switch); + + if (call_return && call_return != Py_None) { + if (!PyCallable_Check(call_return)) { + PyErr_SetString(PyExc_TypeError, "call_return must be callable"); + Py_DECREF(psession); + return NULL; + } + Py_INCREF(call_return); + psession->call_return = call_return; + psession->crp = call_return_processor__new(pyrf_session__call_return_process, + psession); + if (!psession->crp) { + PyErr_SetString(PyExc_MemoryError, + "Failed to allocate call_return_processor"); + Py_DECREF(psession); + return NULL; + } + } #undef ADD_TOOL - if (stat) + if (stat && stat != Py_None) psession->tool.stat_round = pyrf_session_tool__stat_round; - psession->tool.comm = perf_event__process_comm; + psession->tool.auxtrace_info = perf_event__process_auxtrace_info; + psession->tool.auxtrace = perf_event__process_auxtrace; + psession->tool.auxtrace_error = perf_event__process_auxtrace_error; + psession->tool.id_index = perf_event__process_id_index; psession->tool.mmap = perf_event__process_mmap; psession->tool.mmap2 = perf_event__process_mmap2; psession->tool.namespaces = perf_event__process_namespaces; @@ -3996,24 +5062,85 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject 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.event_update = perf_event__process_event_update; psession->tool.feature = perf_event__process_feature; +#ifdef HAVE_LIBTRACEEVENT + psession->tool.tracing_data = perf_event__process_tracing_data; +#endif - session = perf_session__new(&pdata->data, &psession->tool); - if (IS_ERR(session)) { - PyErr_Format(PyExc_IOError, "failed to create session: %ld", PTR_ERR(session)); + if (!vmlinux_str) + vmlinux_str = getenv("PERF_SYMBOL_VMLINUX"); + if (vmlinux_str && vmlinux_str[0]) { + psession->vmlinux_name = strdup(vmlinux_str); + if (!psession->vmlinux_name) { + PyErr_NoMemory(); + goto err_out; + } + } + + if (!kallsyms_str) + kallsyms_str = getenv("PERF_SYMBOL_KALLSYMS"); + if (kallsyms_str && kallsyms_str[0]) { + psession->kallsyms_name = strdup(kallsyms_str); + if (!psession->kallsyms_name) { + PyErr_NoMemory(); + goto err_out; + } + } + + if (!symfs_str) + symfs_str = getenv("PERF_SYMBOL_SYMFS"); + if (symfs_str && symfs_str[0]) { + psession->symfs = strdup(symfs_str); + if (!psession->symfs) { + PyErr_NoMemory(); + goto err_out; + } + } + + if (!itrace_str) + itrace_str = getenv("PERF_ITRACE"); + if (itrace_str) { + memset(&psession->itrace_opts, 0, sizeof(psession->itrace_opts)); + if (itrace_do_parse_synth_opts(&psession->itrace_opts, itrace_str, 0) < 0) { + PyErr_SetString(PyExc_ValueError, "Failed to parse itrace options"); + goto err_out; + } + } + + if (pyrf_session__active) { + PyErr_SetString(PyExc_RuntimeError, + "Another perf.session is currently active"); goto err_out; } - psession->session = session; + symbol_conf.vmlinux_name = psession->vmlinux_name; + symbol_conf.kallsyms_name = psession->kallsyms_name; + symbol_conf.symfs = psession->symfs ?: ""; symbol_conf.use_callchain = true; symbol_conf.show_kernel_path = true; symbol_conf.inline_name = false; + + /* Reading the header of a pipe may block, let other python threads run. */ + pyrf_session__active = psession; + Py_BEGIN_ALLOW_THREADS + session = perf_session__new(&pdata->data, &psession->tool); + Py_END_ALLOW_THREADS + pyrf_session__active = NULL; + if (!psession->kallsyms_name && symbol_conf.kallsyms_name) + psession->kallsyms_name = (char *)symbol_conf.kallsyms_name; + if (IS_ERR(session)) { + PyErr_Format(PyExc_IOError, "failed to create session: %ld", PTR_ERR(session)); + goto err_out; + } + psession->session = session; + psession->session->itrace_synth_opts = &psession->itrace_opts; if (symbol__init(perf_session__env(session)) < 0) { PyErr_SetString(PyExc_OSError, "perf: symbol__init failed"); goto err_out; } - - + if (psession->symfs && !symbol_conf.symfs[0]) + zfree(&psession->symfs); return (PyObject *)psession; err_out: @@ -4024,19 +5151,74 @@ err_out: static void pyrf_session__delete(struct pyrf_session *psession) { perf_session__delete(psession->session); + if (!pyrf_session__active) { + if (symbol_conf.vmlinux_name == psession->vmlinux_name) + symbol_conf.vmlinux_name = NULL; + if (symbol_conf.kallsyms_name == psession->kallsyms_name) + symbol_conf.kallsyms_name = NULL; + if (symbol_conf.symfs == psession->symfs) + symbol_conf.symfs = ""; + } + free(psession->vmlinux_name); + free(psession->kallsyms_name); + free(psession->symfs); + free(psession->itrace_opts.vm_tm_corr_args); + free(psession->itrace_opts.cpu_bitmap); + free(psession->itrace_opts.ptime_range); Py_XDECREF(psession->pdata); Py_XDECREF(psession->sample); Py_XDECREF(psession->stat); + Py_XDECREF(psession->context_switch); + Py_XDECREF(psession->call_return); + if (psession->crp) + call_return_processor__free(psession->crp); Py_TYPE(psession)->tp_free((PyObject *)psession); } static PyObject *pyrf_session__find_thread_events(struct pyrf_session *psession) { + unsigned int stop_requests = pyrf_session__stop_requests; int err; CHECK_INITIALIZED(psession->session, "session"); + if (pyrf_session__active) { + PyErr_SetString(PyExc_RuntimeError, + "Another perf.session is currently active"); + return NULL; + } + + symbol_conf.vmlinux_name = psession->vmlinux_name; + symbol_conf.kallsyms_name = psession->kallsyms_name; + symbol_conf.symfs = psession->symfs ?: ""; + psession->callback_raised = false; + /* Callbacks that use python acquire the GIL, see pyrf_session_tool__sample(). */ + pyrf_session__active = psession; + Py_BEGIN_ALLOW_THREADS err = perf_session__process_events(psession->session); + Py_END_ALLOW_THREADS + pyrf_session__active = NULL; + if (psession->callback_raised) { + /* + * Clear the early stop requested by this session's callback, in + * pyrf_session__callback_raised(), so that later sessions process + * all their events. That function is the only writer of + * session_done in the python module, so resetting it here after + * processing is sufficient and there is no need to also reset it + * before processing. + */ + session_done = 0; + } else if (stop_requests != pyrf_session__stop_requests && !PyErr_Occurred()) { + /* + * A callback of a session being processed concurrently, in + * another thread, raised and set session_done which may have + * stopped processing this session's events early. Raise an error + * rather than silently dropping events. + */ + PyErr_SetString(PyExc_RuntimeError, + "Processing events may have stopped early as a callback of a concurrently processed session raised an exception"); + return NULL; + } if (PyErr_Occurred()) return NULL; @@ -4069,10 +5251,56 @@ static const char pyrf_session__doc[] = PyDoc_STR("perf session object."); static PyObject *pyrf_session__getattro(struct pyrf_session *psession, PyObject *attr_name) { + const char *name_str = PyUnicode_AsUTF8(attr_name); + + if (!name_str) + return NULL; if (!psession->session) { PyErr_SetString(PyExc_ValueError, "session not initialized"); return NULL; } + if (!pyrf_session__check_not_busy(psession)) + return NULL; + if (!strcmp(name_str, "e_machine")) + return PyLong_FromLong(perf_session__e_machine(psession->session, + /*e_flags=*/NULL)); + + if (!strcmp(name_str, "is_64_bit")) { + /* + * Prefer the machine of the recorded threads over the perf_env, + * as in pipe mode the perf_env may not be populated yet and + * perf_env__kernel_is_64_bit would cache the host's bitness. + */ + switch (perf_session__e_machine(psession->session, /*e_flags=*/NULL)) { + case EM_AARCH64: + case EM_ALPHA: + case EM_IA_64: + case EM_LOONGARCH: + case EM_PPC64: + case EM_SPARCV9: + case EM_X86_64: + Py_RETURN_TRUE; + case EM_386: + case EM_ARM: + case EM_PPC: + case EM_SPARC: + Py_RETURN_FALSE; + default: + /* + * Machines like EM_MIPS, EM_PARISC, EM_RISCV and + * EM_S390 are used for both word sizes, fall back on + * the recorded arch string if available. + */ + break; + } + { + struct perf_env *env = perf_session__env(psession->session); + + if (env && env->arch) + return PyBool_FromLong(perf_env__kernel_is_64_bit(env) == 1); + return PyBool_FromLong(sizeof(void *) == 8); + } + } return PyObject_GenericGetAttr((PyObject *) psession, attr_name); } @@ -4083,6 +5311,8 @@ static int pyrf_session__setattro(struct pyrf_session *psession, PyObject *attr_ PyErr_SetString(PyExc_ValueError, "session not initialized"); return -1; } + if (!pyrf_session__check_not_busy(psession)) + return -1; return PyObject_GenericSetAttr((PyObject *) psession, attr_name, value); } @@ -4104,14 +5334,35 @@ static int pyrf_session__setup_types(void) return PyType_Ready(&pyrf_session__type); } +static int pyrf__parse_elf_machine(PyObject *obj, int *elf_machine) +{ + long val; + + if (!obj || obj == Py_None) { + *elf_machine = EM_HOST; + return 0; + } + val = PyLong_AsLong(obj); + if (val == -1 && PyErr_Occurred()) + return -1; + if (val <= 0 || val > UINT16_MAX) + *elf_machine = EM_HOST; + else + *elf_machine = (int)val; + return 0; +} + static PyObject *pyrf__syscall_name(PyObject *self, PyObject *args, PyObject *kwargs) { const char *name; int id; int elf_machine = EM_HOST; + PyObject *elf_machine_obj = NULL; static char *kwlist[] = { "id", "elf_machine", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|$i", kwlist, &id, &elf_machine)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|O", kwlist, &id, &elf_machine_obj)) + return NULL; + if (pyrf__parse_elf_machine(elf_machine_obj, &elf_machine) < 0) return NULL; name = syscalltbl__name(elf_machine, id); @@ -4125,9 +5376,12 @@ static PyObject *pyrf__syscall_id(PyObject *self, PyObject *args, PyObject *kwar const char *name; int id; int elf_machine = EM_HOST; + PyObject *elf_machine_obj = NULL; static char *kwlist[] = { "name", "elf_machine", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|$i", kwlist, &name, &elf_machine)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|O", kwlist, &name, &elf_machine_obj)) + return NULL; + if (pyrf__parse_elf_machine(elf_machine_obj, &elf_machine) < 0) return NULL; id = syscalltbl__id(elf_machine, name); @@ -4138,6 +5392,30 @@ static PyObject *pyrf__syscall_id(PyObject *self, PyObject *args, PyObject *kwar return PyLong_FromLong(id); } +static PyObject *pyrf__arch_strerrno(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *name; + int err; + int elf_machine = EM_HOST; + PyObject *elf_machine_obj = NULL; + static char *kwlist[] = { "err", "elf_machine", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|O", kwlist, &err, &elf_machine_obj)) + return NULL; + if (pyrf__parse_elf_machine(elf_machine_obj, &elf_machine) < 0) + return NULL; + + if (err == INT_MIN) + Py_RETURN_NONE; + if (err < 0) + err = -err; + + name = perf_env__arch_strerrno((uint16_t)elf_machine, err); + if (!name || !strcmp(name, "(unknown)")) + Py_RETURN_NONE; + return PyUnicode_FromString(name); +} + static PyObject *pyrf__config_get(PyObject *self, PyObject *args) { const char *config_name, *val; @@ -4202,6 +5480,12 @@ static PyMethodDef perf__methods[] = { .ml_flags = METH_VARARGS | METH_KEYWORDS, .ml_doc = PyDoc_STR("Turns a syscall name to a number.") }, + { + .ml_name = "arch_strerrno", + .ml_meth = (PyCFunction) pyrf__arch_strerrno, + .ml_flags = METH_VARARGS | METH_KEYWORDS, + .ml_doc = PyDoc_STR("Turns an errno number to a string.") + }, { .ml_name = NULL, } }; @@ -4226,7 +5510,8 @@ PyMODINIT_FUNC PyInit_perf(void) if (module == NULL) return NULL; - if (pyrf_event__setup_types() < 0 || + if (PyType_Ready(&pyrf_call_return__type) < 0 || + pyrf_event__setup_types() < 0 || pyrf_evlist__setup_types() < 0 || pyrf_evsel__setup_types() < 0 || pyrf_thread_map__setup_types() < 0 || @@ -4243,6 +5528,7 @@ PyMODINIT_FUNC PyInit_perf(void) /* The page_size is placed in util object. */ page_size = sysconf(_SC_PAGE_SIZE); + perf_debug_setup(); Py_INCREF(&pyrf_evlist__type); PyModule_AddObject(module, "evlist", (PyObject *)&pyrf_evlist__type); @@ -4277,9 +5563,6 @@ PyMODINIT_FUNC PyInit_perf(void) Py_INCREF(&pyrf_throttle_event__type); PyModule_AddObject(module, "throttle_event", (PyObject *)&pyrf_throttle_event__type); - Py_INCREF(&pyrf_task_event__type); - PyModule_AddObject(module, "task_event", (PyObject *)&pyrf_task_event__type); - Py_INCREF(&pyrf_read_event__type); PyModule_AddObject(module, "read_event", (PyObject *)&pyrf_read_event__type); @@ -4309,6 +5592,8 @@ PyMODINIT_FUNC PyInit_perf(void) Py_INCREF(&pyrf_session__type); PyModule_AddObject(module, "session", (PyObject *)&pyrf_session__type); + Py_INCREF(&pyrf_call_return__type); + PyModule_AddObject(module, "call_return", (PyObject *)&pyrf_call_return__type); Py_INCREF(&pyrf_branch_entry__type); if (PyModule_AddObject(module, "branch_entry", (PyObject *)&pyrf_branch_entry__type) < 0) { |
