summaryrefslogtreecommitdiff
path: root/tools/perf/tests
diff options
context:
space:
mode:
authorIan Rogers <irogers@google.com>2026-09-25 23:19:43 -0700
committerArnaldo Carvalho de Melo <acme@redhat.com>2026-09-26 20:49:28 +0200
commit4425182d426b2180e98d0b537f46cdb03078876d (patch)
tree17bdf04632bf9747a69d2c53a7857b4b8f5d7fcf /tools/perf/tests
parentaf2f226e8c29fc4dfa381043b7de2c145532bbae (diff)
downloadlinux-next-4425182d426b2180e98d0b537f46cdb03078876d.tar.gz
linux-next-4425182d426b2180e98d0b537f46cdb03078876d.zip
perf python: Port stat-cpi to perf module
Port stat-cpi.py from the legacy embedded scripting framework to a standalone Python script in tools/perf/python/ to calculate Cycles Per Instruction (CPI) per interval per CPU or thread. Improvements compared to the legacy script: - Support both perf.data file mode (via perf.session stat callbacks) and live counter collection mode (using perf.parse_events, evlist.open, and evsel.read across intervals), with automatic fallback to user-space (:u) and self-process monitoring when perf_event_paranoid restricts system-wide events (EACCES). - Compute per-interval counter deltas (val, ena, run) keyed by raw event name so cumulative PERF_RECORD_STAT snapshots and hybrid PMU events (e.g. cpu_core/cycles/, cpu_atom/cycles/) are accumulated accurately, and scale counts by time_enabled / time_running when multiplexed. - Replace hard-coded CPU ([0, 1]) and thread ([0]) arrays with dynamic CPU and thread discovery so arbitrary system topologies work automatically. - Add CLI option handling (-i, -I, -p) via argparse and type annotations passing mypy and pylint. Add a shell test (test_stat_cpi_python.sh) to verify the standalone script. Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Ian Rogers <irogers@google.com> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Diffstat (limited to 'tools/perf/tests')
-rwxr-xr-xtools/perf/tests/shell/test_stat_cpi_python.sh115
1 files changed, 115 insertions, 0 deletions
diff --git a/tools/perf/tests/shell/test_stat_cpi_python.sh b/tools/perf/tests/shell/test_stat_cpi_python.sh
new file mode 100755
index 000000000000..0960afa4c797
--- /dev/null
+++ b/tools/perf/tests/shell/test_stat_cpi_python.sh
@@ -0,0 +1,115 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# stat-cpi python test
+
+set -e
+
+shelldir=$(dirname "$0")
+# shellcheck source=lib/setup_python.sh
+. "${shelldir}"/lib/setup_python.sh
+
+# If we don't have the perf python module, we can't test
+if ! "$PYTHON" -c 'import perf' > /dev/null 2>&1; then
+ echo "Skipping test, perf python module not found"
+ return 2 2>/dev/null || exit 2
+fi
+
+script_dir="$(dirname "$0")/../../python"
+script_path="${script_dir}/stat-cpi.py"
+
+if [ ! -f "$script_path" ]; then
+ echo "Skipping test, stat-cpi.py not found at $script_path"
+ return 2 2>/dev/null || exit 2
+fi
+
+err=0
+ran=0
+temp_data=""
+temp_out=""
+
+cleanup() {
+ [ -n "${pid}" ] && kill "$pid" 2>/dev/null || true
+ [ -n "${workload_pid}" ] && kill "$workload_pid" 2>/dev/null || true
+ rm -f "${temp_data}" "${temp_out}"
+ trap - exit term int
+}
+
+trap_cleanup() {
+ cleanup
+ exit 1
+}
+trap trap_cleanup exit term int
+
+temp_data=$(mktemp /tmp/perf.data.XXXXXX)
+temp_out=$(mktemp /tmp/perf.out.XXXXXX)
+
+test_live_mode() {
+ echo "Testing stat-cpi.py live mode..."
+ if ! perf stat -e cycles,instructions -- sleep 0.1 2>/dev/null && \
+ ! perf stat -e cycles:u,instructions:u -- sleep 0.1 2>/dev/null; then
+ echo "perf stat failed (permissions?), skipping live mode test."
+ return 0
+ fi
+ perf test -w noploop &
+ workload_pid=$!
+ if ! perf stat -e cycles,instructions -p "$workload_pid" -- sleep 0.05 2>/dev/null && \
+ ! perf stat -e cycles:u,instructions:u -p "$workload_pid" -- sleep 0.05 2>/dev/null; then
+ kill "$workload_pid" 2>/dev/null || true
+ workload_pid=""
+ echo "perf stat -p failed (ptrace_scope?), skipping live mode test."
+ return 0
+ fi
+ ran=1
+
+ # Run live mode for 1 interval in the background, give it a tiny sleep, then interrupt
+ "$PYTHON" "$script_path" -I 0.1 -p "$workload_pid" > "${temp_out}" &
+ pid=$!
+ sleep 0.5
+ kill -INT "$pid" 2>/dev/null || true
+ set +e
+ wait "$pid"
+ res=$?
+ set -e
+ pid=""
+ kill "$workload_pid" 2>/dev/null || true
+ workload_pid=""
+ if [ $res -ne 0 ] && [ $res -ne 130 ] && [ $res -ne 143 ]; then
+ echo "Live mode failed or crashed"
+ err=1
+ elif ! grep -q "cpi" "${temp_out}"; then
+ echo "Live mode produced no cpi output"
+ err=1
+ else
+ echo "Live mode test passed."
+ fi
+}
+
+test_file_mode() {
+ echo "Testing stat-cpi.py file mode..."
+ # Generate some stat events - perf stat -I represents interval reporting
+ if ! perf stat -e cycles,instructions -I 100 record -o "${temp_data}" \
+ -- sleep 0.5 2>/dev/null && \
+ ! perf stat -e cycles:u,instructions:u -I 100 record -o "${temp_data}" \
+ -- sleep 0.5 2>/dev/null; then
+ echo "perf stat failed (permissions?), skipping file mode test."
+ return
+ fi
+ ran=1
+
+ out=$("$PYTHON" "$script_path" -i "${temp_data}")
+ if ! echo "$out" | grep -q "cpi"; then
+ echo "File mode test failed."
+ err=1
+ else
+ echo "File mode test passed."
+ fi
+}
+
+test_live_mode
+test_file_mode
+
+cleanup
+if [ $ran -eq 0 ]; then
+ exit 2
+fi
+exit $err