* [PATCH v10 00/11] New perf ilist app
@ 2025-08-19 1:39 Ian Rogers
2025-08-19 1:39 ` [PATCH v10 01/11] perf python: Add more exceptions on error paths Ian Rogers
` (11 more replies)
0 siblings, 12 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
This patch series adds a new ilist app written in python using textual
[1] for the UI. The app presents perf PMUs and events, displays the
event information as in `perf list` while at the bottom of the console
showing recent activity of the event in total and across all CPUs. It
also displays metrics, placed in a tree through their metric group,
again with counts being displayed in the bottom panel.
To run it you need the updated perf.cpython.so in your PYTHONPATH and
then execute the script. Expanding PMUs and then selecting events will
cause event informatin to be displayed in the top-right and the
counters values to be displayed as sparklines and counts in the bottom
half of the screen.
There's been feedback on how the app works, simplicity of
implementation has been chosen as the first criteria as the app can be
further refined from what is here. The choice of the name ilist rather
than say istat was deliberate as I wanted the app to encourage PMU,
event and metric discovery, as with perf list. The output counts and
spark lines are just to give an indication of what the event
gathers. ilist comes from interactive list, there's probably a better
name.
[1] https://textual.textualize.io/
v10: Add Howard's reviewed-by and address documentation fix. Rebase
and drop build up patches merged in v6.17 by Namhyung.
v9: sys metric support and pep8 clean up suggested by Xu Yang
<xu.yang_2@nxp.com>.
v8: nit fixing of issues caught by Arnaldo and Namhyung. Add Arnaldo's
tested-by. Fail to repro issue reported by Thomas Falcon but
encounter textual rendering and DOM query race, add an exception
handling path to avoid the race being fatal. The change is minor
in patch 16, so Arnaldo's tested-by is kept.
v7: Better handle errors in the python code and ignore errors when
scanning PMU/events in ilist.py, improving the behavior when not
root. Add a tp_pmu/python clean up. Minor kernel coding style
clean up. Fix behavior of ilist if a search result isn't found but
then next is chosen.
v6: For metrics on hybrid systems don't purely match by name, also
match the CPU and thread so that if the same metric exists for
different PMUs the appropriate one is selected and counters may be
read. Likewise use evsel maps and not the evlists.
v5: Split the series in two. Add metric support. Various clean ups and
tweaks to the app in particular around the handling of searches.
v4: No conflict rebase. Picks up perf-tools-next DRM PMU which
displays as expected.
v3: Add a search dialog to the ilist app with 'n'ext and 'p'revious
keys. No changes in the ground work first 14 patches.
v2: In the jevents event description duplication, some minor changes
accidentally missed from v1 meaning that in v1 the descriptions
were still duplicated. Expand the cover letter with some thoughts
on the series.
Ian Rogers (11):
perf python: Add more exceptions on error paths
perf python: Improve the tracepoint function if no libtraceevent
perf python: Add basic PMU abstraction and pmus sequence
perf python: Add function returning dictionary of all events on a PMU
perf ilist: Add new python ilist command
perf python: Add parse_metrics function
perf python: Add evlist metrics function
perf python: Add evlist compute_metric
perf python: Add metrics function
perf ilist: Add support for metrics
perf tp_pmu: Remove unnecessary check
tools/perf/python/ilist.py | 495 +++++++++++++++++++++++++++++++++++
tools/perf/util/python.c | 522 ++++++++++++++++++++++++++++++++++++-
tools/perf/util/tp_pmu.c | 2 -
3 files changed, 1004 insertions(+), 15 deletions(-)
create mode 100755 tools/perf/python/ilist.py
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v10 01/11] perf python: Add more exceptions on error paths
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 02/11] perf python: Improve the tracepoint function if no libtraceevent Ian Rogers
` (10 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
Returning NULL will cause the python interpreter to fail but not
report an error. If none wants to be returned then Py_None needs
returning. Set the error for the cases returning NULL so that more
meaningful interpreter behavior is had.
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/util/python.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index ea77bea0306f..d47cbc1c2257 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -485,13 +485,19 @@ static PyObject *pyrf_event__new(const union perf_event *event)
if ((event->header.type < PERF_RECORD_MMAP ||
event->header.type > PERF_RECORD_SAMPLE) &&
!(event->header.type == PERF_RECORD_SWITCH ||
- event->header.type == PERF_RECORD_SWITCH_CPU_WIDE))
+ event->header.type == PERF_RECORD_SWITCH_CPU_WIDE)) {
+ PyErr_Format(PyExc_TypeError, "Unexpected header type %u",
+ event->header.type);
return NULL;
+ }
// FIXME this better be dynamic or we need to parse everything
// before calling perf_mmap__consume(), including tracepoint fields.
- if (sizeof(pevent->event) < event->header.size)
+ if (sizeof(pevent->event) < event->header.size) {
+ PyErr_Format(PyExc_TypeError, "Unexpected event size: %zd < %u",
+ sizeof(pevent->event), event->header.size);
return NULL;
+ }
ptype = pyrf_event__type[event->header.type];
pevent = PyObject_New(struct pyrf_event, ptype);
@@ -1209,8 +1215,10 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist,
return NULL;
md = get_md(evlist, cpu);
- if (!md)
+ if (!md) {
+ PyErr_Format(PyExc_TypeError, "Unknown CPU '%d'", cpu);
return NULL;
+ }
if (perf_mmap__read_init(&md->core) < 0)
goto end;
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 02/11] perf python: Improve the tracepoint function if no libtraceevent
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
2025-08-19 1:39 ` [PATCH v10 01/11] perf python: Add more exceptions on error paths Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 03/11] perf python: Add basic PMU abstraction and pmus sequence Ian Rogers
` (9 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
The tracepoint function just returns the tracepoint id, this doesn't
require libtraceevent which is only used for parsing the event format
data. Implement the function using the id function in tp_pmu. No
current code in perf is using this, the previous code migrated to
perf.parse_events, but it feels good to have less ifdef
HAVE_LIBTRACEEVENT.
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/util/python.c | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index d47cbc1c2257..127934af4828 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -18,6 +18,7 @@
#include "record.h"
#include "strbuf.h"
#include "thread_map.h"
+#include "tp_pmu.h"
#include "trace-event.h"
#include "metricgroup.h"
#include "mmap.h"
@@ -1554,10 +1555,6 @@ static const struct perf_constant perf__constants[] = {
static PyObject *pyrf__tracepoint(struct pyrf_evsel *pevsel,
PyObject *args, PyObject *kwargs)
{
-#ifndef HAVE_LIBTRACEEVENT
- return NULL;
-#else
- struct tep_event *tp_format;
static char *kwlist[] = { "sys", "name", NULL };
char *sys = NULL;
char *name = NULL;
@@ -1566,12 +1563,7 @@ static PyObject *pyrf__tracepoint(struct pyrf_evsel *pevsel,
&sys, &name))
return NULL;
- tp_format = trace_event__tp_format(sys, name);
- if (IS_ERR(tp_format))
- return PyLong_FromLong(-1);
-
- return PyLong_FromLong(tp_format->id);
-#endif // HAVE_LIBTRACEEVENT
+ return PyLong_FromLong(tp_pmu__id(sys, name));
}
static PyObject *pyrf_evsel__from_evsel(struct evsel *evsel)
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 03/11] perf python: Add basic PMU abstraction and pmus sequence
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
2025-08-19 1:39 ` [PATCH v10 01/11] perf python: Add more exceptions on error paths Ian Rogers
2025-08-19 1:39 ` [PATCH v10 02/11] perf python: Improve the tracepoint function if no libtraceevent Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 04/11] perf python: Add function returning dictionary of all events on a PMU Ian Rogers
` (8 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
Add an ability to iterate over PMUs and a basic PMU type then can just
show the PMU's name.
An example usage:
```
$ python
Python 3.12.9 (main, Feb 5 2025, 01:31:18) [GCC 14.2.0] on linux
>>> import perf
>>> list(perf.pmus())
[pmu(cpu), pmu(breakpoint), pmu(cstate_core), pmu(cstate_pkg),
pmu(hwmon_acpitz), pmu(hwmon_ac), pmu(hwmon_bat0),
pmu(hwmon_coretemp), pmu(hwmon_iwlwifi_1), pmu(hwmon_nvme),
pmu(hwmon_thinkpad), pmu(hwmon_ucsi_source_psy_usbc000_0),
pmu(hwmon_ucsi_source_psy_usbc000_0), pmu(i915), pmu(intel_bts),
pmu(intel_pt), pmu(kprobe), pmu(msr), pmu(power), pmu(software),
pmu(tool), pmu(tracepoint), pmu(uncore_arb), pmu(uncore_cbox_0),
pmu(uncore_cbox_1), pmu(uncore_cbox_2), pmu(uncore_cbox_3),
pmu(uncore_cbox_4), pmu(uncore_cbox_5), pmu(uncore_cbox_6),
pmu(uncore_cbox_7), pmu(uncore_clock), pmu(uncore_imc_free_running_0),
pmu(uncore_imc_free_running_1), pmu(uprobe)]
```
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/util/python.c | 140 +++++++++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 127934af4828..6f9728d365ae 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -649,6 +649,138 @@ static int pyrf_thread_map__setup_types(void)
return PyType_Ready(&pyrf_thread_map__type);
}
+/**
+ * A python wrapper for perf_pmus that are globally owned by the pmus.c code.
+ */
+struct pyrf_pmu {
+ PyObject_HEAD
+
+ struct perf_pmu *pmu;
+};
+
+static void pyrf_pmu__delete(struct pyrf_pmu *ppmu)
+{
+ Py_TYPE(ppmu)->tp_free((PyObject *)ppmu);
+}
+
+static PyObject *pyrf_pmu__name(PyObject *self)
+{
+ struct pyrf_pmu *ppmu = (void *)self;
+
+ return PyUnicode_FromString(ppmu->pmu->name);
+}
+
+static PyObject *pyrf_pmu__repr(PyObject *self)
+{
+ struct pyrf_pmu *ppmu = (void *)self;
+
+ return PyUnicode_FromFormat("pmu(%s)", ppmu->pmu->name);
+}
+
+static const char pyrf_pmu__doc[] = PyDoc_STR("perf Performance Monitoring Unit (PMU) object.");
+
+static PyMethodDef pyrf_pmu__methods[] = {
+ {
+ .ml_name = "name",
+ .ml_meth = (PyCFunction)pyrf_pmu__name,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = PyDoc_STR("Name of the PMU including suffixes.")
+ },
+ { .ml_name = NULL, }
+};
+
+/** The python type for a perf.pmu. */
+static PyTypeObject pyrf_pmu__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.pmu",
+ .tp_basicsize = sizeof(struct pyrf_pmu),
+ .tp_dealloc = (destructor)pyrf_pmu__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_pmu__doc,
+ .tp_methods = pyrf_pmu__methods,
+ .tp_str = pyrf_pmu__name,
+ .tp_repr = pyrf_pmu__repr,
+};
+
+static int pyrf_pmu__setup_types(void)
+{
+ pyrf_pmu__type.tp_new = PyType_GenericNew;
+ 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
+ struct perf_pmu *pmu;
+};
+
+static void pyrf_pmu_iterator__dealloc(struct pyrf_pmu_iterator *self)
+{
+ Py_TYPE(self)->tp_free((PyObject *) self);
+}
+
+static PyObject *pyrf_pmu_iterator__new(PyTypeObject *type, PyObject *args __maybe_unused,
+ PyObject *kwds __maybe_unused)
+{
+ struct pyrf_pmu_iterator *itr = (void *)type->tp_alloc(type, 0);
+
+ if (itr != NULL)
+ itr->pmu = perf_pmus__scan(/*pmu=*/NULL);
+
+ return (PyObject *) itr;
+}
+
+static PyObject *pyrf_pmu_iterator__iter(PyObject *self)
+{
+ Py_INCREF(self);
+ return self;
+}
+
+static PyObject *pyrf_pmu_iterator__iternext(PyObject *self)
+{
+ struct pyrf_pmu_iterator *itr = (void *)self;
+ struct pyrf_pmu *ppmu;
+
+ if (itr->pmu == NULL) {
+ PyErr_SetNone(PyExc_StopIteration);
+ return NULL;
+ }
+ // Create object to return.
+ ppmu = PyObject_New(struct pyrf_pmu, &pyrf_pmu__type);
+ if (ppmu) {
+ ppmu->pmu = itr->pmu;
+ // Advance iterator.
+ itr->pmu = perf_pmus__scan(itr->pmu);
+ }
+ return (PyObject *)ppmu;
+}
+
+/** The python type for the PMU iterator. */
+static PyTypeObject pyrf_pmu_iterator__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "pmus.iterator",
+ .tp_doc = "Iterator for the pmus string sequence.",
+ .tp_basicsize = sizeof(struct pyrf_pmu_iterator),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = pyrf_pmu_iterator__new,
+ .tp_dealloc = (destructor) pyrf_pmu_iterator__dealloc,
+ .tp_iter = pyrf_pmu_iterator__iter,
+ .tp_iternext = pyrf_pmu_iterator__iternext,
+};
+
+static int pyrf_pmu_iterator__setup_types(void)
+{
+ return PyType_Ready(&pyrf_pmu_iterator__type);
+}
+
+static PyObject *pyrf__pmus(PyObject *self, PyObject *args)
+{
+ // Calling the class creates an instance of the iterator.
+ return PyObject_CallObject((PyObject *) &pyrf_pmu_iterator__type, /*args=*/NULL);
+}
+
struct pyrf_counts_values {
PyObject_HEAD
@@ -1701,6 +1833,12 @@ static PyMethodDef perf__methods[] = {
.ml_flags = METH_VARARGS,
.ml_doc = PyDoc_STR("Parse a string of events and return an evlist.")
},
+ {
+ .ml_name = "pmus",
+ .ml_meth = (PyCFunction) pyrf__pmus,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = PyDoc_STR("Returns a sequence of pmus.")
+ },
{ .ml_name = NULL, }
};
@@ -1728,6 +1866,8 @@ PyMODINIT_FUNC PyInit_perf(void)
pyrf_evsel__setup_types() < 0 ||
pyrf_thread_map__setup_types() < 0 ||
pyrf_cpu_map__setup_types() < 0 ||
+ pyrf_pmu_iterator__setup_types() < 0 ||
+ pyrf_pmu__setup_types() < 0 ||
pyrf_counts_values__setup_types() < 0)
return module;
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 04/11] perf python: Add function returning dictionary of all events on a PMU
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
` (2 preceding siblings ...)
2025-08-19 1:39 ` [PATCH v10 03/11] perf python: Add basic PMU abstraction and pmus sequence Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 05/11] perf ilist: Add new python ilist command Ian Rogers
` (7 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
Allow all events on a PMU to be gathered, similar to how perf list
gathers event information.
An example usage:
```
$ python
Python 3.12.9 (main, Feb 5 2025, 01:31:18) [GCC 14.2.0] on linux
>>> import perf
>>> for pmu in perf.pmus():
... print(pmu.events())
...
[{'name': 'mem_load_retired.l3_hit', 'desc': 'Retired load instructions...
```
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/util/python.c | 71 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 71 insertions(+)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 6f9728d365ae..cf1128435022 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -670,6 +670,71 @@ static PyObject *pyrf_pmu__name(PyObject *self)
return PyUnicode_FromString(ppmu->pmu->name);
}
+static bool add_to_dict(PyObject *dict, const char *key, const char *value)
+{
+ PyObject *pkey, *pvalue;
+ bool ret;
+
+ if (value == NULL)
+ return true;
+
+ pkey = PyUnicode_FromString(key);
+ pvalue = PyUnicode_FromString(value);
+
+ ret = pkey && pvalue && PyDict_SetItem(dict, pkey, pvalue) == 0;
+ Py_XDECREF(pkey);
+ Py_XDECREF(pvalue);
+ return ret;
+}
+
+static int pyrf_pmu__events_cb(void *state, struct pmu_event_info *info)
+{
+ PyObject *py_list = state;
+ PyObject *dict = PyDict_New();
+
+ if (!dict)
+ return -ENOMEM;
+
+ if (!add_to_dict(dict, "name", info->name) ||
+ !add_to_dict(dict, "alias", info->alias) ||
+ !add_to_dict(dict, "scale_unit", info->scale_unit) ||
+ !add_to_dict(dict, "desc", info->desc) ||
+ !add_to_dict(dict, "long_desc", info->long_desc) ||
+ !add_to_dict(dict, "encoding_desc", info->encoding_desc) ||
+ !add_to_dict(dict, "topic", info->topic) ||
+ !add_to_dict(dict, "event_type_desc", info->event_type_desc) ||
+ !add_to_dict(dict, "str", info->str) ||
+ !add_to_dict(dict, "deprecated", info->deprecated ? "deprecated" : NULL) ||
+ PyList_Append(py_list, dict) != 0) {
+ Py_DECREF(dict);
+ return -ENOMEM;
+ }
+ Py_DECREF(dict);
+ return 0;
+}
+
+static PyObject *pyrf_pmu__events(PyObject *self)
+{
+ struct pyrf_pmu *ppmu = (void *)self;
+ PyObject *py_list = PyList_New(0);
+ int ret;
+
+ if (!py_list)
+ return NULL;
+
+ ret = perf_pmu__for_each_event(ppmu->pmu,
+ /*skip_duplicate_pmus=*/false,
+ py_list,
+ pyrf_pmu__events_cb);
+ if (ret) {
+ Py_DECREF(py_list);
+ errno = -ret;
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+ return py_list;
+}
+
static PyObject *pyrf_pmu__repr(PyObject *self)
{
struct pyrf_pmu *ppmu = (void *)self;
@@ -680,6 +745,12 @@ static PyObject *pyrf_pmu__repr(PyObject *self)
static const char pyrf_pmu__doc[] = PyDoc_STR("perf Performance Monitoring Unit (PMU) object.");
static PyMethodDef pyrf_pmu__methods[] = {
+ {
+ .ml_name = "events",
+ .ml_meth = (PyCFunction)pyrf_pmu__events,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = PyDoc_STR("Returns a sequence of events encoded as a dictionaries.")
+ },
{
.ml_name = "name",
.ml_meth = (PyCFunction)pyrf_pmu__name,
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 05/11] perf ilist: Add new python ilist command
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
` (3 preceding siblings ...)
2025-08-19 1:39 ` [PATCH v10 04/11] perf python: Add function returning dictionary of all events on a PMU Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 06/11] perf python: Add parse_metrics function Ian Rogers
` (6 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
The perf ilist command is a textual app [1] similar to perf list. In
the top-left pane a tree of PMUs is displayed. Selecting a PMU expands
the events within it. Selecting an event displays the `perf list`
style event information in the top-right pane.
When an event is selected it is opened and the counters on each CPU
the event is for are periodically read. The bottom of the screen
contains a scrollable set of sparklines showing the events in total
and on each CPU. Scrolling below the sparklines shows the same data as
raw counts. The sparklines are small graphs where the height of the
bar is in relation to maximum of the other counts in the graph.
By default the counts are read with an interval of 0.1 seconds (10
times per second). A -I/--interval command line option allows the
interval to be changed. The oldest read counts are dropped when the
counts fill the line causing the sparkline to move from right to left.
A search box can be pulled up with the 's' key. 'n' and 'p' iterate
through the search results. As some PMUs have hundreds of events a 'c'
key will collapse the events in the current PMU to make navigating the
PMUs easier.
[1] https://textual.textualize.io/
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/python/ilist.py | 385 +++++++++++++++++++++++++++++++++++++
1 file changed, 385 insertions(+)
create mode 100755 tools/perf/python/ilist.py
diff --git a/tools/perf/python/ilist.py b/tools/perf/python/ilist.py
new file mode 100755
index 000000000000..22c70a8b31f3
--- /dev/null
+++ b/tools/perf/python/ilist.py
@@ -0,0 +1,385 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
+"""Interactive perf list."""
+
+import argparse
+from typing import Any, Dict, Optional, Tuple
+import perf
+from textual import on
+from textual.app import App, ComposeResult
+from textual.binding import Binding
+from textual.containers import Horizontal, HorizontalGroup, Vertical, VerticalScroll
+from textual.command import SearchIcon
+from textual.screen import ModalScreen
+from textual.widgets import Button, Footer, Header, Input, Label, Sparkline, Static, Tree
+from textual.widgets.tree import TreeNode
+
+
+class ErrorScreen(ModalScreen[bool]):
+ """Pop up dialog for errors."""
+
+ CSS = """
+ ErrorScreen {
+ align: center middle;
+ }
+ """
+
+ def __init__(self, error: str):
+ self.error = error
+ super().__init__()
+
+ def compose(self) -> ComposeResult:
+ yield Button(f"Error: {self.error}", variant="primary", id="error")
+
+ def on_button_pressed(self, event: Button.Pressed) -> None:
+ self.dismiss(True)
+
+
+class SearchScreen(ModalScreen[str]):
+ """Pop up dialog for search."""
+
+ CSS = """
+ SearchScreen Horizontal {
+ align: center middle;
+ margin-top: 1;
+ }
+ SearchScreen Input {
+ width: 1fr;
+ }
+ """
+
+ def compose(self) -> ComposeResult:
+ yield Horizontal(SearchIcon(), Input(placeholder="Event name"))
+
+ def on_input_submitted(self, event: Input.Submitted) -> None:
+ """Handle the user pressing Enter in the input field."""
+ self.dismiss(event.value)
+
+
+class Counter(HorizontalGroup):
+ """Two labels for a CPU and its counter value."""
+
+ CSS = """
+ Label {
+ gutter: 1;
+ }
+ """
+
+ def __init__(self, cpu: int) -> None:
+ self.cpu = cpu
+ super().__init__()
+
+ def compose(self) -> ComposeResult:
+ label = f"cpu{self.cpu}" if self.cpu >= 0 else "total"
+ yield Label(label + " ")
+ yield Label("0", id=f"counter_{label}")
+
+
+class CounterSparkline(HorizontalGroup):
+ """A Sparkline for a performance counter."""
+
+ def __init__(self, cpu: int) -> None:
+ self.cpu = cpu
+ super().__init__()
+
+ def compose(self) -> ComposeResult:
+ label = f"cpu{self.cpu}" if self.cpu >= 0 else "total"
+ yield Label(label)
+ yield Sparkline([], summary_function=max, id=f"sparkline_{label}")
+
+
+class IListApp(App):
+ TITLE = "Interactive Perf List"
+
+ BINDINGS = [
+ Binding(key="s", action="search", description="Search",
+ tooltip="Search events and PMUs"),
+ Binding(key="n", action="next", description="Next",
+ tooltip="Next search result or item"),
+ Binding(key="p", action="prev", description="Previous",
+ tooltip="Previous search result or item"),
+ Binding(key="c", action="collapse", description="Collapse",
+ tooltip="Collapse the current PMU"),
+ Binding(key="^q", action="quit", description="Quit",
+ tooltip="Quit the app"),
+ ]
+
+ CSS = """
+ /* Make the 'total' sparkline a different color. */
+ #sparkline_total > .sparkline--min-color {
+ color: $accent;
+ }
+ #sparkline_total > .sparkline--max-color {
+ color: $accent 30%;
+ }
+ /*
+ * Make the active_search initially not displayed with the text in
+ * the middle of the line.
+ */
+ #active_search {
+ display: none;
+ width: 100%;
+ text-align: center;
+ }
+ """
+
+ def __init__(self, interval: float) -> None:
+ self.interval = interval
+ self.evlist = None
+ self.search_results: list[TreeNode[str]] = []
+ self.cur_search_result: TreeNode[str] | None = None
+ super().__init__()
+
+ def expand_and_select(self, node: TreeNode[Any]) -> None:
+ """Expand select a node in the tree."""
+ if node.parent:
+ node.parent.expand()
+ if node.parent.parent:
+ node.parent.parent.expand()
+ node.expand()
+ node.tree.select_node(node)
+ node.tree.scroll_to_node(node)
+
+ def set_searched_tree_node(self, previous: bool) -> None:
+ """Set the cur_search_result node to either the next or previous."""
+ l = len(self.search_results)
+
+ if l < 1:
+ tree: Tree[str] = self.query_one("#pmus", Tree)
+ if previous:
+ tree.action_cursor_up()
+ else:
+ tree.action_cursor_down()
+ return
+
+ if self.cur_search_result:
+ idx = self.search_results.index(self.cur_search_result)
+ if previous:
+ idx = idx - 1 if idx > 0 else l - 1
+ else:
+ idx = idx + 1 if idx < l - 1 else 0
+ else:
+ idx = l - 1 if previous else 0
+
+ node = self.search_results[idx]
+ if node == self.cur_search_result:
+ return
+
+ self.cur_search_result = node
+ self.expand_and_select(node)
+
+ def action_search(self) -> None:
+ """Search was chosen."""
+ def set_initial_focus(event: str | None) -> None:
+ """Sets the focus after the SearchScreen is dismissed."""
+
+ search_label = self.query_one("#active_search", Label)
+ search_label.display = True if event else False
+ if not event:
+ return
+ event = event.lower()
+ search_label.update(f'Searching for events matching "{event}"')
+
+ tree: Tree[str] = self.query_one("#pmus", Tree)
+
+ def find_search_results(event: str, node: TreeNode[str],
+ cursor_seen: bool = False,
+ match_after_cursor: Optional[TreeNode[str]] = None
+ ) -> Tuple[bool, Optional[TreeNode[str]]]:
+ """Find nodes that match the search remembering the one after the cursor."""
+ if not cursor_seen and node == tree.cursor_node:
+ cursor_seen = True
+ if node.data and event in node.data:
+ if cursor_seen and not match_after_cursor:
+ match_after_cursor = node
+ self.search_results.append(node)
+
+ if node.children:
+ for child in node.children:
+ (cursor_seen, match_after_cursor) = \
+ find_search_results(event, child, cursor_seen, match_after_cursor)
+ return (cursor_seen, match_after_cursor)
+
+ self.search_results.clear()
+ (_, self.cur_search_result) = find_search_results(event, tree.root)
+ if len(self.search_results) < 1:
+ self.push_screen(ErrorScreen(f"Failed to find pmu/event {event}"))
+ search_label.display = False
+ elif self.cur_search_result:
+ self.expand_and_select(self.cur_search_result)
+ else:
+ self.set_searched_tree_node(previous=False)
+
+ self.push_screen(SearchScreen(), set_initial_focus)
+
+ def action_next(self) -> None:
+ """Next was chosen."""
+ self.set_searched_tree_node(previous=False)
+
+ def action_prev(self) -> None:
+ """Previous was chosen."""
+ self.set_searched_tree_node(previous=True)
+
+ def action_collapse(self) -> None:
+ """Collapse the potentially large number of events under a PMU."""
+ tree: Tree[str] = self.query_one("#pmus", Tree)
+ node = tree.cursor_node
+ if node and node.parent and node.parent.parent:
+ node.parent.collapse_all()
+ node.tree.scroll_to_node(node.parent)
+
+ def update_counts(self) -> None:
+ """Called every interval to update counts."""
+ if not self.evlist:
+ return
+
+ def update_count(cpu: int, count: int):
+ # Update the raw count display.
+ counter: Label = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total")
+ if not counter:
+ return
+ counter = counter.first(Label)
+ counter.update(str(count))
+
+ # Update the sparkline.
+ line: Sparkline = self.query(f"#sparkline_cpu{cpu}" if cpu >= 0 else "#sparkline_total")
+ if not line:
+ return
+ line = line.first(Sparkline)
+ # If there are more events than the width, remove the front event.
+ if len(line.data) > line.size.width:
+ line.data.pop(0)
+ line.data.append(count)
+ line.mutate_reactive(Sparkline.data)
+
+ # Update the total and each CPU counts, assume there's just 1 evsel.
+ total = 0
+ self.evlist.disable()
+ for evsel in self.evlist:
+ for cpu in evsel.cpus():
+ aggr = 0
+ for thread in evsel.threads():
+ counts = evsel.read(cpu, thread)
+ aggr += counts.val
+ update_count(cpu, aggr)
+ total += aggr
+ update_count(-1, total)
+ self.evlist.enable()
+
+ def on_mount(self) -> None:
+ """When App starts set up periodic event updating."""
+ self.update_counts()
+ self.set_interval(self.interval, self.update_counts)
+
+ def set_pmu_and_event(self, pmu: str, event: str) -> None:
+ """Updates the event/description and starts the counters."""
+ # Remove previous event information.
+ if self.evlist:
+ self.evlist.disable()
+ self.evlist.close()
+ lines = self.query(CounterSparkline)
+ for line in lines:
+ line.remove()
+ lines = self.query(Counter)
+ for line in lines:
+ line.remove()
+
+ def pmu_event_description(pmu: str, event: str) -> str:
+ """Find and format event description for {pmu}/{event}/."""
+ def get_info(info: Dict[str, str], key: str):
+ return (info[key] + "\n") if key in info else ""
+
+ for p in perf.pmus():
+ if p.name() != pmu:
+ continue
+ for info in p.events():
+ if "name" not in info or info["name"] != event:
+ continue
+
+ desc = get_info(info, "topic")
+ desc += get_info(info, "event_type_desc")
+ desc += get_info(info, "desc")
+ desc += get_info(info, "long_desc")
+ desc += get_info(info, "encoding_desc")
+ return desc
+ return "description"
+
+ # Parse event, update event text and description.
+ full_name = event if event.startswith(pmu) or ':' in event else f"{pmu}/{event}/"
+ self.query_one("#event_name", Label).update(full_name)
+ self.query_one("#event_description", Static).update(pmu_event_description(pmu, event))
+
+ # Open the event.
+ try:
+ self.evlist = perf.parse_events(full_name)
+ if self.evlist:
+ self.evlist.open()
+ self.evlist.enable()
+ except:
+ self.evlist = None
+
+ if not self.evlist:
+ self.push_screen(ErrorScreen(f"Failed to open {full_name}"))
+ return
+
+ # Add spark lines for all the CPUs. Note, must be done after
+ # open so that the evlist CPUs have been computed by propagate
+ # maps.
+ lines = self.query_one("#lines")
+ line = CounterSparkline(cpu=-1)
+ lines.mount(line)
+ for cpu in self.evlist.all_cpus():
+ line = CounterSparkline(cpu)
+ lines.mount(line)
+ line = Counter(cpu=-1)
+ lines.mount(line)
+ for cpu in self.evlist.all_cpus():
+ line = Counter(cpu)
+ lines.mount(line)
+
+ def compose(self) -> ComposeResult:
+ """Draws the app."""
+ def pmu_event_tree() -> Tree:
+ """Create tree of PMUs with events under."""
+ tree: Tree[str] = Tree("PMUs", id="pmus")
+ tree.root.expand()
+ for pmu in perf.pmus():
+ pmu_name = pmu.name().lower()
+ pmu_node = tree.root.add(pmu_name, data=pmu_name)
+ try:
+ for event in sorted(pmu.events(), key=lambda x: x["name"]):
+ if "name" in event:
+ e = event["name"].lower()
+ if "alias" in event:
+ pmu_node.add_leaf(f'{e} ({event["alias"]})', data=e)
+ else:
+ pmu_node.add_leaf(e, data=e)
+ except:
+ # Reading events may fail with EPERM, ignore.
+ pass
+ return tree
+
+ yield Header(id="header")
+ yield Horizontal(Vertical(pmu_event_tree(), id="events"),
+ Vertical(Label("event name", id="event_name"),
+ Static("description", markup=False, id="event_description"),
+ ))
+ yield Label(id="active_search")
+ yield VerticalScroll(id="lines")
+ yield Footer(id="footer")
+
+ @on(Tree.NodeSelected)
+ def on_tree_node_selected(self, event: Tree.NodeSelected[str]) -> None:
+ """Called when a tree node is selected, selecting the event."""
+ if event.node.parent and event.node.parent.parent:
+ assert event.node.parent.data is not None
+ assert event.node.data is not None
+ self.set_pmu_and_event(event.node.parent.data, event.node.data)
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser()
+ ap.add_argument('-I', '--interval', help="Counter update interval in seconds", default=0.1)
+ args = ap.parse_args()
+ app = IListApp(float(args.interval))
+ app.run()
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 06/11] perf python: Add parse_metrics function
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
` (4 preceding siblings ...)
2025-08-19 1:39 ` [PATCH v10 05/11] perf ilist: Add new python ilist command Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 07/11] perf python: Add evlist metrics function Ian Rogers
` (5 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
Add parse_metrics function that takes a string of metrics and/or
metric groups and returns the evlist containing the events and
metrics.
For example:
```
>>> import perf
>>> perf.parse_metrics("TopdownL1")
evlist([cpu/TOPDOWN.SLOTS/,cpu/topdown-retiring/,cpu/topdown-fe-bound/,
cpu/topdown-be-bound/,cpu/topdown-bad-spec/,cpu/INT_MISC.CLEARS_COUNT/,
cpu/INT_MISC.UOP_DROPPING/])
```
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/util/python.c | 41 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index cf1128435022..48308ed4e1c7 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -1891,6 +1891,40 @@ static PyObject *pyrf__parse_events(PyObject *self, PyObject *args)
return result;
}
+static PyObject *pyrf__parse_metrics(PyObject *self, PyObject *args)
+{
+ const char *input;
+ struct evlist evlist = {};
+ PyObject *result;
+ PyObject *pcpus = NULL, *pthreads = NULL;
+ struct perf_cpu_map *cpus;
+ struct perf_thread_map *threads;
+ int ret;
+
+ if (!PyArg_ParseTuple(args, "s|OO", &input, &pcpus, &pthreads))
+ return NULL;
+
+ threads = pthreads ? ((struct pyrf_thread_map *)pthreads)->threads : NULL;
+ cpus = pcpus ? ((struct pyrf_cpu_map *)pcpus)->cpus : NULL;
+
+ evlist__init(&evlist, cpus, threads);
+ ret = metricgroup__parse_groups(&evlist, /*pmu=*/"all", input,
+ /*metric_no_group=*/ false,
+ /*metric_no_merge=*/ false,
+ /*metric_no_threshold=*/ true,
+ /*user_requested_cpu_list=*/ NULL,
+ /*system_wide=*/true,
+ /*hardware_aware_grouping=*/ false);
+ if (ret) {
+ errno = -ret;
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+ result = pyrf_evlist__from_evlist(&evlist);
+ evlist__exit(&evlist);
+ return result;
+}
+
static PyMethodDef perf__methods[] = {
{
.ml_name = "tracepoint",
@@ -1904,6 +1938,13 @@ static PyMethodDef perf__methods[] = {
.ml_flags = METH_VARARGS,
.ml_doc = PyDoc_STR("Parse a string of events and return an evlist.")
},
+ {
+ .ml_name = "parse_metrics",
+ .ml_meth = (PyCFunction) pyrf__parse_metrics,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = PyDoc_STR(
+ "Parse a string of metics or metric groups and return an evlist.")
+ },
{
.ml_name = "pmus",
.ml_meth = (PyCFunction) pyrf__pmus,
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 07/11] perf python: Add evlist metrics function
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
` (5 preceding siblings ...)
2025-08-19 1:39 ` [PATCH v10 06/11] perf python: Add parse_metrics function Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 08/11] perf python: Add evlist compute_metric Ian Rogers
` (4 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
The function returns a list of the names of metrics within the
evlist. For example:
```
>>> import perf
>>> perf.parse_metrics("TopdownL1").metrics()
['tma_bad_speculation', 'tma_frontend_bound', 'tma_backend_bound', 'tma_retiring']
```
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/util/python.c | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 48308ed4e1c7..31089f8e5519 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -1303,6 +1303,33 @@ static PyObject *pyrf_evlist__all_cpus(struct pyrf_evlist *pevlist)
return (PyObject *)pcpu_map;
}
+static PyObject *pyrf_evlist__metrics(struct pyrf_evlist *pevlist)
+{
+ PyObject *list = PyList_New(/*len=*/0);
+ struct rb_node *node;
+
+ if (!list)
+ return NULL;
+
+ for (node = rb_first_cached(&pevlist->evlist.metric_events.entries); node;
+ node = rb_next(node)) {
+ struct metric_event *me = container_of(node, struct metric_event, nd);
+ struct list_head *pos;
+
+ list_for_each(pos, &me->head) {
+ struct metric_expr *expr = container_of(pos, struct metric_expr, nd);
+ PyObject *str = PyUnicode_FromString(expr->metric_name);
+
+ if (!str || PyList_Append(list, str) != 0) {
+ Py_DECREF(list);
+ return NULL;
+ }
+ Py_DECREF(str);
+ }
+ }
+ return list;
+}
+
static PyObject *pyrf_evlist__mmap(struct pyrf_evlist *pevlist,
PyObject *args, PyObject *kwargs)
{
@@ -1531,6 +1558,12 @@ static PyMethodDef pyrf_evlist__methods[] = {
.ml_flags = METH_NOARGS,
.ml_doc = PyDoc_STR("CPU map union of all evsel CPU maps.")
},
+ {
+ .ml_name = "metrics",
+ .ml_meth = (PyCFunction)pyrf_evlist__metrics,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = PyDoc_STR("List of metric names within the evlist.")
+ },
{
.ml_name = "mmap",
.ml_meth = (PyCFunction)pyrf_evlist__mmap,
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 08/11] perf python: Add evlist compute_metric
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
` (6 preceding siblings ...)
2025-08-19 1:39 ` [PATCH v10 07/11] perf python: Add evlist metrics function Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-09-03 13:35 ` Arnaldo Carvalho de Melo
2025-08-19 1:39 ` [PATCH v10 09/11] perf python: Add metrics function Ian Rogers
` (3 subsequent siblings)
11 siblings, 1 reply; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
Add a compute_metric function that computes a metric double value for a
given evlist, metric name, CPU and thread. For example:
```
>>> import perf
>>> x = perf.parse_metrics("TopdownL1")
>>> x.open()
>>> x.enable()
>>> x.disable()
>>> x.metrics()
['tma_bad_speculation', 'tma_frontend_bound', 'tma_backend_bound', 'tma_retiring']
>>> x.compute_metric('tma_bad_speculation', 0, -1)
0.08605342847131037
```
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/util/python.c | 125 +++++++++++++++++++++++++++++++++++++++
1 file changed, 125 insertions(+)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 31089f8e5519..e0769538b8d9 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -14,6 +14,7 @@
#include "evlist.h"
#include "evsel.h"
#include "event.h"
+#include "expr.h"
#include "print_binary.h"
#include "record.h"
#include "strbuf.h"
@@ -1330,6 +1331,124 @@ static PyObject *pyrf_evlist__metrics(struct pyrf_evlist *pevlist)
return list;
}
+static int prepare_metric(const struct metric_expr *mexp,
+ const struct evsel *evsel,
+ struct expr_parse_ctx *pctx,
+ int cpu_idx, int thread_idx)
+{
+ struct evsel * const *metric_events = mexp->metric_events;
+ struct metric_ref *metric_refs = mexp->metric_refs;
+
+ for (int i = 0; metric_events[i]; i++) {
+ char *n = strdup(evsel__metric_id(metric_events[i]));
+ double val, ena, run;
+ int source_count = evsel__source_count(metric_events[i]);
+ int ret;
+ struct perf_counts_values *old_count, *new_count;
+
+ if (!n)
+ return -ENOMEM;
+
+ if (source_count == 0)
+ source_count = 1;
+
+ ret = evsel__ensure_counts(metric_events[i]);
+ if (ret)
+ return ret;
+
+ /* Set up pointers to the old and newly read counter values. */
+ old_count = perf_counts(metric_events[i]->prev_raw_counts, cpu_idx, thread_idx);
+ new_count = perf_counts(metric_events[i]->counts, cpu_idx, thread_idx);
+ /* Update the value in metric_events[i]->counts. */
+ evsel__read_counter(metric_events[i], cpu_idx, thread_idx);
+
+ val = new_count->val - old_count->val;
+ ena = new_count->ena - old_count->ena;
+ run = new_count->run - old_count->run;
+
+ if (ena != run && run != 0)
+ val = val * ena / run;
+ ret = expr__add_id_val_source_count(pctx, n, val, source_count);
+ if (ret)
+ return ret;
+ }
+
+ for (int i = 0; metric_refs && metric_refs[i].metric_name; i++) {
+ int ret = expr__add_ref(pctx, &metric_refs[i]);
+
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist,
+ PyObject *args, PyObject *kwargs)
+{
+ int ret, cpu = 0, cpu_idx, thread = 0, thread_idx;
+ const char *metric;
+ struct rb_node *node;
+ struct metric_expr *mexp = NULL;
+ struct expr_parse_ctx *pctx;
+ double result = 0;
+
+ if (!PyArg_ParseTuple(args, "sii", &metric, &cpu, &thread))
+ return NULL;
+
+ for (node = rb_first_cached(&pevlist->evlist.metric_events.entries);
+ mexp == NULL && node;
+ node = rb_next(node)) {
+ struct metric_event *me = container_of(node, struct metric_event, nd);
+ struct list_head *pos;
+
+ list_for_each(pos, &me->head) {
+ struct metric_expr *e = container_of(pos, struct metric_expr, nd);
+
+ if (strcmp(e->metric_name, metric))
+ continue;
+
+ if (e->metric_events[0] == NULL)
+ continue;
+
+ cpu_idx = perf_cpu_map__idx(e->metric_events[0]->core.cpus,
+ (struct perf_cpu){.cpu = cpu});
+ if (cpu_idx < 0)
+ continue;
+
+ thread_idx = perf_thread_map__idx(e->metric_events[0]->core.threads,
+ thread);
+ if (thread_idx < 0)
+ continue;
+
+ mexp = e;
+ break;
+ }
+ }
+ if (!mexp) {
+ PyErr_Format(PyExc_TypeError, "Unknown metric '%s' for CPU '%d' and thread '%d'",
+ metric, cpu, thread);
+ return NULL;
+ }
+
+ pctx = expr__ctx_new();
+ if (!pctx)
+ return PyErr_NoMemory();
+
+ ret = prepare_metric(mexp, mexp->metric_events[0], pctx, cpu_idx, thread_idx);
+ if (ret) {
+ expr__ctx_free(pctx);
+ errno = -ret;
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+ if (expr__parse(&result, pctx, mexp->metric_expr))
+ result = 0.0;
+
+ expr__ctx_free(pctx);
+ return PyFloat_FromDouble(result);
+}
+
static PyObject *pyrf_evlist__mmap(struct pyrf_evlist *pevlist,
PyObject *args, PyObject *kwargs)
{
@@ -1564,6 +1683,12 @@ static PyMethodDef pyrf_evlist__methods[] = {
.ml_flags = METH_NOARGS,
.ml_doc = PyDoc_STR("List of metric names within the evlist.")
},
+ {
+ .ml_name = "compute_metric",
+ .ml_meth = (PyCFunction)pyrf_evlist__compute_metric,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("compute metric for given name, cpu and thread")
+ },
{
.ml_name = "mmap",
.ml_meth = (PyCFunction)pyrf_evlist__mmap,
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 09/11] perf python: Add metrics function
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
` (7 preceding siblings ...)
2025-08-19 1:39 ` [PATCH v10 08/11] perf python: Add evlist compute_metric Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 10/11] perf ilist: Add support for metrics Ian Rogers
` (2 subsequent siblings)
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
The metrics function returns a list dictionaries describing metrics as
strings mapping to strings, except for metric groups that are a string
mapping to a list of strings. For example:
```
>>> import perf
>>> perf.metrics()[0]
{'MetricGroup': ['Power'], 'MetricName': 'C10_Pkg_Residency',
'PMU': 'default_core', 'MetricExpr': 'cstate_pkg@c10\\-residency@ / TSC',
'ScaleUnit': '100%', 'BriefDescription': 'C10 residency percent per package'}
```
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/util/python.c | 86 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index e0769538b8d9..56102034d5b8 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -2083,7 +2083,93 @@ static PyObject *pyrf__parse_metrics(PyObject *self, PyObject *args)
return result;
}
+static PyObject *pyrf__metrics_groups(const struct pmu_metric *pm)
+{
+ PyObject *groups = PyList_New(/*len=*/0);
+ const char *mg = pm->metric_group;
+
+ if (!groups)
+ return NULL;
+
+ while (mg) {
+ PyObject *val = NULL;
+ const char *sep = strchr(mg, ';');
+ size_t len = sep ? (size_t)(sep - mg) : strlen(mg);
+
+ if (len > 0) {
+ val = PyUnicode_FromStringAndSize(mg, len);
+ if (val)
+ PyList_Append(groups, val);
+
+ Py_XDECREF(val);
+ }
+ mg = sep ? sep + 1 : NULL;
+ }
+ return groups;
+}
+
+static int pyrf__metrics_cb(const struct pmu_metric *pm,
+ const struct pmu_metrics_table *table __maybe_unused,
+ void *vdata)
+{
+ PyObject *py_list = vdata;
+ PyObject *dict = PyDict_New();
+ PyObject *key = dict ? PyUnicode_FromString("MetricGroup") : NULL;
+ PyObject *value = key ? pyrf__metrics_groups(pm) : NULL;
+
+ if (!value || PyDict_SetItem(dict, key, value) != 0) {
+ Py_XDECREF(key);
+ Py_XDECREF(value);
+ Py_XDECREF(dict);
+ return -ENOMEM;
+ }
+
+ if (!add_to_dict(dict, "MetricName", pm->metric_name) ||
+ !add_to_dict(dict, "PMU", pm->pmu) ||
+ !add_to_dict(dict, "MetricExpr", pm->metric_expr) ||
+ !add_to_dict(dict, "MetricThreshold", pm->metric_threshold) ||
+ !add_to_dict(dict, "ScaleUnit", pm->unit) ||
+ !add_to_dict(dict, "Compat", pm->compat) ||
+ !add_to_dict(dict, "BriefDescription", pm->desc) ||
+ !add_to_dict(dict, "PublicDescription", pm->long_desc) ||
+ PyList_Append(py_list, dict) != 0) {
+ Py_DECREF(dict);
+ return -ENOMEM;
+ }
+ Py_DECREF(dict);
+ return 0;
+}
+
+static PyObject *pyrf__metrics(PyObject *self, PyObject *args)
+{
+ const struct pmu_metrics_table *table = pmu_metrics_table__find();
+ PyObject *list = PyList_New(/*len=*/0);
+ int ret;
+
+ if (!list)
+ return NULL;
+
+ ret = pmu_metrics_table__for_each_metric(table, pyrf__metrics_cb, list);
+ if (!ret)
+ ret = pmu_for_each_sys_metric(pyrf__metrics_cb, list);
+
+ if (ret) {
+ Py_DECREF(list);
+ errno = -ret;
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+ return list;
+}
+
static PyMethodDef perf__methods[] = {
+ {
+ .ml_name = "metrics",
+ .ml_meth = (PyCFunction) pyrf__metrics,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = PyDoc_STR(
+ "Returns a list of metrics represented as string values in dictionaries.")
+ },
{
.ml_name = "tracepoint",
.ml_meth = (PyCFunction) pyrf__tracepoint,
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 10/11] perf ilist: Add support for metrics
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
` (8 preceding siblings ...)
2025-08-19 1:39 ` [PATCH v10 09/11] perf python: Add metrics function Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 11/11] perf tp_pmu: Remove unnecessary check Ian Rogers
2025-09-02 20:06 ` [PATCH v10 00/11] New perf ilist app Arnaldo Carvalho de Melo
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
Cc: Arnaldo Carvalho de Melo
Change tree nodes to having a value of either Metric or PmuEvent,
these values have the ability to match searches, be parsed to create
evlists and to give a value per CPU and per thread to display. Use
perf.metrics to generate a tree of metrics. Most metrics are placed
under their metric group, if the metric group name ends with '_group'
then the metric group is placed next to the associated metric.
Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Reviewed-by: Howard Chu <howardchu95@gmail.com>
---
tools/perf/python/ilist.py | 226 +++++++++++++++++++++++++++----------
1 file changed, 168 insertions(+), 58 deletions(-)
diff --git a/tools/perf/python/ilist.py b/tools/perf/python/ilist.py
index 22c70a8b31f3..9d6465c60df3 100755
--- a/tools/perf/python/ilist.py
+++ b/tools/perf/python/ilist.py
@@ -2,19 +2,121 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
"""Interactive perf list."""
+from abc import ABC, abstractmethod
import argparse
+from dataclasses import dataclass
+import math
from typing import Any, Dict, Optional, Tuple
import perf
from textual import on
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Horizontal, HorizontalGroup, Vertical, VerticalScroll
+from textual.css.query import NoMatches
from textual.command import SearchIcon
from textual.screen import ModalScreen
from textual.widgets import Button, Footer, Header, Input, Label, Sparkline, Static, Tree
from textual.widgets.tree import TreeNode
+def get_info(info: Dict[str, str], key: str):
+ return (info[key] + "\n") if key in info else ""
+
+
+class TreeValue(ABC):
+ """Abstraction for the data of value in the tree."""
+
+ @abstractmethod
+ def name(self) -> str:
+ pass
+
+ @abstractmethod
+ def description(self) -> str:
+ pass
+
+ @abstractmethod
+ def matches(self, query: str) -> bool:
+ pass
+
+ @abstractmethod
+ def parse(self) -> perf.evlist:
+ pass
+
+ @abstractmethod
+ def value(self, evlist: perf.evlist, evsel: perf.evsel, cpu: int, thread: int) -> float:
+ pass
+
+
+@dataclass
+class Metric(TreeValue):
+ """A metric in the tree."""
+ metric_name: str
+
+ def name(self) -> str:
+ return self.metric_name
+
+ def description(self) -> str:
+ """Find and format metric description."""
+ for metric in perf.metrics():
+ if metric["MetricName"] != self.metric_name:
+ continue
+ desc = get_info(metric, "BriefDescription")
+ desc += get_info(metric, "PublicDescription")
+ desc += get_info(metric, "MetricExpr")
+ desc += get_info(metric, "MetricThreshold")
+ return desc
+ return "description"
+
+ def matches(self, query: str) -> bool:
+ return query in self.metric_name
+
+ def parse(self) -> perf.evlist:
+ return perf.parse_metrics(self.metric_name)
+
+ def value(self, evlist: perf.evlist, evsel: perf.evsel, cpu: int, thread: int) -> float:
+ val = evlist.compute_metric(self.metric_name, cpu, thread)
+ return 0 if math.isnan(val) else val
+
+
+@dataclass
+class PmuEvent(TreeValue):
+ """A PMU and event within the tree."""
+ pmu: str
+ event: str
+
+ def name(self) -> str:
+ if self.event.startswith(self.pmu) or ':' in self.event:
+ return self.event
+ else:
+ return f"{self.pmu}/{self.event}/"
+
+ def description(self) -> str:
+ """Find and format event description for {pmu}/{event}/."""
+ for p in perf.pmus():
+ if p.name() != self.pmu:
+ continue
+ for info in p.events():
+ if "name" not in info or info["name"] != self.event:
+ continue
+
+ desc = get_info(info, "topic")
+ desc += get_info(info, "event_type_desc")
+ desc += get_info(info, "desc")
+ desc += get_info(info, "long_desc")
+ desc += get_info(info, "encoding_desc")
+ return desc
+ return "description"
+
+ def matches(self, query: str) -> bool:
+ return query in self.pmu or query in self.event
+
+ def parse(self) -> perf.evlist:
+ return perf.parse_events(self.name())
+
+ def value(self, evlist: perf.evlist, evsel: perf.evsel, cpu: int, thread: int) -> float:
+ return evsel.read(cpu, thread).val
+
+
class ErrorScreen(ModalScreen[bool]):
"""Pop up dialog for errors."""
@@ -126,8 +228,9 @@ class IListApp(App):
def __init__(self, interval: float) -> None:
self.interval = interval
self.evlist = None
- self.search_results: list[TreeNode[str]] = []
- self.cur_search_result: TreeNode[str] | None = None
+ self.selected: Optional[TreeValue] = None
+ self.search_results: list[TreeNode[TreeValue]] = []
+ self.cur_search_result: TreeNode[TreeValue] | None = None
super().__init__()
def expand_and_select(self, node: TreeNode[Any]) -> None:
@@ -145,7 +248,7 @@ class IListApp(App):
l = len(self.search_results)
if l < 1:
- tree: Tree[str] = self.query_one("#pmus", Tree)
+ tree: Tree[TreeValue] = self.query_one("#root", Tree)
if previous:
tree.action_cursor_up()
else:
@@ -180,7 +283,7 @@ class IListApp(App):
event = event.lower()
search_label.update(f'Searching for events matching "{event}"')
- tree: Tree[str] = self.query_one("#pmus", Tree)
+ tree: Tree[str] = self.query_one("#root", Tree)
def find_search_results(event: str, node: TreeNode[str],
cursor_seen: bool = False,
@@ -189,7 +292,7 @@ class IListApp(App):
"""Find nodes that match the search remembering the one after the cursor."""
if not cursor_seen and node == tree.cursor_node:
cursor_seen = True
- if node.data and event in node.data:
+ if node.data and node.data.matches(event):
if cursor_seen and not match_after_cursor:
match_after_cursor = node
self.search_results.append(node)
@@ -203,7 +306,7 @@ class IListApp(App):
self.search_results.clear()
(_, self.cur_search_result) = find_search_results(event, tree.root)
if len(self.search_results) < 1:
- self.push_screen(ErrorScreen(f"Failed to find pmu/event {event}"))
+ self.push_screen(ErrorScreen(f"Failed to find pmu/event or metric {event}"))
search_label.display = False
elif self.cur_search_result:
self.expand_and_select(self.cur_search_result)
@@ -221,16 +324,16 @@ class IListApp(App):
self.set_searched_tree_node(previous=True)
def action_collapse(self) -> None:
- """Collapse the potentially large number of events under a PMU."""
- tree: Tree[str] = self.query_one("#pmus", Tree)
+ """Collapse the part of the tree currently on."""
+ tree: Tree[str] = self.query_one("#root", Tree)
node = tree.cursor_node
- if node and node.parent and node.parent.parent:
+ if node and node.parent:
node.parent.collapse_all()
node.tree.scroll_to_node(node.parent)
def update_counts(self) -> None:
"""Called every interval to update counts."""
- if not self.evlist:
+ if not self.selected or not self.evlist:
return
def update_count(cpu: int, count: int):
@@ -259,8 +362,7 @@ class IListApp(App):
for cpu in evsel.cpus():
aggr = 0
for thread in evsel.threads():
- counts = evsel.read(cpu, thread)
- aggr += counts.val
+ aggr += self.selected.value(self.evlist, evsel, cpu, thread)
update_count(cpu, aggr)
total += aggr
update_count(-1, total)
@@ -271,47 +373,37 @@ class IListApp(App):
self.update_counts()
self.set_interval(self.interval, self.update_counts)
- def set_pmu_and_event(self, pmu: str, event: str) -> None:
+ def set_selected(self, value: TreeValue) -> None:
"""Updates the event/description and starts the counters."""
+ try:
+ label_name = self.query_one("#event_name", Label)
+ event_description = self.query_one("#event_description", Static)
+ lines = self.query_one("#lines")
+ except NoMatches:
+ # A race with rendering, ignore the update as we can't
+ # mount the assumed output widgets.
+ return
+
+ self.selected = value
+
# Remove previous event information.
if self.evlist:
self.evlist.disable()
self.evlist.close()
- lines = self.query(CounterSparkline)
- for line in lines:
- line.remove()
- lines = self.query(Counter)
- for line in lines:
+ old_lines = self.query(CounterSparkline)
+ for line in old_lines:
line.remove()
+ old_counters = self.query(Counter)
+ for counter in old_counters:
+ counter.remove()
- def pmu_event_description(pmu: str, event: str) -> str:
- """Find and format event description for {pmu}/{event}/."""
- def get_info(info: Dict[str, str], key: str):
- return (info[key] + "\n") if key in info else ""
-
- for p in perf.pmus():
- if p.name() != pmu:
- continue
- for info in p.events():
- if "name" not in info or info["name"] != event:
- continue
-
- desc = get_info(info, "topic")
- desc += get_info(info, "event_type_desc")
- desc += get_info(info, "desc")
- desc += get_info(info, "long_desc")
- desc += get_info(info, "encoding_desc")
- return desc
- return "description"
-
- # Parse event, update event text and description.
- full_name = event if event.startswith(pmu) or ':' in event else f"{pmu}/{event}/"
- self.query_one("#event_name", Label).update(full_name)
- self.query_one("#event_description", Static).update(pmu_event_description(pmu, event))
+ # Update event/metric text and description.
+ label_name.update(value.name())
+ event_description.update(value.description())
# Open the event.
try:
- self.evlist = perf.parse_events(full_name)
+ self.evlist = value.parse()
if self.evlist:
self.evlist.open()
self.evlist.enable()
@@ -319,13 +411,12 @@ class IListApp(App):
self.evlist = None
if not self.evlist:
- self.push_screen(ErrorScreen(f"Failed to open {full_name}"))
+ self.push_screen(ErrorScreen(f"Failed to open {value.name()}"))
return
# Add spark lines for all the CPUs. Note, must be done after
# open so that the evlist CPUs have been computed by propagate
# maps.
- lines = self.query_one("#lines")
line = CounterSparkline(cpu=-1)
lines.mount(line)
for cpu in self.evlist.all_cpus():
@@ -339,28 +430,49 @@ class IListApp(App):
def compose(self) -> ComposeResult:
"""Draws the app."""
- def pmu_event_tree() -> Tree:
- """Create tree of PMUs with events under."""
- tree: Tree[str] = Tree("PMUs", id="pmus")
- tree.root.expand()
+ def metric_event_tree() -> Tree:
+ """Create tree of PMUs and metricgroups with events or metrics under."""
+ tree: Tree[TreeValue] = Tree("Root", id="root")
+ pmus = tree.root.add("PMUs")
for pmu in perf.pmus():
pmu_name = pmu.name().lower()
- pmu_node = tree.root.add(pmu_name, data=pmu_name)
+ pmu_node = pmus.add(pmu_name)
try:
for event in sorted(pmu.events(), key=lambda x: x["name"]):
if "name" in event:
e = event["name"].lower()
if "alias" in event:
- pmu_node.add_leaf(f'{e} ({event["alias"]})', data=e)
+ pmu_node.add_leaf(f'{e} ({event["alias"]})',
+ data=PmuEvent(pmu_name, e))
else:
- pmu_node.add_leaf(e, data=e)
+ pmu_node.add_leaf(e, data=PmuEvent(pmu_name, e))
except:
# Reading events may fail with EPERM, ignore.
pass
+ metrics = tree.root.add("Metrics")
+ groups = set()
+ for metric in perf.metrics():
+ groups.update(metric["MetricGroup"])
+
+ def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str):
+ for metric in sorted(perf.metrics(), key=lambda x: x["MetricName"]):
+ if parent in metric["MetricGroup"]:
+ name = metric["MetricName"]
+ node.add_leaf(name, data=Metric(name))
+ child_group_name = f'{name}_group'
+ if child_group_name in groups:
+ add_metrics_to_tree(node.add(child_group_name), child_group_name)
+
+ for group in sorted(groups):
+ if group.endswith('_group'):
+ continue
+ add_metrics_to_tree(metrics.add(group), group)
+
+ tree.root.expand()
return tree
yield Header(id="header")
- yield Horizontal(Vertical(pmu_event_tree(), id="events"),
+ yield Horizontal(Vertical(metric_event_tree(), id="events"),
Vertical(Label("event name", id="event_name"),
Static("description", markup=False, id="event_description"),
))
@@ -369,12 +481,10 @@ class IListApp(App):
yield Footer(id="footer")
@on(Tree.NodeSelected)
- def on_tree_node_selected(self, event: Tree.NodeSelected[str]) -> None:
+ def on_tree_node_selected(self, event: Tree.NodeSelected[TreeValue]) -> None:
"""Called when a tree node is selected, selecting the event."""
- if event.node.parent and event.node.parent.parent:
- assert event.node.parent.data is not None
- assert event.node.data is not None
- self.set_pmu_and_event(event.node.parent.data, event.node.data)
+ if event.node.data:
+ self.set_selected(event.node.data)
if __name__ == "__main__":
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v10 11/11] perf tp_pmu: Remove unnecessary check
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
` (9 preceding siblings ...)
2025-08-19 1:39 ` [PATCH v10 10/11] perf ilist: Add support for metrics Ian Rogers
@ 2025-08-19 1:39 ` Ian Rogers
2025-09-02 20:06 ` [PATCH v10 00/11] New perf ilist app Arnaldo Carvalho de Melo
11 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-08-19 1:39 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Mark Rutland, Alexander Shishkin, Jiri Olsa,
Ian Rogers, Adrian Hunter, Kan Liang, James Clark, Xu Yang,
Masami Hiramatsu (Google), Collin Funk, Howard Chu, Weilin Wang,
Andi Kleen, Dr. David Alan Gilbert, Thomas Richter, Tiezhu Yang,
Gautam Menghani, Thomas Falcon, Chun-Tse Shao, linux-kernel,
linux-perf-users
The "if" condition is also part of the "while" condition, remove the
"if" to reduce the amount of code.
Reported-by: Howard Chu <howardchu95@gmail.com>
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/tp_pmu.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/tools/perf/util/tp_pmu.c b/tools/perf/util/tp_pmu.c
index e7534a973247..eddb9807131a 100644
--- a/tools/perf/util/tp_pmu.c
+++ b/tools/perf/util/tp_pmu.c
@@ -88,8 +88,6 @@ int tp_pmu__for_each_tp_sys(void *state, tp_sys_callback cb)
continue;
ret = cb(state, events_ent->d_name);
- if (ret)
- break;
}
close(events_dir.dirfd);
return ret;
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v10 00/11] New perf ilist app
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
` (10 preceding siblings ...)
2025-08-19 1:39 ` [PATCH v10 11/11] perf tp_pmu: Remove unnecessary check Ian Rogers
@ 2025-09-02 20:06 ` Arnaldo Carvalho de Melo
11 siblings, 0 replies; 15+ messages in thread
From: Arnaldo Carvalho de Melo @ 2025-09-02 20:06 UTC (permalink / raw)
To: Ian Rogers
Cc: Peter Zijlstra, Ingo Molnar, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Adrian Hunter, Kan Liang,
James Clark, Xu Yang, Masami Hiramatsu (Google), Collin Funk,
Howard Chu, Weilin Wang, Andi Kleen, Dr. David Alan Gilbert,
Thomas Richter, Tiezhu Yang, Gautam Menghani, Thomas Falcon,
Chun-Tse Shao, linux-kernel, linux-perf-users
On Mon, Aug 18, 2025 at 06:39:30PM -0700, Ian Rogers wrote:
> This patch series adds a new ilist app written in python using textual
> [1] for the UI. The app presents perf PMUs and events, displays the
> event information as in `perf list` while at the bottom of the console
> showing recent activity of the event in total and across all CPUs. It
> also displays metrics, placed in a tree through their metric group,
> again with counts being displayed in the bottom panel.
>
> To run it you need the updated perf.cpython.so in your PYTHONPATH and
> then execute the script. Expanding PMUs and then selecting events will
> cause event informatin to be displayed in the top-right and the
> counters values to be displayed as sparklines and counts in the bottom
> half of the screen.
>
> There's been feedback on how the app works, simplicity of
> implementation has been chosen as the first criteria as the app can be
> further refined from what is here. The choice of the name ilist rather
> than say istat was deliberate as I wanted the app to encourage PMU,
> event and metric discovery, as with perf list. The output counts and
> spark lines are just to give an indication of what the event
> gathers. ilist comes from interactive list, there's probably a better
> name.
>
> [1] https://textual.textualize.io/
>
> v10: Add Howard's reviewed-by and address documentation fix. Rebase
> and drop build up patches merged in v6.17 by Namhyung.
Thanks, applied to perf-tools-next,
- Arnaldo
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v10 08/11] perf python: Add evlist compute_metric
2025-08-19 1:39 ` [PATCH v10 08/11] perf python: Add evlist compute_metric Ian Rogers
@ 2025-09-03 13:35 ` Arnaldo Carvalho de Melo
2025-09-03 15:56 ` Ian Rogers
0 siblings, 1 reply; 15+ messages in thread
From: Arnaldo Carvalho de Melo @ 2025-09-03 13:35 UTC (permalink / raw)
To: Ian Rogers
Cc: Peter Zijlstra, Ingo Molnar, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Adrian Hunter, Kan Liang,
James Clark, Xu Yang, Masami Hiramatsu (Google), Collin Funk,
Howard Chu, Weilin Wang, Andi Kleen, Dr. David Alan Gilbert,
Thomas Richter, Tiezhu Yang, Gautam Menghani, Thomas Falcon,
Chun-Tse Shao, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
On Mon, Aug 18, 2025 at 06:39:38PM -0700, Ian Rogers wrote:
> Add a compute_metric function that computes a metric double value for a
> given evlist, metric name, CPU and thread. For example:
> ```
> >>> import perf
> >>> x = perf.parse_metrics("TopdownL1")
> >>> x.open()
> >>> x.enable()
> >>> x.disable()
> >>> x.metrics()
> ['tma_bad_speculation', 'tma_frontend_bound', 'tma_backend_bound', 'tma_retiring']
> >>> x.compute_metric('tma_bad_speculation', 0, -1)
> 0.08605342847131037
> ```
Added the following to fix the build on the still not EOLed OpenSUSE
15, ok?
- Arnaldo
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 56102034d5b8c469..47178404802f4069 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -1386,7 +1386,7 @@ static int prepare_metric(const struct metric_expr *mexp,
static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist,
PyObject *args, PyObject *kwargs)
{
- int ret, cpu = 0, cpu_idx, thread = 0, thread_idx;
+ int ret, cpu = 0, cpu_idx = 0, thread = 0, thread_idx = 0;
const char *metric;
struct rb_node *node;
struct metric_expr *mexp = NULL;
Committer notes:
Initialize thread_idx and cpu_idx to zero as albeit them not possibly
coming out unitialized from the loop as mexp would be not NULL only if
they were initialized, some older compilers don't notice that and error
with:
GEN /tmp/build/perf/python/perf.cpython-36m-x86_64-linux-gnu.so
/git/perf-6.17.0-rc3/tools/perf/util/python.c: In function ‘pyrf_evlist__compute_metric’:
/git/perf-6.17.0-rc3/tools/perf/util/python.c:1363:3: error: ‘thread_idx’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
evsel__read_counter(metric_events[i], cpu_idx, thread_idx);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/git/perf-6.17.0-rc3/tools/perf/util/python.c:1389:41: note: ‘thread_idx’ was declared here
int ret, cpu = 0, cpu_idx, thread = 0, thread_idx;
^~~~~~~~~~
/git/perf-6.17.0-rc3/tools/perf/util/python.c:1363:3: error: ‘cpu_idx’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
evsel__read_counter(metric_events[i], cpu_idx, thread_idx);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/git/perf-6.17.0-rc3/tools/perf/util/python.c:1389:20: note: ‘cpu_idx’ was declared here
int ret, cpu = 0, cpu_idx, thread = 0, thread_idx;
^~~~~~~
/git/perf-6.17.0-rc3/tools/perf/util/python.c: At top level:
cc1: error: unrecognized command line option ‘-Wno-cast-function-type’ [-Werror]
cc1: all warnings being treated as errors
error: command 'gcc' failed with exit status 1
cp: cannot stat '/tmp/build/perf/python_ext_build/lib/perf*.so': No such file or directory
- Arnaldo
> Signed-off-by: Ian Rogers <irogers@google.com>
> Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
> Reviewed-by: Howard Chu <howardchu95@gmail.com>
> ---
> tools/perf/util/python.c | 125 +++++++++++++++++++++++++++++++++++++++
> 1 file changed, 125 insertions(+)
>
> diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
> index 31089f8e5519..e0769538b8d9 100644
> --- a/tools/perf/util/python.c
> +++ b/tools/perf/util/python.c
> @@ -14,6 +14,7 @@
> #include "evlist.h"
> #include "evsel.h"
> #include "event.h"
> +#include "expr.h"
> #include "print_binary.h"
> #include "record.h"
> #include "strbuf.h"
> @@ -1330,6 +1331,124 @@ static PyObject *pyrf_evlist__metrics(struct pyrf_evlist *pevlist)
> return list;
> }
>
> +static int prepare_metric(const struct metric_expr *mexp,
> + const struct evsel *evsel,
> + struct expr_parse_ctx *pctx,
> + int cpu_idx, int thread_idx)
> +{
> + struct evsel * const *metric_events = mexp->metric_events;
> + struct metric_ref *metric_refs = mexp->metric_refs;
> +
> + for (int i = 0; metric_events[i]; i++) {
> + char *n = strdup(evsel__metric_id(metric_events[i]));
> + double val, ena, run;
> + int source_count = evsel__source_count(metric_events[i]);
> + int ret;
> + struct perf_counts_values *old_count, *new_count;
> +
> + if (!n)
> + return -ENOMEM;
> +
> + if (source_count == 0)
> + source_count = 1;
> +
> + ret = evsel__ensure_counts(metric_events[i]);
> + if (ret)
> + return ret;
> +
> + /* Set up pointers to the old and newly read counter values. */
> + old_count = perf_counts(metric_events[i]->prev_raw_counts, cpu_idx, thread_idx);
> + new_count = perf_counts(metric_events[i]->counts, cpu_idx, thread_idx);
> + /* Update the value in metric_events[i]->counts. */
> + evsel__read_counter(metric_events[i], cpu_idx, thread_idx);
> +
> + val = new_count->val - old_count->val;
> + ena = new_count->ena - old_count->ena;
> + run = new_count->run - old_count->run;
> +
> + if (ena != run && run != 0)
> + val = val * ena / run;
> + ret = expr__add_id_val_source_count(pctx, n, val, source_count);
> + if (ret)
> + return ret;
> + }
> +
> + for (int i = 0; metric_refs && metric_refs[i].metric_name; i++) {
> + int ret = expr__add_ref(pctx, &metric_refs[i]);
> +
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist,
> + PyObject *args, PyObject *kwargs)
> +{
> + int ret, cpu = 0, cpu_idx, thread = 0, thread_idx;
> + const char *metric;
> + struct rb_node *node;
> + struct metric_expr *mexp = NULL;
> + struct expr_parse_ctx *pctx;
> + double result = 0;
> +
> + if (!PyArg_ParseTuple(args, "sii", &metric, &cpu, &thread))
> + return NULL;
> +
> + for (node = rb_first_cached(&pevlist->evlist.metric_events.entries);
> + mexp == NULL && node;
> + node = rb_next(node)) {
> + struct metric_event *me = container_of(node, struct metric_event, nd);
> + struct list_head *pos;
> +
> + list_for_each(pos, &me->head) {
> + struct metric_expr *e = container_of(pos, struct metric_expr, nd);
> +
> + if (strcmp(e->metric_name, metric))
> + continue;
> +
> + if (e->metric_events[0] == NULL)
> + continue;
> +
> + cpu_idx = perf_cpu_map__idx(e->metric_events[0]->core.cpus,
> + (struct perf_cpu){.cpu = cpu});
> + if (cpu_idx < 0)
> + continue;
> +
> + thread_idx = perf_thread_map__idx(e->metric_events[0]->core.threads,
> + thread);
> + if (thread_idx < 0)
> + continue;
> +
> + mexp = e;
> + break;
> + }
> + }
> + if (!mexp) {
> + PyErr_Format(PyExc_TypeError, "Unknown metric '%s' for CPU '%d' and thread '%d'",
> + metric, cpu, thread);
> + return NULL;
> + }
> +
> + pctx = expr__ctx_new();
> + if (!pctx)
> + return PyErr_NoMemory();
> +
> + ret = prepare_metric(mexp, mexp->metric_events[0], pctx, cpu_idx, thread_idx);
> + if (ret) {
> + expr__ctx_free(pctx);
> + errno = -ret;
> + PyErr_SetFromErrno(PyExc_OSError);
> + return NULL;
> + }
> + if (expr__parse(&result, pctx, mexp->metric_expr))
> + result = 0.0;
> +
> + expr__ctx_free(pctx);
> + return PyFloat_FromDouble(result);
> +}
> +
> static PyObject *pyrf_evlist__mmap(struct pyrf_evlist *pevlist,
> PyObject *args, PyObject *kwargs)
> {
> @@ -1564,6 +1683,12 @@ static PyMethodDef pyrf_evlist__methods[] = {
> .ml_flags = METH_NOARGS,
> .ml_doc = PyDoc_STR("List of metric names within the evlist.")
> },
> + {
> + .ml_name = "compute_metric",
> + .ml_meth = (PyCFunction)pyrf_evlist__compute_metric,
> + .ml_flags = METH_VARARGS | METH_KEYWORDS,
> + .ml_doc = PyDoc_STR("compute metric for given name, cpu and thread")
> + },
> {
> .ml_name = "mmap",
> .ml_meth = (PyCFunction)pyrf_evlist__mmap,
> --
> 2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v10 08/11] perf python: Add evlist compute_metric
2025-09-03 13:35 ` Arnaldo Carvalho de Melo
@ 2025-09-03 15:56 ` Ian Rogers
0 siblings, 0 replies; 15+ messages in thread
From: Ian Rogers @ 2025-09-03 15:56 UTC (permalink / raw)
To: Arnaldo Carvalho de Melo
Cc: Peter Zijlstra, Ingo Molnar, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Adrian Hunter, Kan Liang,
James Clark, Xu Yang, Masami Hiramatsu (Google), Collin Funk,
Howard Chu, Weilin Wang, Andi Kleen, Dr. David Alan Gilbert,
Thomas Richter, Tiezhu Yang, Gautam Menghani, Thomas Falcon,
Chun-Tse Shao, linux-kernel, linux-perf-users,
Arnaldo Carvalho de Melo
On Wed, Sep 3, 2025 at 6:36 AM Arnaldo Carvalho de Melo <acme@kernel.org> wrote:
>
> On Mon, Aug 18, 2025 at 06:39:38PM -0700, Ian Rogers wrote:
> > Add a compute_metric function that computes a metric double value for a
> > given evlist, metric name, CPU and thread. For example:
> > ```
> > >>> import perf
> > >>> x = perf.parse_metrics("TopdownL1")
> > >>> x.open()
> > >>> x.enable()
> > >>> x.disable()
> > >>> x.metrics()
> > ['tma_bad_speculation', 'tma_frontend_bound', 'tma_backend_bound', 'tma_retiring']
> > >>> x.compute_metric('tma_bad_speculation', 0, -1)
> > 0.08605342847131037
> > ```
>
> Added the following to fix the build on the still not EOLed OpenSUSE
> 15, ok?
Looks good to me!
Thanks,
Ian
> - Arnaldo
>
> diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
> index 56102034d5b8c469..47178404802f4069 100644
> --- a/tools/perf/util/python.c
> +++ b/tools/perf/util/python.c
> @@ -1386,7 +1386,7 @@ static int prepare_metric(const struct metric_expr *mexp,
> static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist,
> PyObject *args, PyObject *kwargs)
> {
> - int ret, cpu = 0, cpu_idx, thread = 0, thread_idx;
> + int ret, cpu = 0, cpu_idx = 0, thread = 0, thread_idx = 0;
> const char *metric;
> struct rb_node *node;
> struct metric_expr *mexp = NULL;
>
> Committer notes:
>
> Initialize thread_idx and cpu_idx to zero as albeit them not possibly
> coming out unitialized from the loop as mexp would be not NULL only if
> they were initialized, some older compilers don't notice that and error
> with:
>
> GEN /tmp/build/perf/python/perf.cpython-36m-x86_64-linux-gnu.so
> /git/perf-6.17.0-rc3/tools/perf/util/python.c: In function ‘pyrf_evlist__compute_metric’:
> /git/perf-6.17.0-rc3/tools/perf/util/python.c:1363:3: error: ‘thread_idx’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
> evsel__read_counter(metric_events[i], cpu_idx, thread_idx);
> ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> /git/perf-6.17.0-rc3/tools/perf/util/python.c:1389:41: note: ‘thread_idx’ was declared here
> int ret, cpu = 0, cpu_idx, thread = 0, thread_idx;
> ^~~~~~~~~~
> /git/perf-6.17.0-rc3/tools/perf/util/python.c:1363:3: error: ‘cpu_idx’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
> evsel__read_counter(metric_events[i], cpu_idx, thread_idx);
> ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> /git/perf-6.17.0-rc3/tools/perf/util/python.c:1389:20: note: ‘cpu_idx’ was declared here
> int ret, cpu = 0, cpu_idx, thread = 0, thread_idx;
> ^~~~~~~
> /git/perf-6.17.0-rc3/tools/perf/util/python.c: At top level:
> cc1: error: unrecognized command line option ‘-Wno-cast-function-type’ [-Werror]
> cc1: all warnings being treated as errors
> error: command 'gcc' failed with exit status 1
> cp: cannot stat '/tmp/build/perf/python_ext_build/lib/perf*.so': No such file or directory
>
> - Arnaldo
>
> > Signed-off-by: Ian Rogers <irogers@google.com>
> > Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
> > Reviewed-by: Howard Chu <howardchu95@gmail.com>
> > ---
> > tools/perf/util/python.c | 125 +++++++++++++++++++++++++++++++++++++++
> > 1 file changed, 125 insertions(+)
> >
> > diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
> > index 31089f8e5519..e0769538b8d9 100644
> > --- a/tools/perf/util/python.c
> > +++ b/tools/perf/util/python.c
> > @@ -14,6 +14,7 @@
> > #include "evlist.h"
> > #include "evsel.h"
> > #include "event.h"
> > +#include "expr.h"
> > #include "print_binary.h"
> > #include "record.h"
> > #include "strbuf.h"
> > @@ -1330,6 +1331,124 @@ static PyObject *pyrf_evlist__metrics(struct pyrf_evlist *pevlist)
> > return list;
> > }
> >
> > +static int prepare_metric(const struct metric_expr *mexp,
> > + const struct evsel *evsel,
> > + struct expr_parse_ctx *pctx,
> > + int cpu_idx, int thread_idx)
> > +{
> > + struct evsel * const *metric_events = mexp->metric_events;
> > + struct metric_ref *metric_refs = mexp->metric_refs;
> > +
> > + for (int i = 0; metric_events[i]; i++) {
> > + char *n = strdup(evsel__metric_id(metric_events[i]));
> > + double val, ena, run;
> > + int source_count = evsel__source_count(metric_events[i]);
> > + int ret;
> > + struct perf_counts_values *old_count, *new_count;
> > +
> > + if (!n)
> > + return -ENOMEM;
> > +
> > + if (source_count == 0)
> > + source_count = 1;
> > +
> > + ret = evsel__ensure_counts(metric_events[i]);
> > + if (ret)
> > + return ret;
> > +
> > + /* Set up pointers to the old and newly read counter values. */
> > + old_count = perf_counts(metric_events[i]->prev_raw_counts, cpu_idx, thread_idx);
> > + new_count = perf_counts(metric_events[i]->counts, cpu_idx, thread_idx);
> > + /* Update the value in metric_events[i]->counts. */
> > + evsel__read_counter(metric_events[i], cpu_idx, thread_idx);
> > +
> > + val = new_count->val - old_count->val;
> > + ena = new_count->ena - old_count->ena;
> > + run = new_count->run - old_count->run;
> > +
> > + if (ena != run && run != 0)
> > + val = val * ena / run;
> > + ret = expr__add_id_val_source_count(pctx, n, val, source_count);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + for (int i = 0; metric_refs && metric_refs[i].metric_name; i++) {
> > + int ret = expr__add_ref(pctx, &metric_refs[i]);
> > +
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist,
> > + PyObject *args, PyObject *kwargs)
> > +{
> > + int ret, cpu = 0, cpu_idx, thread = 0, thread_idx;
> > + const char *metric;
> > + struct rb_node *node;
> > + struct metric_expr *mexp = NULL;
> > + struct expr_parse_ctx *pctx;
> > + double result = 0;
> > +
> > + if (!PyArg_ParseTuple(args, "sii", &metric, &cpu, &thread))
> > + return NULL;
> > +
> > + for (node = rb_first_cached(&pevlist->evlist.metric_events.entries);
> > + mexp == NULL && node;
> > + node = rb_next(node)) {
> > + struct metric_event *me = container_of(node, struct metric_event, nd);
> > + struct list_head *pos;
> > +
> > + list_for_each(pos, &me->head) {
> > + struct metric_expr *e = container_of(pos, struct metric_expr, nd);
> > +
> > + if (strcmp(e->metric_name, metric))
> > + continue;
> > +
> > + if (e->metric_events[0] == NULL)
> > + continue;
> > +
> > + cpu_idx = perf_cpu_map__idx(e->metric_events[0]->core.cpus,
> > + (struct perf_cpu){.cpu = cpu});
> > + if (cpu_idx < 0)
> > + continue;
> > +
> > + thread_idx = perf_thread_map__idx(e->metric_events[0]->core.threads,
> > + thread);
> > + if (thread_idx < 0)
> > + continue;
> > +
> > + mexp = e;
> > + break;
> > + }
> > + }
> > + if (!mexp) {
> > + PyErr_Format(PyExc_TypeError, "Unknown metric '%s' for CPU '%d' and thread '%d'",
> > + metric, cpu, thread);
> > + return NULL;
> > + }
> > +
> > + pctx = expr__ctx_new();
> > + if (!pctx)
> > + return PyErr_NoMemory();
> > +
> > + ret = prepare_metric(mexp, mexp->metric_events[0], pctx, cpu_idx, thread_idx);
> > + if (ret) {
> > + expr__ctx_free(pctx);
> > + errno = -ret;
> > + PyErr_SetFromErrno(PyExc_OSError);
> > + return NULL;
> > + }
> > + if (expr__parse(&result, pctx, mexp->metric_expr))
> > + result = 0.0;
> > +
> > + expr__ctx_free(pctx);
> > + return PyFloat_FromDouble(result);
> > +}
> > +
> > static PyObject *pyrf_evlist__mmap(struct pyrf_evlist *pevlist,
> > PyObject *args, PyObject *kwargs)
> > {
> > @@ -1564,6 +1683,12 @@ static PyMethodDef pyrf_evlist__methods[] = {
> > .ml_flags = METH_NOARGS,
> > .ml_doc = PyDoc_STR("List of metric names within the evlist.")
> > },
> > + {
> > + .ml_name = "compute_metric",
> > + .ml_meth = (PyCFunction)pyrf_evlist__compute_metric,
> > + .ml_flags = METH_VARARGS | METH_KEYWORDS,
> > + .ml_doc = PyDoc_STR("compute metric for given name, cpu and thread")
> > + },
> > {
> > .ml_name = "mmap",
> > .ml_meth = (PyCFunction)pyrf_evlist__mmap,
> > --
> > 2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2025-09-03 15:56 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-19 1:39 [PATCH v10 00/11] New perf ilist app Ian Rogers
2025-08-19 1:39 ` [PATCH v10 01/11] perf python: Add more exceptions on error paths Ian Rogers
2025-08-19 1:39 ` [PATCH v10 02/11] perf python: Improve the tracepoint function if no libtraceevent Ian Rogers
2025-08-19 1:39 ` [PATCH v10 03/11] perf python: Add basic PMU abstraction and pmus sequence Ian Rogers
2025-08-19 1:39 ` [PATCH v10 04/11] perf python: Add function returning dictionary of all events on a PMU Ian Rogers
2025-08-19 1:39 ` [PATCH v10 05/11] perf ilist: Add new python ilist command Ian Rogers
2025-08-19 1:39 ` [PATCH v10 06/11] perf python: Add parse_metrics function Ian Rogers
2025-08-19 1:39 ` [PATCH v10 07/11] perf python: Add evlist metrics function Ian Rogers
2025-08-19 1:39 ` [PATCH v10 08/11] perf python: Add evlist compute_metric Ian Rogers
2025-09-03 13:35 ` Arnaldo Carvalho de Melo
2025-09-03 15:56 ` Ian Rogers
2025-08-19 1:39 ` [PATCH v10 09/11] perf python: Add metrics function Ian Rogers
2025-08-19 1:39 ` [PATCH v10 10/11] perf ilist: Add support for metrics Ian Rogers
2025-08-19 1:39 ` [PATCH v10 11/11] perf tp_pmu: Remove unnecessary check Ian Rogers
2025-09-02 20:06 ` [PATCH v10 00/11] New perf ilist app Arnaldo Carvalho de Melo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).