[PATCH v1 40/51] perf stat: Implement --topdown using json metrics

From: Ian Rogers
Date: Sun Feb 19 2023 - 04:39:03 EST


Request the topdown metric group of a level with the metrics in the
group 'TopdownL<level>' rather than through specific events. As more
topdown levels are supported this way, such as 6 on Intel Ice Lake,
default to just showing the level 1 metrics. This can be overridden
using '--td-level'. Rather than determine the maximum topdown level
from sysfs, use the metric group names. Remove some now unused topdown
code.

Signed-off-by: Ian Rogers <irogers@xxxxxxxxxx>
---
tools/perf/arch/x86/util/topdown.c | 48 +-----------
tools/perf/builtin-stat.c | 118 +++++------------------------
tools/perf/util/metricgroup.c | 31 ++++++++
tools/perf/util/metricgroup.h | 1 +
tools/perf/util/topdown.c | 68 +----------------
tools/perf/util/topdown.h | 11 +--
6 files changed, 58 insertions(+), 219 deletions(-)

diff --git a/tools/perf/arch/x86/util/topdown.c b/tools/perf/arch/x86/util/topdown.c
index eb3a7d9652ab..9ad5e5c7bd27 100644
--- a/tools/perf/arch/x86/util/topdown.c
+++ b/tools/perf/arch/x86/util/topdown.c
@@ -1,11 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
-#include <stdio.h>
#include "api/fs/fs.h"
+#include "util/evsel.h"
#include "util/pmu.h"
#include "util/topdown.h"
-#include "util/evlist.h"
-#include "util/debug.h"
-#include "util/pmu-hybrid.h"
#include "topdown.h"
#include "evsel.h"

@@ -33,30 +30,6 @@ bool topdown_sys_has_perf_metrics(void)
return has_perf_metrics;
}

-/*
- * Check whether we can use a group for top down.
- * Without a group may get bad results due to multiplexing.
- */
-bool arch_topdown_check_group(bool *warn)
-{
- int n;
-
- if (sysctl__read_int("kernel/nmi_watchdog", &n) < 0)
- return false;
- if (n > 0) {
- *warn = true;
- return false;
- }
- return true;
-}
-
-void arch_topdown_group_warn(void)
-{
- fprintf(stderr,
- "nmi_watchdog enabled with topdown. May give wrong results.\n"
- "Disable with echo 0 > /proc/sys/kernel/nmi_watchdog\n");
-}
-
#define TOPDOWN_SLOTS 0x0400

/*
@@ -65,7 +38,6 @@ void arch_topdown_group_warn(void)
* Only Topdown metric supports sample-read. The slots
* event must be the leader of the topdown group.
*/
-
bool arch_topdown_sample_read(struct evsel *leader)
{
if (!evsel__sys_has_perf_metrics(leader))
@@ -76,21 +48,3 @@ bool arch_topdown_sample_read(struct evsel *leader)

return false;
}
-
-const char *arch_get_topdown_pmu_name(struct evlist *evlist, bool warn)
-{
- const char *pmu_name;
-
- if (!perf_pmu__has_hybrid())
- return "cpu";
-
- if (!evlist->hybrid_pmu_name) {
- if (warn)
- pr_warning("WARNING: default to use cpu_core topdown events\n");
- evlist->hybrid_pmu_name = perf_pmu__hybrid_type_to_pmu("core");
- }
-
- pmu_name = evlist->hybrid_pmu_name;
-
- return pmu_name;
-}
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 796e98e453f6..bdb1ef4fc6ad 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -124,39 +124,6 @@ static const char * transaction_limited_attrs = {
"}"
};

-static const char * topdown_attrs[] = {
- "topdown-total-slots",
- "topdown-slots-retired",
- "topdown-recovery-bubbles",
- "topdown-fetch-bubbles",
- "topdown-slots-issued",
- NULL,
-};
-
-static const char *topdown_metric_attrs[] = {
- "slots",
- "topdown-retiring",
- "topdown-bad-spec",
- "topdown-fe-bound",
- "topdown-be-bound",
- NULL,
-};
-
-static const char *topdown_metric_L2_attrs[] = {
- "slots",
- "topdown-retiring",
- "topdown-bad-spec",
- "topdown-fe-bound",
- "topdown-be-bound",
- "topdown-heavy-ops",
- "topdown-br-mispredict",
- "topdown-fetch-lat",
- "topdown-mem-bound",
- NULL,
-};
-
-#define TOPDOWN_MAX_LEVEL 2
-
static const char *smi_cost_attrs = {
"{"
"msr/aperf/,"
@@ -1914,86 +1881,41 @@ static int add_default_attributes(void)
}

