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/hotspot-tests.sh | |
| download | trbldoc-657fb0007f39f07cc0401e0c5d03e25df6234aa4.tar.gz trbldoc-657fb0007f39f07cc0401e0c5d03e25df6234aa4.tar.bz2 trbldoc-657fb0007f39f07cc0401e0c5d03e25df6234aa4.zip | |
Inital commit.
Diffstat (limited to 'scripts/hotspot-tests.sh')
| -rw-r--r-- | scripts/hotspot-tests.sh | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/scripts/hotspot-tests.sh b/scripts/hotspot-tests.sh new file mode 100644 index 0000000..8973e6b --- /dev/null +++ b/scripts/hotspot-tests.sh @@ -0,0 +1,204 @@ +#!/bin/sh +# scripts/hotspot-tests.sh +# Repeatable hotspot analysis for the trbldoc test suite. +# +# Produces: +# - per-test timings (parsed from `bats --timing`) +# - line-level hotspots for target scripts from xtrace logs +# - optional phase hotspots for trbldoc.sh +# +# Usage: +# scripts/hotspot-tests.sh [-s all|unit|integration] [-o OUTDIR] +# [-n TOPN] [-t "file.sh ..."] +# +# -s SUITE suite to run (default: all) +# -o OUTDIR output directory (default: reports/hotspots-<timestamp>) +# -n TOPN number of slowest tests to print (default: 25) +# -t LIST space-separated target scripts for trace hotspots (default: trbldoc.sh) + +set -u + +SELF="$0" +. "$(dirname -- "$0")/lib/common.sh" +tb_resolve_paths "$SELF" + +SUITE=all +OUTDIR="" +TOPN=25 +TARGETS="trbldoc.sh" + +while getopts ":s:o:n:t:h" opt; do + case "$opt" in + s) SUITE="$OPTARG" ;; + o) OUTDIR="$OPTARG" ;; + n) TOPN="$OPTARG" ;; + t) TARGETS="$OPTARG" ;; + h) sed -n '2,24p' "$SELF"; exit 0 ;; + :) tb_die "option -$OPTARG requires an argument" ;; + \?) tb_die "unknown option -$OPTARG" ;; + esac +done + +tb_require awk +tb_require sort + +case "$TOPN" in + ''|*[!0-9]*|0) tb_die "-n TOPN must be a positive integer" ;; +esac + +[ -n "$OUTDIR" ] || OUTDIR="$REPO_ROOT/reports/hotspots-$(tb_timestamp)" +case "$OUTDIR" in + /*|[A-Za-z]:[\\/]*) ;; + *) OUTDIR="$REPO_ROOT/$OUTDIR" ;; +esac +mkdir -p "$OUTDIR" || tb_die "cannot create output dir: $OUTDIR" + +SUITE_DIRS="$(tb_suite_dirs "$SUITE")" +SUITE_DIRS_REL="$(tb_suite_dirs_rel "$SUITE")" +TIMING_RAW="$OUTDIR/bats-timing-output.txt" +TRACE_DIR="$OUTDIR/trace" +rm -rf "$TRACE_DIR"; mkdir -p "$TRACE_DIR" + +# Build trace target filter and resolve first target source for HTML context. +TRACE_TARGETS="" +FIRST_TARGET="" +for tf in $TARGETS; do + TRACE_TARGETS="$TRACE_TARGETS ${tf##*/}" + if [ -z "$FIRST_TARGET" ]; then + FIRST_TARGET="$tf" + fi +done + +tb_info "running bats ($SUITE) with --timing output..." +if tb_bats_usable "$SUITE"; then + # shellcheck disable=SC2086 + bats --timing $SUITE_DIRS > "$TIMING_RAW" 2>&1 || tb_info "bats reported failures (timing output still collected)" + BATS_MODE="local" +elif command -v wsl >/dev/null 2>&1; then + tb_info "local bats appears unusable; retrying timing run via WSL bats..." + WSL_REPO="$(tb_to_wsl_path "$REPO_ROOT")" + wsl -- bash -lc \ + "cd '$WSL_REPO' && bats --timing $SUITE_DIRS_REL" \ + > "$TIMING_RAW" 2>&1 || tb_info "bats (WSL) reported failures (timing output still collected)" + BATS_MODE="wsl" +else + tb_die "local bats command appears unusable and no WSL fallback is available" +fi + +# Parse bats timing lines: "ok N <name> in <ms>ms" +awk ' +match($0, /^ok [0-9]+ (.*) in ([0-9]+)ms$/, m) { + name = m[1] + ms = m[2] + 0 + suite = name + sub(/:.*/, "", suite) + printf "%.3f\t%s\t%s\t%d\n", ms / 1000.0, suite, name, ms +} +' "$TIMING_RAW" > "$OUTDIR/timings.tsv" + +if [ ! -s "$OUTDIR/timings.tsv" ]; then + tb_die "no per-test timing rows parsed (check $TIMING_RAW)" +fi + +sort -t "$(printf '\t')" -k1,1nr "$OUTDIR/timings.tsv" > "$OUTDIR/timings.sorted.tsv" +{ + printf 'seconds,suite,test,milliseconds\n' + awk -F '\t' '{ gsub(/"/,"\"\"",$3); printf "%s,%s,\"%s\",%s\n",$1,$2,$3,$4 }' "$OUTDIR/timings.sorted.tsv" +} > "$OUTDIR/timings.csv" + +awk -f "$LIB_DIR/render-timing.awk" \ + -v TITLE="trbldoc test timings ($SUITE)" \ + "$OUTDIR/timings.sorted.tsv" > "$OUTDIR/timings.html" + +tb_info "running bats ($SUITE) under xtrace timing hook..." +if [ "$BATS_MODE" = "local" ]; then + BASH_ENV="$LIB_DIR/trace-init.sh" + TRBLDOC_TRACE_DIR="$TRACE_DIR" + TRBLDOC_TRACE_MODE="time" + TRBLDOC_TRACE_TARGETS="$TRACE_TARGETS" + export BASH_ENV TRBLDOC_TRACE_DIR TRBLDOC_TRACE_MODE TRBLDOC_TRACE_TARGETS + # shellcheck disable=SC2086 + bats $SUITE_DIRS > "$OUTDIR/bats-trace-output.txt" 2>&1 || tb_info "bats reported failures during trace 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 -- 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 $SUITE_DIRS_REL" \ + > "$OUTDIR/bats-trace-output.txt" 2>&1 || tb_info "bats (WSL) reported failures during trace 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" + + case "$FIRST_TARGET" in + /*|[A-Za-z]:[\\/]*) 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 + tb_info "no trace logs captured for target(s):$TRACE_TARGETS" +fi + +# Optional phase hotspots when trbldoc.sh + helper scripts are available. +if [ -f "$OUTDIR/hotspots.tsv" ] && [ -f "$LIB_DIR/trace-phase-extract.awk" ] && [ -f "$LIB_DIR/render-phases.awk" ] && [ -f "$REPO_ROOT/trbldoc.sh" ]; then + _find_line() { + awk -v p="$1" 'index($0, p) { print NR; exit }' "$REPO_ROOT/trbldoc.sh" + } + L_DISCOVER_END="$(_find_line '> "$source_files_list"')" + L_EXTRACT_END="$(_find_line '# Resolve src_repo once before the chunk loop.')" + L_ORDER_END="$(_find_line 'done < "$sorted_chunk_order_file"')" + L_RENDER_END="$(_find_line 'done < "$chunk_render_records_file"')" + L_NAV_END="$(_find_line '# Second pass: resolve inline references now that global.meta is complete.')" + L_REFS_END="$(_find_line 'rm -f "$_ref_find_tmp"')" + L_TOC_END="$(_find_line '# Post-processing pass: substitute {{ toc }} in all rendered index.html files')" + L_ASSEMBLE_END="$(_find_line 'assemble_site "$cache_dir/namespace" "$out_dir"')" + + if [ -n "$L_DISCOVER_END" ] && [ -n "$L_EXTRACT_END" ] && [ -n "$L_ORDER_END" ] \ + && [ -n "$L_RENDER_END" ] && [ -n "$L_NAV_END" ] && [ -n "$L_REFS_END" ] \ + && [ -n "$L_TOC_END" ] && [ -n "$L_ASSEMBLE_END" ]; then + awk -f "$LIB_DIR/trace-phase-extract.awk" \ + -v SRC_BASE="trbldoc.sh" \ + -v L_DISCOVER_END="$L_DISCOVER_END" \ + -v L_EXTRACT_END="$L_EXTRACT_END" \ + -v L_ORDER_END="$L_ORDER_END" \ + -v L_RENDER_END="$L_RENDER_END" \ + -v L_NAV_END="$L_NAV_END" \ + -v L_REFS_END="$L_REFS_END" \ + -v L_TOC_END="$L_TOC_END" \ + -v L_ASSEMBLE_END="$L_ASSEMBLE_END" \ + "$TRACE_DIR"/trace.*.log \ + | sort -t "$(printf '\t')" -k1 -rn > "$OUTDIR/phase-hotspots.tsv" + + { + printf 'total_ms,hits,phase\n' + awk -F '\t' '{ printf "%s,%s,%s\n",$1,$2,$3 }' "$OUTDIR/phase-hotspots.tsv" + } > "$OUTDIR/phase-hotspots.csv" + + awk -f "$LIB_DIR/render-phases.awk" \ + -v TITLE="trbldoc phase hotspots ($SUITE)" \ + "$OUTDIR/phase-hotspots.tsv" > "$OUTDIR/phase-hotspots.html" + fi +fi + +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" +if [ -f "$OUTDIR/hotspots.html" ]; then + printf ' line hotspots : %s\n' "$OUTDIR/hotspots.html" +else + printf ' line hotspots : not generated (target not exercised)\n' +fi +if [ -f "$OUTDIR/phase-hotspots.html" ]; then + printf ' phase hotspots : %s\n' "$OUTDIR/phase-hotspots.html" +fi |
