Files
1jehuang--jcode/scripts/bench_compile.sh
T
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

275 lines
6.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
cd "$repo_root"
usage() {
cat <<'USAGE'
Usage:
scripts/bench_compile.sh <target> [options] [-- <extra cargo args>]
Targets:
check Run cargo check --quiet
build Run cargo build --quiet
release-jcode Run scripts/dev_cargo.sh build --release -p jcode --bin jcode --quiet
selfdev-jcode Run scripts/dev_cargo.sh build --profile selfdev -p jcode --bin jcode --quiet
Options:
--cold Run cargo clean before timing the first run
--touch <path> Touch a source file before each timed run to simulate an edit
--edit <path> Toggle a harmless text edit before each run (restored afterward)
--runs <n> Number of timed runs to execute (default: 1)
--json Print per-run + summary data as JSON
-h, --help Show this help
Examples:
scripts/bench_compile.sh check
scripts/bench_compile.sh check --runs 3 --touch src/server.rs
scripts/bench_compile.sh check --runs 3 --edit src/server.rs
scripts/bench_compile.sh build -- --package jcode --bin test_api
scripts/bench_compile.sh release-jcode --json
scripts/bench_compile.sh selfdev-jcode --json
USAGE
}
if [[ $# -gt 0 ]] && [[ "$1" == "-h" || "$1" == "--help" ]]; then
usage
exit 0
fi
target="${1:-}"
shift || true
if [[ -z "$target" ]]; then
usage
exit 1
fi
cold=0
touch_path=""
edit_path=""
runs=1
json_output=0
extra_args=()
while [[ $# -gt 0 ]]; do
case "$1" in
--cold)
cold=1
;;
--touch)
if [[ $# -lt 2 ]]; then
printf 'error: --touch requires a path\n' >&2
exit 1
fi
touch_path="$2"
shift
;;
--edit)
if [[ $# -lt 2 ]]; then
printf 'error: --edit requires a path\n' >&2
exit 1
fi
edit_path="$2"
shift
;;
--runs)
if [[ $# -lt 2 ]]; then
printf 'error: --runs requires a positive integer\n' >&2
exit 1
fi
runs="$2"
shift
;;
--json)
json_output=1
;;
--)
shift
extra_args=("$@")
break
;;
-h|--help)
usage
exit 0
;;
*)
printf 'error: unknown argument: %s\n' "$1" >&2
exit 1
;;
esac
shift
done
if ! [[ "$runs" =~ ^[1-9][0-9]*$ ]]; then
printf 'error: --runs must be a positive integer (got %s)\n' "$runs" >&2
exit 1
fi
if [[ -n "$touch_path" && -n "$edit_path" ]]; then
printf 'error: --touch and --edit are mutually exclusive\n' >&2
exit 1
fi
case "$target" in
check)
cmd=(cargo check --quiet)
;;
build)
cmd=(cargo build --quiet)
;;
release-jcode)
cmd=(scripts/dev_cargo.sh build --release -p jcode --bin jcode --quiet)
;;
selfdev-jcode)
cmd=(scripts/dev_cargo.sh build --profile selfdev -p jcode --bin jcode --quiet)
;;
*)
printf 'error: unsupported target: %s\n' "$target" >&2
usage
exit 1
;;
esac
if [[ ${#extra_args[@]} -gt 0 ]]; then
cmd+=("${extra_args[@]}")
fi
if [[ -n "$touch_path" ]] && [[ ! -e "$touch_path" ]]; then
printf 'error: touch path does not exist: %s\n' "$touch_path" >&2
exit 1
fi
if [[ -n "$edit_path" ]] && [[ ! -f "$edit_path" ]]; then
printf 'error: edit path must be an existing file: %s\n' "$edit_path" >&2
exit 1
fi
edit_backup=""
cleanup() {
if [[ -n "$edit_backup" && -n "$edit_path" && -f "$edit_backup" ]]; then
cp "$edit_backup" "$edit_path"
rm -f "$edit_backup"
fi
}
trap cleanup EXIT
if [[ -n "$edit_path" ]]; then
edit_backup=$(mktemp)
cp "$edit_path" "$edit_backup"
fi
if [[ $cold -eq 1 ]]; then
echo 'bench_compile: running cargo clean' >&2
cargo clean
fi
printf 'bench_compile: target=%s cold=%s runs=%s\n' "$target" "$cold" "$runs" >&2
printf 'bench_compile: touch=%s\n' "${touch_path:-<none>}" >&2
printf 'bench_compile: edit=%s\n' "${edit_path:-<none>}" >&2
printf 'bench_compile: command=%s\n' "${cmd[*]}" >&2
run_times=()
run_once() {
local run_index="$1"
if [[ -n "$touch_path" ]]; then
echo "bench_compile: touching $touch_path (run $run_index/$runs)" >&2
touch "$touch_path"
elif [[ -n "$edit_path" ]]; then
echo "bench_compile: editing $edit_path (run $run_index/$runs)" >&2
python3 - "$edit_backup" "$edit_path" "$run_index" <<'PY'
from pathlib import Path
import sys
backup = Path(sys.argv[1]).read_bytes()
target = Path(sys.argv[2])
run_index = int(sys.argv[3])
if run_index % 2 == 1:
target.write_bytes(backup + b"\n")
else:
target.write_bytes(backup)
PY
fi
local start_ns end_ns elapsed_ns elapsed_secs
start_ns=$(python3 - <<'PY'
import time
print(time.perf_counter_ns())
PY
)
"${cmd[@]}"
end_ns=$(python3 - <<'PY'
import time
print(time.perf_counter_ns())
PY
)
elapsed_ns=$((end_ns - start_ns))
elapsed_secs=$(python3 - "$elapsed_ns" <<'PY'
import sys
print(f"{int(sys.argv[1]) / 1_000_000_000:.3f}")
PY
)
run_times+=("$elapsed_secs")
if [[ $json_output -eq 0 ]]; then
printf 'bench_compile: run %s/%s real %ss\n' "$run_index" "$runs" "$elapsed_secs" >&2
fi
}
for ((i = 1; i <= runs; i++)); do
run_once "$i"
done
summary_json=$(python3 - "$target" "$cold" "$touch_path" "$edit_path" "$runs" "${cmd[*]}" "${run_times[@]}" <<'PY'
import json
import statistics
import sys
target = sys.argv[1]
cold = sys.argv[2] == "1"
touch = sys.argv[3]
edit = sys.argv[4]
runs = int(sys.argv[5])
command = sys.argv[6]
times = [float(v) for v in sys.argv[7:]]
summary = {
"target": target,
"cold": cold,
"touch": touch or None,
"edit": edit or None,
"runs": runs,
"command": command,
"times_seconds": times,
"min_seconds": min(times),
"max_seconds": max(times),
"avg_seconds": sum(times) / len(times),
"median_seconds": statistics.median(times),
}
print(json.dumps(summary))
PY
)
if [[ $json_output -eq 1 ]]; then
printf '%s\n' "$summary_json"
else
python3 - "$summary_json" <<'PY' >&2
import json
import sys
summary = json.loads(sys.argv[1])
print(
"bench_compile: summary "
f"min={summary['min_seconds']:.3f}s "
f"median={summary['median_seconds']:.3f}s "
f"avg={summary['avg_seconds']:.3f}s "
f"max={summary['max_seconds']:.3f}s"
)
PY
fi