mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Ian Rogers <irogers@google.com>
To: irogers@google.com, acme@kernel.org, alice.mei.rogers@gmail.com,
	 namhyung@kernel.org
Cc: adrian.hunter@intel.com, dapeng1.mi@linux.intel.com,
	 james.clark@linaro.org, leo.yan@linux.dev,
	linux-kernel@vger.kernel.org,  linux-perf-users@vger.kernel.org,
	mingo@redhat.com, peterz@infradead.org,  tmricht@linux.ibm.com
Subject: [PATCH v3 09/49] perf python: Add Intel PT call_return and itrace capability
Date: Wed, 23 Sep 2026 11:11:32 -0700	[thread overview]
Message-ID: <20260923181213.3032038-10-irogers@google.com> (raw)
In-Reply-To: <20260923181213.3032038-1-irogers@google.com>

Add support for Intel PT call_return events and itrace options to the
perf Python extension so standalone Python scripts can decode and
inspect low-level instruction traces.

Specifically:
- Add a call_return callback parameter and itrace option string to
  perf.session, treating explicit None (Py_None) callbacks as omitted.
- Expose the perf.call_return object with db_id, parent_id, insn_count,
  cyc_count, comm, pid, tid, call_time, return_time, branch_count,
  call_ref, return_ref, flags, and call_path (resolved as a
  perf.callchain ordered from outermost caller to callee, populating
  frame->map and frame->sym so call_path entries expose resolved symbol
  and DSO names).
- Export perf.CALL_RETURN_NO_CALL, perf.CALL_RETURN_NO_RETURN, and
  perf.CALL_RETURN_NON_CALL flag constants.
- Expose session.e_machine and session.is_64_bit properties, preferring
  recorded thread ELF machines (perf_session__e_machine) over perf_env
  so pipe-mode traces do not prematurely cache host bitness.
- Support optional vmlinux, kallsyms, and symfs symbol configuration
  parameters (and PERF_SYMBOL_VMLINUX, PERF_SYMBOL_KALLSYMS,
  PERF_SYMBOL_SYMFS environment fallbacks) in perf.session, resetting
  symbol_conf pointers and freeing vm_tm_corr_args, cpu_bitmap, and
  ptime_range in pyrf_session__delete().

Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@google.com>
---
 tools/perf/python/perf.pyi |  78 ++++-
 tools/perf/util/python.c   | 696 ++++++++++++++++++++++++++++++++++---
 2 files changed, 709 insertions(+), 65 deletions(-)

diff --git a/tools/perf/python/perf.pyi b/tools/perf/python/perf.pyi
index 44c693761838..087da00728fd 100644
--- a/tools/perf/python/perf.pyi
+++ b/tools/perf/python/perf.pyi
@@ -96,7 +96,7 @@ class data:
 
 class thread:
     """Represents a thread in the system."""
-    def comm(self) -> str:
+    def comm(self) -> Optional[str]:
         """Get the command name of the thread."""
         ...
     pid: int
@@ -165,7 +165,13 @@ class evsel:
     def __str__(self) -> str:
         """Return string representation of the event."""
         ...
-    def open(self) -> None:
+    def open(
+        self,
+        cpus: Optional['cpu_map'] = None,
+        threads: Optional[thread_map] = None,
+        group: bool = False,
+        inherit: bool = False,
+    ) -> None:
         """Open the event selector file descriptor table."""
         ...
     def read(self, cpu: int, thread: int) -> counts_values:
@@ -211,15 +217,15 @@ class sample_event(_sample_members):
     sample_cyc_count: int
     type: int
     raw_buf: bytes
-    dso: str
-    dso_long_name: str
-    dso_bid: Optional[bytes]
-    map_start: int
-    map_end: int
-    map_pgoff: int
-    symbol: str
-    sym_start: int
-    sym_end: int
+    dso: Optional[str]
+    dso_long_name: Optional[str]
+    dso_bid: Optional[str]
+    map_start: Optional[int]
+    map_end: Optional[int]
+    map_pgoff: Optional[int]
+    symbol: Optional[str]
+    sym_start: Optional[int]
+    sym_end: Optional[int]
     sym_offset: Optional[int]
     addr_dso: Optional[str]
     addr_symbol: Optional[str]
@@ -230,7 +236,7 @@ class sample_event(_sample_members):
     transaction: int
     brstack: Optional['branch_stack']
     callchain: Optional['callchain']
