mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
253 lines
7.1 KiB
Bash
Executable File
253 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
VERSION="1.1.0"
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
$SCRIPT_NAME v$VERSION — Run Go benchmarks with optional comparison
|
|
|
|
USAGE
|
|
bash $SCRIPT_NAME [options] [package]
|
|
|
|
DESCRIPTION
|
|
Wrapper around 'go test -bench' that runs benchmarks multiple times and
|
|
optionally compares results against a saved baseline using benchstat.
|
|
|
|
Results can be saved to a file for future comparison. If benchstat is
|
|
installed and a baseline is provided, a statistical comparison is shown.
|
|
|
|
EXIT CODES
|
|
0 Benchmarks ran successfully
|
|
1 go test failed (compilation error, test failure, no benchmarks found)
|
|
2 Usage error (missing arguments, bad flags, file exists without --force)
|
|
|
|
OPTIONS
|
|
-h, --help Show this help message
|
|
-v, --version Show version
|
|
-n, --count N Number of benchmark iterations (default: 5)
|
|
-b, --baseline FILE Compare results against this baseline file
|
|
-s, --save FILE Save benchmark results to this file
|
|
-f, --filter REGEX Benchmark filter regex (default: ".")
|
|
--json Output metadata as JSON (human output goes to stderr)
|
|
--benchmem Include memory allocation stats (default: on)
|
|
--no-benchmem Disable memory allocation stats
|
|
--force Allow --save to overwrite existing files
|
|
--limit N Max benchmark result lines to include (default: 0 = all)
|
|
|
|
ARGUMENTS
|
|
package Go package to benchmark (default: ./...)
|
|
|
|
EXAMPLES
|
|
bash $SCRIPT_NAME
|
|
bash $SCRIPT_NAME -n 10 ./pkg/parser
|
|
bash $SCRIPT_NAME --save baseline.txt ./...
|
|
bash $SCRIPT_NAME --baseline baseline.txt --save current.txt ./...
|
|
bash $SCRIPT_NAME --filter BenchmarkSort -n 3
|
|
bash $SCRIPT_NAME --json --limit 5 ./...
|
|
bash $SCRIPT_NAME --save results.txt --force ./...
|
|
EOF
|
|
}
|
|
|
|
json_escape() {
|
|
local s="$1"
|
|
s="${s//\\/\\\\}"
|
|
s="${s//\"/\\\"}"
|
|
s="${s//$'\t'/\\t}"
|
|
s="${s//$'\r'/}"
|
|
s="${s//$'\n'/\\n}"
|
|
printf '%s' "$s"
|
|
}
|
|
|
|
# Print human-readable output: stdout in text mode, stderr in JSON mode.
|
|
log() {
|
|
if $JSON_OUTPUT; then
|
|
echo "$@" >&2
|
|
else
|
|
echo "$@"
|
|
fi
|
|
}
|
|
|
|
COUNT=5
|
|
BASELINE=""
|
|
SAVE=""
|
|
FILTER="."
|
|
PACKAGE=""
|
|
JSON_OUTPUT=false
|
|
BENCHMEM=true
|
|
FORCE=false
|
|
LIMIT=0
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-h|--help) usage; exit 0 ;;
|
|
-v|--version) echo "$SCRIPT_NAME v$VERSION"; exit 0 ;;
|
|
-n|--count) COUNT="${2:?error: --count requires a number}"; shift 2 ;;
|
|
-b|--baseline) BASELINE="${2:?error: --baseline requires a file path}"; shift 2 ;;
|
|
-s|--save) SAVE="${2:?error: --save requires a file path}"; shift 2 ;;
|
|
-f|--filter) FILTER="${2:?error: --filter requires a regex}"; shift 2 ;;
|
|
--json) JSON_OUTPUT=true; shift ;;
|
|
--benchmem) BENCHMEM=true; shift ;;
|
|
--no-benchmem) BENCHMEM=false; shift ;;
|
|
--force) FORCE=true; shift ;;
|
|
--limit) LIMIT="${2:?error: --limit requires a number}"; shift 2 ;;
|
|
-*) echo "error: unknown option: $1" >&2; usage >&2; exit 2 ;;
|
|
*) PACKAGE="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
PACKAGE="${PACKAGE:-./...}"
|
|
|
|
if ! command -v go &>/dev/null; then
|
|
echo "error: 'go' command not found in PATH" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if ! [[ "$COUNT" =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "error: --count must be a positive integer, got: $COUNT" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if ! [[ "$LIMIT" =~ ^[0-9]+$ ]]; then
|
|
echo "error: --limit must be a non-negative integer, got: $LIMIT" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ -n "$BASELINE" && ! -f "$BASELINE" ]]; then
|
|
echo "error: baseline file not found: $BASELINE" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ -n "$SAVE" && -f "$SAVE" ]] && ! $FORCE; then
|
|
echo "error: save target already exists: $SAVE (use --force to overwrite)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
HAS_BENCHSTAT=false
|
|
if command -v benchstat &>/dev/null; then
|
|
HAS_BENCHSTAT=true
|
|
fi
|
|
|
|
BENCH_ARGS=(-bench "$FILTER" -count "$COUNT" -run '^$')
|
|
if $BENCHMEM; then
|
|
BENCH_ARGS+=(-benchmem)
|
|
fi
|
|
|
|
TMPFILE=$(mktemp "${TMPDIR:-/tmp}/bench-XXXXXX.txt")
|
|
trap 'rm -f "$TMPFILE"' EXIT
|
|
|
|
log "Running benchmarks: go test ${BENCH_ARGS[*]} $PACKAGE"
|
|
log "Iterations: $COUNT"
|
|
log ""
|
|
|
|
GO_EXIT=0
|
|
if $JSON_OUTPUT; then
|
|
go test "${BENCH_ARGS[@]}" "$PACKAGE" 2>&1 | tee "$TMPFILE" >&2 || GO_EXIT=$?
|
|
else
|
|
go test "${BENCH_ARGS[@]}" "$PACKAGE" 2>&1 | tee "$TMPFILE" || GO_EXIT=$?
|
|
fi
|
|
|
|
BENCH_COUNT=$(grep -cE '^Benchmark' "$TMPFILE" || true)
|
|
|
|
TRUNCATED=false
|
|
if [[ $LIMIT -gt 0 && $BENCH_COUNT -gt $LIMIT ]]; then
|
|
TRUNCATED=true
|
|
fi
|
|
|
|
if ! $JSON_OUTPUT && $TRUNCATED; then
|
|
log ""
|
|
log "Note: $BENCH_COUNT benchmark results found, showing first $LIMIT (--limit $LIMIT)"
|
|
fi
|
|
|
|
if [[ -n "$SAVE" ]]; then
|
|
cp "$TMPFILE" "$SAVE"
|
|
log ""
|
|
log "Results saved to: $SAVE"
|
|
fi
|
|
|
|
if [[ -n "$BASELINE" ]]; then
|
|
log ""
|
|
log "=== Comparison with baseline: $BASELINE ==="
|
|
log ""
|
|
if $HAS_BENCHSTAT; then
|
|
if $JSON_OUTPUT; then
|
|
benchstat "$BASELINE" "$TMPFILE" >&2 || true
|
|
else
|
|
benchstat "$BASELINE" "$TMPFILE" || true
|
|
fi
|
|
else
|
|
log "note: install benchstat for statistical comparison:"
|
|
log " go install golang.org/x/perf/cmd/benchstat@latest"
|
|
log ""
|
|
log "--- Baseline ---"
|
|
if $JSON_OUTPUT; then
|
|
grep -E '^Benchmark' "$BASELINE" >&2 || true
|
|
else
|
|
grep -E '^Benchmark' "$BASELINE" || true
|
|
fi
|
|
log ""
|
|
log "--- Current ---"
|
|
if $JSON_OUTPUT; then
|
|
grep -E '^Benchmark' "$TMPFILE" >&2 || true
|
|
else
|
|
grep -E '^Benchmark' "$TMPFILE" || true
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
FINAL_EXIT=0
|
|
if [[ $GO_EXIT -ne 0 ]]; then
|
|
FINAL_EXIT=1
|
|
if ! $JSON_OUTPUT; then
|
|
log ""
|
|
log "error: go test exited with code $GO_EXIT"
|
|
fi
|
|
elif [[ $BENCH_COUNT -eq 0 ]]; then
|
|
FINAL_EXIT=1
|
|
if ! $JSON_OUTPUT; then
|
|
log ""
|
|
log "error: no benchmarks found matching filter: $FILTER"
|
|
fi
|
|
fi
|
|
|
|
if $JSON_OUTPUT; then
|
|
BENCH_OUTPUT=$(<"$TMPFILE")
|
|
if $TRUNCATED; then
|
|
limited=""
|
|
bench_seen=0
|
|
while IFS= read -r line; do
|
|
if [[ "$line" =~ ^Benchmark ]]; then
|
|
bench_seen=$((bench_seen + 1))
|
|
if [[ $bench_seen -le $LIMIT ]]; then
|
|
limited+="$line"$'\n'
|
|
fi
|
|
else
|
|
limited+="$line"$'\n'
|
|
fi
|
|
done < "$TMPFILE"
|
|
BENCH_OUTPUT="$limited"
|
|
fi
|
|
|
|
escaped_package=$(json_escape "$PACKAGE")
|
|
escaped_filter=$(json_escape "$FILTER")
|
|
escaped_baseline=$(json_escape "$BASELINE")
|
|
escaped_save=$(json_escape "$SAVE")
|
|
escaped_output=$(json_escape "$BENCH_OUTPUT")
|
|
|
|
printf '{"count":%d,' "$COUNT"
|
|
printf '"package":"%s",' "$escaped_package"
|
|
printf '"filter":"%s",' "$escaped_filter"
|
|
printf '"benchmarks_found":%d,' "$BENCH_COUNT"
|
|
printf '"baseline":"%s",' "$escaped_baseline"
|
|
printf '"save":"%s",' "$escaped_save"
|
|
printf '"exit_code":%d,' "$GO_EXIT"
|
|
printf '"output":"%s"' "$escaped_output"
|
|
if $TRUNCATED; then
|
|
printf ',"truncated":true'
|
|
fi
|
|
printf '}\n'
|
|
fi
|
|
|
|
exit $FINAL_EXIT
|