summaryrefslogtreecommitdiff
path: root/tools/perf/util/python.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/util/python.c')
-rw-r--r--tools/perf/util/python.c2005
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) {