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