#!/usr/bin/env bash set -euo pipefail VERSION="1.1.0" SCRIPT_NAME="$(basename "$0")" usage() { cat <&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