rm3100/noise_floor_test.sh
Jeremy Karst 3d6c7251e9 Add exact-interval sampling, chip-grid timing, and analysis tooling
Sample every measurement exactly once, flagging losses in a warning
column rather than dropping them, so sample_index stays a valid
chip-time grid coordinate. Calibrate the true period before recording;
abort if no loss-free stretch exists.

Captures now store only raw counts plus a config header, with tesla and
both time bases reconstructed on load. Cycle count becomes the rate knob
(TMRC has no effect once it governs), raising the ceiling ~4x via
repeated-START reads and a shorter GIL switch interval.
2026-08-23 18:16:43 -04:00

94 lines
2.8 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Run a noise-floor capture and analyse it in one step.
#
# Wraps logger.py -- every logger flag is accepted and passed straight through --
# then runs plot.py and characterize.py on whatever was captured. Works whether
# the run ends on --duration or on Ctrl-C.
#
# ./noise_floor_test.sh --duration 60
# ./noise_floor_test.sh --duration 3600 --cycle-count 400
# ./noise_floor_test.sh # until Ctrl-C
#
# Anything you pass explicitly wins; otherwise logger.py's defaults apply.
set -uo pipefail # deliberately not -e: logger.py exits 1 on a miss,
# and a partial capture is still worth analysing.
cd "$(dirname "$0")"
if [[ ! -f .venv/bin/activate ]]; then
echo "No virtualenv found. Run ./setup.sh first." >&2
exit 1
fi
# shellcheck disable=SC1091
source .venv/bin/activate
args=("$@")
has_flag() {
local needle=$1 a
for a in "${args[@]:-}"; do
[[ "$a" == "$needle" || "$a" == "$needle="* ]] && return 0
done
return 1
}
# The CSV path has to be known up front so the analysis can find it; logger.py
# would otherwise pick a timestamped name we cannot predict without racing it.
output=""
for ((i = 0; i < ${#args[@]}; i++)); do
case "${args[i]}" in
--output=*) output="${args[i]#--output=}" ;;
--output|-o) output="${args[i+1]:-}" ;;
esac
done
if [[ -z "$output" ]]; then
output="noise_$(date +%Y%m%d_%H%M%S).csv"
args+=(--output "$output")
fi
has_flag --bus-speed || args+=(--bus-speed 750)
echo "=== capture -> $output ==="
# Ctrl-C reaches the whole foreground process group. Trapping it here stops bash
# killing the script, so logger.py can shut down cleanly and the analysis below
# still runs. A no-op trap (not 'ignore') keeps the child's own handling intact.
trap ':' INT
python logger.py "${args[@]}"
capture_rc=$?
trap - INT
# A non-zero logger exit covers both "aborted mid-capture" and "never started"
# (no adapter, bad flag). Whether a usable file exists is what distinguishes
# them, so check that before claiming anything about the data.
if [[ ! -s "$output" ]]; then
echo
echo "Capture produced no data (exit $capture_rc); nothing to analyse." >&2
exit 1
fi
if [[ "$capture_rc" -ne 0 ]]; then
echo
echo "Capture ended early (exit $capture_rc). Analysing what was recorded --" \
"it is valid up to that point." >&2
fi
echo
echo "=== plot ==="
python plot.py "$output"
plot_rc=$?
echo
echo "=== characterize ==="
python characterize.py "$output"
char_rc=$?
echo
echo "=== outputs ==="
base="${output%.csv}"
for f in "$output" "$base.png" "${base}_noise.png"; do
[[ -f "$f" ]] && printf ' %-40s %8s\n' "$f" "$(du -h "$f" | cut -f1)"
done
# Non-zero if any stage failed, so this is usable from a scheduler.
[[ $capture_rc -eq 0 && $plot_rc -eq 0 && $char_rc -eq 0 ]] || exit 1