summaryrefslogtreecommitdiff
path: root/tools/perf/arch/x86/tests/topdown.c
blob: 2b6f47ce493247a2978b4f43124af8943532a30b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// SPDX-License-Identifier: GPL-2.0
#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__put(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 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__put(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,
};