From: Ian Rogers <irogers@google.com>
To: Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>
Cc: Jiri Olsa <jolsa@kernel.org>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org,
Ian Rogers <irogers@google.com>
Subject: [PATCH 07/11] perf metricgroup: Route metric and metricgroup lookups through struct perf_pmu
Date: Fri, 13 Nov 2026 13:10:58 -0800 [thread overview]
Message-ID: <20261113211105.3118281-8-irogers@google.com> (raw)
In-Reply-To: <20261113211105.3118281-1-irogers@google.com>
Expose metric and metricgroup lookup and enumeration helpers on struct
perf_pmu and perf_pmus (perf_pmu__find_metric(),
perf_pmu__for_each_metric(), perf_pmu__describe_metricgroup(),
perf_pmus__find_metric(), perf_pmus__find_metric_pmu(),
perf_pmus__for_each_metric(), perf_pmus__for_each_pmu_metric(), and
perf_pmus__describe_metricgroup()) backed by the layered PMU VFS stack,
using struct pmu_metric at the public boundary so struct perf_pmu_metric
remains private.
Scan all core and uncore PMUs without suffix collapsing so heterogeneous
PMUs are preserved, deduplicating identical shared metrics across PMU
instances while resolving sub-metrics and thresholds on their
originating struct perf_pmu and letting user metricgroup overrides on
any PMU take precedence over compiled-in defaults.
Update live callers in metricgroup.c, print-events.c, builtin-list.c,
builtin-script.c, and util/python.c to query metrics and metricgroup
descriptions through struct perf_pmu rather than reading compiled-in
pmu_metrics_table arrays directly, while retaining explicit table
overrides for fake_pmu unit tests.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/builtin-list.c | 2 +-
tools/perf/builtin-script.c | 2 +-
tools/perf/util/metricgroup.c | 369 +++++++++++++++++++++++++++------
tools/perf/util/pmu.c | 65 +++++-
tools/perf/util/pmu.h | 7 +
tools/perf/util/pmus.c | 237 ++++++++++++++++++++-
tools/perf/util/pmus.h | 13 ++
tools/perf/util/print-events.c | 4 +-
tools/perf/util/python.c | 5 +-
9 files changed, 628 insertions(+), 76 deletions(-)
diff --git a/tools/perf/builtin-list.c b/tools/perf/builtin-list.c
index 47668d66c395..53e1834233c8 100644
--- a/tools/perf/builtin-list.c
+++ b/tools/perf/builtin-list.c
@@ -232,7 +232,7 @@ static void default_print_metric(void *ps,
fprintf(fp, "%s ", group);
} else {
const char *gdesc = print_state->desc
- ? describe_metricgroup(group)
+ ? perf_pmus__describe_metricgroup(group)
: NULL;
const char *print_colon = "";
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index b6691ebb1b4e..f4c61172eb84 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2546,7 +2546,7 @@ static void perf_sample__fprint_metric(struct thread *thread,
/*needs_sort=*/false);
}
- metricgroup__for_each_metric(pmu_metrics_table__find(), script_find_metrics, &args);
+ metricgroup__for_each_metric(/*table=*/NULL, script_find_metrics, &args);
init_metrics = true;
}
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 779fe56fe8d0..ebe9873d273f 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -128,6 +128,7 @@ struct metric {
*/
struct expr_parse_ctx *pctx;
const char *pmu;
+ struct perf_pmu *source_pmu;
/** The name of the metric such as "IPC". */
const char *metric_name;
/** Modifier on the metric such as "u" or NULL for none. */
@@ -219,6 +220,7 @@ static void metric__free(struct metric *m)
}
static struct metric *metric__new(const struct pmu_metric *pm,
+ struct perf_pmu *source_pmu,
const char *modifier,
bool metric_no_group,
bool metric_no_threshold,
@@ -237,7 +239,9 @@ static struct metric *metric__new(const struct pmu_metric *pm,
if (!m->pctx)
goto out_err;
- m->pmu = pm->pmu ?: "cpu";
+ m->pmu = (source_pmu && source_pmu->is_core && (!pm->pmu || !pm->pmu[0]))
+ ? "default_core" : (pm->pmu ?: "cpu");
+ m->source_pmu = source_pmu;
m->metric_name = pm->metric_name;
m->default_metricgroup_name = pm->default_metricgroup_name ?: "";
m->modifier = NULL;
@@ -281,6 +285,16 @@ static bool contains_metric_id(struct evsel **metric_events, int num_events,
return false;
}
+static bool is_core_pmu_name(const char *pmu)
+{
+ struct perf_pmu *perf_pmu;
+
+ if (is_pmu_core(pmu))
+ return true;
+ perf_pmu = perf_pmus__find(pmu);
+ return perf_pmu && perf_pmu->is_core;
+}
+
/**
* setup_metric_events - Find a group of events in metric_evlist that correspond
* to the IDs from a parsed metric expression.
@@ -298,7 +312,7 @@ static int setup_metric_events(const char *pmu, struct hashmap *ids,
struct evsel *ev;
size_t ids_size, matched_events, i;
bool all_pmus = !strcmp(pmu, "all") || !strcmp(pmu, "default_core") ||
- perf_pmus__num_core_pmus() == 1 || !is_pmu_core(pmu);
+ perf_pmus__num_core_pmus() == 1 || !is_core_pmu_name(pmu);
*out_metric_events = NULL;
ids_size = hashmap__size(ids);
@@ -359,6 +373,10 @@ static int setup_metric_events(const char *pmu, struct hashmap *ids,
*/
metric_id = evsel__metric_id(ev);
evlist__for_each_entry_continue(metric_evlist, ev) {
+ /* Don't match events for the wrong hybrid PMU. */
+ if (!all_pmus && ev->pmu && evsel__is_hybrid(ev) &&
+ strcmp(ev->pmu->name, pmu))
+ continue;
if (!strcmp(evsel__metric_id(ev), metric_id))
ev->metric_leader = metric_events[i];
}
@@ -386,18 +404,32 @@ static bool match_metric_or_groups(const char *metric_or_groups, const char *sou
return m && match_metric_or_groups(m + 1, sought);
}
-static bool match_pm_metric_or_groups(const struct pmu_metric *pm, const char *pmu,
+static bool match_pm_metric_or_groups(const struct pmu_metric *pm,
+ struct perf_pmu *source_pmu,
+ const char *pmu,
const char *metric_or_groups)
{
const char *pm_pmu = pm->pmu ?: "cpu";
struct perf_pmu *perf_pmu = NULL;
- if (pm->pmu)
- perf_pmu = perf_pmus__find(pm->pmu);
-
- if (strcmp(pmu, "all") && strcmp(pm_pmu, pmu) &&
- (perf_pmu && !perf_pmu__name_wildcard_match(perf_pmu, pmu)))
- return false;
+ if (strcmp(pmu, "all")) {
+ if (source_pmu) {
+ if (!perf_pmu__name_wildcard_match(source_pmu, pmu) &&
+ !perf_pmu__wildcard_match(source_pmu, pmu))
+ return false;
+ } else if (!strcmp(pm_pmu, "default_core") && is_core_pmu_name(pmu)) {
+ /* default_core matches any core PMU filter. */
+ } else {
+ if (pm->pmu)
+ perf_pmu = perf_pmus__find(pm->pmu);
+
+ if (strcmp(pm_pmu, pmu) &&
+ (!perf_pmu ||
+ (!perf_pmu__name_wildcard_match(perf_pmu, pmu) &&
+ !perf_pmu__wildcard_match(perf_pmu, pmu))))
+ return false;
+ }
+ }
return match_metric_or_groups(pm->metric_group, metric_or_groups) ||
match_metric_or_groups(pm->metric_name, metric_or_groups);
@@ -435,6 +467,9 @@ int metricgroup__for_each_metric(const struct pmu_metrics_table *table, pmu_metr
pmu_metrics_table__default(),
};
+ if (!table)
+ return perf_pmus__for_each_metric(fn, data);
+
for (size_t i = 0; i < ARRAY_SIZE(tables); i++) {
int ret;
@@ -696,6 +731,7 @@ struct metricgroup_add_iter_data {
static int add_metric(struct list_head *metric_list,
const struct pmu_metric *pm,
+ struct perf_pmu *source_pmu,
const char *modifier,
bool metric_no_group,
bool metric_no_threshold,
@@ -720,11 +756,13 @@ static int metricgroup__find_metric_callback(const struct pmu_metric *pm,
* resolve_metric - Locate metrics within the root metric and recursively add
* references to them.
* @metric_list: The list the metric is added to.
- * @pmu: The PMU name to resolve metrics on, or "all" for all PMUs.
+ * @pm_pmu: The PMU name pattern to resolve metrics on when @pmu is NULL.
+ * @pmu: Originating PMU struct when known, or target PMU for @fake_pmu.
* @modifier: if non-null event modifiers like "u".
* @metric_no_group: Should events written to events be grouped "{}" or
* global. Grouping is the default but due to multiplexing the
* user may override.
+ * @metric_no_threshold: Should threshold expressions be ignored?
* @user_requested_cpu_list: Command line specified CPUs to record on.
* @system_wide: Are events for all processes recorded.
* @fake_pmu: Are PMUs and events being faked for testing?
@@ -737,6 +775,7 @@ static int metricgroup__find_metric_callback(const struct pmu_metric *pm,
* architecture perf is running upon.
*/
static int resolve_metric(struct list_head *metric_list,
+ const char *pm_pmu,
struct perf_pmu *pmu,
const char *modifier,
bool metric_no_group,
@@ -767,10 +806,18 @@ static int resolve_metric(struct list_head *metric_list,
*/
hashmap__for_each_entry(root_metric->pctx->ids, cur, bkt) {
struct pmu_metric pm;
+ bool found;
- if (pmu_metrics_table__find_metric(table, pmu, cur->pkey,
- metricgroup__find_metric_callback,
- &pm) != PMU_METRICS__NOT_FOUND) {
+ if (fake_pmu && table) {
+ found = pmu_metrics_table__find_metric(table, pmu, cur->pkey,
+ metricgroup__find_metric_callback,
+ &pm) != PMU_METRICS__NOT_FOUND;
+ } else {
+ found = pmu ? perf_pmu__find_metric(pmu, cur->pkey, &pm)
+ : perf_pmus__find_metric_pmu(pm_pmu, cur->pkey, &pm);
+ }
+
+ if (found) {
pending = realloc(pending,
(pending_cnt + 1) * sizeof(struct to_resolve));
if (!pending)
@@ -791,8 +838,9 @@ static int resolve_metric(struct list_head *metric_list,
* context.
*/
for (i = 0; i < pending_cnt; i++) {
- ret = add_metric(metric_list, &pending[i].pm, modifier, metric_no_group,
- metric_no_threshold, user_requested_cpu_list, system_wide,
+ ret = add_metric(metric_list, &pending[i].pm, pmu, modifier,
+ metric_no_group, metric_no_threshold,
+ user_requested_cpu_list, system_wide,
fake_pmu, root_metric, visited, table);
if (ret)
break;
@@ -806,6 +854,7 @@ static int resolve_metric(struct list_head *metric_list,
* __add_metric - Add a metric to metric_list.
* @metric_list: The list the metric is added to.
* @pm: The pmu_metric containing the metric to be added.
+ * @source_pmu: Originating PMU struct when known, or NULL.
* @modifier: if non-null event modifiers like "u".
* @metric_no_group: Should events written to events be grouped "{}" or
* global. Grouping is the default but due to multiplexing the
@@ -825,6 +874,7 @@ static int resolve_metric(struct list_head *metric_list,
*/
static int __add_metric(struct list_head *metric_list,
const struct pmu_metric *pm,
+ struct perf_pmu *source_pmu,
const char *modifier,
bool metric_no_group,
bool metric_no_threshold,
@@ -857,8 +907,10 @@ static int __add_metric(struct list_head *metric_list,
* This metric is the root of a tree and may reference other
* metrics that are added recursively.
*/
- root_metric = metric__new(pm, modifier, metric_no_group, metric_no_threshold,
- runtime, user_requested_cpu_list, system_wide, fake_pmu);
+ root_metric = metric__new(pm, source_pmu, modifier,
+ metric_no_group, metric_no_threshold,
+ runtime, user_requested_cpu_list,
+ system_wide, fake_pmu);
if (!root_metric)
return -ENOMEM;
@@ -922,14 +974,17 @@ static int __add_metric(struct list_head *metric_list,
if (!ret) {
/* Resolve referenced metrics. */
- struct perf_pmu *pmu;
+ const char *pm_pmu = (pm->pmu && pm->pmu[0]) ? pm->pmu : "default_core";
+ struct perf_pmu *pmu = source_pmu;
- if (pm->pmu && pm->pmu[0] != '\0')
- pmu = perf_pmus__find(pm->pmu);
- else
- pmu = perf_pmus__scan_core(/*pmu=*/ NULL);
+ if (!pmu && fake_pmu && table) {
+ if (pm->pmu && pm->pmu[0] != '\0')
+ pmu = perf_pmus__find(pm->pmu);
+ else
+ pmu = perf_pmus__scan_core(/*pmu=*/NULL);
+ }
- ret = resolve_metric(metric_list, pmu, modifier, metric_no_group,
+ ret = resolve_metric(metric_list, pm_pmu, pmu, modifier, metric_no_group,
metric_no_threshold, user_requested_cpu_list,
system_wide, fake_pmu, root_metric, &visited_node,
table);
@@ -946,6 +1001,7 @@ static int __add_metric(struct list_head *metric_list,
static int add_metric(struct list_head *metric_list,
const struct pmu_metric *pm,
+ struct perf_pmu *source_pmu,
const char *modifier,
bool metric_no_group,
bool metric_no_threshold,
@@ -961,9 +1017,10 @@ static int add_metric(struct list_head *metric_list,
pr_debug("metric expr %s for %s\n", pm->metric_expr, pm->metric_name);
if (!strstr(pm->metric_expr, "?")) {
- ret = __add_metric(metric_list, pm, modifier, metric_no_group,
- metric_no_threshold, 0, user_requested_cpu_list,
- system_wide, fake_pmu, root_metric, visited, table);
+ ret = __add_metric(metric_list, pm, source_pmu, modifier,
+ metric_no_group, metric_no_threshold, 0,
+ user_requested_cpu_list, system_wide,
+ fake_pmu, root_metric, visited, table);
} else {
int j, count;
@@ -975,9 +1032,10 @@ static int add_metric(struct list_head *metric_list,
*/
for (j = 0; j < count && !ret; j++)
- ret = __add_metric(metric_list, pm, modifier, metric_no_group,
- metric_no_threshold, j, user_requested_cpu_list,
- system_wide, fake_pmu, root_metric, visited, table);
+ ret = __add_metric(metric_list, pm, source_pmu, modifier,
+ metric_no_group, metric_no_threshold, j,
+ user_requested_cpu_list, system_wide,
+ fake_pmu, root_metric, visited, table);
}
return ret;
@@ -1042,26 +1100,141 @@ struct metricgroup__add_metric_data {
bool has_match;
};
-static int metricgroup__add_metric_callback(const struct pmu_metric *pm,
- const struct pmu_metrics_table *table,
- void *vdata)
+static size_t metric_refs_count(const struct metric_ref *refs)
+{
+ size_t n = 0;
+
+ if (!refs)
+ return 0;
+ while (refs[n].metric_name)
+ n++;
+ return n;
+}
+
+static bool metric__same_definition(const struct metric *a, const struct metric *b)
+{
+ size_t na, nb;
+
+ if (strcmp(a->metric_name ?: "", b->metric_name ?: "") ||
+ strcmp(a->modifier ?: "", b->modifier ?: "") ||
+ strcmp(a->metric_expr ?: "", b->metric_expr ?: "") ||
+ strcmp(a->metric_threshold ?: "", b->metric_threshold ?: "") ||
+ a->pctx->sctx.runtime != b->pctx->sctx.runtime)
+ return false;
+
+ na = metric_refs_count(a->metric_refs);
+ nb = metric_refs_count(b->metric_refs);
+ if (na != nb)
+ return false;
+
+ for (size_t i = 0; i < na; i++) {
+ bool found = false;
+
+ for (size_t j = 0; j < nb; j++) {
+ if (!strcmp(a->metric_refs[i].metric_name,
+ b->metric_refs[j].metric_name) &&
+ !strcmp(a->metric_refs[i].metric_expr ?: "",
+ b->metric_refs[j].metric_expr ?: "")) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ return false;
+ }
+ return true;
+}
+
+static void metricgroup__dedup_and_pin_metrics(struct list_head *list)
+{
+ struct metric *m, *other, *tmp;
+ int total_core_pmus = perf_pmus__num_core_pmus();
+
+ if (total_core_pmus > 1) {
+ list_for_each_entry(m, list, nd) {
+ size_t core_matches = 0;
+ bool uniform = true;
+
+ if (!m->source_pmu || !m->source_pmu->is_core ||
+ strcmp(m->pmu, "default_core") != 0)
+ continue;
+
+ list_for_each_entry(other, list, nd) {
+ if (!other->source_pmu || !other->source_pmu->is_core ||
+ strcmp(other->pmu, "default_core") != 0)
+ continue;
+ if (strcmp(m->metric_name ?: "", other->metric_name ?: "") ||
+ strcmp(m->modifier ?: "", other->modifier ?: "") ||
+ m->pctx->sctx.runtime != other->pctx->sctx.runtime)
+ continue;
+ core_matches++;
+ if (!metric__same_definition(m, other))
+ uniform = false;
+ }
+ if (!uniform || core_matches < (size_t)total_core_pmus) {
+ list_for_each_entry(other, list, nd) {
+ if (!other->source_pmu ||
+ !other->source_pmu->is_core ||
+ strcmp(other->pmu, "default_core") != 0)
+ continue;
+ if (strcmp(m->metric_name ?: "",
+ other->metric_name ?: "") ||
+ strcmp(m->modifier ?: "",
+ other->modifier ?: "") ||
+ m->pctx->sctx.runtime !=
+ other->pctx->sctx.runtime)
+ continue;
+ other->pmu = other->source_pmu->name;
+ }
+ }
+ }
+ }
+
+ list_for_each_entry_safe(m, tmp, list, nd) {
+ list_for_each_entry(other, list, nd) {
+ if (other == m)
+ break;
+ if (!strcmp(other->pmu ?: "", m->pmu ?: "") &&
+ metric__same_definition(other, m)) {
+ list_del_init(&m->nd);
+ metric__free(m);
+ break;
+ }
+ }
+ }
+}
+
+static int metricgroup__add_pmu_metric_callback(struct perf_pmu *pmu,
+ const struct pmu_metric *pm,
+ const struct pmu_metrics_table *table,
+ void *vdata)
{
struct metricgroup__add_metric_data *data = vdata;
int ret = 0;
- if (pm->metric_expr && match_pm_metric_or_groups(pm, data->pmu, data->metric_name)) {
+ if (pm->metric_expr &&
+ match_pm_metric_or_groups(pm, pmu, data->pmu, data->metric_name)) {
bool metric_no_group = data->metric_no_group ||
match_metric_or_groups(pm->metricgroup_no_group, data->metric_name);
data->has_match = true;
- ret = add_metric(data->list, pm, data->modifier, metric_no_group,
- data->metric_no_threshold, data->user_requested_cpu_list,
- data->system_wide, data->fake_pmu, /*root_metric=*/NULL,
+ ret = add_metric(data->list, pm, pmu, data->modifier,
+ metric_no_group, data->metric_no_threshold,
+ data->user_requested_cpu_list,
+ data->system_wide, data->fake_pmu,
+ /*root_metric=*/NULL,
/*visited_metrics=*/NULL, table);
}
return ret;
}
+static int metricgroup__add_metric_callback(const struct pmu_metric *pm,
+ const struct pmu_metrics_table *table,
+ void *vdata)
+{
+ return metricgroup__add_pmu_metric_callback(/*pmu=*/NULL, pm, table, vdata);
+}
+
/**
* metricgroup__add_metric - Find and add a metric, or a metric group.
* @pmu: The PMU name to search for metrics on, or "all" for all PMUs.
@@ -1105,7 +1278,16 @@ static int metricgroup__add_metric(const char *pmu, const char *metric_name, con
* Iterate over all metrics seeing if metric matches either the
* name or group. When it does add the metric to the list.
*/
- ret = metricgroup__for_each_metric(table, metricgroup__add_metric_callback, &data);
+ if (!table) {
+ ret = perf_pmus__for_each_pmu_metric(metricgroup__add_pmu_metric_callback,
+ &data);
+ if (!ret)
+ metricgroup__dedup_and_pin_metrics(&list);
+ } else {
+ ret = metricgroup__for_each_metric(table,
+ metricgroup__add_metric_callback,
+ &data);
+ }
if (!ret && !data.has_match)
ret = -ENOENT;
@@ -1219,14 +1401,68 @@ static void find_tool_events(const struct list_head *metric_list,
}
}
+static bool metric__has_unqualified_id(const struct metric *m)
+{
+ struct hashmap_entry *cur;
+ size_t bkt;
+
+ hashmap__for_each_entry(m->pctx->ids, cur, bkt) {
+ const char *id = cur->pkey;
+
+ if (tool_pmu__str_to_event(id) == TOOL_PMU__EVENT_NONE &&
+ !strchr(id, '@'))
+ return true;
+ }
+ return false;
+}
+
+static bool pmu_filter_allows_other_core_pmu(const char *pmu_filter,
+ const char *target_pmu)
+{
+ struct perf_pmu *core_pmu = NULL;
+
+ while ((core_pmu = perf_pmus__scan_core(core_pmu)) != NULL) {
+ if (perf_pmu__name_wildcard_match(core_pmu, target_pmu))
+ continue;
+ if (!pmu_filter || !strcmp(pmu_filter, "all") ||
+ perf_pmu__wildcard_match(core_pmu, pmu_filter) ||
+ perf_pmu__name_wildcard_match(core_pmu, pmu_filter))
+ return true;
+ }
+ return false;
+}
+
+static bool metric__needs_per_pmu_evlist(const struct metric *m,
+ const char *pmu,
+ bool fake_pmu)
+{
+ if (fake_pmu || !m->pmu || !strcmp(m->pmu, "all") ||
+ !strcmp(m->pmu, "default_core") || !is_core_pmu_name(m->pmu))
+ return false;
+
+ return pmu_filter_allows_other_core_pmu(pmu, m->pmu) &&
+ metric__has_unqualified_id(m);
+}
+
+static bool metric__can_use_combined_evlist(const struct metric *m,
+ const char *pmu,
+ bool fake_pmu)
+{
+ return !m->group_events && !m->modifier &&
+ !metric__needs_per_pmu_evlist(m, pmu, fake_pmu);
+}
+
/**
* build_combined_expr_ctx - Make an expr_parse_ctx with all !group_events
* metric IDs, as the IDs are held in a set,
* duplicates will be removed.
* @metric_list: List to take metrics from.
+ * @pmu: Top-level PMU filter, or "all".
+ * @fake_pmu: Are PMUs and events being faked for testing?
* @combined: Out argument for result.
*/
static int build_combined_expr_ctx(const struct list_head *metric_list,
+ const char *pmu, bool fake_pmu,
struct expr_parse_ctx **combined)
{
struct hashmap_entry *cur;
@@ -1240,7 +1476,7 @@ static int build_combined_expr_ctx(const struct list_head *metric_list,
return -ENOMEM;
list_for_each_entry(m, metric_list, nd) {
- if (!m->group_events && !m->modifier) {
+ if (metric__can_use_combined_evlist(m, pmu, fake_pmu)) {
hashmap__for_each_entry(m->pctx->ids, cur, bkt) {
dup = strdup(cur->pkey);
if (!dup) {
@@ -1430,7 +1666,8 @@ static int parse_groups(struct evlist *perf_evlist,
find_tool_events(&metric_list, tool_events);
- ret = build_combined_expr_ctx(&metric_list, &combined);
+ ret = build_combined_expr_ctx(&metric_list, pmu, fake_pmu,
+ &combined);
if (!ret && combined && hashmap__size(combined->ids)) {
ret = parse_ids(metric_no_merge, fake_pmu, combined,
@@ -1458,7 +1695,8 @@ static int parse_groups(struct evlist *perf_evlist,
struct metric *n;
struct metric_expr *expr;
- if (combined_evlist && !m->group_events) {
+ if (combined_evlist &&
+ metric__can_use_combined_evlist(m, pmu, fake_pmu)) {
metric_evlist = combined_evlist;
} else if (!metric_no_merge) {
/*
@@ -1493,10 +1731,23 @@ static int parse_groups(struct evlist *perf_evlist,
}
}
if (!metric_evlist) {
+ const char *m_filter_pmu =
+ (pmu && strcmp(pmu, "all") != 0) ? pmu : NULL;
+ bool m_cputype_filter = cputype_filter;
+
+ if (!fake_pmu && m->pmu &&
+ strcmp(m->pmu, "all") != 0 &&
+ strcmp(m->pmu, "default_core") != 0 &&
+ is_core_pmu_name(m->pmu) &&
+ pmu_filter_allows_other_core_pmu(pmu, m->pmu)) {
+ if (!m_filter_pmu)
+ m_cputype_filter = true;
+ m_filter_pmu = m->pmu;
+ }
+
ret = parse_ids(metric_no_merge, fake_pmu, m->pctx, m->modifier,
m->group_events, tool_events, &m->evlist,
- (pmu && strcmp(pmu, "all") == 0) ? NULL : pmu,
- cputype_filter);
+ m_filter_pmu, m_cputype_filter);
if (ret)
goto out;
@@ -1589,14 +1840,12 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
bool system_wide,
bool hardware_aware_grouping)
{
- const struct pmu_metrics_table *table = pmu_metrics_table__find();
-
if (hardware_aware_grouping)
pr_debug("Use hardware aware grouping instead of traditional metric grouping method\n");
return parse_groups(perf_evlist, pmu, cputype_filter, str, metric_no_group, metric_no_merge,
metric_no_threshold, user_requested_cpu_list, system_wide,
- /*fake_pmu=*/false, table);
+ /*fake_pmu=*/false, /*table=*/NULL);
}
int metricgroup__parse_groups_test(struct evlist *evlist,
@@ -1617,28 +1866,30 @@ struct metricgroup__has_metric_data {
const char *pmu;
const char *metric_or_groups;
};
-static int metricgroup__has_metric_or_groups_callback(const struct pmu_metric *pm,
- const struct pmu_metrics_table *table
- __maybe_unused,
- void *vdata)
+static int metricgroup__has_pmu_metric_or_groups_callback(struct perf_pmu *pmu,
+ const struct pmu_metric *pm,
+ const struct pmu_metrics_table *table
+ __maybe_unused,
+ void *vdata)
{
struct metricgroup__has_metric_data *data = vdata;
- return match_pm_metric_or_groups(pm, data->pmu, data->metric_or_groups) ? 1 : 0;
+ if (!pm->metric_expr)
+ return 0;
+
+ return match_pm_metric_or_groups(pm, pmu, data->pmu,
+ data->metric_or_groups) ? 1 : 0;
}
bool metricgroup__has_metric_or_groups(const char *pmu, const char *metric_or_groups)
{
- const struct pmu_metrics_table *table = pmu_metrics_table__find();
struct metricgroup__has_metric_data data = {
.pmu = pmu,
.metric_or_groups = metric_or_groups,
};
- return metricgroup__for_each_metric(table,
- metricgroup__has_metric_or_groups_callback,
- &data)
- ? true : false;
+ return perf_pmus__for_each_pmu_metric(metricgroup__has_pmu_metric_or_groups_callback,
+ &data) > 0;
}
static int metricgroup__topdown_max_level_callback(const struct pmu_metric *pm,
@@ -1662,13 +1913,9 @@ static int metricgroup__topdown_max_level_callback(const struct pmu_metric *pm,
unsigned int metricgroups__topdown_max_level(void)
{
unsigned int max_level = 0;
- const struct pmu_metrics_table *table = pmu_metrics_table__find();
-
- if (!table)
- return false;
- pmu_metrics_table__for_each_metric(table, metricgroup__topdown_max_level_callback,
- &max_level);
+ perf_pmus__for_each_metric(metricgroup__topdown_max_level_callback,
+ &max_level);
return max_level;
}
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index a3e1630c5a84..9713d266393e 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -421,6 +421,53 @@ static struct perf_pmu_alias *perf_pmu__find_alias(struct perf_pmu *pmu,
return pmu_layer__find_event(pmu, name);
}
+bool perf_pmu__find_metric(struct perf_pmu *pmu, const char *name,
+ struct pmu_metric *out)
+{
+ struct perf_pmu_metric *m = pmu_layer__find_metric(pmu, name);
+
+ if (!m)
+ return false;
+ if (out)
+ perf_pmu_metric__to_pmu_metric(m, out);
+ return true;
+}
+
+int perf_pmu__for_each_metric(struct perf_pmu *pmu, pmu_metric_iter_fn fn,
+ void *data)
+{
+ struct hashmap_entry *entry;
+ size_t bkt;
+ int ret;
+
+ if (!pmu)
+ return 0;
+
+ ret = pmu_layer__load_metrics(pmu);
+ if (ret)
+ return ret;
+
+ hashmap__for_each_entry(pmu->metrics, entry, bkt) {
+ const struct perf_pmu_metric *m = entry->pvalue;
+ struct pmu_metric pm;
+
+ if (!m->metric_expr)
+ continue;
+
+ perf_pmu_metric__to_pmu_metric(m, &pm);
+ ret = fn(&pm, pmu->metrics_table, data);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+const char *perf_pmu__describe_metricgroup(struct perf_pmu *pmu,
+ const char *group)
+{
+ return pmu_layer__describe_metricgroup(pmu, group);
+}
+
static int pmu_alias_terms(struct perf_pmu_alias *alias, struct list_head *terms)
{
struct parse_events_terms alias_terms;
@@ -855,6 +902,22 @@ static __u32 wellknown_pmu_type(const char *pmu_name)
return PERF_TYPE_MAX;
}
+bool perf_pmu__is_core_at(int dirfd, const char *name)
+{
+ int fd;
+
+ if (is_pmu_core(name))
+ return true;
+ if (dirfd < 0)
+ return false;
+
+ fd = perf_pmu__pathname_fd(dirfd, name, "cpus", O_RDONLY);
+ if (fd < 0)
+ return false;
+ close(fd);
+ return true;
+}
+
struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *name,
bool eager_load)
{
@@ -892,7 +955,7 @@ struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char
return NULL;
}
- pmu->is_core = is_pmu_core(name);
+ pmu->is_core = perf_pmu__is_core_at(dirfd, name);
pmu->cpus = pmu_cpumask(dirfd, name, pmu->is_core);
pmu->is_uncore = pmu_is_uncore(dirfd, name);
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 42a0f5655b1c..ef437cc4c7e6 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -270,6 +270,12 @@ typedef int (*pmu_event_callback)(void *state, struct pmu_event_info *info);
typedef int (*pmu_format_callback)(void *state, const char *name, int config,
const unsigned long *bits);
+bool perf_pmu__find_metric(struct perf_pmu *pmu, const char *name,
+ struct pmu_metric *out);
+int perf_pmu__for_each_metric(struct perf_pmu *pmu, pmu_metric_iter_fn fn,
+ void *data);
+const char *perf_pmu__describe_metricgroup(struct perf_pmu *pmu,
+ const char *group);
void pmu_add_sys_aliases(struct perf_pmu *pmu);
int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr,
struct parse_events_terms *head_terms,
@@ -353,6 +359,7 @@ int perf_pmu__event_source_devices_fd(void);
int perf_pmu__pathname_fd(int dirfd, const char *pmu_name, const char *filename, int flags);
int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name);
+bool perf_pmu__is_core_at(int dirfd, const char *name);
struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *lookup_name,
bool eager_load);
struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pmus);
diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c
index 6ecea8f0d382..928dfc80f8db 100644
--- a/tools/perf/util/pmus.c
+++ b/tools/perf/util/pmus.c
@@ -7,7 +7,9 @@
#include <subcmd/pager.h>
#include <sys/types.h>
#include <ctype.h>
+#include <errno.h>
#include <pthread.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -15,6 +17,7 @@
#include "debug.h"
#include "drm_pmu.h"
#include "evsel.h"
+#include "hashmap.h"
#include "pmus.h"
#include "pmu.h"
#include "pmu-layer.h"
@@ -23,6 +26,7 @@
#include "print-events.h"
#include "strbuf.h"
#include "string2.h"
+#include "strlist.h"
/*
* core_pmus: A PMU belongs to core_pmus if it's name is "cpu" or it's sysfs
@@ -61,6 +65,7 @@ enum perf_tool_pmu_type {
PERF_TOOL_PMU_TYPE_DRM_MASK)
};
static unsigned int read_pmu_types;
+static int core_pmus_count;
static void pmu_read_sysfs(unsigned int to_read_pmus);
@@ -125,6 +130,7 @@ void perf_pmus__destroy(void)
perf_pmu__delete(pmu);
}
read_pmu_types = 0;
+ core_pmus_count = 0;
pmu_layers__exit();
}
@@ -491,6 +497,31 @@ static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu)
return NULL;
}
+struct perf_pmu *perf_pmus__scan_for_metrics(struct perf_pmu *pmu)
+{
+ bool use_core_pmus = !pmu || pmu->is_core;
+
+ if (!pmu) {
+ const unsigned int to_read_pmus =
+ PERF_TOOL_PMU_TYPE_PE_CORE_MASK |
+ PERF_TOOL_PMU_TYPE_PE_OTHER_MASK |
+ PERF_TOOL_PMU_TYPE_TOOL_MASK;
+
+ pmu_read_sysfs(to_read_pmus);
+ pmu = list_prepare_entry(pmu, &core_pmus, list);
+ }
+ if (use_core_pmus) {
+ list_for_each_entry_continue(pmu, &core_pmus, list)
+ return pmu;
+
+ pmu = NULL;
+ pmu = list_prepare_entry(pmu, &other_pmus, list);
+ }
+ list_for_each_entry_continue(pmu, &other_pmus, list)
+ return pmu;
+ return NULL;
+}
+
struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmu *pmu, const char *compat)
{
if (!pmu) {
@@ -805,17 +836,204 @@ bool perf_pmus__have_event(const char *pname, const char *name)
return pmu && perf_pmu__have_event(pmu, name);
}
-int perf_pmus__num_core_pmus(void)
+bool perf_pmus__find_metric_pmu(const char *pmu_name, const char *name,
+ struct pmu_metric *out)
+{
+ struct perf_pmu *pmu = NULL;
+
+ if (!name || !name[0])
+ return false;
+
+ if (pmu_name && pmu_name[0] && strcmp(pmu_name, "all") != 0) {
+ struct pmu_metric fallback = {};
+ bool have_fallback = false;
+
+ if (!strcmp(pmu_name, "default_core")) {
+ while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
+ struct pmu_metric pm;
+
+ if (!perf_pmu__find_metric(pmu, name, &pm))
+ continue;
+ if (!pm.pmu || !pm.pmu[0] ||
+ !strcmp(pm.pmu, "default_core")) {
+ if (out)
+ *out = pm;
+ return true;
+ }
+ if (!have_fallback) {
+ fallback = pm;
+ have_fallback = true;
+ }
+ }
+ if (have_fallback && out)
+ *out = fallback;
+ return have_fallback;
+ }
+
+ pmu = perf_pmus__find(pmu_name);
+ if (pmu)
+ return perf_pmu__find_metric(pmu, name, out);
+
+ while ((pmu = perf_pmus__scan_for_metrics(pmu)) != NULL) {
+ struct pmu_metric pm;
+ const char *m_pmu;
+
+ if (!perf_pmu__name_wildcard_match(pmu, pmu_name) &&
+ !perf_pmu__wildcard_match(pmu, pmu_name))
+ continue;
+ if (!perf_pmu__find_metric(pmu, name, &pm))
+ continue;
+ m_pmu = (pm.pmu && pm.pmu[0]) ? pm.pmu :
+ (pmu->is_core ? "default_core" : pmu->name);
+ if (!strcmp(m_pmu, pmu_name)) {
+ if (out)
+ *out = pm;
+ return true;
+ }
+ if (!have_fallback) {
+ fallback = pm;
+ have_fallback = true;
+ }
+ }
+ if (have_fallback && out)
+ *out = fallback;
+ return have_fallback;
+ }
+
+ while ((pmu = perf_pmus__scan_for_metrics(pmu)) != NULL) {
+ if (perf_pmu__find_metric(pmu, name, out))
+ return true;
+ }
+ return false;
+}
+
+bool perf_pmus__find_metric(const char *name, struct pmu_metric *out)
+{
+ return perf_pmus__find_metric_pmu(NULL, name, out);
+}
+
+struct pmus_pmu_metric_iter_ctx {
+ struct perf_pmu *pmu;
+ pmus_metric_iter_fn fn;
+ void *data;
+};
+
+static int pmus_for_each_pmu_metric_cb(const struct pmu_metric *pm,
+ const struct pmu_metrics_table *table,
+ void *vdata)
+{
+ struct pmus_pmu_metric_iter_ctx *ctx = vdata;
+
+ return ctx->fn(ctx->pmu, pm, table, ctx->data);
+}
+
+int perf_pmus__for_each_pmu_metric(pmus_metric_iter_fn fn, void *data)
{
- static int count;
+ struct pmus_pmu_metric_iter_ctx ctx = {
+ .fn = fn,
+ .data = data,
+ };
+ int ret = 0;
+
+ while ((ctx.pmu = perf_pmus__scan_for_metrics(ctx.pmu)) != NULL) {
+ ret = perf_pmu__for_each_metric(ctx.pmu,
+ pmus_for_each_pmu_metric_cb,
+ &ctx);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+struct pmus_metric_iter_adapter_ctx {
+ pmu_metric_iter_fn fn;
+ void *data;
+ struct strlist *seen;
+};
+
+static int pmus_metric_iter_adapter_cb(struct perf_pmu *pmu __maybe_unused,
+ const struct pmu_metric *pm,
+ const struct pmu_metrics_table *table,
+ void *vdata)
+{
+ struct pmus_metric_iter_adapter_ctx *ctx = vdata;
+ char *key = NULL;
+ int ret;
+
+ if (asprintf(&key, "%s\x1f%s\x1f%s\x1f%s",
+ (pm->pmu && pm->pmu[0]) ? pm->pmu : "default_core",
+ pm->metric_name ?: "",
+ pm->metric_expr ?: "",
+ pm->metric_threshold ?: "") < 0)
+ return -ENOMEM;
+
+ if (strlist__has_entry(ctx->seen, key)) {
+ free(key);
+ return 0;
+ }
+ ret = strlist__add(ctx->seen, key);
+ free(key);
+ if (ret)
+ return ret;
+
+ return ctx->fn(pm, table, ctx->data);
+}
- if (!count) {
+int perf_pmus__for_each_metric(pmu_metric_iter_fn fn, void *data)
+{
+ struct pmus_metric_iter_adapter_ctx ctx = {
+ .fn = fn,
+ .data = data,
+ .seen = strlist__new(NULL, NULL),
+ };
+ int ret;
+
+ if (!ctx.seen)
+ return -ENOMEM;
+
+ ret = perf_pmus__for_each_pmu_metric(pmus_metric_iter_adapter_cb, &ctx);
+ strlist__delete(ctx.seen);
+ return ret;
+}
+
+const char *perf_pmus__describe_metricgroup(const char *group)
+{
+ struct perf_pmu *pmu = NULL;
+ const char *builtin_desc = NULL;
+ bool saw_core_pmu = false;
+
+ if (!group || !group[0])
+ return NULL;
+
+ while ((pmu = perf_pmus__scan_for_metrics(pmu)) != NULL) {
+ const char *desc = perf_pmu__describe_metricgroup(pmu, group);
+ struct perf_pmu_metricgroup *mg = NULL;
+
+ if (pmu->is_core)
+ saw_core_pmu = true;
+ if (!desc)
+ continue;
+ if (pmu->metricgroups)
+ hashmap__find(pmu->metricgroups, group, &mg);
+ if (mg && !mg->from_builtin)
+ return desc;
+ if (!builtin_desc)
+ builtin_desc = desc;
+ }
+ if (saw_core_pmu)
+ return builtin_desc;
+ return describe_metricgroup(group);
+}
+
+int perf_pmus__num_core_pmus(void)
+{
+ if (!core_pmus_count) {
struct perf_pmu *pmu = NULL;
while ((pmu = perf_pmus__scan_core(pmu)) != NULL)
- count++;
+ core_pmus_count++;
}
- return count;
+ return core_pmus_count;
}
static bool __perf_pmus__supports_extended_type(void)
@@ -893,12 +1111,19 @@ struct perf_pmu *perf_pmus__find_core_pmu(void)
struct perf_pmu *perf_pmus__add_test_pmu(int test_sysfs_dirfd, const char *name)
{
+ bool is_core = perf_pmu__is_core_at(test_sysfs_dirfd, name);
+ struct perf_pmu *pmu;
+
/*
* Some PMU functions read from the sysfs mount point, so care is
* needed, hence passing the eager_load flag to load things like the
* format files.
*/
- return perf_pmu__lookup(&other_pmus, test_sysfs_dirfd, name, /*eager_load=*/true);
+ pmu = perf_pmu__lookup(is_core ? &core_pmus : &other_pmus,
+ test_sysfs_dirfd, name, /*eager_load=*/true);
+ if (pmu && is_core)
+ core_pmus_count = 0;
+ return pmu;
}
struct perf_pmu *perf_pmus__add_test_hwmon_pmu(const char *hwmon_dir,
diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h
index 0d55edb3f2fc..f4295526b2ae 100644
--- a/tools/perf/util/pmus.h
+++ b/tools/perf/util/pmus.h
@@ -4,6 +4,7 @@
#include <stdbool.h>
#include <stddef.h>
+#include "pmu-events/pmu-events.h"
struct perf_event_attr;
struct perf_pmu;
@@ -23,6 +24,7 @@ struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu);
struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu);
struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmu *pmu, const char *event);
struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const char *wildcard);
+struct perf_pmu *perf_pmus__scan_for_metrics(struct perf_pmu *pmu);
struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmu *pmu, const char *compat);
const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(const char *str);
@@ -30,6 +32,17 @@ const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(const char *str);
void perf_pmus__print_pmu_events(const struct print_callbacks *print_cb, void *print_state);
void perf_pmus__print_raw_pmu_events(const struct print_callbacks *print_cb, void *print_state);
bool perf_pmus__have_event(const char *pname, const char *name);
+typedef int (*pmus_metric_iter_fn)(struct perf_pmu *pmu,
+ const struct pmu_metric *pm,
+ const struct pmu_metrics_table *table,
+ void *data);
+
+bool perf_pmus__find_metric_pmu(const char *pmu_name, const char *name,
+ struct pmu_metric *out);
+bool perf_pmus__find_metric(const char *name, struct pmu_metric *out);
+int perf_pmus__for_each_pmu_metric(pmus_metric_iter_fn fn, void *data);
+int perf_pmus__for_each_metric(pmu_metric_iter_fn fn, void *data);
+const char *perf_pmus__describe_metricgroup(const char *group);
int perf_pmus__num_core_pmus(void);
bool perf_pmus__supports_extended_type(void);
diff --git a/tools/perf/util/print-events.c b/tools/perf/util/print-events.c
index 77428d9e73c4..eb44026972b9 100644
--- a/tools/perf/util/print-events.c
+++ b/tools/perf/util/print-events.c
@@ -289,14 +289,14 @@ void metricgroup__print(const struct print_callbacks *print_cb, void *print_stat
{
struct rblist groups;
struct rb_node *node, *next;
- const struct pmu_metrics_table *table = pmu_metrics_table__find();
rblist__init(&groups);
groups.node_new = mep_new;
groups.node_cmp = mep_cmp;
groups.node_delete = mep_delete;
- metricgroup__for_each_metric(table, metricgroup__add_to_mep_groups_callback, &groups);
+ metricgroup__for_each_metric(/*table=*/NULL,
+ metricgroup__add_to_mep_groups_callback, &groups);
for (node = rb_first_cached(&groups.entries); node; node = next) {
struct mep *me = container_of(node, struct mep, nd);
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 42c587cd54a5..9002288d24f4 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -3953,16 +3953,13 @@ static int pyrf__metrics_cb(const struct pmu_metric *pm,
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);
+ ret = metricgroup__for_each_metric(/*table=*/NULL, pyrf__metrics_cb, list);
if (ret) {
Py_DECREF(list);
--
2.56.1.703.gf848ed807c-goog
next prev parent reply other threads:[~2026-11-13 21:11 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-13 21:10 [PATCH 00/11] perf pmu: Layered sysfs-style PMU event and metric directories Ian Rogers
2026-11-13 21:10 ` [PATCH 01/11] perf pmu: Generalize pmu_alias_info_file() and lazy single-event sysfs loading Ian Rogers
2026-11-13 21:10 ` [PATCH 02/11] perf pmu: Add metric and metricgroup storage on struct perf_pmu Ian Rogers
2026-11-13 21:10 ` [PATCH 03/11] perf pmu-layer: Introduce stateless struct pmu_layer abstraction and builtin adapter Ian Rogers
2026-11-13 21:10 ` [PATCH 04/11] perf pmu-layer: Add openat-based directory layer with literal CPUID/symlink lookup Ian Rogers
2026-11-13 21:10 ` [PATCH 05/11] perf config: Add pmu-events.dir configuration option Ian Rogers
2026-11-13 21:10 ` [PATCH 06/11] perf pmu: Switch alias resolution and enumeration to layered PMU stack Ian Rogers
2026-11-13 21:10 ` Ian Rogers [this message]
2026-11-13 21:10 ` [PATCH 08/11] perf list, jevents: Add --export-dir and --output-dir sysfs-style exporters Ian Rogers
2026-11-13 21:11 ` [PATCH 09/11] perf jevents: Add compiled-in model and sys table iterators Ian Rogers
2026-11-13 21:11 ` [PATCH 10/11] perf list: Add --export-cpuid to export compiled-in CPU models Ian Rogers
2026-11-13 21:11 ` [PATCH 11/11] perf tests: Add round-trip export, CPUID symlink, and pmu-events.dir overlay tests Ian Rogers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261113211105.3118281-8-irogers@google.com \
--to=irogers@google.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®