-    def srccode(self) -> Optional[tuple[Optional[str], int, Optional[str]]]: ...
+    def srccode(self, addr: int = ..., /) -> Optional[tuple[Optional[str], int, Optional[str]]]: ...
     def insn(self) -> Optional[bytes]: ...
     def __getattr__(self, name: str) -> Any: ...
 
@@ -339,6 +345,7 @@ class branch_stack:
     """Sequence of branch entries in the branch stack."""
     def __len__(self) -> int: ...
     def __getitem__(self, index: int) -> branch_entry: ...
+    def __iter__(self) -> Iterator[branch_entry]: ...
 
 class callchain_node:
     """Represents a frame in the callchain."""
@@ -387,10 +394,10 @@ class evlist:
     def close(self) -> None:
         """Close the events in the list."""
         ...
-    def mmap(self) -> None:
+    def mmap(self, pages: int = 128, overwrite: bool = False) -> None:
         """Memory map the event buffers."""
         ...
-    def poll(self, timeout: int) -> int:
+    def poll(self, timeout: int = -1) -> int:
         """Poll for events.
 
         Args:
@@ -400,7 +407,7 @@ class evlist:
             Number of events ready.
         """
         ...
-    def read_on_cpu(self, cpu: int) -> Optional[Any]:
+    def read_on_cpu(self, cpu: int, sample_id_all: bool = True) -> Optional[Any]:
         """Read a sample event from a specific CPU.
 
         Args:
@@ -437,17 +444,38 @@ class evlist:
     def enable(self) -> None:
         """Enable all events in the list."""
         ...
-    def get_pollfd(self) -> List[int]:
+    def get_pollfd(self) -> List[Any]:
         """Get a list of file descriptors for polling."""
         ...
-    def add(self, evsel: evsel) -> int:
+    def add(self, evsel: evsel, /) -> int:
         """Add an event to the list."""
         ...
+    def __len__(self) -> int: ...
+    def __getitem__(self, index: int) -> evsel: ...
     def __iter__(self) -> Iterator[evsel]:
         """Iterate over the events (evsel) in the list."""
         ...
 
 
+class call_return:
+    """Represents a call/return event from instruction trace decoding."""
+    db_id: int
+    parent_id: int
+    insn_count: int
+    cyc_count: int
+    comm: Optional[str]
+    machine_pid: int
+    pid: Optional[int]
+    tid: Optional[int]
+    call_time: int
+    return_time: int
+    branch_count: int
+    call_ref: int
+    return_ref: int
+    flags: int
+    call_path: Optional[callchain]
+
+
 class session:
     def __init__(
         self,
@@ -455,6 +483,11 @@ class session:
         sample: Optional[Callable[[sample_event], None]] = None,
         stat: Optional[Callable[[Any, Optional[str]], None]] = None,
         context_switch: Optional[Callable[[switch_event], None]] = None,
+        call_return: Optional[Callable[[call_return], None]] = None,
+        itrace: Optional[str] = None,
+        vmlinux: Optional[str] = None,
+        kallsyms: Optional[str] = None,
+        symfs: Optional[str] = None
     ) -> None:
         """Initialize a perf session.
 
