aboutsummaryrefslogtreecommitdiff
path: root/scripts/profile-run.sh
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/profile-run.sh')
-rw-r--r--scripts/profile-run.sh180
1 files changed, 180 insertions, 0 deletions
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-<timestamp>)
+# -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