All of lore.kernel.org
 help / color / mirror / Atom feed
From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	 Arnaldo Carvalho de Melo <acme@kernel.org>,
	Namhyung Kim <namhyung@kernel.org>, Jiri Olsa <jolsa@kernel.org>,
	 Ian Rogers <irogers@google.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	 James Clark <james.clark@linaro.org>,
	Thomas Falcon <thomas.falcon@intel.com>,
	 Alice Rogers <alice.mei.rogers@gmail.com>,
	Changbin Du <changbin.du@huawei.com>,
	 Tengda Wu <wutengda@huaweicloud.com>, tanze <tanze@kylinos.cn>,
	 Athira Rajeev <atrajeev@linux.ibm.com>,
	Dapeng Mi <dapeng1.mi@linux.intel.com>,
	 linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org
Subject: [PATCH v1 03/13] perf python: Lazily copy events and samples from process_events
Date: Fri,  2 Oct 2026 11:26:12 -0700	[thread overview]
Message-ID: <20261002182624.3259797-4-irogers@google.com> (raw)
In-Reply-To: <20261002182624.3259797-1-irogers@google.com>

When perf.session.process_events() delivers an event to a Python
callback, pyrf_event__new() previously copied the raw perf_event into a
4,160-byte union perf_event embedded in struct pyrf_event and called
evsel__parse_sample() a second time to re-parse the sample against the
copied buffer.

In the common case, the Python callback inspects the event and returns
without storing a reference to it. Avoid the event memcpy, the duplicate
evsel__parse_sample() call, and the large PyObject allocation by:
1. Storing pointers to the underlying union perf_event and
   struct perf_sample in struct pyrf_event (using PyGetSetDef descriptors
   backed by PyMember_GetOne() to read fields through those pointers),
   shrinking struct pyrf_event so it fits in CPython's small-object
   allocator pool.
2. Borrowing the event and pre-parsed sample pointers from
   process_events() for the duration of the Python callback.
3. Checking Py_REFCNT(pyevent) > 1 after the callback returns and only
   allocating event_copy and re-parsing into sample_storage when the
   Python callback retained a reference to the event object (or immediately
   in pyrf_evlist__read_on_cpu() where no pre-parsed sample is passed).

Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/util/python.c | 664 ++++++++++++++++++++++-----------------
 1 file changed, 384 insertions(+), 280 deletions(-)

diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 5b4963d5784d..a40e89c1b329 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -70,16 +70,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)) { \
@@ -98,8 +88,12 @@ 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. */
@@ -110,11 +104,85 @@ struct pyrf_event {
 	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;
+};
+
+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 || !pevent->sample->evsel)
+		Py_RETURN_NONE;
+
+	return pyrf_evsel__from_evsel(pevent->sample->evsel);
+}
+
 #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"),		 \
@@ -125,39 +193,21 @@ struct pyrf_event {
 	sample_member_def(sample_period, period, T_ULONGLONG, "event period"),		 \
 	sample_member_def(sample_cpu, cpu, T_UINT, "event cpu"),
 
-static PyObject *pyrf_event__get_evsel(PyObject *self, void *closure __maybe_unused)
-{
-	struct pyrf_event *pevent = (void *)self;
-
-	if (!pevent->sample.evsel)
-		Py_RETURN_NONE;
-
-	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, },
-};
-
 static void pyrf_event__delete(struct pyrf_event *pevent)
 {
 	if (pevent->al_resolved)
 		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"),
@@ -166,7 +216,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, },
 };
 
@@ -174,13 +224,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);
@@ -196,8 +248,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,
 };
 
@@ -207,64 +258,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,
@@ -298,31 +354,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);
@@ -337,14 +380,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"),
@@ -359,12 +401,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 = {
@@ -374,28 +416,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 = {
@@ -405,14 +455,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"),
@@ -424,11 +473,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);
 }
 
