[PATCH v10 08/11] perf python: Add evlist compute_metric

Ian Rogers posted 11 patches 1 month, 2 weeks ago
[PATCH v10 08/11] perf python: Add evlist compute_metric
Posted by Ian Rogers 1 month, 2 weeks ago
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
Re: [PATCH v10 08/11] perf python: Add evlist compute_metric
Posted by Arnaldo Carvalho de Melo 1 month ago
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
Re: [PATCH v10 08/11] perf python: Add evlist compute_metric
Posted by Ian Rogers 1 month ago
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