#include <errno.h>
#include "arch-tests.h"
#include "../util/topdown.h"
#include "debug.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, false, &ret, event_cb))
break;
}
return ret;
}
#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_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) {
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")) {
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,
};