1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/compiler.h> |
3 | #include <string.h> |
4 | #include <perf/cpumap.h> |
5 | #include <perf/evlist.h> |
6 | #include "metricgroup.h" |
7 | #include "tests.h" |
8 | #include "pmu-events/pmu-events.h" |
9 | #include "evlist.h" |
10 | #include "rblist.h" |
11 | #include "debug.h" |
12 | #include "expr.h" |
13 | #include "stat.h" |
14 | #include "pmus.h" |
15 | |
16 | struct value { |
17 | const char *event; |
18 | u64 val; |
19 | }; |
20 | |
21 | static u64 find_value(const char *name, struct value *values) |
22 | { |
23 | struct value *v = values; |
24 | |
25 | while (v->event) { |
26 | if (!strcmp(name, v->event)) |
27 | return v->val; |
28 | v++; |
29 | } |
30 | return 0; |
31 | } |
32 | |
33 | static void load_runtime_stat(struct evlist *evlist, struct value *vals) |
34 | { |
35 | struct evsel *evsel; |
36 | u64 count; |
37 | |
38 | evlist__alloc_aggr_stats(evlist, 1); |
39 | evlist__for_each_entry(evlist, evsel) { |
40 | count = find_value(name: evsel->name, values: vals); |
41 | evsel->supported = true; |
42 | evsel->stats->aggr->counts.val = count; |
43 | if (evsel__name_is(evsel, "duration_time" )) |
44 | update_stats(&walltime_nsecs_stats, count); |
45 | } |
46 | } |
47 | |
48 | static double compute_single(struct rblist *metric_events, struct evlist *evlist, |
49 | const char *name) |
50 | { |
51 | struct metric_expr *mexp; |
52 | struct metric_event *me; |
53 | struct evsel *evsel; |
54 | |
55 | evlist__for_each_entry(evlist, evsel) { |
56 | me = metricgroup__lookup(metric_events, evsel, false); |
57 | if (me != NULL) { |
58 | list_for_each_entry (mexp, &me->head, nd) { |
59 | if (strcmp(mexp->metric_name, name)) |
60 | continue; |
61 | return test_generic_metric(mexp, 0); |
62 | } |
63 | } |
64 | } |
65 | return 0.; |
66 | } |
67 | |
68 | static int __compute_metric(const char *name, struct value *vals, |
69 | const char *name1, double *ratio1, |
70 | const char *name2, double *ratio2) |
71 | { |
72 | struct rblist metric_events = { |
73 | .nr_entries = 0, |
74 | }; |
75 | const struct pmu_metrics_table *pme_test; |
76 | struct perf_cpu_map *cpus; |
77 | struct evlist *evlist; |
78 | int err; |
79 | |
80 | /* |
81 | * We need to prepare evlist for stat mode running on CPU 0 |
82 | * because that's where all the stats are going to be created. |
83 | */ |
84 | evlist = evlist__new(); |
85 | if (!evlist) |
86 | return -ENOMEM; |
87 | |
88 | cpus = perf_cpu_map__new("0" ); |
89 | if (!cpus) { |
90 | evlist__delete(evlist); |
91 | return -ENOMEM; |
92 | } |
93 | |
94 | perf_evlist__set_maps(&evlist->core, cpus, NULL); |
95 | |
96 | /* Parse the metric into metric_events list. */ |
97 | pme_test = find_core_metrics_table("testarch" , "testcpu" ); |
98 | err = metricgroup__parse_groups_test(evlist, pme_test, name, |
99 | &metric_events); |
100 | if (err) |
101 | goto out; |
102 | |
103 | err = evlist__alloc_stats(/*config=*/NULL, evlist, /*alloc_raw=*/false); |
104 | if (err) |
105 | goto out; |
106 | |
107 | /* Load the runtime stats with given numbers for events. */ |
108 | load_runtime_stat(evlist, vals); |
109 | |
110 | /* And execute the metric */ |
111 | if (name1 && ratio1) |
112 | *ratio1 = compute_single(metric_events: &metric_events, evlist, name: name1); |
113 | if (name2 && ratio2) |
114 | *ratio2 = compute_single(metric_events: &metric_events, evlist, name: name2); |
115 | |
116 | out: |
117 | /* ... cleanup. */ |
118 | metricgroup__rblist_exit(&metric_events); |
119 | evlist__free_stats(evlist); |
120 | perf_cpu_map__put(cpus); |
121 | evlist__delete(evlist); |
122 | return err; |
123 | } |
124 | |
125 | static int compute_metric(const char *name, struct value *vals, double *ratio) |
126 | { |
127 | return __compute_metric(name, vals, name1: name, ratio1: ratio, NULL, NULL); |
128 | } |
129 | |
130 | static int compute_metric_group(const char *name, struct value *vals, |
131 | const char *name1, double *ratio1, |
132 | const char *name2, double *ratio2) |
133 | { |
134 | return __compute_metric(name, vals, name1, ratio1, name2, ratio2); |
135 | } |
136 | |
137 | static int test_ipc(void) |
138 | { |
139 | double ratio; |
140 | struct value vals[] = { |
141 | { .event = "inst_retired.any" , .val = 300 }, |
142 | { .event = "cpu_clk_unhalted.thread" , .val = 200 }, |
143 | { .event = NULL, }, |
144 | }; |
145 | |
146 | TEST_ASSERT_VAL("failed to compute metric" , |
147 | compute_metric("IPC" , vals, &ratio) == 0); |
148 | |
149 | TEST_ASSERT_VAL("IPC failed, wrong ratio" , |
150 | ratio == 1.5); |
151 | return 0; |
152 | } |
153 | |
154 | static int test_frontend(void) |
155 | { |
156 | double ratio; |
157 | struct value vals[] = { |
158 | { .event = "idq_uops_not_delivered.core" , .val = 300 }, |
159 | { .event = "cpu_clk_unhalted.thread" , .val = 200 }, |
160 | { .event = "cpu_clk_unhalted.one_thread_active" , .val = 400 }, |
161 | { .event = "cpu_clk_unhalted.ref_xclk" , .val = 600 }, |
162 | { .event = NULL, }, |
163 | }; |
164 | |
165 | TEST_ASSERT_VAL("failed to compute metric" , |
166 | compute_metric("Frontend_Bound_SMT" , vals, &ratio) == 0); |
167 | |
168 | TEST_ASSERT_VAL("Frontend_Bound_SMT failed, wrong ratio" , |
169 | ratio == 0.45); |
170 | return 0; |
171 | } |
172 | |
173 | static int test_cache_miss_cycles(void) |
174 | { |
175 | double ratio; |
176 | struct value vals[] = { |
177 | { .event = "l1d-loads-misses" , .val = 300 }, |
178 | { .event = "l1i-loads-misses" , .val = 200 }, |
179 | { .event = "inst_retired.any" , .val = 400 }, |
180 | { .event = NULL, }, |
181 | }; |
182 | |
183 | TEST_ASSERT_VAL("failed to compute metric" , |
184 | compute_metric("cache_miss_cycles" , vals, &ratio) == 0); |
185 | |
186 | TEST_ASSERT_VAL("cache_miss_cycles failed, wrong ratio" , |
187 | ratio == 1.25); |
188 | return 0; |
189 | } |
190 | |
191 | |
192 | /* |
193 | * DCache_L2_All_Hits = l2_rqsts.demand_data_rd_hit + l2_rqsts.pf_hit + l2_rqsts.rfo_hi |
194 | * DCache_L2_All_Miss = max(l2_rqsts.all_demand_data_rd - l2_rqsts.demand_data_rd_hit, 0) + |
195 | * l2_rqsts.pf_miss + l2_rqsts.rfo_miss |
196 | * DCache_L2_All = dcache_l2_all_hits + dcache_l2_all_miss |
197 | * DCache_L2_Hits = d_ratio(dcache_l2_all_hits, dcache_l2_all) |
198 | * DCache_L2_Misses = d_ratio(dcache_l2_all_miss, dcache_l2_all) |
199 | * |
200 | * l2_rqsts.demand_data_rd_hit = 100 |
201 | * l2_rqsts.pf_hit = 200 |
202 | * l2_rqsts.rfo_hi = 300 |
203 | * l2_rqsts.all_demand_data_rd = 400 |
204 | * l2_rqsts.pf_miss = 500 |
205 | * l2_rqsts.rfo_miss = 600 |
206 | * |
207 | * DCache_L2_All_Hits = 600 |
208 | * DCache_L2_All_Miss = MAX(400 - 100, 0) + 500 + 600 = 1400 |
209 | * DCache_L2_All = 600 + 1400 = 2000 |
210 | * DCache_L2_Hits = 600 / 2000 = 0.3 |
211 | * DCache_L2_Misses = 1400 / 2000 = 0.7 |
212 | */ |
213 | static int test_dcache_l2(void) |
214 | { |
215 | double ratio; |
216 | struct value vals[] = { |
217 | { .event = "l2_rqsts.demand_data_rd_hit" , .val = 100 }, |
218 | { .event = "l2_rqsts.pf_hit" , .val = 200 }, |
219 | { .event = "l2_rqsts.rfo_hit" , .val = 300 }, |
220 | { .event = "l2_rqsts.all_demand_data_rd" , .val = 400 }, |
221 | { .event = "l2_rqsts.pf_miss" , .val = 500 }, |
222 | { .event = "l2_rqsts.rfo_miss" , .val = 600 }, |
223 | { .event = NULL, }, |
224 | }; |
225 | |
226 | TEST_ASSERT_VAL("failed to compute metric" , |
227 | compute_metric("DCache_L2_Hits" , vals, &ratio) == 0); |
228 | |
229 | TEST_ASSERT_VAL("DCache_L2_Hits failed, wrong ratio" , |
230 | ratio == 0.3); |
231 | |
232 | TEST_ASSERT_VAL("failed to compute metric" , |
233 | compute_metric("DCache_L2_Misses" , vals, &ratio) == 0); |
234 | |
235 | TEST_ASSERT_VAL("DCache_L2_Misses failed, wrong ratio" , |
236 | ratio == 0.7); |
237 | return 0; |
238 | } |
239 | |
240 | static int test_recursion_fail(void) |
241 | { |
242 | double ratio; |
243 | struct value vals[] = { |
244 | { .event = "inst_retired.any" , .val = 300 }, |
245 | { .event = "cpu_clk_unhalted.thread" , .val = 200 }, |
246 | { .event = NULL, }, |
247 | }; |
248 | |
249 | TEST_ASSERT_VAL("failed to find recursion" , |
250 | compute_metric("M1" , vals, &ratio) == -1); |
251 | |
252 | TEST_ASSERT_VAL("failed to find recursion" , |
253 | compute_metric("M3" , vals, &ratio) == -1); |
254 | return 0; |
255 | } |
256 | |
257 | static int test_memory_bandwidth(void) |
258 | { |
259 | double ratio; |
260 | struct value vals[] = { |
261 | { .event = "l1d.replacement" , .val = 4000000 }, |
262 | { .event = "duration_time" , .val = 200000000 }, |
263 | { .event = NULL, }, |
264 | }; |
265 | |
266 | TEST_ASSERT_VAL("failed to compute metric" , |
267 | compute_metric("L1D_Cache_Fill_BW" , vals, &ratio) == 0); |
268 | TEST_ASSERT_VAL("L1D_Cache_Fill_BW, wrong ratio" , |
269 | 1.28 == ratio); |
270 | |
271 | return 0; |
272 | } |
273 | |
274 | static int test_metric_group(void) |
275 | { |
276 | double ratio1, ratio2; |
277 | struct value vals[] = { |
278 | { .event = "cpu_clk_unhalted.thread" , .val = 200 }, |
279 | { .event = "l1d-loads-misses" , .val = 300 }, |
280 | { .event = "l1i-loads-misses" , .val = 200 }, |
281 | { .event = "inst_retired.any" , .val = 400 }, |
282 | { .event = NULL, }, |
283 | }; |
284 | |
285 | TEST_ASSERT_VAL("failed to find recursion" , |
286 | compute_metric_group("group1" , vals, |
287 | "IPC" , &ratio1, |
288 | "cache_miss_cycles" , &ratio2) == 0); |
289 | |
290 | TEST_ASSERT_VAL("group IPC failed, wrong ratio" , |
291 | ratio1 == 2.0); |
292 | |
293 | TEST_ASSERT_VAL("group cache_miss_cycles failed, wrong ratio" , |
294 | ratio2 == 1.25); |
295 | return 0; |
296 | } |
297 | |
298 | static int test__parse_metric(struct test_suite *test __maybe_unused, int subtest __maybe_unused) |
299 | { |
300 | TEST_ASSERT_VAL("IPC failed" , test_ipc() == 0); |
301 | TEST_ASSERT_VAL("frontend failed" , test_frontend() == 0); |
302 | TEST_ASSERT_VAL("DCache_L2 failed" , test_dcache_l2() == 0); |
303 | TEST_ASSERT_VAL("recursion fail failed" , test_recursion_fail() == 0); |
304 | TEST_ASSERT_VAL("Memory bandwidth" , test_memory_bandwidth() == 0); |
305 | TEST_ASSERT_VAL("cache_miss_cycles failed" , test_cache_miss_cycles() == 0); |
306 | TEST_ASSERT_VAL("test metric group" , test_metric_group() == 0); |
307 | return 0; |
308 | } |
309 | |
310 | DEFINE_SUITE("Parse and process metrics" , parse_metric); |
311 | |