@@ -464,6 +497,8 @@ class session:
             stat: Callback for stat events.
         """
         ...
+    e_machine: int
+    is_64_bit: bool
     def process_events(self) -> None:
         """Process all events in the session."""
         ...
@@ -696,3 +731,12 @@ RECORD_STAT_ROUND: int
 
 RECORD_MISC_SWITCH_OUT: int
 """MISC_SWITCH_OUT record."""
+
+CALL_RETURN_NO_CALL: int
+"""'return' but no matching 'call'."""
+
+CALL_RETURN_NO_RETURN: int
+"""'call' but no matching 'return'."""
+
+CALL_RETURN_NON_CALL: int
+"""A branch but not a 'call' to the start of a different symbol."""
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 05b558b6aa66..3915769143a3 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -2,14 +2,17 @@
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
 
+#include <elf.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <limits.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>
@@ -25,6 +28,9 @@
 #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"
@@ -32,6 +38,7 @@
 #include "branch.h"
 #include "evlist.h"
 #include "evsel.h"
+#include "symbol.h"
 #include "expr.h"
 #include "map.h"
 #include "metricgroup.h"
@@ -829,6 +836,12 @@ 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;
 
@@ -857,6 +870,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);
@@ -900,11 +919,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);
 }
 
@@ -917,6 +938,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]");
@@ -974,6 +997,7 @@ struct pyrf_callchain_frame {
 	u64 ip;
 	struct map *map;
 	struct symbol *sym;
+	char *sym_name;
 };
 
 struct pyrf_callchain {
@@ -985,8 +1009,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);
@@ -1016,6 +1042,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;
 }
@@ -1587,8 +1614,8 @@ static PyTypeObject *pyrf_event__type[] = {
 };
 
 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;
@@ -1646,9 +1673,26 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
 	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);
+		/*
+		 * 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);
@@ -2013,7 +2057,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
@@ -2424,12 +2467,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});
@@ -2954,10 +2998,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
@@ -2999,9 +3039,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++) {
@@ -3019,6 +3065,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;
@@ -3027,7 +3074,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);
@@ -3169,10 +3216,7 @@ static PyObject *pyrf_evlist__get_pollfd(struct pyrf_evlist *pevlist,
 	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;
@@ -3250,7 +3294,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();
@@ -3384,7 +3428,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.")
 	},
 	{
@@ -3618,6 +3662,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, },
 };
 
@@ -3628,7 +3677,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;
 
@@ -3896,6 +3945,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;
@@ -4052,31 +4105,427 @@ 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;
+	char *vmlinux_name;
+	char *kallsyms_name;
+	char *symfs;
+};
+
+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)
+{
+	return PyUnicode_FromString("{ type: call_return }");
+}
+
+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;
+}
+
+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;
+}
+
+static int pyrf_session__call_return_process(struct call_return *cr,
+					     u64 *parent_db_id,
+					     void *data)
+{
+	struct pyrf_session *psession = data;
+	PyObject *pyevent, *ret;
+
+	if (!psession->call_return)
+		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);
+	if (!ret) {
+		Py_DECREF(pyevent);
+		return -1;
+	}
+	Py_DECREF(ret);
+	Py_DECREF(pyevent);
+	return 0;
+}
+
 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);
-	PyObject *pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine);
-	PyObject *ret;
+	u64 sample_db_id = ++psession->sample_last_db_id;
 
-	if (pyevent == NULL)
-		return -ENOMEM;
+	if (psession->crp) {
+		struct addr_location al, addr_al;
+		struct thread *thread = NULL;
 
-	ret = PyObject_CallFunction(psession->sample, "O", pyevent);
-	if (!ret) {
+		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);
+	}
+
+	if (psession->sample) {
+		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);
-		return -1;
+		if (!ret)
+			return -1;
+		Py_DECREF(ret);
 	}
-	Py_DECREF(ret);
-	Py_DECREF(pyevent);
 	return 0;
 }
 
@@ -4086,7 +4535,7 @@ static int pyrf_session_tool__context_switch(const struct perf_tool *tool,
 					     struct machine *machine)
 {
 	struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
-	PyObject *pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine);
+	PyObject *pyevent = pyrf_event__new(event, sample->evsel, psession->session, machine, NULL);
 	PyObject *ret;
 
 	if (perf_event__process_switch(tool, event, sample, machine) < 0) {
@@ -4114,7 +4563,7 @@ static int pyrf_session_tool__stat(const struct perf_tool *tool,
 	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);
+					    /*machine=*/NULL, NULL);
 	const char *name = evsel ? evsel__name(evsel) : "unknown";
 	PyObject *ret;
 
@@ -4137,7 +4586,7 @@ 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 = pyrf_event__new(event, /*evsel=*/NULL, psession->session,
-					    /*machine=*/NULL);
+					    /*machine=*/NULL, NULL);
 	PyObject *ret;
 
 	if (pyevent == NULL)
@@ -4165,17 +4614,28 @@ static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObje
 	if (!PyArg_ParseTuple(args, "i|i", &pid, &tid))
 		return NULL;
 
-	if (tid == -1)
+	if (tid == -1) {
 		tid = pid;
+		pid = -1;
+	}
 
-	/* Look up the thread in the host machine first, then fall back to guest machines. */
+	/*
+	 * 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, 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, tid);
+		if (!thread) {
+			machine = machines__find(&psession->session->machines,
+						 DEFAULT_GUEST_KERNEL_ID);
+			if (machine)
+				thread = machine__find_thread(machine, pid, tid);
+		}
 	}
 
 	/* Return None rather than raising TypeError when a PID/TID is not known. */
@@ -4190,23 +4650,40 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject
 {
 	struct pyrf_data *pdata;
 	PyObject *sample = NULL, *stat = NULL, *context_switch = NULL;
-	static char *kwlist[] = { "data", "sample", "stat", "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!|OOO", kwlist, &pyrf_data__type, &pdata,
-					 &sample, &stat, &context_switch))
+	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->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;
@@ -4216,7 +4693,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;				\
@@ -4228,15 +4705,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;
@@ -4260,6 +4760,49 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject
 	}
 	psession->session = session;
 
+	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");
+			Py_DECREF(psession);
+			return NULL;
+		}
+		psession->session->itrace_synth_opts = &psession->itrace_opts;
+	}
+
+	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;
+		}
+		symbol_conf.vmlinux_name = psession->vmlinux_name;
+	}
+
+	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;
+		}
+		symbol_conf.kallsyms_name = psession->kallsyms_name;
+	}
+
+	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;
+		}
+		symbol_conf.symfs = psession->symfs;
+	}
+
 	symbol_conf.use_callchain = true;
 	symbol_conf.show_kernel_path = true;
 	symbol_conf.inline_name = false;
@@ -4268,8 +4811,6 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject
 		goto err_out;
 	}
 
-
-
 	return (PyObject *)psession;
 err_out:
 	Py_DECREF(psession);
@@ -4279,10 +4820,25 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject
 static void pyrf_session__delete(struct pyrf_session *psession)
 {
 	perf_session__delete(psession->session);
+	if (symbol_conf.vmlinux_name == psession->vmlinux_name)
+		symbol_conf.vmlinux_name = NULL;
+	free(psession->vmlinux_name);
+	if (symbol_conf.kallsyms_name == psession->kallsyms_name)
+		symbol_conf.kallsyms_name = NULL;
+	free(psession->kallsyms_name);
+	if (symbol_conf.symfs == psession->symfs)
+		symbol_conf.symfs = "";
+	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);
 }
 
@@ -4325,10 +4881,54 @@ 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 (!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);
 }
 
@@ -4536,7 +5136,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 ||
@@ -4587,9 +5188,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);
 
@@ -4619,6 +5217,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) {
-- 
2.56.0.rc1.310.g51773c2048-goog


  parent reply	other threads:[~2026-09-23 18:13 UTC|newest]

Thread overview: 161+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-20  5:20 [PATCH v1 00/49] perf: Complete transition to standalone Python scripts Ian Rogers
2026-09-20  5:20 ` [PATCH v1 01/49] perf python: Update syscall format string to optional positional Ian Rogers
2026-09-20  5:20 ` [PATCH v1 02/49] perf python: Update callchain stubs and session thread lookup Ian Rogers
2026-09-20  5:20 ` [PATCH v1 03/49] perf python: Clean up pylint warnings in ilist.py Ian Rogers
2026-09-20  5:20 ` [PATCH v1 04/49] perf python: Clean up pylint warnings in treport.py Ian Rogers
2026-09-20  5:20 ` [PATCH v1 05/49] perf python: Clean up pylint warnings in tracepoint.py Ian Rogers
2026-09-20  5:20 ` [PATCH v1 06/49] perf python: Clean up pylint warnings in twatch.py Ian Rogers
2026-09-20  5:20 ` [PATCH v1 07/49] perf python: Improve perf script -l descriptions Ian Rogers
2026-09-20  5:21 ` [PATCH v1 08/49] perf python: Expose addr location, transaction, and context_switch Ian Rogers
2026-09-20  5:21 ` [PATCH v1 09/49] perf python: Add Intel PT call_return and itrace capability Ian Rogers
2026-09-20  5:21 ` [PATCH v1 10/49] perf python: Allow KeyboardInterrupt to propagate in LiveSession Ian Rogers
2026-09-20  5:21 ` [PATCH v1 11/49] perf pmu-events: Clean up mypy and pylint issues Ian Rogers
2026-09-20  5:21 ` [PATCH v1 12/49] perf test: Clean up mypy and pylint issues in shell test libraries Ian Rogers
2026-09-20  5:21 ` [PATCH v1 13/49] perf build: Make mypy build test opt-out (NO_MYPY=1) Ian Rogers
2026-09-20  5:21 ` [PATCH v1 14/49] perf build: Make pylint build test opt-out (NO_PYLINT=1) Ian Rogers
2026-09-20  5:21 ` [PATCH v1 15/49] perf Makefile: Install standalone Python scripts during transition Ian Rogers
2026-09-20  5:21 ` [PATCH v1 16/49] perf python: Port stat-cpi to perf module Ian Rogers
2026-09-20  5:21 ` [PATCH v1 17/49] perf python: Port mem-phys-addr " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 18/49] perf python: Port stackcollapse " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 19/49] perf python: Port flamegraph " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 20/49] perf python: Port gecko " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 21/49] perf python: Port event_analyzing_sample " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 22/49] perf python: Port syscall-counts " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 23/49] perf python: Port syscall-counts-by-pid " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 24/49] perf python: Port failed-syscalls-by-pid " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 25/49] perf python: Port failed-syscalls from Perl " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 26/49] perf python: Port sctop " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 27/49] perf python: Port rw-by-file from Perl " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 28/49] perf python: Port rw-by-pid " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 29/49] perf python: Port rwtop " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 30/49] perf python: Port futex-contention " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 31/49] perf python: Port task-analyzer " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 32/49] perf python: Port sched-migration and SchedGui " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 33/49] perf python: Port wakeup-latency from Perl " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 34/49] perf python: Port compaction-times " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 35/49] perf python: Port net_dropmonitor " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 36/49] perf python: Port netdev-times " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 37/49] perf python: Port check-perf-trace " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 38/49] perf python: Port arm-cs-trace-disasm " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 39/49] perf python: Port powerpc-hcalls " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 40/49] perf python: Port intel-pt-events and libxed " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 41/49] perf test: Migrate Intel PT virtual LBR test to Python API Ian Rogers
2026-09-20  5:21 ` [PATCH v1 42/49] perf python: Port export-to-sqlite to perf module Ian Rogers
2026-09-20  5:21 ` [PATCH v1 43/49] perf python: Port export-to-postgresql " Ian Rogers
2026-09-20  5:21 ` [PATCH v1 44/49] perf python: Move and clean up exported-sql-viewer.py Ian Rogers
2026-09-20  5:21 ` [PATCH v1 45/49] perf python: Move and clean up parallel-perf.py Ian Rogers
2026-09-20  5:21 ` [PATCH v1 46/49] perf: Remove libpython support and legacy Python scripts Ian Rogers
2026-09-20  5:21 ` [PATCH v1 47/49] perf Makefile: Update Python script installation path Ian Rogers
2026-09-20  5:21 ` [PATCH v1 48/49] perf script: Support standalone scripts and remove embedded scripting Ian Rogers
2026-09-20  5:21 ` [PATCH v1 49/49] perf Documentation: Update for standalone Python scripts Ian Rogers
2026-09-21  5:06 ` [PATCH v2 00/49] perf: Complete transition to " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 01/49] perf python: Update syscall format string to optional positional Ian Rogers
2026-09-21  5:06   ` [PATCH v2 02/49] perf python: Update callchain stubs and session thread lookup Ian Rogers
2026-09-21  5:06   ` [PATCH v2 03/49] perf python: Clean up pylint warnings in ilist.py Ian Rogers
2026-09-21  5:06   ` [PATCH v2 04/49] perf python: Clean up pylint warnings in treport.py Ian Rogers
2026-09-21  5:06   ` [PATCH v2 05/49] perf python: Clean up pylint warnings in tracepoint.py Ian Rogers
2026-09-21  5:06   ` [PATCH v2 06/49] perf python: Clean up pylint warnings in twatch.py Ian Rogers
2026-09-21  5:06   ` [PATCH v2 07/49] perf python: Improve perf script -l descriptions Ian Rogers
2026-09-21  5:06   ` [PATCH v2 08/49] perf python: Expose addr location, transaction, and context_switch Ian Rogers
2026-09-21  5:06   ` [PATCH v2 09/49] perf python: Add Intel PT call_return and itrace capability Ian Rogers
2026-09-22 13:11     ` James Clark
2026-09-22 17:00       ` Ian Rogers
2026-09-21  5:06   ` [PATCH v2 10/49] perf python: Allow KeyboardInterrupt to propagate in LiveSession Ian Rogers
2026-09-21  5:06   ` [PATCH v2 11/49] perf pmu-events: Clean up mypy and pylint issues Ian Rogers
2026-09-21  5:06   ` [PATCH v2 12/49] perf test: Clean up mypy and pylint issues in shell test libraries Ian Rogers
2026-09-21  5:06   ` [PATCH v2 13/49] perf build: Make mypy build test opt-out (NO_MYPY=1) Ian Rogers
2026-09-22 13:11     ` James Clark
2026-09-22 16:59       ` Ian Rogers
2026-09-21  5:06   ` [PATCH v2 14/49] perf build: Make pylint build test opt-out (NO_PYLINT=1) Ian Rogers
2026-09-21  5:06   ` [PATCH v2 15/49] perf Makefile: Install standalone Python scripts during transition Ian Rogers
2026-09-21  5:06   ` [PATCH v2 16/49] perf python: Port stat-cpi to perf module Ian Rogers
2026-09-21  5:06   ` [PATCH v2 17/49] perf python: Port mem-phys-addr " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 18/49] perf python: Port stackcollapse " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 19/49] perf python: Port flamegraph " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 20/49] perf python: Port gecko " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 21/49] perf python: Port event_analyzing_sample " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 22/49] perf python: Port syscall-counts " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 23/49] perf python: Port syscall-counts-by-pid " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 24/49] perf python: Port failed-syscalls-by-pid " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 25/49] perf python: Port failed-syscalls from Perl " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 26/49] perf python: Port sctop " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 27/49] perf python: Port rw-by-file from Perl " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 28/49] perf python: Port rw-by-pid " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 29/49] perf python: Port rwtop " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 30/49] perf python: Port futex-contention " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 31/49] perf python: Port task-analyzer " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 32/49] perf python: Port sched-migration and SchedGui " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 33/49] perf python: Port wakeup-latency from Perl " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 34/49] perf python: Port compaction-times " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 35/49] perf python: Port net_dropmonitor " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 36/49] perf python: Port netdev-times " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 37/49] perf python: Port check-perf-trace " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 38/49] perf python: Port arm-cs-trace-disasm " Ian Rogers
2026-09-22 13:12     ` James Clark
2026-09-23  5:23       ` Ian Rogers
2026-09-23  8:16         ` James Clark
2026-09-23  9:00     ` Leo Yan
2026-09-23  9:08       ` Leo Yan
2026-09-23 13:16         ` Ian Rogers
2026-09-23 13:40       ` Ian Rogers
2026-09-21  5:06   ` [PATCH v2 39/49] perf python: Port powerpc-hcalls " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 40/49] perf python: Port intel-pt-events and libxed " Ian Rogers
2026-09-21  5:06   ` [PATCH v2 41/49] perf test: Migrate Intel PT virtual LBR test to Python API Ian Rogers
2026-09-21  5:07   ` [PATCH v2 42/49] perf python: Port export-to-sqlite to perf module Ian Rogers
2026-09-21  5:07   ` [PATCH v2 43/49] perf python: Port export-to-postgresql " Ian Rogers
2026-09-21  5:07   ` [PATCH v2 44/49] perf python: Move and clean up exported-sql-viewer.py Ian Rogers
2026-09-21  5:07   ` [PATCH v2 45/49] perf python: Move and clean up parallel-perf.py Ian Rogers
2026-09-21  5:07   ` [PATCH v2 46/49] perf: Remove libpython support and legacy Python scripts Ian Rogers
2026-09-21  5:07   ` [PATCH v2 47/49] perf Makefile: Update Python script installation path Ian Rogers
2026-09-21  5:07   ` [PATCH v2 48/49] perf script: Support standalone scripts and remove embedded scripting Ian Rogers
2026-09-21  5:07   ` [PATCH v2 49/49] perf Documentation: Update for standalone Python scripts Ian Rogers
2026-09-23 18:11   ` [PATCH v3 00/49] perf: Complete transition to " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 01/49] perf python: Update syscall helpers and expose arch_strerrno Ian Rogers
2026-09-23 18:11     ` [PATCH v3 02/49] perf python: Update callchain stubs and session thread lookup Ian Rogers
2026-09-23 18:11     ` [PATCH v3 03/49] perf python: Clean up pylint warnings in ilist.py Ian Rogers
2026-09-23 18:11     ` [PATCH v3 04/49] perf python: Clean up pylint warnings in treport.py Ian Rogers
2026-09-23 18:11     ` [PATCH v3 05/49] perf python: Clean up pylint warnings in tracepoint.py Ian Rogers
2026-09-23 18:11     ` [PATCH v3 06/49] perf python: Clean up pylint warnings in twatch.py Ian Rogers
2026-09-23 18:11     ` [PATCH v3 07/49] perf python: Improve perf script -l descriptions Ian Rogers
2026-09-23 18:11     ` [PATCH v3 08/49] perf python: Expose addr location, transaction, and context_switch Ian Rogers
2026-09-23 18:11     ` Ian Rogers [this message]
2026-09-23 18:11     ` [PATCH v3 10/49] perf python: Allow KeyboardInterrupt to propagate in LiveSession Ian Rogers
2026-09-23 18:11     ` [PATCH v3 11/49] perf pmu-events: Clean up mypy and pylint issues Ian Rogers
2026-09-23 18:11     ` [PATCH v3 12/49] perf test: Clean up mypy and pylint issues in shell test libraries Ian Rogers
2026-09-23 18:11     ` [PATCH v3 13/49] perf build: Make mypy build test opt-out (NO_MYPY=1) Ian Rogers
2026-09-23 18:11     ` [PATCH v3 14/49] perf build: Make pylint build test opt-out (NO_PYLINT=1) Ian Rogers
2026-09-23 18:11     ` [PATCH v3 15/49] perf Makefile: Install standalone Python scripts during transition Ian Rogers
2026-09-23 18:11     ` [PATCH v3 16/49] perf python: Port stat-cpi to perf module Ian Rogers
2026-09-23 18:11     ` [PATCH v3 17/49] perf python: Port mem-phys-addr " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 18/49] perf python: Port stackcollapse " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 19/49] perf python: Port flamegraph " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 20/49] perf python: Port gecko " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 21/49] perf python: Port event_analyzing_sample " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 22/49] perf python: Port syscall-counts " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 23/49] perf python: Port syscall-counts-by-pid " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 24/49] perf python: Port failed-syscalls-by-pid " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 25/49] perf python: Port failed-syscalls from Perl " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 26/49] perf python: Port sctop " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 27/49] perf python: Port rw-by-file from Perl " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 28/49] perf python: Port rw-by-pid " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 29/49] perf python: Port rwtop " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 30/49] perf python: Port futex-contention " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 31/49] perf python: Port task-analyzer " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 32/49] perf python: Port sched-migration and SchedGui " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 33/49] perf python: Port wakeup-latency from Perl " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 34/49] perf python: Port compaction-times " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 35/49] perf python: Port net_dropmonitor " Ian Rogers
2026-09-23 18:11     ` [PATCH v3 36/49] perf python: Port netdev-times " Ian Rogers
2026-09-23 18:12     ` [PATCH v3 37/49] perf python: Port check-perf-trace " Ian Rogers
2026-09-23 18:12     ` [PATCH v3 38/49] perf python: Port arm-cs-trace-disasm " Ian Rogers
2026-09-23 18:12     ` [PATCH v3 39/49] perf python: Port powerpc-hcalls " Ian Rogers
2026-09-23 18:12     ` [PATCH v3 40/49] perf python: Port intel-pt-events and libxed " Ian Rogers
2026-09-23 18:12     ` [PATCH v3 41/49] perf test: Migrate Intel PT virtual LBR test to Python API Ian Rogers
2026-09-23 18:12     ` [PATCH v3 42/49] perf python: Port export-to-sqlite to perf module Ian Rogers
2026-09-23 18:12     ` [PATCH v3 43/49] perf python: Port export-to-postgresql " Ian Rogers
2026-09-23 18:12     ` [PATCH v3 44/49] perf python: Move and clean up exported-sql-viewer.py Ian Rogers
2026-09-23 18:12     ` [PATCH v3 45/49] perf python: Move and clean up parallel-perf.py Ian Rogers
2026-09-23 18:12     ` [PATCH v3 46/49] perf: Remove libpython support and legacy Python scripts Ian Rogers
2026-09-23 18:12     ` [PATCH v3 47/49] perf Makefile: Update Python script installation path Ian Rogers
2026-09-23 18:12     ` [PATCH v3 48/49] perf script: Support standalone scripts and remove embedded scripting Ian Rogers
2026-09-23 18:12     ` [PATCH v3 49/49] perf Documentation: Update for standalone Python scripts Ian Rogers

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=20260923181213.3032038-10-irogers@google.com \
    --to=irogers@google.com \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=alice.mei.rogers@gmail.com \
    --cc=dapeng1.mi@linux.intel.com \
    --cc=james.clark@linaro.org \
    --cc=leo.yan@linux.dev \
    --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=tmricht@linux.ibm.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®