perf metricgroup: Binary search when resolving referred to metrics
authorIan Rogers <irogers@google.com>
Mon, 12 May 2025 19:46:22 +0000 (12:46 -0700)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Tue, 13 May 2025 19:36:51 +0000 (16:36 -0300)
Unlike with events, metrics can be matched by name or a list of metric
groups.

However, when a metric refers to another metric it isn't referring to a
group but the singular metric in question.

Prior to this change every "id" in a metric expression is checked to see
if it is a metric by scanning all the metrics in the metrics table.

As the table is sorted my metric name we can speed the search in the
resolution case by binary searching for the metric.

Rename some of the metricgroup functions to make it clearer whether
they match a metric by name or by both name and group.

Before:
```
$ time perf test -v 10
 10: PMU JSON event tests                                            :
 10.1: PMU event table sanity                                        : Ok
 10.2: PMU event map aliases                                         : Ok
 10.3: Parsing of PMU event table metrics                            : Ok
 10.4: Parsing of PMU event table metrics with fake PMUs             : Ok
 10.5: Parsing of metric thresholds with fake PMUs                   : Ok

real    0m15.972s
user    0m13.176s
sys     0m3.001s
```

After:
```
$ time perf test -v 10
 10: PMU JSON event tests                                            :
 10.1: PMU event table sanity                                        : Ok
 10.2: PMU event map aliases                                         : Ok
 10.3: Parsing of PMU event table metrics                            : Ok
 10.4: Parsing of PMU event table metrics with fake PMUs             : Ok
 10.5: Parsing of metric thresholds with fake PMUs                   : Ok

real    0m5.343s
user    0m1.871s
sys     0m2.128s
```

Committer testing:

  root@number:~# grep -m1 'model name' /proc/cpuinfo
  model name : AMD Ryzen 9 9950X3D 16-Core Processor
  root@number:~#

Before:

  root@number:~# time perf test "Parsing of PMU event table metrics"
   10.3: Parsing of PMU event table metrics                            : Ok
   10.4: Parsing of PMU event table metrics with fake PMUs             : Ok

  real 0m9.286s
  user 0m9.354s
  sys 0m0.062s
  root@number:~#

After:

  root@number:~# time perf test "Parsing of PMU event table metrics"
   10.3: Parsing of PMU event table metrics                            : Ok
   10.4: Parsing of PMU event table metrics with fake PMUs             : Ok

  real 0m0.689s
  user 0m0.766s
  sys 0m0.042s
  root@number:~# time perf test 10
   10: PMU JSON event tests                                            :
   10.1: PMU event table sanity                                        : Ok
   10.2: PMU event map aliases                                         : Ok
   10.3: Parsing of PMU event table metrics                            : Ok
   10.4: Parsing of PMU event table metrics with fake PMUs             : Ok
   10.5: Parsing of metric thresholds with fake PMUs                   : Ok

  real 0m0.696s
  user 0m0.807s
  sys 0m0.064s
  root@number:~#

Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Tested-by: Namhyung Kim <namhyung@kernel.org>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ravi Bangoria <ravi.bangoria@amd.com>
Cc: Thomas Richter <tmricht@linux.ibm.com>
Cc: Xu Yang <xu.yang_2@nxp.com>
Link: https://lore.kernel.org/r/20250512194622.33258-4-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/builtin-stat.c
tools/perf/pmu-events/empty-pmu-events.c
tools/perf/pmu-events/jevents.py
tools/perf/pmu-events/pmu-events.h
tools/perf/util/metricgroup.c
tools/perf/util/metricgroup.h

index 300b6393bb411e47a3137af093876fdcbb5189db..bf0e5e12d992f0a56a85fe0031f0a1f6cf09acb6 100644 (file)
@@ -1854,7 +1854,7 @@ static int add_default_events(void)
                 * will use this approach. To determine transaction support
                 * on an architecture test for such a metric name.
                 */
