projects/linux·blob master · refs

614 lines · 16.5 KiB

1#!/bin/bash
2# perf stat tests
3# SPDX-License-Identifier: GPL-2.0
4
5set -e
6
7err=0
8stat_output=$(mktemp /tmp/perf-stat-test-output.XXXXX)
9
10cleanup() {
11 rm -f "${stat_output}"
12 trap - EXIT TERM INT
13}
14
15trap_cleanup() {
16 echo "Unexpected signal in ${FUNCNAME[1]}"
17 cleanup
18 exit 1
19}
20
21trap trap_cleanup EXIT TERM INT
22
23test_default_stat() {
24 echo "Basic stat command test"
25 if ! perf stat true 2>&1 | grep -E -q "Performance counter stats for 'true':"
26 then
27 echo "Basic stat command test [Failed]"
28 err=1
29 return
30 fi
31 echo "Basic stat command test [Success]"
32}
33
34test_null_stat() {
35 echo "Null stat command test"
36 if ! perf stat --null true 2>&1 | grep -E -q "Performance counter stats for 'true':"
37 then
38 echo "Null stat command test [Failed]"
39 err=1
40 return
41 fi
42 echo "Null stat command test [Success]"
43}
44
45find_offline_cpu() {
46 for i in $(seq 1 4096)
47 do
48 if [[ ! -f /sys/devices/system/cpu/cpu$i/online || \
49 $(cat /sys/devices/system/cpu/cpu$i/online) == "0" ]]
50 then
51 echo $i
52 return
53 fi
54 done
55 echo "Failed to find offline CPU"
56 exit 1
57}
58
59test_offline_cpu_stat() {
60 cpu=$(find_offline_cpu)
61 echo "Offline CPU stat command test (cpu $cpu)"
62 if ! perf stat "-C$cpu" -e cycles true 2>&1 | grep -E -q "No supported events found."
63 then
64 echo "Offline CPU stat command test [Failed]"
65 err=1
66 return
67 fi
68 echo "Offline CPU stat command test [Success]"
69}
70
71test_stat_record_report() {
72 echo "stat record and report test"
73 if ! perf stat record -e task-clock -o - true | perf stat report -i - 2>&1 | \
74 grep -E -q "Performance counter stats for 'pipe':"
75 then
76 echo "stat record and report test [Failed]"
77 err=1
78 return
79 fi
80 echo "stat record and report test [Success]"
81}
82
83test_stat_record_script() {
84 echo "stat record and script test"
85 if ! perf stat record -e task-clock -o - true | perf script -i - 2>&1 | \
86 grep -E -q "CPU[[:space:]]+THREAD[[:space:]]+VAL[[:space:]]+ENA[[:space:]]+RUN[[:space:]]+TIME[[:space:]]+EVENT"
87 then
88 echo "stat record and script test [Failed]"
89 err=1
90 return
91 fi
92 echo "stat record and script test [Success]"
93}
94
95test_stat_repeat_weak_groups() {
96 echo "stat repeat weak groups test"
97 if ! perf stat -e '{cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles}' \
98 true 2>&1 | grep -q 'seconds time elapsed'
99 then
100 echo "stat repeat weak groups test [Skipped event parsing failed]"
101 return
102 fi
103 if ! perf stat -r2 -e '{cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles,cycles}:W' \
104 true > /dev/null 2>&1
105 then
106 echo "stat repeat weak groups test [Failed]"
107 err=1
108 return
109 fi
110 echo "stat repeat weak groups test [Success]"
111}
112
113test_topdown_groups() {
114 # Topdown events must be grouped with the slots event first. Test that
115 # parse-events reorders this.
116 echo "Topdown event group test"
117 if ! perf stat -e '{slots,topdown-retiring}' true > /dev/null 2>&1
118 then
119 echo "Topdown event group test [Skipped event parsing failed]"
120 return
121 fi
122 td_err=0
123 do_topdown_group_test() {
124 events=$1
125 failure=$2
126 if perf stat -e "$events" true 2>&1 | grep -E -q "<not supported>"
127 then
128 echo "Topdown event group test [Failed $failure for '$events']"
129 td_err=1
130 return
131 fi
132 }
133 do_topdown_group_test "{slots,topdown-retiring}" "events not supported"
134 do_topdown_group_test "{instructions,r400,r8000}" "raw format slots not reordered first"
135 filler_events=("instructions" "cycles"
136 "context-switches" "faults")
137 for ((i = 0; i < ${#filler_events[@]}; i+=2))
138 do
139 filler1=${filler_events[i]}
140 filler2=${filler_events[i+1]}
141 do_topdown_group_test "$filler1,topdown-retiring,slots" \
142 "slots not reordered first in no-group case"
143 do_topdown_group_test "slots,$filler1,topdown-retiring" \
144 "topdown metrics event not reordered in no-group case"
145 do_topdown_group_test "{$filler1,topdown-retiring,slots}" \
146 "slots not reordered first in single group case"
147 do_topdown_group_test "{$filler1,slots},topdown-retiring" \
148 "topdown metrics event not move into slots group"
149 do_topdown_group_test "topdown-retiring,{$filler1,slots}" \
150 "topdown metrics event not move into slots group last"
151 do_topdown_group_test "{$filler1,slots},{topdown-retiring}" \
152 "topdown metrics group not merge into slots group"
153 do_topdown_group_test "{topdown-retiring},{$filler1,slots}" \
154 "topdown metrics group not merge into slots group last"
155 do_topdown_group_test "{$filler1,slots},$filler2,topdown-retiring" \
156 "non-adjacent topdown metrics group not move into slots group"
157 do_topdown_group_test "$filler2,topdown-retiring,{$filler1,slots}" \
158 "non-adjacent topdown metrics group not move into slots group last"
159 do_topdown_group_test "{$filler1,slots},{$filler2,topdown-retiring}" \
160 "metrics group not merge into slots group"
161 do_topdown_group_test "{$filler1,topdown-retiring},{$filler2,slots}" \
162 "metrics group not merge into slots group last"
163 done
164 if test "$td_err" -eq 0
165 then
166 echo "Topdown event group test [Success]"
167 else
168 err="$td_err"
169 fi
170}
171
172test_topdown_weak_groups() {
173 # Weak groups break if the perf_event_open of multiple grouped events
174 # fails. Breaking a topdown group causes the events to fail. Test a very large
175 # grouping to see that the topdown events aren't broken out.
176 echo "Topdown weak groups test"
177 ok_grouping="{slots,topdown-bad-spec,topdown-be-bound,topdown-fe-bound,topdown-retiring},branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,mem-loads,mem-stores,ref-cycles,cache-misses,cache-references"
178 if ! perf stat --no-merge -e "$ok_grouping" true > /dev/null 2>&1
179 then
180 echo "Topdown weak groups test [Skipped event parsing failed]"
181 return
182 fi
183 group_needs_break="{slots,topdown-bad-spec,topdown-be-bound,topdown-fe-bound,topdown-retiring,branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,mem-loads,mem-stores,ref-cycles,cache-misses,cache-references}:W"
184 if perf stat --no-merge -e "$group_needs_break" true 2>&1 | grep -E -q "<not supported>"
185 then
186 echo "Topdown weak groups test [Failed events not supported]"
187 err=1
188 return
189 fi
190 echo "Topdown weak groups test [Success]"
191}
192
193test_cputype() {
194 # Test --cputype argument.
195 echo "cputype test"
196
197 # Bogus PMU should fail.
198 if perf stat --cputype="123" -e instructions true > /dev/null 2>&1
199 then
200 echo "cputype test [Bogus PMU didn't fail]"
201 err=1
202 return
203 fi
204
205 # Find a known PMU for cputype.
206 pmu=""
207 devs="/sys/bus/event_source/devices"
208 for i in $devs/cpu $devs/cpu_atom $devs/armv8_pmuv3_0 $devs/armv8_cortex_*
209 do
210 i_base=$(basename "$i")
211 if test -d "$i"
212 then
213 pmu="$i_base"
214 break
215 fi
216 if perf stat -e "$i_base/instructions/" true > /dev/null 2>&1
217 then
218 pmu="$i_base"
219 break
220 fi
221 done
222 if test "x$pmu" = "x"
223 then
224 echo "cputype test [Skipped known PMU not found]"
225 return
226 fi
227
228 # Test running with cputype produces output.
229 if ! perf stat --cputype="$pmu" -e instructions true 2>&1 | grep -E -q "instructions"
230 then
231 echo "cputype test [Failed count missed with given filter]"
232 err=1
233 return
234 fi
235 echo "cputype test [Success]"
236}
237
238test_hybrid() {
239 # Test the default stat command on hybrid devices opens one cycles event for
240 # each CPU type.
241 echo "hybrid test"
242
243 # Count the number of core PMUs, assume minimum of 1
244 pmus=$(ls /sys/bus/event_source/devices/*/cpus 2>/dev/null | wc -l)
245 if [ "$pmus" -lt 1 ]
246 then
247 pmus=1
248 fi
249
250 # Run default Perf stat
251 cycles_events=$(perf stat -a -- sleep 0.1 2>&1 | grep -E "/cpu-cycles/[uH]*| cpu-cycles[:uH]* " | wc -l)
252
253 # The expectation is that default output will have a cycles events on each
254 # hybrid PMU. In situations with no cycles PMU events, like virtualized, this
255 # can fall back to task-clock and so the end count may be 0. Fail if neither
256 # condition holds.
257 if [ "$pmus" -ne "$cycles_events" ] && [ "0" -ne "$cycles_events" ]
258 then
259 echo "hybrid test [Found $pmus PMUs but $cycles_events cycles events. Failed]"
260 err=1
261 return
262 fi
263 echo "hybrid test [Success]"
264}
265
266test_stat_cpu() {
267 echo "stat -C <cpu> test"
268 # Test the full online CPU list (ranges and lists)
269 online_cpus=$(cat /sys/devices/system/cpu/online)
270 if ! perf stat -C "$online_cpus" -a true > "${stat_output}" 2>&1
271 then
272 echo "stat -C <cpu> test [Failed - command failed for cpus $online_cpus]"
273 cat "${stat_output}"
274 err=1
275 return
276 fi
277
278 if ! grep -E -q "Performance counter stats for" "${stat_output}"
279 then
280 echo "stat -C <cpu> test [Failed - missing output for cpus $online_cpus]"
281 cat "${stat_output}"
282 err=1
283 return
284 fi
285
286 # Test each individual online CPU
287 for cpu_dir in /sys/devices/system/cpu/cpu[0-9]*; do
288 cpu=${cpu_dir##*/cpu}
289 # Check if online
290 if [ -f "$cpu_dir/online" ] && [ "$(cat "$cpu_dir/online")" -eq 0 ]
291 then
292 continue
293 fi
294
295 if ! perf stat -C "$cpu" -a true > "${stat_output}" 2>&1
296 then
297 echo "stat -C <cpu> test [Failed - command failed for cpu $cpu]"
298 cat "${stat_output}"
299 err=1
300 return
301 fi
302 if ! grep -E -q "Performance counter stats for" "${stat_output}"
303 then
304 echo "stat -C <cpu> test [Failed - missing output for cpu $cpu]"
305 cat "${stat_output}"
306 err=1
307 return
308 fi
309 done
310
311 # Test synthetic list and range if cpu0 and cpu1 are online
312 c0_online=0
313 c1_online=0
314 if [ -d "/sys/devices/system/cpu/cpu0" ]
315 then
316 if [ ! -f "/sys/devices/system/cpu/cpu0/online" ] || [ "$(cat /sys/devices/system/cpu/cpu0/online)" -eq 1 ]
317 then
318 c0_online=1
319 fi
320 fi
321 if [ -d "/sys/devices/system/cpu/cpu1" ]
322 then
323 if [ ! -f "/sys/devices/system/cpu/cpu1/online" ] || [ "$(cat /sys/devices/system/cpu/cpu1/online)" -eq 1 ]
324 then
325 c1_online=1
326 fi
327 fi
328
329 if [ $c0_online -eq 1 ] && [ $c1_online -eq 1 ]
330 then
331 # Test list "0,1"
332 if ! perf stat -C "0,1" -a true > "${stat_output}" 2>&1
333 then
334 echo "stat -C <cpu> test [Failed - command failed for cpus 0,1]"
335 cat "${stat_output}"
336 err=1
337 return
338 fi
339 if ! grep -E -q "Performance counter stats for" "${stat_output}"
340 then
341 echo "stat -C <cpu> test [Failed - missing output for cpus 0,1]"
342 cat "${stat_output}"
343 err=1
344 return
345 fi
346
347 # Test range "0-1"
348 if ! perf stat -C "0-1" -a true > "${stat_output}" 2>&1
349 then
350 echo "stat -C <cpu> test [Failed - command failed for cpus 0-1]"
351 cat "${stat_output}"
352 err=1
353 return
354 fi
355 if ! grep -E -q "Performance counter stats for" "${stat_output}"
356 then
357 echo "stat -C <cpu> test [Failed - missing output for cpus 0-1]"
358 cat "${stat_output}"
359 err=1
360 return
361 fi
362 fi
363
364 echo "stat -C <cpu> test [Success]"
365}
366
367test_stat_no_aggr() {
368 echo "stat -A test"
369 if ! perf stat -A -a true > "${stat_output}" 2>&1
370 then
371 echo "stat -A test [Failed - command failed]"
372 cat "${stat_output}"
373 err=1
374 return
375 fi
376
377 if ! grep -E -q "CPU" "${stat_output}"
378 then
379 echo "stat -A test [Failed - missing CPU column]"
380 cat "${stat_output}"
381 err=1
382 return
383 fi
384 echo "stat -A test [Success]"
385}
386
387test_stat_detailed() {
388 echo "stat -d test"
389 if ! perf stat -d true > "${stat_output}" 2>&1
390 then
391 echo "stat -d test [Failed - command failed]"
392 cat "${stat_output}"
393 err=1
394 return
395 fi
396
397 if ! grep -E -q "Performance counter stats" "${stat_output}"
398 then
399 echo "stat -d test [Failed - missing output]"
400 cat "${stat_output}"
401 err=1
402 return
403 fi
404
405 if ! perf stat -dd true > "${stat_output}" 2>&1
406 then
407 echo "stat -dd test [Failed - command failed]"
408 cat "${stat_output}"
409 err=1
410 return
411 fi
412
413 if ! grep -E -q "Performance counter stats" "${stat_output}"
414 then
415 echo "stat -dd test [Failed - missing output]"
416 cat "${stat_output}"
417 err=1
418 return
419 fi
420
421 if ! perf stat -ddd true > "${stat_output}" 2>&1
422 then
423 echo "stat -ddd test [Failed - command failed]"
424 cat "${stat_output}"
425 err=1
426 return
427 fi
428
429 if ! grep -E -q "Performance counter stats" "${stat_output}"
430 then
431 echo "stat -ddd test [Failed - missing output]"
432 cat "${stat_output}"
433 err=1
434 return
435 fi
436
437 echo "stat -d test [Success]"
438}
439
440test_stat_repeat() {
441 echo "stat -r test"
442 if ! perf stat -r 2 true > "${stat_output}" 2>&1
443 then
444 echo "stat -r test [Failed - command failed]"
445 cat "${stat_output}"
446 err=1
447 return
448 fi
449
450 if ! grep -E -q "\([[:space:]]*\+-.*%[[:space:]]*\)" "${stat_output}"
451 then
452 echo "stat -r test [Failed - missing variance]"
453 cat "${stat_output}"
454 err=1
455 return
456 fi
457 echo "stat -r test [Success]"
458}
459
460test_stat_pid() {
461 echo "stat -p test"
462 sleep 1 &
463 pid=$!
464 if ! perf stat -p $pid > "${stat_output}" 2>&1
465 then
466 echo "stat -p test [Failed - command failed]"
467 cat "${stat_output}"
468 err=1
469 kill $pid 2>/dev/null || true
470 wait $pid 2>/dev/null || true
471 return
472 fi
473
474 if ! grep -E -q "Performance counter stats" "${stat_output}"
475 then
476 echo "stat -p test [Failed - missing output]"
477 cat "${stat_output}"
478 err=1
479 else
480 echo "stat -p test [Success]"
481 fi
482 kill $pid 2>/dev/null || true
483 wait $pid 2>/dev/null || true
484}
485
486test_stat_delay() {
487 echo "stat -D test"
488 if ! env LC_ALL=C perf stat -D 1000 -e duration_time sleep 2 > "${stat_output}" 2>&1
489 then
490 echo "stat -D test [Failed - command failed]"
491 cat "${stat_output}"
492 err=1
493 return
494 fi
495
496 duration=$(grep "duration_time" "${stat_output}" | awk '{print $1}' | tr -d ',')
497 elapsed=$(grep "seconds time elapsed" "${stat_output}" | awk '{print $1}')
498
499 if [ -z "$duration" ] || [ -z "$elapsed" ]
500 then
501 echo "stat -D test [Failed - failed to find duration_time or time elapsed in output]"
502 cat "${stat_output}"
503 err=1
504 return
505 fi
506
507 # Compare duration (ns) and elapsed (s) using awk to handle float and allow tolerance.
508 if ! awk -v d="$duration" -v e="$elapsed" '
509 BEGIN {
510 diff = d - (e * 1e9);
511 if (diff < 0) diff = -diff;
512 # Allow 200ms tolerance (200,000,000 ns) for loaded CI machines.
513 if (diff > 200000000) {
514 printf "Fail: duration (%d ns) and elapsed (%f s) mismatch (diff %d ns)\n", d, e, diff;
515 exit 1;
516 }
517 # Lower bound check: must be at least 0.5s.
518 if (d < 500000000) {
519 printf "Fail: duration (%d ns) is abnormally small\n", d;
520 exit 1;
521 }
522 # Upper bound check: must be strictly less than 1.7s (proving delay was excluded).
523 if (d > 1700000000) {
524 printf "Fail: duration (%d ns) is too large (delay might not be excluded)\n", d;
525 exit 1;
526 }
527 exit 0;
528 }'
529 then
530 echo "stat -D test [Failed - validation failed]"
531 cat "${stat_output}"
532 err=1
533 return
534 fi
535 echo "stat -D test [Success]"
536}
537
538test_csv_json_fail() {
539 echo "stat -x <sep> -j test"
540 if perf stat -x , -j true > /dev/null 2>&1
541 then
542 echo "stat -x <sep> -j test [Failed - command should have errored]"
543 err=1
544 else
545 echo "stat -x <sep> -j test [Success]"
546 fi
547}
548
549test_hide_zero_events_stat() {
550 echo "Hide zero events stat test"
551 if ! perf stat -e context-switches,cpu-migrations true > "${stat_output}" 2>&1
552 then
553 echo "Hide zero events stat test [Skipped event parsing failed]"
554 return
555 fi
556
557 zero_event=""
558 if grep -q -E "[[:space:]]+0[[:space:]]+context-switches" "${stat_output}"; then
559 zero_event="context-switches"
560 elif grep -q -E "[[:space:]]+0[[:space:]]+cpu-migrations" "${stat_output}"; then
561 zero_event="cpu-migrations"
562 fi
563
564 if [ -z "$zero_event" ]; then
565 echo "Hide zero events stat test [Skipped - no zero count event found]"
566 return
567 fi
568
569 if ! perf stat --hide-zero-events -e context-switches,cpu-migrations true > "${stat_output}" 2>&1
570 then
571 echo "Hide zero events stat test [Failed - command failed]"
572 err=1
573 return
574 fi
575
576 if grep -q -E "$zero_event" "${stat_output}"
577 then
578 echo "Hide zero events stat test [Failed - zero event $zero_event was not hidden]"
579 err=1
580 return
581 fi
582
583 # Check that --metric-only works with --hide-zero-events
584 if ! perf stat --hide-zero-events --metric-only -e instructions,cycles true > "${stat_output}" 2>&1
585 then
586 echo "Hide zero events stat test [Failed - metric-only command failed]"
587 err=1
588 return
589 fi
590
591 echo "Hide zero events stat test [Success]"
592}
593
594test_default_stat
595test_null_stat
596test_offline_cpu_stat
597test_stat_record_report
598test_stat_record_script
599test_stat_repeat_weak_groups
600test_topdown_groups
601test_topdown_weak_groups
602test_cputype
603test_hybrid
604test_stat_cpu
605test_stat_no_aggr
606test_stat_detailed
607test_stat_repeat
608test_stat_pid
609test_stat_delay
610test_csv_json_fail
611test_hide_zero_events_stat
612
613cleanup
614exit $err