From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dl1-f74.google.com (mail-dl1-f74.google.com [74.125.82.74]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 35F8C3DB625 for ; Fri, 12 Jun 2026 22:13:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.74 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781302386; cv=none; b=peHoIf/EW9m4TCFxR2i1kk/CX9nXK13wIu3DsoV8+PwyjhL+lmg7NQJ60tHb85oSL8+fWmUatmpfX0l58u51EBkzooRJDHOI1prHHY50g27h7DZX2/IjHlpdwlBEJ1mV7eicFSOSeVC7e0k08pUQepl97iMfABOBok3bzTGyPuE= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1781302386; c=relaxed/simple; bh=I30U5LKJx3Q+beBKDmxGt8nHgKeKIbsZt/wu75imOK4=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=Mj9TMmP8rf/XZv8HKU1HgsrqqMlZDQPuBlxZq7I2kn8EgMLPnOi8No2FSXlFlKxFuhxgpMF0Omwj8otbwtiJ4bOXL6xNZHDQZOIfe+nJMD24idpmaaZ1n2/qJ9CXSgxTK1X/aWs0EeYnHSTMKMd/gGfmMYsNc9wCimZNgo+8/Cs= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=JmF+S98g; arc=none smtp.client-ip=74.125.82.74 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--irogers.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="JmF+S98g" Received: by mail-dl1-f74.google.com with SMTP id a92af1059eb24-137fc6f8e9fso1510661c88.1 for ; Fri, 12 Jun 2026 15:13:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20251104; t=1781302383; x=1781907183; darn=vger.kernel.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=x7tglg++Gt0LQtKg/BdD0EdXD6gEWai0wOP/qb4d8Rs=; b=JmF+S98gOqHfatsTAcDQWCitVYTKLYjgv0726Uqz0KhSbGtC0EaAWuVurXFCd1GUo7 ey0vcIGZmxqFRw6xocUK9fzQoqxlYGkzUAwX/g/FFTdJuodkIDWluBE3S0fV4HrZZL8P /n3Km9xll/A9DCP9fEyi0eyZv9Y8KkeZNl+SPVdcgm32qOmjcSXpud47TOsFzI6YRoQQ fgvjouJTGVy5zuxgRu1fzDSjPWGIrt6OyOwKepJXRGwq97rbENGU1aSbUia2wKnj1KVm lk1KcRyO5KDFIZ1VTJgtzP+rRyyecPcUBwhoVvFSwJKI8UHg1iMyu+D/O306IlpGv6kU J/6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781302383; x=1781907183; 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=x7tglg++Gt0LQtKg/BdD0EdXD6gEWai0wOP/qb4d8Rs=; b=OhyJJDIVjm+zltBxAjjPMLRQxuEinNTaw+5ui29BfyQL0be/3Og6NytlLiRVEzq5B0 RyANa2xRnIcLJcI9UHCjWVPpAZN+hIUpgB/BvWY62gA5/bM7rfsamjKMC+gR5ozLMEGL D4/3YZtNZ3Vm/v8Q58WYLK05adEL7UrcKySoXq6I0ltKxEtmFpeVocnUVn5Q/Osxum6c 0z9CuXwpM1YLMVdj4wylXaX8+6GHJcXOQiBnv0JHta88K3XpzEGIjwbucH62tTwRR/L2 kG5kX1Wzpchdwr1h1SOZTCLuLsaXGtU6jnKafBXj7ECnilvdyRPs7EAP9C+tZadDfQEF xoUg== X-Forwarded-Encrypted: i=1; AFNElJ9+90jQCWLKQAEG6yF0+lkTaokOW567j2ef43WQ/bjhmlF/nr8zuIJJ6ZnphjPXXZGpkjfVrGLjDZc7dRUtKo1h@vger.kernel.org X-Gm-Message-State: AOJu0Yw2wSj9rjgcM7evr6w2xwBZHeKooa2XUGF0frcLDnpETrz1kFGU EnjWFmhLLgIpyPdn/RFtd6clYEYOL4e2FHHJEeQfLX4/rfFKByn4nOcAgrH06UfyagdBlXG2Pno /OimMbDeugQ== X-Received: from dyad22.prod.google.com ([2002:a05:693c:8356:b0:304:e192:a836]) (user=irogers job=prod-delivery.src-stubby-dispatcher) by 2002:a05:7300:6407:b0:2d9:f0b3:1d98 with SMTP id 5a478bee46e88-3081ff3ddc7mr3242601eec.7.1781302382880; Fri, 12 Jun 2026 15:13:02 -0700 (PDT) Date: Fri, 12 Jun 2026 15:12:06 -0700 In-Reply-To: <20260612221211.1221305-1-irogers@google.com> Precedence: bulk X-Mailing-List: linux-perf-users@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260612180956.1105352-1-irogers@google.com> <20260612221211.1221305-1-irogers@google.com> X-Mailer: git-send-email 2.54.0.1136.gdb2ca164c4-goog Message-ID: <20260612221211.1221305-15-irogers@google.com> Subject: [PATCH v15 14/19] perf python: Extend API for stat events in python.c From: Ian Rogers To: irogers@google.com, acme@kernel.org, namhyung@kernel.org Cc: adrian.hunter@intel.com, alice.mei.rogers@gmail.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 Content-Type: text/plain; charset="UTF-8" Add stat information to the session. Add call backs for stat events. Assisted-by: Gemini:gemini-3.1-pro-preview Signed-off-by: Ian Rogers --- v12: - Made `psession->tool.stat = perf_event__process_stat_event;` conditional on `!stat` so it doesn't unconditionally overwrite user stat callbacks. v5: 1. Fix Memory Corruption: Corrected the memory offset for `stat_round_type` in `pyrf_stat_round_event__members` by adding the base offset of `struct pyrf_event`. 2. Fix Memory Leak: Added `Py_XDECREF()` to free the unused return value of the Python callback in `pyrf_session_tool__stat_round()`. --- v7: - Added comprehensive size checks in pyrf_event__new for all event types. - Fixed line length warning. v10: - Added CHECK_INITIALIZED check to pyrf_evsel__get_ids to prevent NULL pointer dereference on uninitialized evsel objects. --- tools/perf/util/python.c | 188 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 183 insertions(+), 5 deletions(-) diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index ce1e32e91919..ff8b893af2ba 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -459,6 +459,77 @@ static PyTypeObject pyrf_lost_event__type = { .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[] = { + sample_members + member_def(perf_event_header, type, T_UINT, "event type"), + member_def(perf_record_stat, id, T_ULONGLONG, "event id"), + member_def(perf_record_stat, cpu, T_UINT, "event cpu"), + member_def(perf_record_stat, thread, T_UINT, "event thread"), + member_def(perf_record_stat, val, T_ULONGLONG, "counter value"), + member_def(perf_record_stat, ena, T_ULONGLONG, "enabled time"), + member_def(perf_record_stat, run, T_ULONGLONG, "running time"), + { .name = NULL, }, +}; + +static PyObject *pyrf_stat_event__repr(const struct pyrf_event *pevent) +{ + 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); +} + +static PyTypeObject pyrf_stat_event__type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "perf.stat_event", + .tp_basicsize = sizeof(struct pyrf_event), + .tp_new = PyType_GenericNew, + .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_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[] = { + 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" }, + 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) +{ + return PyUnicode_FromFormat("{ type: stat_round, type: %llu, time: %llu }", + pevent->event.stat_round.type, + pevent->event.stat_round.time); +} + +static PyTypeObject pyrf_stat_round_event__type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "perf.stat_round_event", + .tp_basicsize = sizeof(struct pyrf_event), + .tp_new = PyType_GenericNew, + .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_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[] = { @@ -1113,6 +1184,12 @@ static int pyrf_event__setup_types(void) if (err < 0) goto out; err = PyType_Ready(&pyrf_context_switch_event__type); + if (err < 0) + goto out; + err = PyType_Ready(&pyrf_stat_event__type); + if (err < 0) + goto out; + err = PyType_Ready(&pyrf_stat_round_event__type); if (err < 0) goto out; err = PyType_Ready(&pyrf_callchain_node__type); @@ -1138,6 +1215,8 @@ static PyTypeObject *pyrf_event__type[] = { [PERF_RECORD_SAMPLE] = &pyrf_sample_event__type, [PERF_RECORD_SWITCH] = &pyrf_context_switch_event__type, [PERF_RECORD_SWITCH_CPU_WIDE] = &pyrf_context_switch_event__type, + [PERF_RECORD_STAT] = &pyrf_stat_event__type, + [PERF_RECORD_STAT_ROUND] = &pyrf_stat_round_event__type, }; static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *evsel, @@ -2123,7 +2202,45 @@ static PyObject *pyrf_evsel__get_attr_wakeup_events(PyObject *self, void *closur return PyLong_FromUnsignedLong(pevsel->evsel->core.attr.wakeup_events); } +static PyObject *pyrf_evsel__get_ids(struct pyrf_evsel *pevsel, void *closure __maybe_unused) +{ + struct evsel *evsel; + PyObject *list; + + CHECK_INITIALIZED(pevsel->evsel, "evsel"); + + evsel = pevsel->evsel; + list = PyList_New(0); + + if (!list) + return NULL; + + for (u32 i = 0; i < evsel->core.ids; i++) { + PyObject *id = PyLong_FromUnsignedLongLong(evsel->core.id[i]); + int ret; + + if (!id) { + Py_DECREF(list); + return NULL; + } + ret = PyList_Append(list, id); + Py_DECREF(id); + if (ret < 0) { + Py_DECREF(list); + return NULL; + } + } + + return list; +} + static PyGetSetDef pyrf_evsel__getset[] = { + { + .name = "ids", + .get = (getter)pyrf_evsel__get_ids, + .set = NULL, + .doc = "event IDs.", + }, { .name = "tracking", .get = pyrf_evsel__get_tracking, @@ -2971,6 +3088,8 @@ static const struct perf_constant perf__constants[] = { PERF_CONST(RECORD_LOST_SAMPLES), PERF_CONST(RECORD_SWITCH), PERF_CONST(RECORD_SWITCH_CPU_WIDE), + PERF_CONST(RECORD_STAT), + PERF_CONST(RECORD_STAT_ROUND), PERF_CONST(RECORD_MISC_SWITCH_OUT), { .name = NULL, }, @@ -3366,6 +3485,7 @@ struct pyrf_session { struct perf_tool tool; struct pyrf_data *pdata; PyObject *sample; + PyObject *stat; }; static int pyrf_session_tool__sample(const struct perf_tool *tool, @@ -3390,6 +3510,50 @@ static int pyrf_session_tool__sample(const struct perf_tool *tool, return 0; } +static int pyrf_session_tool__stat(const struct perf_tool *tool, + struct perf_session *session, + union perf_event *event) +{ + 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, psession->session, /*machine=*/NULL); + const char *name = evsel ? evsel__name(evsel) : "unknown"; + PyObject *ret; + + if (pyevent == NULL) + return -ENOMEM; + + ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, name); + if (!ret) { + Py_DECREF(pyevent); + return -1; + } + Py_DECREF(ret); + Py_DECREF(pyevent); + return 0; +} + +static int pyrf_session_tool__stat_round(const struct perf_tool *tool, + struct perf_session *session __maybe_unused, + union perf_event *event) +{ + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool); + PyObject *pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session, /*machine=*/NULL); + PyObject *ret; + + if (pyevent == NULL) + return -ENOMEM; + + ret = PyObject_CallFunction(psession->stat, "Oz", pyevent, NULL); + if (!ret) { + Py_DECREF(pyevent); + return -1; + } + Py_DECREF(ret); + Py_DECREF(pyevent); + return 0; +} + static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObject *args) { struct machine *machine; @@ -3423,13 +3587,13 @@ static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObje static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { struct pyrf_data *pdata; - PyObject *sample = NULL; - static char *kwlist[] = { "data", "sample", NULL }; + PyObject *sample = NULL, *stat = NULL; + static char *kwlist[] = { "data", "sample", "stat", NULL }; struct pyrf_session *psession; struct perf_session *session; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|O", kwlist, &pyrf_data__type, &pdata, - &sample)) + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|OO", kwlist, &pyrf_data__type, &pdata, + &sample, &stat)) return NULL; psession = PyObject_New(struct pyrf_session, type); @@ -3438,6 +3602,7 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject psession->session = NULL; psession->sample = NULL; + psession->stat = NULL; psession->pdata = NULL; Py_INCREF(pdata); @@ -3460,8 +3625,13 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject } while (0) ADD_TOOL(sample); + ADD_TOOL(stat); #undef ADD_TOOL + if (stat) + psession->tool.stat_round = pyrf_session_tool__stat_round; + + psession->tool.comm = perf_event__process_comm; psession->tool.mmap = perf_event__process_mmap; psession->tool.mmap2 = perf_event__process_mmap2; @@ -3474,7 +3644,8 @@ static PyObject *pyrf_session__new(PyTypeObject *type, PyObject *args, PyObject psession->tool.build_id = perf_event__process_build_id; psession->tool.attr = perf_event__process_attr; psession->tool.feature = perf_event__process_feature; - psession->tool.stat = perf_event__process_stat_event; + if (!stat) + psession->tool.stat = perf_event__process_stat_event; session = perf_session__new(&pdata->data, &psession->tool); if (IS_ERR(session)) { PyErr_Format(PyExc_IOError, "failed to create session: %ld", PTR_ERR(session)); @@ -3504,6 +3675,7 @@ static void pyrf_session__delete(struct pyrf_session *psession) perf_session__delete(psession->session); Py_XDECREF(psession->pdata); Py_XDECREF(psession->sample); + Py_XDECREF(psession->stat); Py_TYPE(psession)->tp_free((PyObject *)psession); } @@ -3691,6 +3863,12 @@ PyMODINIT_FUNC PyInit_perf(void) Py_INCREF(&pyrf_context_switch_event__type); PyModule_AddObject(module, "switch_event", (PyObject *)&pyrf_context_switch_event__type); + Py_INCREF(&pyrf_stat_event__type); + PyModule_AddObject(module, "stat_event", (PyObject *)&pyrf_stat_event__type); + + Py_INCREF(&pyrf_stat_round_event__type); + PyModule_AddObject(module, "stat_round_event", (PyObject *)&pyrf_stat_round_event__type); + Py_INCREF(&pyrf_thread_map__type); PyModule_AddObject(module, "thread_map", (PyObject*)&pyrf_thread_map__type); -- 2.54.0.1136.gdb2ca164c4-goog