@@ -439,14 +488,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"),
@@ -461,7 +509,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);
@@ -477,14 +525,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"),
@@ -498,14 +545,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 = {
@@ -516,28 +565,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 = {
@@ -548,14 +596,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"),
@@ -566,8 +613,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 ;-)
@@ -581,27 +628,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"),
-	sample_member_def(flags, flags, T_UINT, "event flags"),
-	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;
@@ -619,16 +651,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;
@@ -668,7 +702,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;
 
@@ -693,7 +727,7 @@ 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 evsel *evsel = pevent->sample->evsel ?: pevent->evsel;
 	struct evlist *evlist = evsel ? evsel->evlist : NULL;
 	struct perf_session *session = evlist ? evlist__session(evlist) : NULL;
 	struct machine *machine;
@@ -714,7 +748,7 @@ static int pyrf_sample_event__resolve_al(struct pyrf_event *pevent)
 	machine = pevent->machine ? pevent->machine : &session->machines.host;
 
 	addr_location__init(&pevent->al);
-	if (machine__resolve(machine, &pevent->al, &pevent->sample) < 0) {
+	if (machine__resolve(machine, &pevent->al, pevent->sample) < 0) {
 		addr_location__exit(&pevent->al);
 		return -1;
 	}
@@ -819,17 +853,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;
@@ -838,8 +872,8 @@ static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args)
 	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
+	 * 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.
 	 */
@@ -849,9 +883,9 @@ static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args)
 	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);
@@ -905,14 +939,14 @@ static PyObject *pyrf_sample_event__insn(PyObject *self, PyObject *args __maybe_
 	if (!machine)
 		Py_RETURN_NONE;
 
-	if (pevent->sample.ip && !pevent->sample.insn_len)
-		perf_sample__fetch_insn(&pevent->sample, thread, machine);
+	if (pevent->sample->ip && !pevent->sample->insn_len)
+		perf_sample__fetch_insn(pevent->sample, thread, 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 {
@@ -1242,8 +1276,8 @@ static int pyrf_sample_event__resolve_addr_al(struct pyrf_event *pevent,
 	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);
+	thread__find_symbol_fb(pevent->al.thread, pevent->sample->cpumode,
+			       pevent->sample->addr, addr_al);
 	return 0;
 }
 
@@ -1295,23 +1329,32 @@ static PyObject *pyrf_sample_event__get_addr_sym_offset(struct pyrf_event *peven
 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);
+	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));
+	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);
+	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,
@@ -1361,12 +1404,6 @@ static PyGetSetDef pyrf_sample_event__getset[] = {
 		.set = NULL,
 		.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,
@@ -1453,7 +1490,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,
@@ -1462,36 +1498,26 @@ 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[] = {
-	sample_members
-	member_def(perf_event_header, type, T_UINT, "event type"),
-	member_def(perf_event_header, misc, T_USHORT, "event misc"),
-	{ .name = NULL, },
-};
-
 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);
+	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);
+	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[] = {
-	{
-		.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"),
 	{
 		.name = "next_prev_pid",
 		.get = (getter)pyrf_context_switch_event__get_next_prev_pid,
@@ -1513,15 +1539,15 @@ static PyObject *pyrf_context_switch_event__repr(const struct pyrf_event *pevent
 	char *s;
 	int res;
 
-	if (pevent->event.header.type == PERF_RECORD_SWITCH_CPU_WIDE) {
+	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));
+			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));
+			       !!(pevent->event->header.misc & PERF_RECORD_MISC_SWITCH_OUT));
 	}
 	if (res < 0) {
 		ret = PyErr_NoMemory();
@@ -1539,7 +1565,6 @@ 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_context_switch_event__getset,
 	.tp_repr	= (reprfunc)pyrf_context_switch_event__repr,
 };
