diff --git a/tools/perf/arch/x86/tests/topdown.c b/tools/perf/arch/x86/tests/topdown.c index 3ee4e5e71be3..221f2c4bbb61 100644 --- a/tools/perf/arch/x86/tests/topdown.c +++ b/tools/perf/arch/x86/tests/topdown.c @@ -75,4 +75,168 @@ static int test__x86_topdown(struct test_suite *test __maybe_unused, int subtest return ret; } -DEFINE_SUITE("x86 topdown", x86_topdown); +#define CHECK_COND(cond, text) \ +do { \ + if (!(cond)) { \ + pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \ + ret = TEST_FAIL; \ + goto out_err; \ + } \ +} while (0) + +#define CHECK_EQUAL(val, expected, text) \ +do { \ + if ((val) != (expected)) { \ + pr_debug("FAILED %s:%d %s (%d != %d)\n", \ + __FILE__, __LINE__, text, (val), (expected)); \ + ret = TEST_FAIL; \ + goto out_err; \ + } \ +} while (0) + +static int test_sort(const char *str, int expected_slots_group_size, + int expected_instructions_group_size) +{ + struct evlist *evlist = NULL; + struct parse_events_error err; + struct evsel *evsel; + int ret = TEST_FAIL; + bool slots_seen = false; + + parse_events_error__init(&err); + + evlist = evlist__new(); + if (!evlist) + goto out_err; + + if (parse_events(evlist, str, &err)) { + pr_debug("parse_events failed for %s\n", str); + goto out_err; + } + + evlist__for_each_entry(evlist, evsel) { + if (!evsel__is_group_leader(evsel)) + continue; + + if (strstr(evsel__name(evsel), "slots")) { + /* + * Slots as a leader means the PMU is for a perf metric + * group as the slots event isn't present when not. + */ + slots_seen = true; + CHECK_EQUAL(evsel->core.nr_members, expected_slots_group_size, + "slots group size"); + if (expected_slots_group_size == 3) { + struct evsel *next = evsel__next(evsel); + struct evsel *next2 = evsel__next(next); + + CHECK_COND(strstr(evsel__name(next), "instructions") != NULL, + "slots second event is instructions"); + CHECK_COND(strstr(evsel__name(next2), "topdown-retiring") != NULL, + "slots third event is topdown-retiring"); + } else if (expected_slots_group_size == 2) { + struct evsel *next = evsel__next(evsel); + + CHECK_COND(strstr(evsel__name(next), "topdown-retiring") != NULL, + "slots second event is topdown-retiring"); + } + } else if (strstr(evsel__name(evsel), "instructions")) { + CHECK_EQUAL(evsel->core.nr_members, expected_instructions_group_size, + "instructions group size"); + if (expected_instructions_group_size == 2) { + /* + * On Intel hybrid CPUs (e.g., Alder Lake/ + * Raptor Lake), E-cores (cpu_atom) do not + * support/enforce the slots event. When + * parsing event groups containing slots + * across all PMUs, slots is automatically + * filtered out from cpu_atom, leaving + * {cpu_atom/instructions/, + * cpu_atom/topdown-retiring/}. On cpu_atom, + * instructions correctly leads this group of + * 2 without slots reordering. + */ + struct evsel *next = evsel__next(evsel); + + CHECK_COND(strstr(evsel__name(next), "topdown-retiring") != NULL, + "instructions second event is topdown-retiring"); + } + } else if (strstr(evsel__name(evsel), "topdown-retiring")) { + /* + * A perf metric event where the PMU doesn't require + * slots as a leader. + */ + CHECK_EQUAL(evsel->core.nr_members, 1, "topdown-retiring group size"); + } else if (strstr(evsel__name(evsel), "cycles")) { + CHECK_EQUAL(evsel->core.nr_members, 1, "cycles group size"); + } + } + CHECK_COND(slots_seen, "slots seen"); + ret = TEST_OK; +out_err: + evlist__delete(evlist); + parse_events_error__exit(&err); + return ret; +} + +static int test__x86_topdown_sorting(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int ret; + + if (!topdown_sys_has_perf_metrics()) + return TEST_OK; + + ret = test_sort("{instructions,topdown-retiring,slots}", 3, 2); + TEST_ASSERT_EQUAL("all events in a group", ret, TEST_OK); + ret = test_sort("instructions,topdown-retiring,slots", 2, 1); + TEST_ASSERT_EQUAL("all events not in a group", ret, TEST_OK); + ret = test_sort("{instructions,slots},topdown-retiring", 2, 1); + TEST_ASSERT_EQUAL("slots event in a group but topdown metrics events outside the group", + ret, TEST_OK); + ret = test_sort("{instructions,slots},{topdown-retiring}", 2, 1); + TEST_ASSERT_EQUAL("slots event and topdown metrics events in two groups", + ret, TEST_OK); + ret = test_sort("{instructions,slots},cycles,topdown-retiring", 2, 1); + TEST_ASSERT_EQUAL("slots event and metrics event are not in a group and not adjacent", + ret, TEST_OK); + + return TEST_OK; +} + +static int test__x86_topdown_slots_injection(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) +{ + int ret; + + if (!topdown_sys_has_perf_metrics()) + return TEST_OK; + + ret = test_sort("{instructions,topdown-retiring}", 3, 2); + TEST_ASSERT_EQUAL("all events in a group", ret, TEST_OK); + ret = test_sort("instructions,topdown-retiring", 2, 1); + TEST_ASSERT_EQUAL("all events not in a group", ret, TEST_OK); + ret = test_sort("{instructions},topdown-retiring", 2, 1); + TEST_ASSERT_EQUAL("event in a group but topdown metrics events outside the group", + ret, TEST_OK); + ret = test_sort("{instructions},{topdown-retiring}", 2, 1); + TEST_ASSERT_EQUAL("event and topdown metrics events in two groups", + ret, TEST_OK); + ret = test_sort("{instructions},cycles,topdown-retiring", 2, 1); + TEST_ASSERT_EQUAL("event and metrics event are not in a group and not adjacent", + ret, TEST_OK); + + return TEST_OK; +} + +static struct test_case x86_topdown_tests[] = { + TEST_CASE("topdown events", x86_topdown), + TEST_CASE("topdown sorting", x86_topdown_sorting), + TEST_CASE("topdown slots injection", x86_topdown_slots_injection), + { .name = NULL, } +}; + +struct test_suite suite__x86_topdown = { + .desc = "x86 topdown", + .test_cases = x86_topdown_tests, +};