perf topdown: Use attribute to see an event is a topdown metic or slots

The string comparisons were overly broad and could fire for the
incorrect PMU and events. Switch to using the config in the attribute
then add a perf test to confirm the attribute config values match
those of parsed events of that name and don't match others. This
exposed matches for slots events that shouldn't have matched as the
slots fixed counter event, such as topdown.slots_p.

Fixes: fbc798316b ("perf x86/topdown: Refine helper arch_is_topdown_metrics()")
Signed-off-by: Ian Rogers <irogers@google.com>
Link: https://lore.kernel.org/r/20250719030517.1990983-14-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
This commit is contained in:
Ian Rogers 2025-07-18 20:05:15 -07:00 committed by Namhyung Kim
parent 811082e4b6
commit 5b546de9cc
7 changed files with 108 additions and 55 deletions

View File

@ -2,6 +2,8 @@
#ifndef ARCH_TESTS_H #ifndef ARCH_TESTS_H
#define ARCH_TESTS_H #define ARCH_TESTS_H
#include "tests/tests.h"
struct test_suite; struct test_suite;
/* Tests */ /* Tests */
@ -17,6 +19,8 @@ int test__amd_ibs_via_core_pmu(struct test_suite *test, int subtest);
int test__amd_ibs_period(struct test_suite *test, int subtest); int test__amd_ibs_period(struct test_suite *test, int subtest);
int test__hybrid(struct test_suite *test, int subtest); int test__hybrid(struct test_suite *test, int subtest);
DECLARE_SUITE(x86_topdown);
extern struct test_suite *arch_tests[]; extern struct test_suite *arch_tests[];
#endif #endif

View File

@ -11,6 +11,7 @@ endif
perf-test-$(CONFIG_X86_64) += bp-modify.o perf-test-$(CONFIG_X86_64) += bp-modify.o
perf-test-y += amd-ibs-via-core-pmu.o perf-test-y += amd-ibs-via-core-pmu.o
perf-test-y += amd-ibs-period.o perf-test-y += amd-ibs-period.o
perf-test-y += topdown.o
ifdef SHELLCHECK ifdef SHELLCHECK
SHELL_TESTS := gen-insn-x86-dat.sh SHELL_TESTS := gen-insn-x86-dat.sh

View File

@ -53,5 +53,6 @@ struct test_suite *arch_tests[] = {
&suite__amd_ibs_via_core_pmu, &suite__amd_ibs_via_core_pmu,
&suite__amd_ibs_period, &suite__amd_ibs_period,
&suite__hybrid, &suite__hybrid,
&suite__x86_topdown,
NULL, NULL,
}; };

View File

@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-2.0
#include "arch-tests.h"
#include "../util/topdown.h"
#include "evlist.h"
#include "parse-events.h"
#include "pmu.h"
#include "pmus.h"
static int event_cb(void *state, struct pmu_event_info *info)
{
char buf[256];
struct parse_events_error parse_err;
int *ret = state, err;
struct evlist *evlist = evlist__new();
struct evsel *evsel;
if (!evlist)
return -ENOMEM;
parse_events_error__init(&parse_err);
snprintf(buf, sizeof(buf), "%s/%s/", info->pmu->name, info->name);
err = parse_events(evlist, buf, &parse_err);
if (err) {
parse_events_error__print(&parse_err, buf);
*ret = TEST_FAIL;
}
parse_events_error__exit(&parse_err);
evlist__for_each_entry(evlist, evsel) {
bool fail = false;
bool p_core_pmu = evsel->pmu->type == PERF_TYPE_RAW;
const char *name = evsel__name(evsel);
if (strcasestr(name, "uops_retired.slots") ||
strcasestr(name, "topdown.backend_bound_slots") ||
strcasestr(name, "topdown.br_mispredict_slots") ||
strcasestr(name, "topdown.memory_bound_slots") ||
strcasestr(name, "topdown.bad_spec_slots") ||
strcasestr(name, "topdown.slots_p")) {
if (arch_is_topdown_slots(evsel) || arch_is_topdown_metrics(evsel))
fail = true;
} else if (strcasestr(name, "slots")) {
if (arch_is_topdown_slots(evsel) != p_core_pmu ||
arch_is_topdown_metrics(evsel))
fail = true;
} else if (strcasestr(name, "topdown")) {
if (arch_is_topdown_slots(evsel) ||
arch_is_topdown_metrics(evsel) != p_core_pmu)
fail = true;
} else if (arch_is_topdown_slots(evsel) || arch_is_topdown_metrics(evsel)) {
fail = true;
}
if (fail) {
pr_debug("Broken topdown information for '%s'\n", evsel__name(evsel));
*ret = TEST_FAIL;
}
}
evlist__delete(evlist);
return 0;
}
static int test__x86_topdown(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int ret = TEST_OK;
struct perf_pmu *pmu = NULL;
if (!topdown_sys_has_perf_metrics())
return TEST_OK;
while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
if (perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false, &ret, event_cb))
break;
}
return ret;
}
DEFINE_SUITE("x86 topdown", x86_topdown);

View File

