From: Kunwu Chan <kunwu.chan@gmail.com>
To: sj@kernel.org, akpm@linux-foundation.org
Cc: damon@lists.linux.dev, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, linux-trace-kernel@vger.kernel.org,
linux-kselftest@vger.kernel.org, rostedt@goodmis.org,
mhiramat@kernel.org, mathieu.desnoyers@efficios.com,
shuah@kernel.org, lianux.mm@gmail.com,
Kunwu Chan <kunwu.chan@gmail.com>
Subject: [RFC PATCH 5/7] selftests/damon: add automated layer-by-layer observability test
Date: Tue, 18 Aug 2026 14:10:29 +0800 [thread overview]
Message-ID: <20260818061031.827057-6-kunwu.chan@linux.dev> (raw)
In-Reply-To: <20260818061031.827057-1-kunwu.chan@linux.dev>
From: Kunwu Chan <kunwu.chan@gmail.com>
Drive the observe framework end-to-end with a software page-fault PMU
positive control (no hardware PMU required): create a kdamond with a
perf event via the DAMON sysfs interface, run a memory-pressure
workload, capture a bounded trace window, and assert each pipeline
layer from per-run snapshot/delta counter deltas (counters are
cumulative since boot, so the snapshot is taken before the workload
window): callbacks, valid data addresses, ring enqueue/dequeue/
overflow, drain match/update, the four DAMON perf tracepoints, and the
per-CPU state column advancing CREATED/BOUND/ENABLED to RUNNING after
the first callback.
A clean-session guard refuses to run while a kdamond already exists.
Ends with a PMU support verdict (FULLY INTEGRATED / PLUMBING-ONLY /
UNUSABLE). Cleanup is ownership safe: it tears down only the kdamond,
workload and debugfs mount created by this invocation, and retains the
raw evidence directory by default.
Co-developed-by: Lian Wang <lianux.mm@gmail.com>
Signed-off-by: Lian Wang <lianux.mm@gmail.com>
Signed-off-by: Kunwu Chan <kunwu.chan@gmail.com>
---
tools/testing/selftests/damon/Makefile | 1 +
.../selftests/damon/damon_perf_obs_test.sh | 562 ++++++++++++++++++
2 files changed, 563 insertions(+)
create mode 100755 tools/testing/selftests/damon/damon_perf_obs_test.sh
diff --git a/tools/testing/selftests/damon/Makefile b/tools/testing/selftests/damon/Makefile
index 2180c328a825..1db8fa95ba2d 100644
--- a/tools/testing/selftests/damon/Makefile
+++ b/tools/testing/selftests/damon/Makefile
@@ -23,4 +23,5 @@ TEST_PROGS += sysfs_no_op_commit_break.py
EXTRA_CLEAN = __pycache__
+TEST_PROGS += damon_perf_obs_test.sh
include ../lib.mk
diff --git a/tools/testing/selftests/damon/damon_perf_obs_test.sh b/tools/testing/selftests/damon/damon_perf_obs_test.sh
new file mode 100755
index 000000000000..4c4074cdd191
--- /dev/null
+++ b/tools/testing/selftests/damon/damon_perf_obs_test.sh
@@ -0,0 +1,562 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# DAMON Perf Observability Framework — Automated Layer-by-Layer Test
+#
+# Validates all 7 pipeline stages:
+# Layer 1: Event Create Layer 2: Event Bind
+# Layer 3: Event Enable Layer 4: Sampling (callback)
+# Layer 5: Ring Layer 6: Drain
+# Layer 7: Match & Update
+#
+# The framework counters are cumulative since boot, so this script
+# snapshots them before the workload and reports per-run deltas. It
+# also clears the trace buffer before the sampling window so trace.txt
+# carries only records produced by this run.
+#
+# Usage:
+# # Software page-fault positive control (exercises the FULL pipeline,
+# # works on any machine, no HW PMU required). Defaults to sampling
+# # every page fault (period 1); override with --freq/--period:
+# sudo ./damon_perf_obs_test.sh --pmu software
+# sudo ./damon_perf_obs_test.sh --pmu software --freq 1 --sample-freq 100
+#
+# # With ARM SPE:
+# sudo ./damon_perf_obs_test.sh --pmu arm_spe_0 --freq 0 --period 256
+#
+# # With any PMU type number:
+# sudo ./damon_perf_obs_test.sh --pmu-type 38 --freq 0 --period 256
+#
+# Notes:
+# - `--config N` sets perf_event_attr.config. For PERF_TYPE_SOFTWARE,
+# config 2 (PERF_COUNT_SW_PAGE_FAULTS) is the only software event
+# that populates data->addr; cpu-clock (config 0) delivers callbacks
+# with addr always 0, so it can only validate Layers 1-4.
+# - ARM SPE cannot sample through perf_event_create_kernel_counter()
+# (it requires an AUX ring buffer, see arm_spe_pmu.c), so an SPE run
+# is expected to report zero callbacks until an AUX backend exists.
+# A zero-callback delta is the correct "PMU not usable" verdict.
+#
+# Requirements:
+# - CONFIG_DAMON_PERF_OBSERVE=y (fatal if missing)
+# - Root privileges
+# - debugfs mounted
+
+set -e
+
+# ---- defaults ----
+PMU_NAME=""
+PMU_TYPE=""
+PMU_CONFIG=0
+CONFIG_EXPLICIT=0
+FREQ=0
+PERIOD=256
+FREQ_EXPLICIT=0
+PERIOD_EXPLICIT=0
+SAMPLE_FREQ=100
+TIMEOUT=10
+TRACE_WINDOW=3
+TARGET_PID=""
+RESULTS_DIR="/tmp/damon_perf_test_$$"
+PASSED=0
+FAILED=0
+SKIPPED=0
+STRESS_PID=""
+CREATED_KDAMOND=0
+MOUNTED_DEBUGFS=0
+KEEP_RESULTS=${KEEP_RESULTS:-1}
+
+# ---- helpers ----
+pass() { echo " [PASS] $1"; PASSED=$((PASSED + 1)); }
+fail() { echo " [FAIL] $1 — $2"; FAILED=$((FAILED + 1)); }
+skip() { echo " [SKIP] $1 — $2"; SKIPPED=$((SKIPPED + 1)); }
+die() { echo "FATAL: $1"; exit 1; }
+
+# ---- sysfs roots (kept under 100 columns) ----
+KD=/sys/kernel/mm/damon/admin/kdamonds
+ADMIN=$KD/0
+TRACE=/sys/kernel/debug/tracing
+TPD=$TRACE/events/damon
+PE=$ADMIN/contexts/0/monitoring_attrs/sample/perf_events
+
+# ---- saved pre-test state (restored in cleanup so the test is
+# ---- side-effect free: tracepoints, tracing_on)
+ORIG_TRACING_ON=$(cat $TRACE/tracing_on 2>/dev/null || echo 0)
+ORIG_TP_SAMPLE=$(cat $TRACE/events/damon/damon_perf_sample/enable 2>/dev/null || echo 0)
+ORIG_TP_OVERFLOW=$(cat $TRACE/events/damon/damon_perf_ring_overflow/enable 2>/dev/null || echo 0)
+ORIG_TP_MISSED=$(cat $TRACE/events/damon/damon_perf_report_missed/enable 2>/dev/null || echo 0)
+ORIG_TP_DRAIN=$(cat $TRACE/events/damon/damon_perf_drain/enable 2>/dev/null || echo 0)
+
+cleanup() {
+ echo ""
+ echo "=== Cleaning up ==="
+ if [[ -n "$STRESS_PID" ]]; then
+ kill "$STRESS_PID" 2>/dev/null || true
+ wait "$STRESS_PID" 2>/dev/null || true
+ STRESS_PID=""
+ fi
+ # Tear down only the kdamond instance created by this test. In
+ # particular, the early "existing kdamonds" guard must be read-only.
+ if [[ "$CREATED_KDAMOND" == "1" ]]; then
+ echo off > $ADMIN/state 2>/dev/null || true
+ echo 0 > $KD/nr_kdamonds 2>/dev/null || true
+ CREATED_KDAMOND=0
+ fi
+ # Restore tracepoint and tracing state
+ echo 0 > $TRACE/tracing_on 2>/dev/null || true
+ echo "$ORIG_TP_SAMPLE" > $TRACE/events/damon/damon_perf_sample/enable 2>/dev/null || true
+ echo "$ORIG_TP_OVERFLOW" > $TPD/damon_perf_ring_overflow/enable 2>/dev/null || true
+ echo "$ORIG_TP_MISSED" > $TPD/damon_perf_report_missed/enable 2>/dev/null || true
+ echo "$ORIG_TP_DRAIN" > $TRACE/events/damon/damon_perf_drain/enable 2>/dev/null || true
+ echo "$ORIG_TRACING_ON" > $TRACE/tracing_on 2>/dev/null || true
+ if [[ "$MOUNTED_DEBUGFS" == "1" ]]; then
+ umount /sys/kernel/debug 2>/dev/null || true
+ MOUNTED_DEBUGFS=0
+ fi
+ [ "$KEEP_RESULTS" != "1" ] && rm -rf "$RESULTS_DIR"
+}
+trap cleanup EXIT
+
+# ---- argument parsing ----
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --pmu) PMU_NAME="$2"; shift 2 ;;
+ --pmu-type) PMU_TYPE="$2"; shift 2 ;;
+ --config) PMU_CONFIG="$2"; CONFIG_EXPLICIT=1; shift 2 ;;
+ --freq) FREQ="$2"; FREQ_EXPLICIT=1; shift 2 ;;
+ --period) PERIOD="$2"; PERIOD_EXPLICIT=1; shift 2 ;;
+ --sample-freq) SAMPLE_FREQ="$2"; shift 2 ;;
+ --trace-window) TRACE_WINDOW="$2"; shift 2 ;;
+ --timeout) TIMEOUT="$2"; shift 2 ;;
+ --pid) TARGET_PID="$2"; shift 2 ;;
+ *) echo "Unknown: $1"; exit 1 ;;
+ esac
+done
+
+# Resolve PMU type
+if [[ -n "$PMU_NAME" && -z "$PMU_TYPE" ]]; then
+ if [[ "$PMU_NAME" == "software" ]]; then
+ PMU_TYPE=1
+ else
+ PMU_TYPE=$(cat /sys/bus/event_source/devices/$PMU_NAME/type 2>/dev/null) ||
+ die "Cannot find PMU: $PMU_NAME"
+ fi
+fi
+[[ -z "$PMU_TYPE" ]] && die "Specify --pmu <name> or --pmu-type <number>"
+
+# For PERF_TYPE_SOFTWARE default to PERF_COUNT_SW_PAGE_FAULTS (config 2):
+# the only software event that carries a data address, i.e. the only one
+# that can exercise Layers 5-7. Override with --config 0 for a pure
+# plumbing (cpu-clock) smoke test.
+if [[ "$PMU_TYPE" == "1" && "$CONFIG_EXPLICIT" == "0" ]]; then
+ PMU_CONFIG=2
+fi
+
+# For the page-fault positive control, sample every fault (period 1) by
+# default so enough reports flow for the ring/drain/match checks to be
+# meaningful on a short run. Explicit --freq/--period override this.
+if [[ "$PMU_TYPE" == "1" && "$PMU_CONFIG" == "2" &&
+ "$FREQ_EXPLICIT" == "0" && "$PERIOD_EXPLICIT" == "0" ]]; then
+ FREQ=0
+ PERIOD=1
+fi
+
+# For cpu-clock (config 0), sample_period is a TIME in ns, so the
+# default period 256 would mean 4 MHz of callbacks per CPU. Never
+# let an unguarded default hit that: fall back to a gentle 100 Hz.
+if [[ "$PMU_TYPE" == "1" && "$PMU_CONFIG" == "0" &&
+ "$FREQ_EXPLICIT" == "0" && "$PERIOD_EXPLICIT" == "0" ]]; then
+ FREQ=1
+ SAMPLE_FREQ=100
+fi
+
+# ---- Layer 0: Environment ----
+echo "=========================================="
+echo " DAMON Perf Observability — Layer-by-Layer Test"
+echo "=========================================="
+echo "PMU type: $PMU_TYPE config: $PMU_CONFIG freq: $FREQ period: $PERIOD timeout: ${TIMEOUT}s"
+if [[ -n "$TARGET_PID" ]]; then
+ echo "Target PID: $TARGET_PID (explicit)"
+else
+ echo "Target PID: workload process (started below)"
+fi
+echo ""
+
+mkdir -p "$RESULTS_DIR"
+
+echo "--- Layer 0: Environment ---"
+
+# Check kernel config
+CONFIG=""
+if [[ -f /proc/config.gz ]]; then
+ CONFIG=$(zcat /proc/config.gz)
+elif [[ -f /boot/config-$(uname -r) ]]; then
+ CONFIG=$(cat /boot/config-$(uname -r))
+else
+ die "Cannot read /proc/config.gz or /boot/config-$(uname -r)"
+fi
+
+for opt in DAMON DAMON_SYSFS DAMON_VADDR PERF_EVENTS DEBUG_FS TRACING \
+ TRACEPOINTS; do
+ if echo "$CONFIG" | grep -q "CONFIG_${opt}=y"; then
+ pass "CONFIG_${opt}=y"
+ else
+ fail "CONFIG_${opt}" "not enabled"
+ fi
+done
+
+# DAMON_PERF_OBSERVE is fatal — the test cannot run without it
+if echo "$CONFIG" | grep -q "CONFIG_DAMON_PERF_OBSERVE=y"; then
+ pass "CONFIG_DAMON_PERF_OBSERVE=y"
+else
+ die "kernel not built with CONFIG_DAMON_PERF_OBSERVE=y"
+fi
+
+# Check root
+[[ $(id -u) -eq 0 ]] || die "Must run as root"
+
+# Mount debugfs only when this test owns the mount, and undo it on exit.
+if ! mountpoint -q /sys/kernel/debug; then
+ mount -t debugfs none /sys/kernel/debug || die "Cannot mount debugfs"
+ MOUNTED_DEBUGFS=1
+fi
+[[ -d /sys/kernel/debug/damon ]] || die "debugfs damon/ not found"
+pass "debugfs mounted"
+
+# Check tracepoints
+for tp in damon_perf_sample damon_perf_ring_overflow damon_perf_report_missed damon_perf_drain; do
+ if [[ -d /sys/kernel/debug/tracing/events/damon/$tp ]]; then
+ pass "tracepoint $tp exists"
+ else
+ fail "tracepoint $tp" "not found"
+ fi
+done
+
+# Check debugfs file (perf_stats only; format is debug-only, not an ABI)
+if [[ -f /sys/kernel/debug/damon/perf_stats ]]; then
+ pass "debugfs perf_stats exists"
+else
+ fail "debugfs perf_stats" "not found"
+fi
+
+# ---- Guard: refuse to run if kdamonds already exist ----
+NR_KDAMONDS=$(cat $KD/nr_kdamonds 2>/dev/null || echo 0)
+if [[ "$NR_KDAMONDS" -gt 0 ]]; then
+ skip "runtime" "kdamonds exist (nr_kdamonds=$NR_KDAMONDS) — refusing"
+ KEEP_RESULTS=1
+ exit 0
+fi
+
+# ---- Generate memory pressure workload first: the default DAMON
+# ---- target must be the workload process itself, so the workload
+# ---- must be running before the target PID is written.
+echo ""
+echo "Starting memory workload for ${TIMEOUT}s..."
+if command -v stress-ng &>/dev/null; then
+ stress-ng --vm 2 --vm-bytes 256M --timeout "${TIMEOUT}s" &
+ STRESS_PID=$!
+elif command -v stress &>/dev/null; then
+ stress --vm 2 --vm-bytes 256M --timeout "${TIMEOUT}s" &
+ STRESS_PID=$!
+else
+ # Fallback: dd-based memory pressure
+ dd if=/dev/zero of=/dev/null bs=1M count=1024 &
+ STRESS_PID=$!
+fi
+
+# Resolve target PID: explicit --pid wins, otherwise the workload
+if [[ -z "$TARGET_PID" ]]; then
+ TARGET_PID=$STRESS_PID
+fi
+
+echo ""
+echo "--- Layer 1: Event Create ---"
+
+# Configure kdamond
+echo 1 > /sys/kernel/mm/damon/admin/kdamonds/nr_kdamonds
+CREATED_KDAMOND=1
+echo 1 > /sys/kernel/mm/damon/admin/kdamonds/0/contexts/nr_contexts
+echo 1 > /sys/kernel/mm/damon/admin/kdamonds/0/contexts/0/targets/nr_targets
+echo "$TARGET_PID" > /sys/kernel/mm/damon/admin/kdamonds/0/contexts/0/targets/0/pid_target
+
+# Disable page-fault-based access check
+echo 0 > $PE/nr_perf_events 2>/dev/null || true
+
+# Configure perf event
+echo 1 > $PE/nr_perf_events
+echo "$PMU_TYPE" > $PE/0/type
+echo "$PMU_CONFIG" > $PE/0/config
+
+if [[ "$FREQ" -eq 1 ]]; then
+ echo "$SAMPLE_FREQ" > $PE/0/sample_freq
+else
+ echo "$PERIOD" > $PE/0/sample_period
+fi
+echo "$FREQ" > $PE/0/freq
+
+# Take a dmesg snapshot before enabling the kdamond, so we can
+# detect error messages that appear during the test.
+DMESG_BEFORE="$RESULTS_DIR/dmesg_before.txt"
+dmesg > "$DMESG_BEFORE" 2>/dev/null || true
+# Snapshot counters BEFORE the workload window. All counters are
+# cumulative since boot; every Analysis number below is a delta against
+# this snapshot. Read before echo on so the window starts at zero.
+STATS_BASE="$RESULTS_DIR/perf_stats_base.txt"
+cat /sys/kernel/debug/damon/perf_stats > "$STATS_BASE" 2>/dev/null || true
+
+echo on > /sys/kernel/mm/damon/admin/kdamonds/0/state
+sleep 2
+
+DMESG_OUT="$RESULTS_DIR/dmesg_create.txt"
+DMESG_DELTA="$RESULTS_DIR/dmesg_delta.txt"
+dmesg > "$DMESG_OUT" 2>/dev/null || true
+# Delta: lines in $DMESG_OUT not already present in $DMESG_BEFORE
+awk 'NR==FNR { seen[$0]++ }
+ NR>FNR { if (seen[$0] > 0) seen[$0]--; else print }' \
+ "$DMESG_BEFORE" "$DMESG_OUT" > "$DMESG_DELTA" || true
+
+# Check state is on
+STATE_VAL=$(cat /sys/kernel/mm/damon/admin/kdamonds/0/state 2>/dev/null)
+if [[ "$STATE_VAL" == "on" ]]; then
+ pass "Kdamond state is on"
+else
+ fail "Kdamond state" "expected 'on', got '$STATE_VAL'"
+fi
+
+echo ""
+echo "--- Layer 2-3: Enable & Run (via per-CPU state) ---"
+
+# Parse the maximum per-CPU state from the debugfs output.
+# The per-CPU line format is:
+# CPU%02d: st=<state> cb=... enq=... ...
+max_cpu_state() {
+ awk -F'st=' '/^ CPU/ {
+ split($2, a, " ")
+ s = a[1]
+ # Map state name to numeric rank
+ if (s == "ERROR") v = 5
+ else if (s == "RUNNING") v = 4
+ else if (s == "ENABLED") v = 3
+ else if (s == "BOUND") v = 2
+ else if (s == "CREATED") v = 1
+ else v = 0
+ if (v > max) max = v
+ } END { print max+0 }' "$1" 2>/dev/null
+}
+
+CPU_ST_BASE=$(max_cpu_state "$STATS_BASE")
+
+if [[ "$CPU_ST_BASE" -ge 1 ]]; then
+ pass "Event Created (max per-CPU state >= CREATED)"
+else
+ fail "Event Created" "max per-CPU state is $CPU_ST_BASE"
+fi
+
+if [[ "$CPU_ST_BASE" -ge 2 ]]; then
+ pass "Event Bound (max per-CPU state >= BOUND)"
+else
+ fail "Event Bound" "max per-CPU state is $CPU_ST_BASE"
+fi
+
+if [[ "$CPU_ST_BASE" -ge 3 ]]; then
+ pass "Event Enabled (max per-CPU state >= ENABLED)"
+else
+ fail "Event Enabled" "max per-CPU state is $CPU_ST_BASE"
+fi
+
+# Check dmesg delta for errors (vaddr.c pr_warn_ratelimited paths)
+if grep -q 'damon-perf.*failed\|event create failed' "$DMESG_DELTA" 2>/dev/null; then
+ fail "Perf event" "perf event creation failed (see dmesg delta)"
+else
+ pass "Perf event creation (no errors in dmesg delta)"
+fi
+
+echo ""
+echo "--- Layer 4: Sampling (Callback) ---"
+
+# Clear the trace buffer so trace.txt only contains records produced by
+# this run, then enable tracepoints for a bounded sampling window.
+echo 0 > $TRACE/tracing_on 2>/dev/null || true
+echo > $TRACE/trace 2>/dev/null || true
+echo 1 > $TRACE/events/damon/damon_perf_sample/enable
+echo 1 > $TRACE/events/damon/damon_perf_ring_overflow/enable
+echo 1 > $TRACE/events/damon/damon_perf_report_missed/enable
+echo 1 > $TRACE/events/damon/damon_perf_drain/enable
+echo 1 > $TRACE/tracing_on
+
+# Sampling window: keeps trace.txt bounded even on PMUs that sample at
+# tens of kHz. Counters keep accumulating for the full TIMEOUT.
+sleep "$TRACE_WINDOW"
+echo 0 > $TRACE/tracing_on 2>/dev/null || true
+[[ "$TIMEOUT" -gt "$TRACE_WINDOW" ]] && sleep $((TIMEOUT - TRACE_WINDOW))
+
+kill $STRESS_PID 2>/dev/null || true
+wait $STRESS_PID 2>/dev/null || true
+STRESS_PID=""
+echo "Workload done."
+
+sleep 2 # let kdamond drain
+
+# Collect trace
+TRACE_OUT="$RESULTS_DIR/trace.txt"
+cat /sys/kernel/debug/tracing/trace > "$TRACE_OUT" 2>/dev/null || true
+TRACE_LINES=$(wc -l < "$TRACE_OUT" 2>/dev/null || echo 0)
+
+# Collect stats (re-read after workload)
+STATS_OUT="$RESULTS_DIR/perf_stats.txt"
+cat /sys/kernel/debug/damon/perf_stats > "$STATS_OUT" 2>/dev/null || true
+
+# ---- Analysis ----
+echo ""
+echo "--- Analysis ---"
+
+
+# Aggregate counter reads
+stat_of() {
+ awk -v k="$1" '$1==k {print $2}' "$2" 2>/dev/null
+}
+
+# Per-run delta between baseline and post-workload snapshots
+delta() {
+ local b a
+ b=$(stat_of "$1" "$STATS_BASE")
+ a=$(stat_of "$1" "$STATS_OUT")
+ [[ -z "$b" ]] && b=0
+ [[ -z "$a" ]] && a=0
+ echo $((a-b))
+}
+
+CALLBACK=$(delta callback)
+VALID=$(delta valid)
+ADDR_ZERO=$(delta addr_zero)
+KERNEL=$(delta kernel)
+ENQUEUE=$(delta enqueue)
+DEQUEUE=$(delta dequeue)
+OVERFLOW=$(delta overflow)
+MATCH=$(delta match)
+UPDATE=$(delta update)
+
+echo " Callback delta this run: ${CALLBACK} (cumulative totals in perf_stats.txt)"
+
+if [[ "$CALLBACK" -gt 0 ]]; then
+ pass "Sampling: ${CALLBACK} callbacks received"
+else
+ fail "Sampling" "0 callbacks — PMU is not delivering samples to DAMON"
+fi
+
+echo " Callback breakdown (delta): valid=${VALID} addr_zero=${ADDR_ZERO} kernel=${KERNEL}"
+
+# Verify RUNNING state: first callback advances per-CPU state to
+# RUNNING, which persists until the kdamond is stopped.
+CPU_ST_FINAL=$(max_cpu_state "$STATS_OUT")
+if [[ "$CALLBACK" -gt 0 && "$CPU_ST_FINAL" -ge 4 ]]; then
+ pass "Event Running (max per-CPU state >= RUNNING)"
+elif [[ "$CALLBACK" -eq 0 ]]; then
+ skip "Event Running" "no callbacks — state cannot advance past ENABLED"
+else
+ fail "Event Running" "callbacks > 0 but max state is $CPU_ST_FINAL"
+fi
+
+echo " Ring: enqueue=${ENQUEUE} dequeue=${DEQUEUE} overflow=${OVERFLOW}"
+
+if [[ "$ENQUEUE" -gt 0 ]]; then
+ pass "Ring: enqueue > 0"
+ if [[ "$DEQUEUE" -gt 0 ]]; then
+ pass "Ring: dequeue > 0"
+ else
+ fail "Ring: dequeue" "enqueued but never dequeued"
+ fi
+else
+ skip "Ring" "no enqueues (no valid samples: addr=0 or no callbacks)"
+fi
+
+echo " Match: match=${MATCH} update=${UPDATE}"
+
+if [[ "$MATCH" -gt 0 ]]; then
+ pass "Drain & Match: ${MATCH} matched"
+ if [[ "$UPDATE" -gt 0 ]]; then
+ pass "Update: ${UPDATE} region updates"
+ else
+ fail "Update" "matched but never updated"
+ fi
+else
+ skip "Match/Update" "no matches (no valid samples reached region matching)"
+fi
+
+# Drain tracepoint: kdamond fires damon_perf_drain whenever it drained
+# at least one report. The drain tracepoint is only expected when the
+# ring actually produced entries.
+DRAIN_COUNT=$(grep -c "damon_perf_drain" "$TRACE_OUT" 2>/dev/null || true)
+DRAIN_COUNT=${DRAIN_COUNT:-0}
+echo " Drain tracepoint: ${DRAIN_COUNT} records"
+if [[ "$MATCH" -gt 0 ]]; then
+ if [[ "$DRAIN_COUNT" -gt 0 ]]; then
+ pass "Drain tracepoint fired (${DRAIN_COUNT} records)"
+ else
+ fail "Drain tracepoint" "matches occurred but damon_perf_drain never fired"
+ fi
+else
+ skip "Drain tracepoint" "no drained reports to summarize"
+fi
+
+# Context verification
+if grep -q 'context=' "$TRACE_OUT" 2>/dev/null; then
+ NMI_COUNT=$(grep -c 'context=3' "$TRACE_OUT" 2>/dev/null || true)
+ PROC_COUNT=$(grep -c 'context=0' "$TRACE_OUT" 2>/dev/null || true)
+ NMI_COUNT=${NMI_COUNT:-0}
+ PROC_COUNT=${PROC_COUNT:-0}
+ echo " Context: NMI=${NMI_COUNT} process=${PROC_COUNT}"
+ pass "Context field in trace output"
+else
+ skip "Context" "no trace output to analyze"
+fi
+
+# Show a few sample records as raw evidence (they are the per-sample
+# view of the pipeline; useful for PMU support evaluation).
+echo ""
+echo " First damon_perf_sample record(s) this run:"
+if grep -q "damon_perf_sample" "$TRACE_OUT" 2>/dev/null; then
+ grep -m 3 "damon_perf_sample" "$TRACE_OUT" | sed 's/^/ /'
+else
+ echo " (none — no samples captured in the ${TRACE_WINDOW}s window)"
+fi
+echo " First damon_perf_drain record(s) this run:"
+if grep -q "damon_perf_drain" "$TRACE_OUT" 2>/dev/null; then
+ grep -m 3 "damon_perf_drain" "$TRACE_OUT" | sed 's/^/ /'
+else
+ echo " (none)"
+fi
+
+# ---- PMU support verdict ----
+echo ""
+echo "--- PMU support verdict ---"
+if [[ "$CALLBACK" -eq 0 ]]; then
+ echo " UNUSABLE: the PMU never delivered a sample to DAMON"
+ echo " (e.g. ARM SPE requires an AUX ring buffer that kernel"
+ echo " counters do not provide; see arm_spe_pmu.c)"
+elif [[ "$VALID" -eq 0 ]]; then
+ echo " PLUMBING-ONLY: callbacks flow but no data addresses"
+ echo " (address-less PMU, e.g. cpu-clock / task-clock)"
+elif [[ "$ENQUEUE" -gt 0 && "$MATCH" -gt 0 ]]; then
+ echo " FULLY INTEGRATED: samples carry addresses and reach"
+ echo " DAMON region matching/update"
+else
+ echo " PARTIAL: callbacks with addresses, but the drain/match"
+ echo " pipeline did not complete (see counters above)"
+fi
+
+# ---- Summary ----
+echo ""
+echo "=========================================="
+echo " SUMMARY: $PASSED passed, $FAILED failed, $SKIPPED skipped"
+echo "=========================================="
+echo ""
+echo "Results retained in: $RESULTS_DIR (set KEEP_RESULTS=0 to remove)"
+
+if [[ "$FAILED" -gt 0 ]]; then
+ echo "Overall: FAIL ($FAILED checks failed)"
+ exit 1
+else
+ echo "Overall: PASS"
+ exit 0
+fi
--
2.43.0
next prev parent reply other threads:[~2026-08-18 6:11 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-18 6:10 [RFC PATCH 0/7] mm/damon/perf: observability framework for hardware-sampled access reports Kunwu Chan
2026-08-18 6:10 ` [RFC PATCH 1/7] mm/damon/perf: add observability framework with tracepoints and CONFIG switch Kunwu Chan
2026-08-18 6:10 ` [RFC PATCH 2/7] mm/damon/perf: implement observe API and per-CPU statistics engine Kunwu Chan
2026-08-18 6:10 ` [RFC PATCH 3/7] mm/damon/perf: add debugfs statistics interface Kunwu Chan
2026-08-18 6:10 ` [RFC PATCH 4/7] mm/damon: integrate observe API into vaddr overflow handlers and core drain Kunwu Chan
2026-08-18 6:10 ` Kunwu Chan [this message]
2026-08-18 6:10 ` [RFC PATCH 6/7] Docs/mm/damon: document the perf observability framework Kunwu Chan
2026-08-18 6:10 ` [RFC PATCH 7/7] mm/damon/perf: add CONFIG_DAMON_PERF_DEBUG and pipeline health check Kunwu Chan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260818061031.827057-6-kunwu.chan@linux.dev \
--to=kunwu.chan@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=damon@lists.linux.dev \
--cc=lianux.mm@gmail.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=mathieu.desnoyers@efficios.com \
--cc=mhiramat@kernel.org \
--cc=rostedt@goodmis.org \
--cc=shuah@kernel.org \
--cc=sj@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.