@@ -1614,15 +1639,102 @@ 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 (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 or
+	 * ring buffer.
+	 */
+	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_sample *sample_arg)
 {
 	struct pyrf_event *pevent;
 	struct perf_sample *sample;
-	int err;
 	u32 min_size;
-	bool needs_swap;
 
 	if (event->header.type >= ARRAY_SIZE(pyrf_event__type) ||
 	    pyrf_event__type[event->header.type] == NULL) {
@@ -1635,31 +1747,35 @@ 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 = machine;
 	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) {
 		if (!pevent->machine && session)
@@ -1667,53 +1783,25 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
 		return (PyObject *)pevent;
 	}
 
-	/* Parse the sample again so that pointers are within the copied event. */
-	needs_swap = evsel->needs_swap;
-
-	evsel->needs_swap = false;
-	err = evsel__parse_sample(evsel, &pevent->event, &pevent->sample);
-	evsel->needs_swap = needs_swap;
-	if (err < 0) {
-		/*
-		 * Synthesized events (e.g. Intel PT itrace) may not have raw sample
-		 * buffers for evsel__parse_sample(); use the pre-parsed sample_arg.
-		 */
-		if (sample_arg) {
-			perf_sample__exit(&pevent->sample);
-			pevent->sample = *sample_arg;
-			if (pevent->sample.evsel)
-				pevent->sample.evsel = evsel__get(pevent->sample.evsel);
-			pevent->sample.merged_callchain = false;
-
-			pevent->sample.user_regs = NULL;
-			pevent->sample.intr_regs = NULL;
-			pevent->sample.raw_data = NULL;
-			pevent->sample.raw_size = 0;
-		} else {
-			Py_DECREF(pevent);
-			return PyErr_Format(PyExc_OSError,
-					    "perf: can't parse sample, err=%d", err);
-		}
-	}
-	if (session && session->evlist && perf_guest && pevent->sample.id) {
-		struct perf_sample_id *sid = evlist__id2sid(session->evlist, pevent->sample.id);
+	sample = pevent->sample;
+	if (session && session->evlist && perf_guest && sample->id) {
+		struct perf_sample_id *sid = evlist__id2sid(session->evlist, sample->id);
 
 		if (sid) {
-			pevent->sample.machine_pid = sid->machine_pid;
-			pevent->sample.vcpu = sid->vcpu.cpu;
+			sample->machine_pid = sid->machine_pid;
+			sample->vcpu = sid->vcpu.cpu;
 		}
 	}
 	if (!machine && session) {
-		machine = pevent->sample.machine_pid ?
-			machines__find(&session->machines, pevent->sample.machine_pid) :
+		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 && !pevent->sample.machine_pid)
-		pevent->sample.machine_pid = machine->pid;
-	sample = &pevent->sample;
+	if (machine && machine->pid > 0 && !sample->machine_pid)
+		sample->machine_pid = machine->pid;
 	if (machine && sample->callchain) {
 		struct addr_location al;
 		struct callchain_cursor *cursor;
@@ -4524,6 +4612,41 @@ static int pyrf_session__call_return_process(struct call_return *cr,
 	return 0;
 }
 
+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__sample(const struct perf_tool *tool,
 				     union perf_event *event,
 				     struct perf_sample *sample,
@@ -4575,16 +4698,12 @@ static int pyrf_session_tool__sample(const struct perf_tool *tool,
 		PyObject *pyevent = pyrf_event__new(event, sample->evsel,
 						    psession->session,
 						    machine, sample);
-		PyObject *ret;
 
 		if (pyevent == NULL)
 			return -ENOMEM;
 
-		ret = PyObject_CallFunction(psession->sample, "O", pyevent);
-		Py_DECREF(pyevent);
-		if (!ret)
-			return pyrf_session__callback_raised(psession);
-		Py_DECREF(ret);
+		return pyrf_session__call_event(psession, psession->sample, pyevent,
+						/*extra_arg=*/NULL, /*has_extra_arg=*/false);
 	}
 	return 0;
 }
@@ -4596,7 +4715,6 @@ static int pyrf_session_tool__context_switch(const struct perf_tool *tool,
 {
 	struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
 	PyObject *pyevent;
-	PyObject *ret;
 
 	if (pyrf_session__exception_pending()) {
 		/*
@@ -4607,7 +4725,7 @@ static int pyrf_session_tool__context_switch(const struct perf_tool *tool,
 		return perf_event__process_switch(tool, event, sample, machine);
 	}
 
-	pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine, NULL);
+	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;
@@ -4616,12 +4734,8 @@ static int pyrf_session_tool__context_switch(const struct perf_tool *tool,
 	if (pyevent == NULL)
 		return -ENOMEM;
 
-	ret = PyObject_CallFunction(psession->context_switch, "O", pyevent);
-	Py_DECREF(pyevent);
-	if (!ret)
-		return pyrf_session__callback_raised(psession);
-	Py_DECREF(ret);
-	return 0;
+	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,
@@ -4632,7 +4746,6 @@ static int pyrf_session_tool__stat(const struct perf_tool *tool,
 	struct evsel *evsel = evlist__id2evsel(session->evlist, event->stat.id);
 	const char *name = evsel ? evsel__name(evsel) : "unknown";
 	PyObject *pyevent;
-	PyObject *ret;
 
 	if (pyrf_session__exception_pending())
 		return 0;
@@ -4642,12 +4755,8 @@ static int pyrf_session_tool__stat(const struct perf_tool *tool,
 	if (pyevent == NULL)
 		return -ENOMEM;
 
-	ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, name);
-	Py_DECREF(pyevent);
-	if (!ret)
-		return pyrf_session__callback_raised(psession);
-	Py_DECREF(ret);
-	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,
@@ -4656,7 +4765,6 @@ static int pyrf_session_tool__stat_round(const struct perf_tool *tool,
 {
 	struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
 	PyObject *pyevent;
-	PyObject *ret;
 
 	if (pyrf_session__exception_pending())
 		return 0;
@@ -4666,12 +4774,8 @@ static int pyrf_session_tool__stat_round(const struct perf_tool *tool,
 	if (pyevent == NULL)
 		return -ENOMEM;
 
-	ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, NULL);
-	Py_DECREF(pyevent);
-	if (!ret)
-		return pyrf_session__callback_raised(psession);
-	Py_DECREF(ret);
-	return 0;
+	return pyrf_session__call_event(psession, psession->stat, pyevent,
+					/*extra_arg=*/NULL, /*has_extra_arg=*/true);
 }
 
 static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObject *args)
-- 
2.56.0.rc1.315.gc6ed9934b7-goog


  parent reply	other threads:[~2026-10-02 18:26 UTC|newest]

Thread overview: 37+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-02 18:26 [PATCH v1 00/13] perf timechart/list/treport: Interactive Textual TUIs and perf python session improvements Ian Rogers
2026-10-02 18:26 ` [PATCH v1 01/13] perf session: Don't flush remaining events once processing is done Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 02/13] perf python: Quietly stop processing events when a callback raises Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` Ian Rogers [this message]
2026-10-03  1:33   ` [PATCH v1 03/13] perf python: Lazily copy events and samples from process_events sashiko-bot
2026-10-02 18:26 ` [PATCH v1 04/13] perf python: Lazily resolve sample callchains Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 05/13] perf python: Release the GIL while processing session events Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 06/13] perf list: Add a --tui option to launch ilist Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 07/13] perf test: Add a test for the ilist script Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 08/13] perf treport: Show the profile while it loads Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-03  9:31   ` Arnaldo Carvalho de Melo
2026-10-02 18:26 ` [PATCH v1 09/13] perf test: Add a test for the treport script Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-03  9:35   ` Arnaldo Carvalho de Melo
2026-10-02 18:26 ` [PATCH v1 10/13] perf timechart: Add an interactive --tui mode Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 11/13] perf test: Add a test for perf timechart --tui Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 12/13] perf timechart: Add a --live mode to the TUI Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-02 18:26 ` [PATCH v1 13/13] perf test: Test perf timechart --live Ian Rogers
2026-10-03  1:33   ` sashiko-bot
2026-10-03  9:10 ` [PATCH v1 00/13] perf timechart/list/treport: Interactive Textual TUIs and perf python session improvements Arnaldo Carvalho de Melo
2026-10-03  9:12   ` Arnaldo Carvalho de Melo
2026-10-03  9:15     ` Arnaldo Carvalho de Melo
2026-10-14  4:22       ` [PATCH v2 0/2] perf timechart: Add --live mode to the TUI and test Ian Rogers
2026-10-14  4:22         ` [PATCH v2 1/2] perf timechart: Add a --live mode to the TUI Ian Rogers
2026-10-14  4:35           ` sashiko-bot
2026-10-14  4:22         ` [PATCH v2 2/2] perf test: Test perf timechart --live Ian Rogers
2026-10-14  4:28           ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261002182624.3259797-4-irogers@google.com \
    --to=irogers@google.com \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=alice.mei.rogers@gmail.com \
    --cc=atrajeev@linux.ibm.com \
    --cc=changbin.du@huawei.com \
    --cc=dapeng1.mi@linux.intel.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mingo@redhat.com \
    --cc=namhyung@kernel.org \
    --cc=peterz@infradead.org \
    --cc=tanze@kylinos.cn \
    --cc=thomas.falcon@intel.com \
    --cc=wutengda@huaweicloud.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.