-               if (!metricgroup__has_metric(pmu, "transaction")) {
+               if (!metricgroup__has_metric_or_groups(pmu, "transaction")) {
                        pr_err("Missing transaction metrics\n");
                        ret = -1;
                        goto out;
@@ -1888,7 +1888,7 @@ static int add_default_events(void)
                        smi_reset = true;
                }
 
-               if (!metricgroup__has_metric(pmu, "smi")) {
+               if (!metricgroup__has_metric_or_groups(pmu, "smi")) {
                        pr_err("Missing smi metrics\n");
                        ret = -1;
                        goto out;
@@ -1978,7 +1978,7 @@ static int add_default_events(void)
                 * Add TopdownL1 metrics if they exist. To minimize
                 * multiplexing, don't request threshold computation.
                 */
-               if (metricgroup__has_metric(pmu, "Default")) {
+               if (metricgroup__has_metric_or_groups(pmu, "Default")) {
                        struct evlist *metric_evlist = evlist__new();
 
                        if (!metric_evlist) {
index 0361bcc1eb5861f8edee348b913cac2344a51548..d4017007a991ac49b5daa71460c897132eb7be28 100644 (file)
@@ -449,7 +449,7 @@ int pmu_events_table__find_event(const struct pmu_events_table *table,
                 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
                 int ret;
 
-                if (!perf_pmu__name_wildcard_match(pmu, pmu_name))
+                if (pmu && !perf_pmu__name_wildcard_match(pmu, pmu_name))
                         continue;
 
                 ret = pmu_events_table__find_event_pmu(table, table_pmu, name, fn, data);
@@ -495,6 +495,49 @@ static int pmu_metrics_table__for_each_metric_pmu(const struct pmu_metrics_table
         return 0;
 }
 
+static int pmu_metrics_table__find_metric_pmu(const struct pmu_metrics_table *table,
+                                            const struct pmu_table_entry *pmu,
+                                            const char *metric,
+                                            pmu_metric_iter_fn fn,
+                                            void *data)
+{
+        struct pmu_metric pm = {
+                .pmu = &big_c_string[pmu->pmu_name.offset],
+        };
+        int low = 0, high = pmu->num_entries - 1;
+
+        while (low <= high) {
+                int cmp, mid = (low + high) / 2;
+
+                decompress_metric(pmu->entries[mid].offset, &pm);
+
+                if (!pm.metric_name && !metric)
+                        goto do_call;
+
+                if (!pm.metric_name && metric) {
+                        low = mid + 1;
+                        continue;
+                }
+                if (pm.metric_name && !metric) {
+                        high = mid - 1;
+                        continue;
+                }
+
+                cmp = strcmp(pm.metric_name, metric);
+                if (cmp < 0) {
+                        low = mid + 1;
+                        continue;
+                }
+                if (cmp > 0) {
+                        high = mid - 1;
+                        continue;
+                }
+  do_call:
+                return fn ? fn(&pm, table, data) : 0;
+        }
+        return PMU_METRICS__NOT_FOUND;
+}
+
 int pmu_metrics_table__for_each_metric(const struct pmu_metrics_table *table,
                                      pmu_metric_iter_fn fn,
                                      void *data)
@@ -509,6 +552,27 @@ int pmu_metrics_table__for_each_metric(const struct pmu_metrics_table *table,
         return 0;
 }
 
+int pmu_metrics_table__find_metric(const struct pmu_metrics_table *table,
+                                 struct perf_pmu *pmu,
+                                 const char *metric,
+                                 pmu_metric_iter_fn fn,
+                                 void *data)
+{
+        for (size_t i = 0; i < table->num_pmus; i++) {
+                const struct pmu_table_entry *table_pmu = &table->pmus[i];
+                const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
+                int ret;
+
+                if (pmu && !perf_pmu__name_wildcard_match(pmu, pmu_name))
+                        continue;
+
+                ret = pmu_metrics_table__find_metric_pmu(table, table_pmu, metric, fn, data);
+                if (ret != PMU_METRICS__NOT_FOUND)
+                        return ret;
+        }
+        return PMU_METRICS__NOT_FOUND;
+}
+
 static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
 {
         static struct {
index e3a55486c08e4f840d7cce66099913d125613afc..a1899f35ec745aa2714d306c5be55d97ad4a2a39 100755 (executable)
@@ -972,7 +972,7 @@ int pmu_events_table__find_event(const struct pmu_events_table *table,
                 const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
                 int ret;
 
-                if (!perf_pmu__name_wildcard_match(pmu, pmu_name))
+                if (pmu && !perf_pmu__name_wildcard_match(pmu, pmu_name))
                         continue;
 
                 ret = pmu_events_table__find_event_pmu(table, table_pmu, name, fn, data);
@@ -1018,6 +1018,49 @@ static int pmu_metrics_table__for_each_metric_pmu(const struct pmu_metrics_table
         return 0;
 }
 
+static int pmu_metrics_table__find_metric_pmu(const struct pmu_metrics_table *table,
+                                            const struct pmu_table_entry *pmu,
+                                            const char *metric,
+                                            pmu_metric_iter_fn fn,
+                                            void *data)
+{
+        struct pmu_metric pm = {
+                .pmu = &big_c_string[pmu->pmu_name.offset],
+        };
+        int low = 0, high = pmu->num_entries - 1;
+
+        while (low <= high) {
+                int cmp, mid = (low + high) / 2;
+
+                decompress_metric(pmu->entries[mid].offset, &pm);
+
+                if (!pm.metric_name && !metric)
+                        goto do_call;
+
+                if (!pm.metric_name && metric) {
+                        low = mid + 1;
+                        continue;
+                }
+                if (pm.metric_name && !metric) {
+                        high = mid - 1;
+                        continue;
+                }
+
+                cmp = strcmp(pm.metric_name, metric);
+                if (cmp < 0) {
+                        low = mid + 1;
+                        continue;
+                }
+                if (cmp > 0) {
+                        high = mid - 1;
+                        continue;
+                }
+  do_call:
+                return fn ? fn(&pm, table, data) : 0;
+        }
+        return PMU_METRICS__NOT_FOUND;
+}
+
 int pmu_metrics_table__for_each_metric(const struct pmu_metrics_table *table,
                                      pmu_metric_iter_fn fn,
                                      void *data)
@@ -1032,6 +1075,27 @@ int pmu_metrics_table__for_each_metric(const struct pmu_metrics_table *table,
         return 0;
 }
 
+int pmu_metrics_table__find_metric(const struct pmu_metrics_table *table,
+                                 struct perf_pmu *pmu,
+                                 const char *metric,
+                                 pmu_metric_iter_fn fn,
+                                 void *data)
+{
+        for (size_t i = 0; i < table->num_pmus; i++) {
+                const struct pmu_table_entry *table_pmu = &table->pmus[i];
+                const char *pmu_name = &big_c_string[table_pmu->pmu_name.offset];
+                int ret;
+
+                if (pmu && !perf_pmu__name_wildcard_match(pmu, pmu_name))
+                        continue;
+
+                ret = pmu_metrics_table__find_metric_pmu(table, table_pmu, metric, fn, data);
+                if (ret != PMU_METRICS__NOT_FOUND)
+                        return ret;
+        }
+        return PMU_METRICS__NOT_FOUND;
+}
+
 static const struct pmu_events_map *map_for_cpu(struct perf_cpu cpu)
 {
         static struct {
index a95fee56162290fe7e37270369a4011b2ee38fe6..a523936846e043e713e0cd9b190d1efb70040407 100644 (file)
@@ -74,6 +74,7 @@ struct pmu_events_table;
 struct pmu_metrics_table;
 
 #define PMU_EVENTS__NOT_FOUND -1000
+#define PMU_METRICS__NOT_FOUND -1000
 
 typedef int (*pmu_event_iter_fn)(const struct pmu_event *pe,
                                 const struct pmu_events_table *table,
@@ -88,11 +89,11 @@ int pmu_events_table__for_each_event(const struct pmu_events_table *table,
                                    pmu_event_iter_fn fn,
                                    void *data);
 /*
- * Search for table and entry matching with pmu__name_match. Each matching event
- * has fn called on it. 0 implies to success/continue the search while non-zero
- * means to terminate. The special value PMU_EVENTS__NOT_FOUND is used to
- * indicate no event was found in one of the tables which doesn't terminate the
- * search of all tables.
+ * Search for a table and entry matching with pmu__name_wildcard_match or any
+ * tables if pmu is NULL. Each matching event has fn called on it. 0 implies to
+ * success/continue the search while non-zero means to terminate. The special
+ * value PMU_EVENTS__NOT_FOUND is used to indicate no event was found in one of
+ * the tables which doesn't terminate the search of all tables.
  */
 int pmu_events_table__find_event(const struct pmu_events_table *table,
                                  struct perf_pmu *pmu,
@@ -104,6 +105,18 @@ size_t pmu_events_table__num_events(const struct pmu_events_table *table,
 
 int pmu_metrics_table__for_each_metric(const struct pmu_metrics_table *table, pmu_metric_iter_fn fn,
                                     void *data);
+/*
+ * Search for a table and entry matching with pmu__name_wildcard_match or any
+ * tables if pmu is NULL. Each matching metric has fn called on it. 0 implies to
+ * success/continue the search while non-zero means to terminate. The special
+ * value PMU_METRICS__NOT_FOUND is used to indicate no metric was found in one
+ * of the tables which doesn't terminate the search of all tables.
+ */
+int pmu_metrics_table__find_metric(const struct pmu_metrics_table *table,
+                                  struct perf_pmu *pmu,
+                                  const char *metric,
+                                  pmu_metric_iter_fn fn,
+                                  void *data);
 
 const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu);
 const struct pmu_metrics_table *pmu_metrics_table__find(void);
index 46920ebadfd1bc8bdd959cc456810eb81427d532..126a631686b082d5d97ee0b000b3be4df59b2169 100644 (file)
@@ -353,7 +353,7 @@ static int setup_metric_events(const char *pmu, struct hashmap *ids,
        return 0;
 }
 
-static bool match_metric(const char *metric_or_groups, const char *sought)
+static bool match_metric_or_groups(const char *metric_or_groups, const char *sought)
 {
        int len;
        char *m;
@@ -369,18 +369,19 @@ static bool match_metric(const char *metric_or_groups, const char *sought)
            (metric_or_groups[len] == 0 || metric_or_groups[len] == ';'))
                return true;
        m = strchr(metric_or_groups, ';');
-       return m && match_metric(m + 1, sought);
+       return m && match_metric_or_groups(m + 1, sought);
 }
 
-static bool match_pm_metric(const struct pmu_metric *pm, const char *pmu, const char *metric)
+static bool match_pm_metric_or_groups(const struct pmu_metric *pm, const char *pmu,
+                                     const char *metric_or_groups)
 {
        const char *pm_pmu = pm->pmu ?: "cpu";
 
        if (strcmp(pmu, "all") && strcmp(pm_pmu, pmu))
                return false;
 
-       return match_metric(pm->metric_group, metric) ||
-              match_metric(pm->metric_name, metric);
+       return match_metric_or_groups(pm->metric_group, metric_or_groups) ||
+              match_metric_or_groups(pm->metric_name, metric_or_groups);
 }
 
 /** struct mep - RB-tree node for building printing information. */
@@ -802,11 +803,6 @@ struct metricgroup_add_iter_data {
        const struct pmu_metrics_table *table;
 };
 
-static bool metricgroup__find_metric(const char *pmu,
-                                    const char *metric,
-                                    const struct pmu_metrics_table *table,
-                                    struct pmu_metric *pm);
-
 static int add_metric(struct list_head *metric_list,
                      const struct pmu_metric *pm,
                      const char *modifier,
@@ -818,6 +814,16 @@ static int add_metric(struct list_head *metric_list,
                      const struct visited_metric *visited,
                      const struct pmu_metrics_table *table);
 
+static int metricgroup__find_metric_callback(const struct pmu_metric *pm,
+                                            const struct pmu_metrics_table *table  __maybe_unused,
+                                            void *vdata)
+{
+       struct pmu_metric *copied_pm = vdata;
+
+       memcpy(copied_pm, pm, sizeof(*pm));
+       return 0;
+}
+
 /**
  * resolve_metric - Locate metrics within the root metric and recursively add
  *                    references to them.
@@ -838,7 +844,7 @@ static int add_metric(struct list_head *metric_list,
  *       architecture perf is running upon.
  */
 static int resolve_metric(struct list_head *metric_list,
-                         const char *pmu,
+                         struct perf_pmu *pmu,
                          const char *modifier,
                          bool metric_no_group,
                          bool metric_no_threshold,
@@ -868,7 +874,9 @@ static int resolve_metric(struct list_head *metric_list,
        hashmap__for_each_entry(root_metric->pctx->ids, cur, bkt) {
                struct pmu_metric pm;
 
-               if (metricgroup__find_metric(pmu, cur->pkey, table, &pm)) {
+               if (pmu_metrics_table__find_metric(table, pmu, cur->pkey,
+                                                  metricgroup__find_metric_callback,
+                                                  &pm) != PMU_METRICS__NOT_FOUND) {
                        pending = realloc(pending,
                                        (pending_cnt + 1) * sizeof(struct to_resolve));
                        if (!pending)
@@ -1019,7 +1027,12 @@ static int __add_metric(struct list_head *metric_list,
        }
        if (!ret) {
                /* Resolve referenced metrics. */
-               const char *pmu = pm->pmu ?: "cpu";
+               struct perf_pmu *pmu;
+
+               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,
                                     metric_no_threshold, user_requested_cpu_list,
@@ -1036,44 +1049,6 @@ static int __add_metric(struct list_head *metric_list,
        return ret;
 }
 
-struct metricgroup__find_metric_data {
-       const char *pmu;
-       const char *metric;
-       struct pmu_metric *pm;
-};
-
-static int metricgroup__find_metric_callback(const struct pmu_metric *pm,
-                                            const struct pmu_metrics_table *table  __maybe_unused,
-                                            void *vdata)
-{
-       struct metricgroup__find_metric_data *data = vdata;
-       const char *pm_pmu = pm->pmu ?: "cpu";
-
-       if (strcmp(data->pmu, "all") && strcmp(pm_pmu, data->pmu))
-               return 0;
-
-       if (!match_metric(pm->metric_name, data->metric))
-               return 0;
-
-       memcpy(data->pm, pm, sizeof(*pm));
-       return 1;
-}
-
-static bool metricgroup__find_metric(const char *pmu,
-                                    const char *metric,
-                                    const struct pmu_metrics_table *table,
-                                    struct pmu_metric *pm)
-{
-       struct metricgroup__find_metric_data data = {
-               .pmu = pmu,
-               .metric = metric,
-               .pm = pm,
-       };
-
-       return pmu_metrics_table__for_each_metric(table, metricgroup__find_metric_callback, &data)
-               ? true : false;
-}
-
 static int add_metric(struct list_head *metric_list,
                      const struct pmu_metric *pm,
                      const char *modifier,
@@ -1119,7 +1094,7 @@ static int metricgroup__add_metric_sys_event_iter(const struct pmu_metric *pm,
        struct metricgroup_add_iter_data *d = data;
        int ret;
 
-       if (!match_pm_metric(pm, d->pmu, d->metric_name))
+       if (!match_pm_metric_or_groups(pm, d->pmu, d->metric_name))
                return 0;
 
        ret = add_metric(d->metric_list, pm, d->modifier, d->metric_no_group,
@@ -1200,9 +1175,9 @@ static int metricgroup__add_metric_callback(const struct pmu_metric *pm,
        struct metricgroup__add_metric_data *data = vdata;
        int ret = 0;
 
-       if (pm->metric_expr && match_pm_metric(pm, data->pmu, data->metric_name)) {
+       if (pm->metric_expr && match_pm_metric_or_groups(pm, data->pmu, data->metric_name)) {
                bool metric_no_group = data->metric_no_group ||
-                       match_metric(pm->metricgroup_no_group, data->metric_name);
+                       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,
@@ -1723,29 +1698,32 @@ int metricgroup__parse_groups_test(struct evlist *evlist,
 
 struct metricgroup__has_metric_data {
        const char *pmu;
-       const char *metric;
+       const char *metric_or_groups;
 };
-static int metricgroup__has_metric_callback(const struct pmu_metric *pm,
-                                           const struct pmu_metrics_table *table __maybe_unused,
-                                           void *vdata)
+static int metricgroup__has_metric_or_groups_callback(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(pm, data->pmu, data->metric) ? 1 : 0;
+       return match_pm_metric_or_groups(pm, data->pmu, data->metric_or_groups) ? 1 : 0;
 }
 
-bool metricgroup__has_metric(const char *pmu, const char *metric)
+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 = metric,
+               .metric_or_groups = metric_or_groups,
        };
 
        if (!table)
                return false;
 
-       return pmu_metrics_table__for_each_metric(table, metricgroup__has_metric_callback, &data)
+       return pmu_metrics_table__for_each_metric(table,
+                                                 metricgroup__has_metric_or_groups_callback,
+                                                 &data)
                ? true : false;
 }
 
index 779f6ede1b511456731a7fd8942efa66ee4db642..a04ac1afa6cce6853966fa4812406824309a19f3 100644 (file)
@@ -85,7 +85,7 @@ int metricgroup__parse_groups_test(struct evlist *evlist,
                                   struct rblist *metric_events);
 
 void metricgroup__print(const struct print_callbacks *print_cb, void *print_state);
-bool metricgroup__has_metric(const char *pmu, const char *metric);
+bool metricgroup__has_metric_or_groups(const char *pmu, const char *metric_or_groups);
 unsigned int metricgroups__topdown_max_level(void);
 int arch_get_runtimeparam(const struct pmu_metric *pm);
 void metricgroup__rblist_exit(struct rblist *metric_events);