if (topdown_run) {
- const char **metric_attrs = topdown_metric_attrs;
- unsigned int max_level = 1;
- char *str = NULL;
- bool warn = false;
- const char *pmu_name = arch_get_topdown_pmu_name(evsel_list, true);
+ unsigned int max_level = metricgroups__topdown_max_level();
+ char str[] = "TopdownL1";

if (!force_metric_only)
stat_config.metric_only = true;

- if (pmu_have_event(pmu_name, topdown_metric_L2_attrs[5])) {
- metric_attrs = topdown_metric_L2_attrs;
- max_level = 2;
+ if (!max_level) {
+ pr_err("Topdown requested but the topdown metric groups aren't present.\n"
+ "(See perf list the metric groups have names like TopdownL1)");
+ return -1;
}
-
if (stat_config.topdown_level > max_level) {
pr_err("Invalid top-down metrics level. The max level is %u.\n", max_level);
return -1;
} else if (!stat_config.topdown_level)
- stat_config.topdown_level = max_level;
+ stat_config.topdown_level = 1;

- if (topdown_filter_events(metric_attrs, &str, 1, pmu_name) < 0) {
- pr_err("Out of memory\n");
- return -1;
- }
-
- if (metric_attrs[0] && str) {
- if (!stat_config.interval && !stat_config.metric_only) {
- fprintf(stat_config.output,
- "Topdown accuracy may decrease when measuring long periods.\n"
- "Please print the result regularly, e.g. -I1000\n");
- }
- goto setup_metrics;
- }
-
- zfree(&str);
-
- if (stat_config.aggr_mode != AGGR_GLOBAL &&
- stat_config.aggr_mode != AGGR_CORE) {
- pr_err("top down event configuration requires --per-core mode\n");
- return -1;
- }
- stat_config.aggr_mode = AGGR_CORE;
- if (nr_cgroups || !target__has_cpu(&target)) {
- pr_err("top down event configuration requires system-wide mode (-a)\n");
- return -1;
- }
-
- if (topdown_filter_events(topdown_attrs, &str,
- arch_topdown_check_group(&warn),
- pmu_name) < 0) {
- pr_err("Out of memory\n");
- return -1;
+ if (!stat_config.interval && !stat_config.metric_only) {
+ fprintf(stat_config.output,
+ "Topdown accuracy may decrease when measuring long periods.\n"
+ "Please print the result regularly, e.g. -I1000\n");
}
-
- if (topdown_attrs[0] && str) {
- struct parse_events_error errinfo;
- if (warn)
- arch_topdown_group_warn();
-setup_metrics:
- parse_events_error__init(&errinfo);
- err = parse_events(evsel_list, str, &errinfo);
- if (err) {
- fprintf(stderr,
- "Cannot set up top down events %s: %d\n",
- str, err);
- parse_events_error__print(&errinfo, str);
- parse_events_error__exit(&errinfo);
- free(str);
- return -1;
- }
- parse_events_error__exit(&errinfo);
- } else {
- fprintf(stderr, "System does not support topdown\n");
+ str[8] = stat_config.topdown_level + '0';
+ if (metricgroup__parse_groups(evsel_list, str,
+ /*metric_no_group=*/false,
+ /*metric_no_merge=*/false,
+ /*metric_no_threshold=*/true,
+ stat_config.user_requested_cpu_list,
+ stat_config.system_wide,
+ &stat_config.metric_events) < 0)
return -1;
- }
- free(str);
}

if (!stat_config.topdown_level)
- stat_config.topdown_level = TOPDOWN_MAX_LEVEL;
+ stat_config.topdown_level = 1;

if (!evsel_list->core.nr_entries) {
/* No events so add defaults. */
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 64a35f2787dc..de6dd527a2ba 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -1665,6 +1665,37 @@ bool metricgroup__has_metric(const char *metric)
(void *)metric) ? true : false;
}

+static int metricgroup__topdown_max_level_callback(const struct pmu_metric *pm,
+ const struct pmu_metrics_table *table __maybe_unused,
+ void *data)
+{
+ unsigned int *max_level = data;
+ unsigned int level;
+ const char *p = strstr(pm->metric_group, "TopdownL");
+
+ if (!p || p[8] == '\0')
+ return 0;
+
+ level = p[8] - '0';
+ if (level > *max_level)
+ *max_level = level;
+
+ return 0;
+}
+
+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);
+ return max_level;
+}
+
int metricgroup__copy_metric_events(struct evlist *evlist, struct cgroup *cgrp,
struct rblist *new_metric_events,
struct rblist *old_metric_events)
diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
index 8d50052c5b4c..77472e35705e 100644
--- a/tools/perf/util/metricgroup.h
+++ b/tools/perf/util/metricgroup.h
@@ -81,6 +81,7 @@ int metricgroup__parse_groups_test(struct evlist *evlist,

void metricgroup__print(const struct print_callbacks *print_cb, void *print_state);
bool metricgroup__has_metric(const char *metric);
+unsigned int metricgroups__topdown_max_level(void);
int arch_get_runtimeparam(const struct pmu_metric *pm);
void metricgroup__rblist_exit(struct rblist *metric_events);

diff --git a/tools/perf/util/topdown.c b/tools/perf/util/topdown.c
index 1090841550f7..18fd5fed5d1a 100644
--- a/tools/perf/util/topdown.c
+++ b/tools/perf/util/topdown.c
@@ -1,74 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
-#include <stdio.h>
-#include "pmu.h"
-#include "pmu-hybrid.h"
#include "topdown.h"
-
-int topdown_filter_events(const char **attr, char **str, bool use_group,
- const char *pmu_name)
-{
- int off = 0;
- int i;
- int len = 0;
- char *s;
- bool is_hybrid = perf_pmu__is_hybrid(pmu_name);
-
- for (i = 0; attr[i]; i++) {
- if (pmu_have_event(pmu_name, attr[i])) {
- if (is_hybrid)
- len += strlen(attr[i]) + strlen(pmu_name) + 3;
- else
- len += strlen(attr[i]) + 1;
- attr[i - off] = attr[i];
- } else
- off++;
- }
- attr[i - off] = NULL;
-
- *str = malloc(len + 1 + 2);
- if (!*str)
- return -1;
- s = *str;
- if (i - off == 0) {
- *s = 0;
- return 0;
- }
- if (use_group)
- *s++ = '{';
- for (i = 0; attr[i]; i++) {
- if (!is_hybrid)
- strcpy(s, attr[i]);
- else
- sprintf(s, "%s/%s/", pmu_name, attr[i]);
- s += strlen(s);
- *s++ = ',';
- }
- if (use_group) {
- s[-1] = '}';
- *s = 0;
- } else
- s[-1] = 0;
- return 0;
-}
-
-__weak bool arch_topdown_check_group(bool *warn)
-{
- *warn = false;
- return false;
-}
-
-__weak void arch_topdown_group_warn(void)
-{
-}
+#include <linux/kernel.h>

__weak bool arch_topdown_sample_read(struct evsel *leader __maybe_unused)
{
return false;
}
-
-__weak const char *arch_get_topdown_pmu_name(struct evlist *evlist
- __maybe_unused,
- bool warn __maybe_unused)
-{
- return "cpu";
-}
diff --git a/tools/perf/util/topdown.h b/tools/perf/util/topdown.h
index f9531528c559..1996c5fedcd7 100644
--- a/tools/perf/util/topdown.h
+++ b/tools/perf/util/topdown.h
@@ -1,14 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef TOPDOWN_H
#define TOPDOWN_H 1
-#include "evsel.h"
-#include "evlist.h"

-bool arch_topdown_check_group(bool *warn);
-void arch_topdown_group_warn(void);
+#include <stdbool.h>
+
+struct evsel;
+
bool arch_topdown_sample_read(struct evsel *leader);
-const char *arch_get_topdown_pmu_name(struct evlist *evlist, bool warn);
-int topdown_filter_events(const char **attr, char **str, bool use_group,
- const char *pmu_name);

#endif
--
2.39.2.637.g21b0678d19-goog