From 7356986259fe952391f3721b173c20799bdd7228 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Fri, 25 Sep 2026 04:43:25 +0100 Subject: [PATCH] test: enforce soak resource budgets --- docs/native-soak-testing.md | 11 ++++-- scripts/soak-native.sh | 69 +++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/docs/native-soak-testing.md b/docs/native-soak-testing.md index eced6d5..799892d 100644 --- a/docs/native-soak-testing.md +++ b/docs/native-soak-testing.md @@ -52,8 +52,15 @@ Environment controls: `target/debug/ferese-malformed-client`; - `FERESE_PID`: compositor PID; automatic lookup is only a fallback; - `FERESE_SOAK_LOG`: result log, default `/tmp/ferese-soak-.log`. +- `FERESE_SOAK_MAX_RSS_GROWTH_KIB`: maximum RSS growth, default `131072` + (128 MiB); +- `FERESE_SOAK_MAX_FD_GROWTH`: maximum open-file-descriptor growth, default + `32`; +- `FERESE_SOAK_MAX_THREAD_GROWTH`: maximum thread growth, default `8`. Run the same workload once with the nested backend and once from a direct DRM -session. A passing run requires the compositor to remain alive, IPC queries to -succeed, and RSS samples to stabilize rather than grow without bound. Preserve +session. Samples are taken after spawned clients are reaped. A passing run +requires the compositor to remain alive, IPC queries to succeed, and RSS, file +descriptor, and thread growth to stay within the declared budgets. Tighten the +defaults for release hardware when its normal cache behavior is known. Preserve the soak log and the `ferese::render` summaries with the release artifacts. diff --git a/scripts/soak-native.sh b/scripts/soak-native.sh index 15ea55c..1d7375b 100755 --- a/scripts/soak-native.sh +++ b/scripts/soak-native.sh @@ -9,6 +9,9 @@ feresectl_program="${FERESECTL:-target/debug/feresectl}" malformed_client_program="${FERESE_MALFORMED_CLIENT:-target/debug/ferese-malformed-client}" compositor_pid="${FERESE_PID:-}" log_path="${FERESE_SOAK_LOG:-/tmp/ferese-soak-$$.log}" +max_rss_growth_kib="${FERESE_SOAK_MAX_RSS_GROWTH_KIB:-131072}" +max_fd_growth="${FERESE_SOAK_MAX_FD_GROWTH:-32}" +max_thread_growth="${FERESE_SOAK_MAX_THREAD_GROWTH:-8}" if [[ -z "${WAYLAND_DISPLAY:-}" ]]; then echo "WAYLAND_DISPLAY must name the Ferese socket" >&2 @@ -20,6 +23,13 @@ if [[ ! "$soak_seconds" =~ ^[1-9][0-9]*$ ]]; then exit 2 fi +for value in "$max_rss_growth_kib" "$max_fd_growth" "$max_thread_growth"; do + if [[ ! "$value" =~ ^[0-9]+$ ]]; then + echo "soak resource-growth limits must be non-negative integers" >&2 + exit 2 + fi +done + if [[ ! -x "$feresectl_program" ]]; then cargo build -p feresectl fi @@ -60,6 +70,36 @@ rss_kib() { awk '/^VmRSS:/ { print $2; exit }' "/proc/$compositor_pid/status" } +thread_count() { + awk '/^Threads:/ { print $2; exit }' "/proc/$compositor_pid/status" +} + +fd_count() { + find "/proc/$compositor_pid/fd" -mindepth 1 -maxdepth 1 -printf . | wc -c +} + +check_resource_budget() { + local rss_growth=$((current_rss - initial_rss)) + local fd_growth=$((current_fds - initial_fds)) + local thread_growth=$((current_threads - initial_threads)) + + if ((rss_growth > max_rss_growth_kib)); then + echo "FAIL RSS growth ${rss_growth} KiB exceeds ${max_rss_growth_kib} KiB" \ + | tee -a "$log_path" + exit 1 + fi + if ((fd_growth > max_fd_growth)); then + echo "FAIL file-descriptor growth $fd_growth exceeds $max_fd_growth" \ + | tee -a "$log_path" + exit 1 + fi + if ((thread_growth > max_thread_growth)); then + echo "FAIL thread growth $thread_growth exceeds $max_thread_growth" \ + | tee -a "$log_path" + exit 1 + fi +} + control() { "$feresectl_program" "$@" >/dev/null } @@ -101,12 +141,17 @@ start_time="$SECONDS" deadline=$((start_time + soak_seconds)) iteration=0 initial_rss="$(rss_kib)" +initial_fds="$(fd_count)" +initial_threads="$(thread_count)" maximum_rss="$initial_rss" +maximum_fds="$initial_fds" +maximum_threads="$initial_threads" { echo "ferese native soak" echo "pid=$compositor_pid display=$WAYLAND_DISPLAY client=$client_program" - echo "duration_seconds=$soak_seconds initial_rss_kib=$initial_rss" + echo "duration_seconds=$soak_seconds initial_rss_kib=$initial_rss initial_fds=$initial_fds initial_threads=$initial_threads" + echo "limits rss_growth_kib=$max_rss_growth_kib fd_growth=$max_fd_growth thread_growth=$max_thread_growth" } | tee "$log_path" while ((SECONDS < deadline)); do @@ -146,13 +191,23 @@ while ((SECONDS < deadline)); do reap_clients current_rss="$(rss_kib)" + current_fds="$(fd_count)" + current_threads="$(thread_count)" if ((current_rss > maximum_rss)); then maximum_rss="$current_rss" fi + if ((current_fds > maximum_fds)); then + maximum_fds="$current_fds" + fi + if ((current_threads > maximum_threads)); then + maximum_threads="$current_threads" + fi + check_resource_budget if ((iteration % 100 == 0)); then - printf 'iteration=%d elapsed=%d rss_kib=%d max_rss_kib=%d\n' \ + printf 'iteration=%d elapsed=%d rss_kib=%d max_rss_kib=%d fds=%d max_fds=%d threads=%d max_threads=%d\n' \ "$iteration" "$((SECONDS - start_time))" "$current_rss" "$maximum_rss" \ + "$current_fds" "$maximum_fds" "$current_threads" "$maximum_threads" \ | tee -a "$log_path" fi @@ -163,7 +218,15 @@ done control get-outputs control get-workspaces final_rss="$(rss_kib)" +final_fds="$(fd_count)" +final_threads="$(thread_count)" +current_rss="$final_rss" +current_fds="$final_fds" +current_threads="$final_threads" +check_resource_budget -printf 'PASS iterations=%d final_rss_kib=%d max_rss_kib=%d growth_kib=%d\n' \ +printf 'PASS iterations=%d final_rss_kib=%d max_rss_kib=%d rss_growth_kib=%d final_fds=%d max_fds=%d fd_growth=%d final_threads=%d max_threads=%d thread_growth=%d\n' \ "$iteration" "$final_rss" "$maximum_rss" "$((final_rss - initial_rss))" \ + "$final_fds" "$maximum_fds" "$((final_fds - initial_fds))" \ + "$final_threads" "$maximum_threads" "$((final_threads - initial_threads))" \ | tee -a "$log_path"