summaryrefslogtreecommitdiff
path: root/tools/perf/tests/shell/stat_metrics_cgrp.sh
blob: d4226ee0ae9801cbb07723bfb3be8ba02dc7b936 (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
#!/bin/bash
# perf stat metrics --for-each-cgroup test
# SPDX-License-Identifier: GPL-2.0

set -e

test_cgroups=

log_verbose() {
	echo "$1"
}

is_numeric_and_non_zero() {
	local val="$1"
	if [[ "${val}" =~ ^[0-9]+$ ]] && [ "${val}" -gt 0 ]
	then
		return 0 # True
	fi
	return 1 # False
}

# skip if system-wide is not supported
check_system_wide()
{
	log_verbose "Checking system-wide..."
	if ! perf stat -a --metrics=insn_per_cycle sleep 0.01 > /dev/null 2>&1
	then
		log_verbose "Skipping: system-wide monitoring not supported"
		exit 2
	fi
}

# find two cgroups to measure
find_cgroups()
{
	log_verbose "Finding cgroups..."
	# try usual systemd slices first
	if [ -d /sys/fs/cgroup/system.slice ] && [ -d /sys/fs/cgroup/user.slice ]
	then
		test_cgroups="system.slice,user.slice"
		log_verbose "Found cgroups: ${test_cgroups}"
		return
	fi

	# try root and self cgroups
	find_cgroups_self_cgrp=$(grep perf_event /proc/self/cgroup | cut -d: -f3)
	if [ -z "${find_cgroups_self_cgrp}" ]
	then
		# cgroup v2 doesn't specify perf_event
		find_cgroups_self_cgrp=$(grep ^0: /proc/self/cgroup | cut -d: -f3)
	fi

	if [ -z "${find_cgroups_self_cgrp}" ]
	then
		test_cgroups="/"
	else
		test_cgroups="/,${find_cgroups_self_cgrp}"
	fi
	log_verbose "Found cgroups: ${test_cgroups}"
}

# Check if metric is reported for each cgroup
# $1: extra options (e.g. --bpf-counters)
check_metric_reported()
{
	local opts="$1"
	local output

	log_verbose "Running check_metric_reported with opts '${opts}'..."
	# Run perf stat
	if ! output=$(perf stat -a ${opts} \
			--metrics=insn_per_cycle \
			--for-each-cgroup "${test_cgroups}" \
			-x, sleep 0.1 2>&1)
	then
		echo "FAIL: perf stat failed with exit code $?"
		echo "Output: ${output}"
		exit 1
	fi

	log_verbose "perf stat output:"
	log_verbose "${output}"

	# Split test_cgroups by comma
	IFS=',' read -r -a cgrps <<< "${test_cgroups}"

	for cgrp in "${cgrps[@]}"; do
		# Find metric lines for this cgroup
		# We use exact cgroup match with surrounding commas
		local cgrp_lines
		cgrp_lines=$(echo "${output}" | grep -F ",${cgrp}," | grep "insn_per_cycle" || true)

		if [ -z "${cgrp_lines}" ]
		then
			echo "FAIL: No metric lines found for cgroup '${cgrp}'"
			exit 1
		fi

		# Parse each metric line
		while read -r line; do
			[ -z "${line}" ] && continue

			local val1
			val1=$(echo "${line}" | cut -d, -f1)

			local event_name
			event_name=$(echo "${line}" | cut -d, -f3)

			local cycles_val=""
			local inst_val=""

			if echo "${event_name}" | grep -q -i "cycles"
			then
				cycles_val="${val1}"
				# Find corresponding instructions event
				local inst_event_name
				inst_event_name="${event_name/cpu-cycles/instructions}"
				inst_event_name="${inst_event_name/cycles/instructions}"

				local inst_line
				inst_line=$(echo "${output}" | \
					grep -F ",${cgrp}," | \
					grep -F "${inst_event_name}" || true)
				inst_val=$(echo "${inst_line}" | cut -d, -f1)
			elif echo "${event_name}" | grep -q -i "instructions"
			then
				inst_val="${val1}"
				# Find corresponding cycles event (try cpu-cycles
				# first, then cycles)
				local cycles_event_name
				cycles_event_name="${event_name/instructions/cpu-cycles}"
				local cycles_line
				cycles_line=$(echo "${output}" | \
					grep -F ",${cgrp}," | \
					grep -F "${cycles_event_name}" || true)

				if [ -z "${cycles_line}" ]
				then
					# Try "cycles" instead of "cpu-cycles"
					cycles_event_name="${event_name/instructions/cycles}"
					cycles_line=$(echo "${output}" | \
						grep -F ",${cgrp}," | \
						grep -F "${cycles_event_name}" || true)
				fi
				cycles_val=$(echo "${cycles_line}" | cut -d, -f1)
			fi

			log_verbose "Cgroup '${cgrp}': event '${event_name}' \
val '${cycles_val}', inst val '${inst_val}'"

			# Only enforce metric check if both cycles and
			# instructions have non-zero numeric counts
			if is_numeric_and_non_zero "${cycles_val}" && \
			   is_numeric_and_non_zero "${inst_val}"
			then
				log_verbose "Enforcing metric check for cgroup '${cgrp}' \
event '${event_name}'"
				# Check for nan or nested in the metric value (7th field)
				# Actually we can just check the whole line for simplicity
				if echo "${line}" | grep -q -i -E ",nan,|,nested,"
				then
					echo "FAIL: Invalid metric value (nan/nested) \
for cgroup '${cgrp}'"
					echo "Line: ${line}"
					exit 1
				fi
				# Check for empty metric value (2 consecutive
				# commas before the unit)
				if echo "${line}" | grep -q -E ",,[[:space:]]*[^,]*insn_per_cycle"
				then
					echo "FAIL: Empty metric value for cgroup '${cgrp}'"
					echo "Line: ${line}"
					exit 1
				fi
			else
				log_verbose "Skipping metric check for cgroup '${cgrp}' \
event '${event_name}' (idle or not counted)"
			fi
		done <<< "${cgrp_lines}"
	done
	log_verbose "check_metric_reported passed for opts '${opts}'"
}

check_system_wide
find_cgroups

# Test 1: Without BPF counters
check_metric_reported ""

# Test 2: With BPF counters (if supported)
log_verbose "Checking BPF support..."
if perf stat -a --bpf-counters --for-each-cgroup / true > /dev/null 2>&1
then
	log_verbose "BPF supported, running Test 2..."
	check_metric_reported "--bpf-counters"
else
	log_verbose "BPF not supported, skipping Test 2"
fi

exit 0