xref: /linux/tools/perf/tests/shell/stat_metrics_cgrp.sh (revision 3d5e48944e824bddc20d7b874e784f7b279636fe)
1#!/bin/bash
2# perf stat metrics --for-each-cgroup test
3# SPDX-License-Identifier: GPL-2.0
4
5set -e
6
7test_cgroups=
8
9log_verbose() {
10	echo "$1"
11}
12
13is_numeric_and_non_zero() {
14	local val="$1"
15	if [[ "${val}" =~ ^[0-9]+$ ]] && [ "${val}" -gt 0 ]
16	then
17		return 0 # True
18	fi
19	return 1 # False
20}
21
22# skip if system-wide is not supported
23check_system_wide()
24{
25	log_verbose "Checking system-wide..."
26	if ! perf stat -a --metrics=insn_per_cycle sleep 0.01 > /dev/null 2>&1
27	then
28		log_verbose "Skipping: system-wide monitoring not supported"
29		exit 2
30	fi
31}
32
33# find two cgroups to measure
34find_cgroups()
35{
36	log_verbose "Finding cgroups..."
37	# try usual systemd slices first
38	if [ -d /sys/fs/cgroup/system.slice ] && [ -d /sys/fs/cgroup/user.slice ]
39	then
40		test_cgroups="system.slice,user.slice"
41		log_verbose "Found cgroups: ${test_cgroups}"
42		return
43	fi
44
45	# try root and self cgroups
46	find_cgroups_self_cgrp=$(grep perf_event /proc/self/cgroup | cut -d: -f3)
47	if [ -z "${find_cgroups_self_cgrp}" ]
48	then
49		# cgroup v2 doesn't specify perf_event
50		find_cgroups_self_cgrp=$(grep ^0: /proc/self/cgroup | cut -d: -f3)
51	fi
52
53	if [ -z "${find_cgroups_self_cgrp}" ]
54	then
55		test_cgroups="/"
56	else
57		test_cgroups="/,${find_cgroups_self_cgrp}"
58	fi
59	log_verbose "Found cgroups: ${test_cgroups}"
60}
61
62# Check if metric is reported for each cgroup
63# $1: extra options (e.g. --bpf-counters)
64check_metric_reported()
65{
66	local opts="$1"
67	local output
68
69	log_verbose "Running check_metric_reported with opts '${opts}'..."
70	# Run perf stat
71	if ! output=$(perf stat -a ${opts} \
72			--metrics=insn_per_cycle \
73			--for-each-cgroup "${test_cgroups}" \
74			-x, sleep 0.1 2>&1)
75	then
76		echo "FAIL: perf stat failed with exit code $?"
77		echo "Output: ${output}"
78		exit 1
79	fi
80
81	log_verbose "perf stat output:"
82	log_verbose "${output}"
83
84	# Split test_cgroups by comma
85	IFS=',' read -r -a cgrps <<< "${test_cgroups}"
86
87	for cgrp in "${cgrps[@]}"; do
88		# Find metric lines for this cgroup
89		# We use exact cgroup match with surrounding commas
90		local cgrp_lines
91		cgrp_lines=$(echo "${output}" | grep -F ",${cgrp}," | grep "insn_per_cycle" || true)
92
93		if [ -z "${cgrp_lines}" ]
94		then
95			echo "FAIL: No metric lines found for cgroup '${cgrp}'"
96			exit 1
97		fi
98
99		# Parse each metric line
100		while read -r line; do
101			[ -z "${line}" ] && continue
102
103			local val1
104			val1=$(echo "${line}" | cut -d, -f1)
105
106			local event_name
107			event_name=$(echo "${line}" | cut -d, -f3)
108
109			local cycles_val=""
110			local inst_val=""
111
112			if echo "${event_name}" | grep -q -i "cycles"
113			then
114				cycles_val="${val1}"
115				# Find corresponding instructions event
116				local inst_event_name
117				inst_event_name="${event_name/cpu-cycles/instructions}"
118				inst_event_name="${inst_event_name/cycles/instructions}"
119
120				local inst_line
121				inst_line=$(echo "${output}" | \
122					grep -F ",${cgrp}," | \
123					grep -F "${inst_event_name}" || true)
124				inst_val=$(echo "${inst_line}" | cut -d, -f1)
125			elif echo "${event_name}" | grep -q -i "instructions"
126			then
127				inst_val="${val1}"
128				# Find corresponding cycles event (try cpu-cycles
129				# first, then cycles)
130				local cycles_event_name
131				cycles_event_name="${event_name/instructions/cpu-cycles}"
132				local cycles_line
133				cycles_line=$(echo "${output}" | \
134					grep -F ",${cgrp}," | \
135					grep -F "${cycles_event_name}" || true)
136
137				if [ -z "${cycles_line}" ]
138				then
139					# Try "cycles" instead of "cpu-cycles"
140					cycles_event_name="${event_name/instructions/cycles}"
141					cycles_line=$(echo "${output}" | \
142						grep -F ",${cgrp}," | \
143						grep -F "${cycles_event_name}" || true)
144				fi
145				cycles_val=$(echo "${cycles_line}" | cut -d, -f1)
146			fi
147
148			log_verbose "Cgroup '${cgrp}': event '${event_name}' \
149val '${cycles_val}', inst val '${inst_val}'"
150
151			# Only enforce metric check if both cycles and
152			# instructions have non-zero numeric counts
153			if is_numeric_and_non_zero "${cycles_val}" && \
154			   is_numeric_and_non_zero "${inst_val}"
155			then
156				log_verbose "Enforcing metric check for cgroup '${cgrp}' \
157event '${event_name}'"
158				# Check for nan or nested in the metric value (7th field)
159				# Actually we can just check the whole line for simplicity
160				if echo "${line}" | grep -q -i -E ",nan,|,nested,"
161				then
162					echo "FAIL: Invalid metric value (nan/nested) \
163for cgroup '${cgrp}'"
164					echo "Line: ${line}"
165					exit 1
166				fi
167				# Check for empty metric value (2 consecutive
168				# commas before the unit)
169				if echo "${line}" | grep -q -E ",,[[:space:]]*[^,]*insn_per_cycle"
170				then
171					echo "FAIL: Empty metric value for cgroup '${cgrp}'"
172					echo "Line: ${line}"
173					exit 1
174				fi
175			else
176				log_verbose "Skipping metric check for cgroup '${cgrp}' \
177event '${event_name}' (idle or not counted)"
178			fi
179		done <<< "${cgrp_lines}"
180	done
181	log_verbose "check_metric_reported passed for opts '${opts}'"
182}
183
184check_system_wide
185find_cgroups
186
187# Test 1: Without BPF counters
188check_metric_reported ""
189
190# Test 2: With BPF counters (if supported)
191log_verbose "Checking BPF support..."
192if perf stat -a --bpf-counters --for-each-cgroup / true > /dev/null 2>&1
193then
194	log_verbose "BPF supported, running Test 2..."
195	check_metric_reported "--bpf-counters"
196else
197	log_verbose "BPF not supported, skipping Test 2"
198fi
199
200exit 0
201