From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 1CD5AFF885C for ; Sat, 25 Apr 2026 22:43:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender:List-Subscribe:List-Help :List-Post:List-Archive:List-Unsubscribe:List-Id:Content-Type:Cc:To:From: Subject:Message-ID:References:Mime-Version:In-Reply-To:Date:Reply-To: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=FZwlD8Dg9RxeiBkuuR5VQDv1GiXEHsJfT+1oBNmSxHU=; b=0fkozShxCIbEcvdcf9AOEskhOE eLm2FknA8TKwe0gIbpk0/KDF5oobCvFef5JtQNt/v3IZz5hRT7mAO+onItrTN2N4kJpD7Jwk/G4KZ pebrIpSGE092gZDweKiFADlRfZVEkNJb1gSWuKgAef2htjYnAK3M4j9+eZDhxVjQalLv9umRLME5X LsaeOnQHNXY1L/DRbvASnidzz0FPraQXKbHw2zk2Ti0RKd1vSCecJ8+QtgEjItg7boDER0zOuv//z 962qLaW08yyLFGLAKRrAHJTjYepv+NP/NVdVSXXDEd8Kvr3jnukwdvbcmVXLJaSiRoXO8pM1prOFO MwUwD3uA==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.98.2 #2 (Red Hat Linux)) id 1wGli1-0000000Etu3-3ZO5; Sat, 25 Apr 2026 22:42:45 +0000 Received: from mail-dl1-x1249.google.com ([2607:f8b0:4864:20::1249]) by bombadil.infradead.org with esmtps (Exim 4.98.2 #2 (Red Hat Linux)) id 1wGlhY-0000000EtJI-1ENU for linux-arm-kernel@lists.infradead.org; Sat, 25 Apr 2026 22:42:17 +0000 Received: by mail-dl1-x1249.google.com with SMTP id a92af1059eb24-12c8d7d4a79so7467700c88.1 for ; Sat, 25 Apr 2026 15:42:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1777156935; x=1777761735; darn=lists.infradead.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=FZwlD8Dg9RxeiBkuuR5VQDv1GiXEHsJfT+1oBNmSxHU=; b=aMaK+A1cjbUYZr+COcjzCoLNXCPWlJnqf0ytyHk1bcub2gYhCxx6Uc653lpCwPLnzE Cw1+rpTX8B5CsRadlvp2yqkkxU8Mozcf9Pbs9DKoOWMp7NYfSnrvne4kXylfcDpz6dTY Mg4gRuDJjr5kLXGd7BwAU3+cS4uFAt+Bb2+alP14jzi/44PXA1xZOrEbalfEtV4HFEFm V2/6Y7ouRHzWYFflQOuinLuub4zxQ6DJhMkxdMTDCXMF2zJtOKD9RNB10b2P/45LThLv mYC4tJfP4gOuSGMfRkASNIYsIzCmjPcGrwDspeUwgMQmpSW1nzirKeDLFU8Xka4dkO4m IDoQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777156935; x=1777761735; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=FZwlD8Dg9RxeiBkuuR5VQDv1GiXEHsJfT+1oBNmSxHU=; b=lFnG0HZFpXj1XhUpozWnzj52+w0Y547+L4x/+bmDZ5qoFLuO4ymfBGk/ZzHgIj3JxN i9CtVIiUrQNwz54gCNoENygdoyrC+tpWpugYUt4QBKnfF9pDHN7uEeNKrfAWn0GsYo3o Veez6dDHMHAgcg50JyFahpz3bMi3fqnNkq3B6c8NKDwv2jkvN8g3+8gy32NRPZ5yRtaV t/b6Ch6bINhsmW/2v+Om+PW//ksFpmSQVEyLa77b+twJjWdx8ObKGz9tHxy4y9Ly5Jgy GbYDUdmi6TfLrUdQ+/MXZHtODRdYcmWQv6uaS43W0clG0fLiBWjkl6D1nv7uNl2y3qoK xTWg== X-Forwarded-Encrypted: i=1; AFNElJ+VtdJjE28Hl/wsfs6Bw6duDFsK2Fkvfy7Oaja5IHyunm/p/5i7Mfr4m+eRBxGq9ycg1zK1IvnbDI2rWCOqjbpc@lists.infradead.org X-Gm-Message-State: AOJu0YxgHzLg9zxMXBDeyZltHuW0FW3aoeIrfmxl/7gjfITO69RqV719 tB6yH4CYM5qgp0D57OEXPR8xbpkT5fQIj6Q+E9fL5xYzG1U6xfMw1Q+kb2wBKQ1q5+pKGOg7aL/ EUXRpWULUWg== X-Received: from dlbuu4.prod.google.com ([2002:a05:7022:7e84:b0:12d:bafb:54f6]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7022:6882:b0:11b:923d:7753 with SMTP id a92af1059eb24-12c73f6d69dmr18327008c88.3.1777156934480; Sat, 25 Apr 2026 15:42:14 -0700 (PDT) Date: Sat, 25 Apr 2026 15:40:46 -0700 In-Reply-To: <20260425224125.160890-1-irogers@google.com> Mime-Version: 1.0 References: <20260425174858.3922152-1-irogers@google.com> <20260425224125.160890-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.545.g6539524ca2-goog Message-ID: <20260425224125.160890-22-irogers@google.com> Subject: [PATCH v7 21/59] perf python: Expose brstack in sample event From: Ian Rogers To: acme@kernel.org, adrian.hunter@intel.com, james.clark@linaro.org, leo.yan@linux.dev, namhyung@kernel.org, tmricht@linux.ibm.com Cc: alice.mei.rogers@gmail.com, dapeng1.mi@linux.intel.com, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org, mingo@redhat.com, peterz@infradead.org, Ian Rogers Content-Type: text/plain; charset="UTF-8" X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20260425_154216_434764_7B9DE714 X-CRM114-Status: GOOD ( 20.77 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org 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 --- v2: 1. Avoided Keyword Collision: Renamed the properties "from" and "to" to "from_ip" and "to_ip" in pyrf_branch_entry__getset[] to prevent syntax errors in Python. 2. Eager Branch Stack Resolution: Updated pyrf_session_tool__sample() to allocate and copy the branch stack entries eagerly when the event is processed, instead of deferring it to iteration time. This avoids reading from potentially overwritten or unmapped mmap buffers. 3. Updated Iterators: Updated pyrf_branch_stack and pyrf_branch_stack__next() to use the copied entries rather than pointing directly to the sample's buffer. 4. Avoided Reference Leak on Init Failure: Added proper error checking for PyModule_AddObject() in the module initialization function, decrementing references on failure. v6: - Moved branch stack resolution from `session_tool__sample` to `pyrf_event__new`. --- tools/perf/util/python.c | 188 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index 17d0ee15336f..256129fef4f8 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" @@ -69,6 +70,8 @@ struct pyrf_event { bool al_resolved; /** @callchain: Resolved callchain, eagerly computed if requested. */ 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; }; @@ -107,6 +110,7 @@ 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); Py_TYPE(pevent)->tp_free((PyObject *)pevent); } @@ -871,6 +875,144 @@ static PyObject *pyrf_sample_event__get_callchain(PyObject *self, void *closure return pevent->callchain; } +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_ip", .get = (getter)pyrf_branch_entry__get_from, }, + { .name = "to_ip", .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; + struct branch_entry *entries; + u64 nr; + u64 pos; +}; + +static void pyrf_branch_stack__delete(struct pyrf_branch_stack *pstack) +{ + Py_XDECREF(pstack->pevent); + free(pstack->entries); + Py_TYPE(pstack)->tp_free((PyObject *)pstack); +} + +static PyObject *pyrf_branch_stack__next(struct pyrf_branch_stack *pstack) +{ + struct pyrf_branch_entry *pentry; + + if (pstack->pos >= pstack->nr) + return NULL; + + pentry = PyObject_New(struct pyrf_branch_entry, &pyrf_branch_entry__type); + if (!pentry) + return NULL; + + pentry->from = pstack->entries[pstack->pos].from; + pentry->to = pstack->entries[pstack->pos].to; + pentry->flags = pstack->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; + + if (!pevent->brstack) + Py_RETURN_NONE; + + Py_INCREF(pevent->brstack); + return pevent->brstack; +} + static PyObject* pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name) { @@ -891,6 +1033,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, @@ -1071,6 +1219,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; } @@ -1251,6 +1405,7 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev memcpy(&pevent->event, event, event->header.size); perf_sample__init(&pevent->sample, /*all=*/true); pevent->callchain = NULL; + pevent->brstack = NULL; pevent->al_resolved = false; addr_location__init(&pevent->al); @@ -1307,6 +1462,27 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev addr_location__exit(&al); } } + if (sample->branch_stack) { + struct branch_stack *bs = sample->branch_stack; + struct branch_entry *entries = perf_sample__branch_entries(sample); + struct pyrf_branch_stack *pstack; + + pstack = PyObject_New(struct pyrf_branch_stack, &pyrf_branch_stack__type); + if (pstack) { + Py_INCREF(pevent); + pstack->pevent = pevent; + pstack->pos = 0; + pstack->nr = bs->nr; + pstack->entries = calloc(bs->nr, sizeof(struct branch_entry)); + if (pstack->entries) { + memcpy(pstack->entries, entries, + bs->nr * sizeof(struct branch_entry)); + pevent->brstack = (PyObject *)pstack; + } else { + Py_DECREF(pstack); + } + } + } return (PyObject *)pevent; } @@ -3708,6 +3884,18 @@ PyMODINIT_FUNC PyInit_perf(void) Py_INCREF(&pyrf_session__type); PyModule_AddObject(module, "session", (PyObject *)&pyrf_session__type); + Py_INCREF(&pyrf_branch_entry__type); + if (PyModule_AddObject(module, "branch_entry", (PyObject *)&pyrf_branch_entry__type) < 0) { + Py_DECREF(&pyrf_branch_entry__type); + goto error; + } + + Py_INCREF(&pyrf_branch_stack__type); + if (PyModule_AddObject(module, "branch_stack", (PyObject *)&pyrf_branch_stack__type) < 0) { + Py_DECREF(&pyrf_branch_stack__type); + goto error; + } + dict = PyModule_GetDict(module); if (dict == NULL) goto error; -- 2.54.0.545.g6539524ca2-goog