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