From 657fb0007f39f07cc0401e0c5d03e25df6234aa4 Mon Sep 17 00:00:00 2001 From: Alexander M Pickering Date: Tue, 21 Jul 2026 20:19:46 -0500 Subject: Inital commit. --- scripts/profile-run.sh | 180 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 scripts/profile-run.sh (limited to 'scripts/profile-run.sh') diff --git a/scripts/profile-run.sh b/scripts/profile-run.sh new file mode 100644 index 0000000..74ab425 --- /dev/null +++ b/scripts/profile-run.sh @@ -0,0 +1,180 @@ +#!/bin/sh +# scripts/profile-run.sh +# Profile direct trbldoc invocations (outside bats) using the existing xtrace +# hook and report pipeline. +# +# Outputs: +# - run-summary.tsv/csv wall-clock per run +# - hotspots.tsv/csv/html per-line cumulative self-time +# - phase-hotspots.tsv/csv/html per-phase cumulative self-time +# +# Usage: +# scripts/profile-run.sh [-o OUTDIR] [-n RUNS] [-C] [-t "file.sh ..."] -- [trbldoc args...] +# +# -o OUTDIR output directory (default: reports/profile-run-) +# -n RUNS number of repeated runs (default: 1) +# -C force clean rebuild (-C) on every run +# -t LIST trace target script basenames (default: trbldoc.sh) + +set -u + +SELF="$0" +. "$(dirname -- "$0")/lib/common.sh" +tb_resolve_paths "$SELF" + +OUTDIR="" +RUNS=1 +CLEAN=0 +TARGETS="trbldoc.sh" + +while getopts ":o:n:Ct:h" opt; do + case "$opt" in + o) OUTDIR="$OPTARG" ;; + n) RUNS="$OPTARG" ;; + C) CLEAN=1 ;; + 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 +shift $((OPTIND - 1)) + +[ "${1:-}" = "--" ] && shift + +tb_require awk +tb_require sort +tb_require bash + +case "$RUNS" in + ''|*[!0-9]*|0) tb_die "-n RUNS must be a positive integer" ;; +esac + +[ -n "$OUTDIR" ] || OUTDIR="$REPO_ROOT/reports/profile-run-$(tb_timestamp)" +case "$OUTDIR" in + /*|[A-Za-z]:[\\/]*) ;; + *) OUTDIR="$REPO_ROOT/$OUTDIR" ;; +esac +mkdir -p "$OUTDIR" || tb_die "cannot create output dir: $OUTDIR" + +TRACE_DIR="$OUTDIR/trace" +RUN_DIR="$OUTDIR/runs" +mkdir -p "$TRACE_DIR" "$RUN_DIR" + +TRBLDOC_PATH="$REPO_ROOT/trbldoc.sh" +[ -f "$TRBLDOC_PATH" ] || tb_die "trbldoc source not found: $TRBLDOC_PATH" + +TRACE_TARGETS="" +for tf in $TARGETS; do + TRACE_TARGETS="$TRACE_TARGETS ${tf##*/}" +done + +# Resolve line anchors once for phase aggregation. If any anchor is missing, +# skip phase aggregation rather than failing the whole profiling run. +tb_find_line() { + _pat="$1" + awk -v p="$_pat" 'index($0, p) { print NR; exit }' "$TRBLDOC_PATH" +} +L_DISCOVER_END="$(tb_find_line '> "$source_files_list"')" +L_EXTRACT_END="$(tb_find_line '# Resolve src_repo once before the chunk loop.')" +L_ORDER_END="$(tb_find_line 'done < "$sorted_chunk_order_file"')" +L_RENDER_END="$(tb_find_line 'done < "$chunk_render_records_file"')" +L_NAV_END="$(tb_find_line '# Second pass: resolve inline references now that global.meta is complete.')" +L_REFS_END="$(tb_find_line 'rm -f "$_ref_find_tmp"')" +L_TOC_END="$(tb_find_line '# Post-processing pass: substitute {{ toc }} in all rendered index.html files')" +L_ASSEMBLE_END="$(tb_find_line 'assemble_site "$cache_dir/namespace" "$out_dir"')" + +: > "$OUTDIR/run-summary.tsv" + +run_idx=1 +while [ "$run_idx" -le "$RUNS" ]; do + tb_info "profiling run $run_idx/$RUNS..." + run_out="$RUN_DIR/run-$run_idx" + mkdir -p "$run_out" + run_stdout="$run_out/stdout.txt" + run_stderr="$run_out/stderr.txt" + run_start_s="$(date +%s)" + + 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 + + if [ "$CLEAN" -eq 1 ]; then + (cd "$REPO_ROOT" && bash "$TRBLDOC_PATH" -C "$@") >"$run_stdout" 2>"$run_stderr" + else + (cd "$REPO_ROOT" && bash "$TRBLDOC_PATH" "$@") >"$run_stdout" 2>"$run_stderr" + fi + run_exit=$? + unset BASH_ENV TRBLDOC_TRACE_DIR TRBLDOC_TRACE_MODE TRBLDOC_TRACE_TARGETS + + run_end_s="$(date +%s)" + run_ms=$(( (run_end_s - run_start_s) * 1000 )) + printf '%s\t%s\t%s\n' "$run_idx" "$run_ms" "$run_exit" >> "$OUTDIR/run-summary.tsv" + run_idx=$((run_idx + 1)) +done + +sort -t "$(printf '\t')" -k2 -rn "$OUTDIR/run-summary.tsv" > "$OUTDIR/run-summary.sorted.tsv" +{ + printf 'run,total_ms,exit_code\n' + awk -F '\t' '{ printf "%s,%s,%s\n",$1,$2,$3 }' "$OUTDIR/run-summary.sorted.tsv" +} > "$OUTDIR/run-summary.csv" + +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" + + FIRST_TARGET="${TARGETS%% *}" + 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 (direct runs)" \ + -v SRC="$SRC_PATH" \ + "$OUTDIR/hotspots.tsv" > "$OUTDIR/hotspots.html" +else + tb_die "no trace logs captured (check run stderr logs under $RUN_DIR)" +fi + +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 (direct runs)" \ + "$OUTDIR/phase-hotspots.tsv" > "$OUTDIR/phase-hotspots.html" +else + tb_info "phase anchor discovery incomplete; skipping phase-hotspots report" +fi + +printf '\n==== slowest runs ====\n' +awk -F '\t' '{ printf "run %s %8.1f ms exit=%s\n",$1,$2,$3 }' "$OUTDIR/run-summary.sorted.tsv" +printf '\nReports written to: %s\n' "$OUTDIR" +printf ' run summary : %s\n' "$OUTDIR/run-summary.csv" +printf ' line hotspots : %s\n' "$OUTDIR/hotspots.html" +if [ -f "$OUTDIR/phase-hotspots.html" ]; then + printf ' phase hotspots : %s\n' "$OUTDIR/phase-hotspots.html" +fi -- cgit v1.2.3-70-g09d2