[PATCH v1 21/58] perf python: Expose brstack in sample event

Ian Rogers
2026-04-20T00:00:10Z

Implement pyrf_branch_entry and pyrf_branch_stack for lazy
iteration over branch stack entries.

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

diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 1e393f354ea0..52970c78106f 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -19,6 +19,7 @@
#include "dso.h"
#include "dwarf-regs.h"
#include "event.h"
+#include "branch.h"
#include "evlist.h"
#include "evsel.h"
#include "expr.h"
@@ -866,6 +867,150 @@ static PyObject *pyrf_sample_event__get_callchain(PyObject *self, void */*closur
return (PyObject *)pchain;
}

+struct pyrf_branch_entry {
+ PyObject_HEAD
+ u64 from;
+ u64 to;
+ struct branch_flags flags;
+};
+
+static void pyrf_branch_entry__delete(struct pyrf_branch_entry *pentry)
+{
+ Py_TYPE(pentry)->tp_free((PyObject *)pentry);
+}
+
+static PyObject *pyrf_branch_entry__get_from(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyLong_FromUnsignedLongLong(pentry->from);
+}
+
+static PyObject *pyrf_branch_entry__get_to(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyLong_FromUnsignedLongLong(pentry->to);
+}
+
+static PyObject *pyrf_branch_entry__get_mispred(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyBool_FromLong(pentry->flags.mispred);
+}
+
+static PyObject *pyrf_branch_entry__get_predicted(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyBool_FromLong(pentry->flags.predicted);
+}
+
+static PyObject *pyrf_branch_entry__get_in_tx(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyBool_FromLong(pentry->flags.in_tx);
+}
+
+static PyObject *pyrf_branch_entry__get_abort(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyBool_FromLong(pentry->flags.abort);
+}
+
+static PyObject *pyrf_branch_entry__get_cycles(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyLong_FromUnsignedLongLong(pentry->flags.cycles);
+}
+
+static PyObject *pyrf_branch_entry__get_type(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyLong_FromLong(pentry->flags.type);
+}
+
+static PyGetSetDef pyrf_branch_entry__getset[] = {
+ { .name = "from", .get = (getter)pyrf_branch_entry__get_from, },
+ { .name = "to", .get = (getter)pyrf_branch_entry__get_to, },
+ { .name = "mispred", .get = (getter)pyrf_branch_entry__get_mispred, },
+ { .name = "predicted", .get = (getter)pyrf_branch_entry__get_predicted, },
+ { .name = "in_tx", .get = (getter)pyrf_branch_entry__get_in_tx, },
+ { .name = "abort", .get = (getter)pyrf_branch_entry__get_abort, },
+ { .name = "cycles", .get = (getter)pyrf_branch_entry__get_cycles, },
+ { .name = "type", .get = (getter)pyrf_branch_entry__get_type, },
+ { .name = NULL, },
+};
+
+static PyTypeObject pyrf_branch_entry__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.branch_entry",
+ .tp_basicsize = sizeof(struct pyrf_branch_entry),
+ .tp_dealloc = (destructor)pyrf_branch_entry__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = "perf branch entry object.",
+ .tp_getset = pyrf_branch_entry__getset,
+};
+
+struct pyrf_branch_stack {
+ PyObject_HEAD
+ struct pyrf_event *pevent;
+ u64 pos;
+};
+
+static void pyrf_branch_stack__delete(struct pyrf_branch_stack *pstack)
+{
+ Py_XDECREF(pstack->pevent);
+ Py_TYPE(pstack)->tp_free((PyObject *)pstack);
+}
+
+static PyObject *pyrf_branch_stack__next(struct pyrf_branch_stack *pstack)
+{
+ struct pyrf_branch_entry *pentry;
+ struct branch_stack *bs = pstack->pevent->sample.branch_stack;
+ struct branch_entry *entries = perf_sample__branch_entries(&pstack->pevent->sample);
+
+ if (!bs || !entries || pstack->pos >= bs->nr)
+ return NULL;
+
+ pentry = PyObject_New(struct pyrf_branch_entry, &pyrf_branch_entry__type);
+ if (!pentry)
+ return NULL;
+
+ pentry->from = entries[pstack->pos].from;
+ pentry->to = entries[pstack->pos].to;
+ pentry->flags = entries[pstack->pos].flags;
+
+ pstack->pos++;
+ return (PyObject *)pentry;
+}
+
+static PyTypeObject pyrf_branch_stack__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.branch_stack",
+ .tp_basicsize = sizeof(struct pyrf_branch_stack),
+ .tp_dealloc = (destructor)pyrf_branch_stack__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = "perf branch stack object.",
+ .tp_iter = PyObject_SelfIter,
+ .tp_iternext = (iternextfunc)pyrf_branch_stack__next,
+};
+
+static PyObject *pyrf_sample_event__get_brstack(PyObject *self, void *closure __maybe_unused)
+{
+ struct pyrf_event *pevent = (void *)self;
+ struct pyrf_branch_stack *pstack;
+
+ if (!pevent->sample.branch_stack)
+ Py_RETURN_NONE;
+
+ pstack = PyObject_New(struct pyrf_branch_stack, &pyrf_branch_stack__type);
+ if (!pstack)
+ return NULL;
+
+ Py_INCREF(pevent);
+ pstack->pevent = pevent;
+ pstack->pos = 0;
+ return (PyObject *)pstack;
+}
+
static PyObject*
pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name)
{
@@ -886,6 +1031,12 @@ static PyGetSetDef pyrf_sample_event__getset[] = {
.set = NULL,
.doc = "event callchain.",
},
+ {
+ .name = "brstack",
+ .get = pyrf_sample_event__get_brstack,
+ .set = NULL,
+ .doc = "event branch stack.",
+ },
{
.name = "raw_buf",
.get = (getter)pyrf_sample_event__get_raw_buf,
@@ -1066,6 +1217,12 @@ static int pyrf_event__setup_types(void)
err = PyType_Ready(&pyrf_callchain__type);
if (err < 0)
goto out;
+ err = PyType_Ready(&pyrf_branch_entry__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_branch_stack__type);
+ if (err < 0)
+ goto out;
out:
return err;
}
@@ -3434,6 +3591,12 @@ PyMODINIT_FUNC PyInit_perf(void)
Py_INCREF(&pyrf_session__type);
PyModule_AddObject(module, "session", (PyObject *)&pyrf_session__type);

+ Py_INCREF(&pyrf_branch_entry__type);
+ PyModule_AddObject(module, "branch_entry", (PyObject *)&pyrf_branch_entry__type);
+
+ Py_INCREF(&pyrf_branch_stack__type);
+ PyModule_AddObject(module, "branch_stack", (PyObject *)&pyrf_branch_stack__type);
+
dict = PyModule_GetDict(module);
if (dict == NULL)
goto error;
--
2.54.0.rc1.513.gad8abe7a5a-goog