@ -23,47 +23,25 @@ void arch_evsel__set_sample_weight(struct evsel *evsel)
bool evsel__sys_has_perf_metrics(const struct evsel *evsel) bool evsel__sys_has_perf_metrics(const struct evsel *evsel)
{ {
struct perf_pmu *pmu; struct perf_pmu *pmu;
u32 type = evsel->core.attr.type;
if (!topdown_sys_has_perf_metrics())
return false;
/* /*
* The PERF_TYPE_RAW type is the core PMU type, e.g., "cpu" PMU * The PERF_TYPE_RAW type is the core PMU type, e.g., "cpu" PMU on a
* on a non-hybrid machine, "cpu_core" PMU on a hybrid machine. * non-hybrid machine, "cpu_core" PMU on a hybrid machine. The
* The slots event is only available for the core PMU, which * topdown_sys_has_perf_metrics checks the slots event is only available
* supports the perf metrics feature. * for the core PMU, which supports the perf metrics feature. Checking
* Checking both the PERF_TYPE_RAW type and the slots event * both the PERF_TYPE_RAW type and the slots event should be good enough
* should be good enough to detect the perf metrics feature. * to detect the perf metrics feature.
*/ */
again: pmu = evsel__find_pmu(evsel);
switch (type) { return pmu && pmu->type == PERF_TYPE_RAW;
case PERF_TYPE_HARDWARE:
case PERF_TYPE_HW_CACHE:
type = evsel->core.attr.config >> PERF_PMU_TYPE_SHIFT;
if (type)
goto again;
break;
case PERF_TYPE_RAW:
break;
default:
return false;
}
pmu = evsel->pmu;
if (pmu && perf_pmu__is_fake(pmu))
pmu = NULL;
if (!pmu) {
while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
if (pmu->type == PERF_TYPE_RAW)
break;
}
}
return pmu && perf_pmu__have_event(pmu, "slots");
} }
bool arch_evsel__must_be_in_group(const struct evsel *evsel) bool arch_evsel__must_be_in_group(const struct evsel *evsel)
{ {
if (!evsel__sys_has_perf_metrics(evsel) || !evsel->name || if (!evsel__sys_has_perf_metrics(evsel))
strcasestr(evsel->name, "uops_retired.slots"))
return false; return false;
return arch_is_topdown_metrics(evsel) || arch_is_topdown_slots(evsel); return arch_is_topdown_metrics(evsel) || arch_is_topdown_slots(evsel);

View File

@ -1,6 +1,4 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "api/fs/fs.h"
#include "util/evsel.h"
#include "util/evlist.h" #include "util/evlist.h"
#include "util/pmu.h" #include "util/pmu.h"
#include "util/pmus.h" #include "util/pmus.h"
@ -8,6 +6,9 @@
#include "topdown.h" #include "topdown.h"
#include "evsel.h" #include "evsel.h"
// cmask=0, inv=0, pc=0, edge=0, umask=4, event=0
#define TOPDOWN_SLOTS 0x0400
/* Check whether there is a PMU which supports the perf metrics. */ /* Check whether there is a PMU which supports the perf metrics. */
bool topdown_sys_has_perf_metrics(void) bool topdown_sys_has_perf_metrics(void)
{ {
@ -32,31 +33,19 @@ bool topdown_sys_has_perf_metrics(void)
return has_perf_metrics; return has_perf_metrics;
} }
#define TOPDOWN_SLOTS 0x0400
bool arch_is_topdown_slots(const struct evsel *evsel) bool arch_is_topdown_slots(const struct evsel *evsel)
{ {
if (evsel->core.attr.config == TOPDOWN_SLOTS) return evsel->core.attr.type == PERF_TYPE_RAW &&
return true; evsel->core.attr.config == TOPDOWN_SLOTS &&
evsel->core.attr.config1 == 0;
return false;
} }
bool arch_is_topdown_metrics(const struct evsel *evsel) bool arch_is_topdown_metrics(const struct evsel *evsel)
{ {
int config = evsel->core.attr.config; // cmask=0, inv=0, pc=0, edge=0, umask=0x80-0x87, event=0
const char *name_from_config; return evsel->core.attr.type == PERF_TYPE_RAW &&
struct perf_pmu *pmu; (evsel->core.attr.config & 0xFFFFF8FF) == 0x8000 &&
evsel->core.attr.config1 == 0;
/* All topdown events have an event code of 0. */
if ((config & 0xFF) != 0)
return false;
pmu = evsel__find_pmu(evsel);
if (!pmu || !pmu->is_core)
return false;
name_from_config = perf_pmu__name_from_config(pmu, config);
return name_from_config && strcasestr(name_from_config, "topdown");
} }
/* /*

View File

@ -2,6 +2,10 @@
#ifndef _TOPDOWN_H #ifndef _TOPDOWN_H
#define _TOPDOWN_H 1 #define _TOPDOWN_H 1
#include <stdbool.h>
struct evsel;
bool topdown_sys_has_perf_metrics(void); bool topdown_sys_has_perf_metrics(void);
bool arch_is_topdown_slots(const struct evsel *evsel); bool arch_is_topdown_slots(const struct evsel *evsel);
bool arch_is_topdown_metrics(const struct evsel *evsel); bool arch_is_topdown_metrics(const struct evsel *evsel);