diff options
| author | Alexander M Pickering <alex@cogarr.net> | 2026-07-21 20:19:46 -0500 |
|---|---|---|
| committer | Alexander M Pickering <alex@cogarr.net> | 2026-07-21 20:19:46 -0500 |
| commit | 657fb0007f39f07cc0401e0c5d03e25df6234aa4 (patch) | |
| tree | f73fe23232dc80802f39caed738c38464a60ec6d /scripts/profile-tests.sh | |
| download | trbldoc-657fb0007f39f07cc0401e0c5d03e25df6234aa4.tar.gz trbldoc-657fb0007f39f07cc0401e0c5d03e25df6234aa4.tar.bz2 trbldoc-657fb0007f39f07cc0401e0c5d03e25df6234aa4.zip | |
Inital commit.
Diffstat (limited to 'scripts/profile-tests.sh')
| -rw-r--r-- | scripts/profile-tests.sh | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/scripts/profile-tests.sh b/scripts/profile-tests.sh new file mode 100644 index 0000000..498ae25 --- /dev/null +++ b/scripts/profile-tests.sh @@ -0,0 +1,184 @@ +#!/bin/sh +# scripts/profile-tests.sh +# Generate timing reports for the trbldoc test suite. +# +# Two levels of detail: +# 1. Per-test wall-clock timings from bats (--timing + JUnit report). Always +# produced. Answers "which tests take a long time". +# 2. Per-line hotspots inside trbldoc.sh (optional, --hotspots). Uses the +# BASH_ENV xtrace hook to time individual source lines across the whole +# run. Answers "which code takes a long time". +# +# POSIX sh; works under mingw bash, WSL bash and Alpine/busybox ash. +# +# Usage: +# scripts/profile-tests.sh [-s all|unit|integration] [-o OUTDIR] +# [-n TOPN] [-p] [-t "file.sh ..."] +# +# -s SUITE suite to run (default: integration) +# -o OUTDIR output directory (default: reports/profile-<timestamp>) +# -n TOPN number of rows in the printed "slowest" summary (default: 20) +# -p also collect per-line hotspots inside the target script(s) +# -t LIST space-separated target source files for -p (default: trbldoc.sh) + +set -u + +SELF="$0" +. "$(dirname -- "$0")/lib/common.sh" +tb_resolve_paths "$SELF" + +SUITE=integration +OUTDIR="" +TOPN=20 +HOTSPOTS=0 +TARGETS="trbldoc.sh" + +while getopts ":s:o:n:pt:h" opt; do + case "$opt" in + s) SUITE="$OPTARG" ;; + o) OUTDIR="$OPTARG" ;; + n) TOPN="$OPTARG" ;; + p) HOTSPOTS=1 ;; + t) TARGETS="$OPTARG" ;; + h) sed -n '2,30p' "$SELF"; exit 0 ;; + :) tb_die "option -$OPTARG requires an argument" ;; + \?) tb_die "unknown option -$OPTARG" ;; + esac +done + +tb_require awk +tb_require sort + +[ -n "$OUTDIR" ] || OUTDIR="$REPO_ROOT/reports/profile-$(tb_timestamp)" +case "$OUTDIR" in + /*|[A-Za-z]:[\\/]*) + ;; + *) + OUTDIR="$REPO_ROOT/$OUTDIR" + ;; +esac +mkdir -p "$OUTDIR" || tb_die "cannot create output dir: $OUTDIR" +JUNIT_DIR="$OUTDIR/junit" +mkdir -p "$JUNIT_DIR" + +SUITE_DIRS="$(tb_suite_dirs "$SUITE")" +SUITE_DIRS_REL="$(tb_suite_dirs_rel "$SUITE")" + +# ── 1. per-test timing via bats JUnit ─────────────────────────────────────── +tb_info "running bats ($SUITE) with timing..." +# --timing adds per-test durations; the junit report captures them in XML. +# We do not abort on test failures: timing is still useful. +if tb_bats_usable "$SUITE"; then + # shellcheck disable=SC2086 + bats --timing --report-formatter junit --output "$JUNIT_DIR" $SUITE_DIRS \ + > "$OUTDIR/bats-output.txt" 2>&1 || tb_info "bats reported failures (see bats-output.txt)" + BATS_MODE="local" +elif command -v wsl >/dev/null 2>&1; then + tb_info "local bats appears unusable; retrying via WSL bats..." + WSL_REPO="$(tb_to_wsl_path "$REPO_ROOT")" + WSL_JUNIT="$(tb_to_wsl_path "$JUNIT_DIR")" + wsl -- bash -lc \ + "cd '$WSL_REPO' && bats --timing --report-formatter junit --output '$WSL_JUNIT' $SUITE_DIRS_REL" \ + > "$OUTDIR/bats-output.txt" 2>&1 || tb_info "bats (WSL) reported failures (see bats-output.txt)" + BATS_MODE="wsl" +else + tb_die "local bats command appears unusable and no WSL fallback is available" +fi + +# Collect all JUnit XML the formatter produced. +: > "$OUTDIR/timings.tsv" +for xml in "$JUNIT_DIR"/*.xml; do + [ -f "$xml" ] || continue + awk -f "$LIB_DIR/timing-extract.awk" "$xml" >> "$OUTDIR/timings.tsv" +done + +if [ ! -s "$OUTDIR/timings.tsv" ]; then + if grep -qi "bash\\\\r" "$OUTDIR/bats-output.txt" 2>/dev/null; then + tb_die "bats appears broken (CRLF shebang: 'bash\\r'). Reinstall bats or run dos2unix on the bats scripts, then retry." + fi + tb_die "no per-test timings parsed (check $OUTDIR/bats-output.txt)" +fi + +# Sort slowest first. +sort -t "$(printf '\t')" -k1 -rn "$OUTDIR/timings.tsv" > "$OUTDIR/timings.sorted.tsv" + +# CSV summary. +{ + printf 'seconds,suite,test\n' + awk -F '\t' '{ gsub(/"/,"\"\"",$3); printf "%s,%s,\"%s\"\n",$1,$2,$3 }' \ + "$OUTDIR/timings.sorted.tsv" +} > "$OUTDIR/timings.csv" + +# HTML report. +awk -f "$LIB_DIR/render-timing.awk" \ + -v TITLE="trbldoc test timings ($SUITE)" \ + "$OUTDIR/timings.sorted.tsv" > "$OUTDIR/timings.html" + +# ── 2. optional per-line hotspots ──────────────────────────────────────────── +if [ "$HOTSPOTS" -eq 1 ]; then + tb_info "collecting per-line hotspots (this reruns the suite under xtrace)..." + TRACE_DIR="$OUTDIR/trace" + rm -rf "$TRACE_DIR"; mkdir -p "$TRACE_DIR" + + # Build the trace-target basename filter. + TRACE_TARGETS="" + for tf in $TARGETS; do + TRACE_TARGETS="$TRACE_TARGETS ${tf##*/}" + done + + BASH_ENV="$LIB_DIR/trace-init.sh" + TRBLDOC_TRACE_DIR="$TRACE_DIR" + TRBLDOC_TRACE_MODE="time" + TRBLDOC_TRACE_TARGETS="$TRACE_TARGETS" + if [ "$BATS_MODE" = "local" ]; then + export BASH_ENV TRBLDOC_TRACE_DIR TRBLDOC_TRACE_MODE TRBLDOC_TRACE_TARGETS + # shellcheck disable=SC2086 + bats --report-formatter junit --output "$JUNIT_DIR" $SUITE_DIRS \ + > "$OUTDIR/bats-hotspots-output.txt" 2>&1 \ + || tb_info "bats reported failures during hotspot run" + unset BASH_ENV TRBLDOC_TRACE_DIR TRBLDOC_TRACE_MODE TRBLDOC_TRACE_TARGETS + else + WSL_REPO="$(tb_to_wsl_path "$REPO_ROOT")" + WSL_LIB="$(tb_to_wsl_path "$LIB_DIR")" + WSL_TRACE="$(tb_to_wsl_path "$TRACE_DIR")" + WSL_JUNIT="$(tb_to_wsl_path "$JUNIT_DIR")" + wsl -- bash -lc \ + "cd '$WSL_REPO' && export BASH_ENV='$WSL_LIB/trace-init.sh' TRBLDOC_TRACE_DIR='$WSL_TRACE' TRBLDOC_TRACE_MODE='time' TRBLDOC_TRACE_TARGETS='$TRACE_TARGETS'; bats --report-formatter junit --output '$WSL_JUNIT' $SUITE_DIRS_REL" \ + > "$OUTDIR/bats-hotspots-output.txt" 2>&1 \ + || tb_info "bats (WSL) reported failures during hotspot run" + fi + + if ls "$TRACE_DIR"/trace.*.log >/dev/null 2>&1; then + awk -f "$LIB_DIR/trace-extract.awk" -v MODE=time "$TRACE_DIR"/trace.*.log \ + | sort -t "$(printf '\t')" -k1 -rn > "$OUTDIR/hotspots.tsv" + { + printf 'total_ms,hits,file,line\n' + awk -F '\t' '{ printf "%s,%s,%s,%s\n",$1,$2,$3,$4 }' "$OUTDIR/hotspots.tsv" + } > "$OUTDIR/hotspots.csv" + # Resolve first target path for source display in the HTML. + FIRST_TARGET="${TARGETS%% *}" + case "$FIRST_TARGET" in + /*) SRC_PATH="$FIRST_TARGET" ;; + *) SRC_PATH="$REPO_ROOT/$FIRST_TARGET" ;; + esac + awk -f "$LIB_DIR/render-hotspots.awk" \ + -v TITLE="trbldoc line hotspots ($SUITE)" \ + -v SRC="$SRC_PATH" \ + "$OUTDIR/hotspots.tsv" > "$OUTDIR/hotspots.html" + else + if grep -qi "bash\\\\r" "$OUTDIR/bats-hotspots-output.txt" 2>/dev/null; then + tb_die "bats appears broken (CRLF shebang: 'bash\\r') during hotspot run. Reinstall bats or run dos2unix on the bats scripts." + fi + tb_info "no trace data captured; skipping hotspots report" + HOTSPOTS=0 + fi +fi + +# ── printed summary ────────────────────────────────────────────────────────── +printf '\n==== slowest %s tests ====\n' "$TOPN" +awk -F '\t' -v n="$TOPN" 'NR<=n { printf "%8.3fs %s\n", $1, $3 }' \ + "$OUTDIR/timings.sorted.tsv" + +printf '\nReports written to: %s\n' "$OUTDIR" +printf ' per-test timing : %s\n' "$OUTDIR/timings.html" +[ "$HOTSPOTS" -eq 1 ] && printf ' line hotspots : %s\n' "$OUTDIR/hotspots.html" |
