[PATCH] perf python: Set up callchain parameters for sessions
From: Lalit Maganti
Date: Sun Oct 18 2026 - 04:15:07 EST
perf.session never calls callchain_param_setup(), so callchains are
resolved with callchain_param.record_mode left as CALLCHAIN_NONE rather
than set from the sample type as in perf script and perf report.
On arm64 this loses frames: when a sample hits a function before it has
pushed its frame record, its caller is only in the link register, and
recovering it needs record_mode CALLCHAIN_FP. Comparing the callchain of
every sample with perf script, on a frame pointer recording of a
two-thread workload on arm64, 4 of 4494 samples miss a frame, e.g.:
perf script: clock_gettime@plt <- now <- spin <- other_path
perf.session: clock_gettime@plt <- spin <- other_path
Call callchain_param_setup() once the session is created and, as perf
script does, again as attrs are processed, for pipe mode. With that,
all callchains match perf script on arm64, for frame pointer and DWARF
recordings, from a file or a pipe. On x86 they matched before and still
do.
Assisted-by: LLM
Signed-off-by: Lalit Maganti <lalitmaganti@xxxxxxxxx>
---
tools/perf/util/python.c | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 60dbe2dad5eb..92d18e24049d 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -4842,6 +4842,24 @@ static int __pyrf_session_tool__stat_round(const struct perf_tool *tool,
*/
static struct pyrf_session *pyrf_session__active;
+/*
+ * In pipe mode attrs are only known once processed, so set up callchains again,
+ * as perf script does.
+ */
+static int pyrf_session__process_attr(const struct perf_tool *tool,
+ union perf_event *event,
+ struct evlist **pevlist)
+{
+ int err = perf_event__process_attr(tool, event, pevlist);
+
+ if (err)
+ return err;
+ callchain_param_setup(evlist__combined_sample_type(*pevlist),
+ perf_session__e_machine(evsel__session(evlist__last(*pevlist)),
+ /*e_flags=*/NULL));
+ return 0;
+}
+
static bool pyrf_session__check_not_busy(struct pyrf_session *psession)
{
if (pyrf_session__active == psession &&
@@ -5061,7 +5079,7 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject
psession->tool.ksymbol = perf_event__process_ksymbol;
psession->tool.text_poke = perf_event__process_text_poke;
psession->tool.build_id = perf_event__process_build_id;
- psession->tool.attr = perf_event__process_attr;
+ psession->tool.attr = pyrf_session__process_attr;
psession->tool.event_update = perf_event__process_event_update;
psession->tool.feature = perf_event__process_feature;
#ifdef HAVE_LIBTRACEEVENT
@@ -5139,6 +5157,8 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject
PyErr_SetString(PyExc_OSError, "perf: symbol__init failed");
goto err_out;
}
+ callchain_param_setup(evlist__combined_sample_type(session->evlist),
+ perf_session__e_machine(session, /*e_flags=*/NULL));
if (psession->symfs && !symbol_conf.symfs[0])
zfree(&psession->symfs);
base-commit: 1c90f0a3c8a1c4e38f9edd97af9e20848805a7fb
--
2.56.0