#!/usr/bin/env bash set -u usage() { cat <<'USAGE' Usage: run_performance.sh [--quick|--full] [--no-cross] [--baseline ] [--fail-on-regression] [--throughput-drop-pct N] [--p99-worse-pct N] Runs the Proto Socket performance validation standard separately from the compatibility matrix. The wrapper executes run_stress.sh components, merges the measured rows into one local record, and compares them with a prior performance record when a baseline is supplied. Defaults: --quick throughput regression warning threshold: 20% p99 latency regression warning threshold: 25% Recommended: Quick performance signal: bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick Full local baseline candidate: bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --full Regression comparison: bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --quick --baseline agent-test/runs/.md USAGE } mode="quick" run_cross=1 baseline_file="" fail_on_regression=0 throughput_drop_pct="20" p99_worse_pct="25" while [ "$#" -gt 0 ]; do case "$1" in --quick) mode="quick" ;; --full) mode="full" ;; --no-cross) run_cross=0 ;; --baseline=*) baseline_file="${1#--baseline=}" ;; --baseline) shift; baseline_file="${1:-}" ;; --fail-on-regression) fail_on_regression=1 ;; --throughput-drop-pct=*) throughput_drop_pct="${1#--throughput-drop-pct=}" ;; --throughput-drop-pct) shift; throughput_drop_pct="${1:-}" ;; --p99-worse-pct=*) p99_worse_pct="${1#--p99-worse-pct=}" ;; --p99-worse-pct) shift; p99_worse_pct="${1:-}" ;; -h|--help) usage; exit 0 ;; *) usage >&2; exit 2 ;; esac shift done case "$mode" in quick|full) ;; *) usage >&2; exit 2 ;; esac case "$throughput_drop_pct" in ''|*[!0-9.]*) printf 'invalid --throughput-drop-pct: %s\n' "$throughput_drop_pct" >&2 exit 2 ;; esac case "$p99_worse_pct" in ''|*[!0-9.]*) printf 'invalid --p99-worse-pct: %s\n' "$p99_worse_pct" >&2 exit 2 ;; esac script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" repo_root="$(cd "$script_dir/../../../../.." && pwd)" stress_script="$script_dir/run_stress.sh" run_started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" run_record_stamp="$(date -u +%Y%m%d-%H%M%S)" record_profile="proto-socket-performance-${mode}" record_dir="$repo_root/agent-test/runs" record_file="$record_dir/${run_record_stamp}-${record_profile}.md" log_dir="$(mktemp -d "${TMPDIR:-/tmp}/proto-socket-performance.XXXXXX")" git_ref="$(git -C "$repo_root" rev-parse --short HEAD 2>/dev/null || echo "unknown")" mkdir -p "$record_dir" if [ -n "$baseline_file" ]; then case "$baseline_file" in /*) ;; *) baseline_file="$repo_root/$baseline_file" ;; esac if [ ! -f "$baseline_file" ]; then printf 'baseline file not found: %s\n' "$baseline_file" >&2 exit 2 fi fi run_cmd="bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh --$mode" [ "$run_cross" -eq 0 ] && run_cmd="$run_cmd --no-cross" [ -n "$baseline_file" ] && run_cmd="$run_cmd --baseline ${baseline_file#$repo_root/}" [ "$fail_on_regression" -eq 1 ] && run_cmd="$run_cmd --fail-on-regression" run_cmd="$run_cmd --throughput-drop-pct $throughput_drop_pct --p99-worse-pct $p99_worse_pct" component_names=() component_cmds=() component_logs=() component_records=() component_results=() component_exits=() add_component() { component_names+=("$1") component_cmds+=("$2") } same_profiles="roundtrip,burst,sustained,parallel,payload" all_langs="dart,go,kotlin,python,typescript" all_transports="tcp,ws" add_component "same-language" "bash $(printf '%q' "$stress_script") --$mode --lang $all_langs --transport $all_transports --profile $same_profiles" if [ "$run_cross" -eq 1 ]; then add_component "cross-language" "bash $(printf '%q' "$stress_script") --$mode --lang $all_langs --transport $all_transports --profile cross" fi add_component "typescript-gateway" "bash $(printf '%q' "$stress_script") --$mode --lang typescript --transport $all_transports --profile gateway" component_result_from_exit() { local exit_code="$1" parsed="$2" if [ -n "$parsed" ]; then printf '%s' "$parsed" return fi case "$exit_code" in 0) printf 'PASS' ;; 3) printf 'BLOCKED' ;; 4) printf 'INCOMPLETE' ;; *) printf 'FAIL' ;; esac } run_component() { local index="$1" local name="${component_names[$index]}" local cmd="${component_cmds[$index]}" local log_file="$log_dir/${name}.log" local exit_code parsed_result record_path printf 'RUN performance %s: %s\n' "$name" "$cmd" >&2 ( cd "$repo_root" && bash -c "$cmd" ) >"$log_file" 2>&1 exit_code=$? tail -n 20 "$log_file" >&2 || true parsed_result="$(sed -n 's/^- 전체 결과값: //p; s/^전체 결과값: //p' "$log_file" | tail -n 1)" record_path="$(sed -n 's/^결과 기록 파일: `\(.*\)`/\1/p' "$log_file" | tail -n 1)" if [ -n "$record_path" ]; then case "$record_path" in /*) ;; *) record_path="$repo_root/$record_path" ;; esac fi component_logs[$index]="$log_file" component_records[$index]="$record_path" component_results[$index]="$(component_result_from_exit "$exit_code" "$parsed_result")" component_exits[$index]="$exit_code" } extract_measurement_rows() { local file="$1" [ -f "$file" ] || return 0 awk ' /^## 측정 결과/ { in_rows=1; next } in_rows && /^## / { in_rows=0 } in_rows && /^\| / { if ($0 ~ /^\|---/) next if ($0 ~ /^\| Profile /) next print } ' "$file" } build_component_summary() { local i printf '| 구성 | 결과 | exit code | 결과 기록 | 로그 |\n' printf '|---|---|---:|---|---|\n' for i in "${!component_names[@]}"; do local record="${component_records[$i]:-}" local log="${component_logs[$i]:-}" [ -n "$record" ] && record="${record#$repo_root/}" [ -n "$log" ] && log="$log" printf '| %s | %s | %s | %s | %s |\n' \ "${component_names[$i]}" "${component_results[$i]:--}" "${component_exits[$i]:--}" \ "${record:-미기록}" "$log" done } trim_awk=' function trim(s) { gsub(/^[[:space:]]+|[[:space:]]+$/, "", s) return s } ' compare_rows() { local baseline_rows="$1" current_rows="$2" out_file="$3" status_file="$4" awk -F'|' \ -v throughput_limit="$throughput_drop_pct" \ -v p99_limit="$p99_worse_pct" \ -v status_file="$status_file" \ "$trim_awk"' function row_key() { return profile "|" axis "|" language "|" transport "|" payload "|" clients "|" gateway } function parse_row() { profile=trim($2) axis=trim($3) language=trim($4) transport=trim($5) payload=trim($6) clients=trim($7) request_count=trim($8) throughput=trim($9) + 0 p99=trim($12) + 0 gateway=trim($20) result=trim($22) } BEGIN { warning_count=0 compared_count=0 missing_count=0 printf "| Key | Baseline throughput | Current throughput | Drop %% | Baseline p99 | Current p99 | Worse %% | 결과 |\n" printf "|---|---:|---:|---:|---:|---:|---:|---|\n" } FNR == NR { parse_row() key=row_key() base_throughput[key]=throughput base_p99[key]=p99 next } { parse_row() key=row_key() label=sprintf("%s / %s / %s / %s / %sB / clients=%s / requests=%s / gateway=%s", profile, axis, language, transport, payload, clients, request_count, gateway) if (!(key in base_throughput)) { missing_count++ printf "| %s | - | %.3f | - | - | %.3f | - | SKIPPED(no baseline) |\n", label, throughput, p99 next } compared_count++ drop=0 worse=0 row_status="PASS" if (base_throughput[key] > 0) { drop=((base_throughput[key] - throughput) / base_throughput[key]) * 100 } if (base_p99[key] > 0) { worse=((p99 - base_p99[key]) / base_p99[key]) * 100 } if (drop >= throughput_limit || worse >= p99_limit) { row_status="WARN" warning_count++ } printf "| %s | %.3f | %.3f | %.1f | %.3f | %.3f | %.1f | %s |\n", \ label, base_throughput[key], throughput, drop, base_p99[key], p99, worse, row_status } END { if (warning_count > 0) { print "WARN" > status_file } else { print "PASS" > status_file } printf "%d\n", compared_count > status_file ".compared" printf "%d\n", missing_count > status_file ".missing" printf "%d\n", warning_count > status_file ".warnings" } ' "$baseline_rows" "$current_rows" >"$out_file" } for i in "${!component_names[@]}"; do run_component "$i" done overall_result="PASS" for result in "${component_results[@]}"; do case "$result" in FAIL) overall_result="FAIL" ;; esac done if [ "$overall_result" = "PASS" ]; then for result in "${component_results[@]}"; do case "$result" in INCOMPLETE) overall_result="INCOMPLETE" ;; esac done fi if [ "$overall_result" = "PASS" ]; then for result in "${component_results[@]}"; do case "$result" in BLOCKED) overall_result="BLOCKED" ;; esac done fi current_rows_file="$log_dir/current.rows" : >"$current_rows_file" for record in "${component_records[@]}"; do extract_measurement_rows "$record" >>"$current_rows_file" done regression_result="SKIPPED" comparison_file="$log_dir/regression-comparison.md" compared_count=0 missing_count=0 warning_count=0 if [ -n "$baseline_file" ]; then baseline_rows_file="$log_dir/baseline.rows" extract_measurement_rows "$baseline_file" >"$baseline_rows_file" status_file="$log_dir/regression.status" if compare_rows "$baseline_rows_file" "$current_rows_file" "$comparison_file" "$status_file"; then regression_result="$(cat "$status_file" 2>/dev/null || echo "PASS")" compared_count="$(cat "$status_file.compared" 2>/dev/null || echo "0")" missing_count="$(cat "$status_file.missing" 2>/dev/null || echo "0")" warning_count="$(cat "$status_file.warnings" 2>/dev/null || echo "0")" if [ "$overall_result" = "PASS" ] && [ "$regression_result" = "WARN" ]; then overall_result="WARN" fi else regression_result="FAIL" overall_result="FAIL" printf '| 오류 | baseline 비교 파서 실패 | - | - | - | - | - | FAIL |\n' >"$comparison_file" fi fi { printf '%s\n' '---' printf 'test_env: local\n' printf 'record_type: performance-result\n' printf 'test_profile: %s\n' "$record_profile" printf 'created_at: %s\n' "$run_started_at" printf 'overall_result: %s\n' "$overall_result" printf 'regression_result: %s\n' "$regression_result" printf '%s\n\n' '---' printf '# %s local 결과 기록\n\n' "$record_profile" printf '## 실행 정보\n\n' printf -- '- 실행 일시: %s\n' "$run_started_at" printf -- '- git ref: %s\n' "$git_ref" printf -- '- 실행 명령: `%s`\n' "$run_cmd" printf -- '- mode: %s\n' "$mode" printf -- '- 로그 디렉터리: `%s`\n' "$log_dir" if [ -n "$baseline_file" ]; then printf -- '- baseline: `%s`\n' "${baseline_file#$repo_root/}" else printf -- '- baseline: 없음\n' fi printf -- '- throughput regression threshold: %s%% drop\n' "$throughput_drop_pct" printf -- '- p99 regression threshold: %s%% worse\n' "$p99_worse_pct" printf -- '- 전체 결과값: %s\n' "$overall_result" printf -- '- regression 결과값: %s\n\n' "$regression_result" printf '## 성능 판정 기준\n\n' printf -- '- stability hard gate: timeout, nonce mismatch, response type mismatch, per-connection FIFO violation, pending leak, queue/gateway backlog leak가 모두 0이어야 한다.\n' printf -- '- performance regression gate: baseline이 주어지면 같은 row key에서 throughput %s%% 이상 하락 또는 p99 latency %s%% 이상 악화를 WARN으로 기록한다.\n' "$throughput_drop_pct" "$p99_worse_pct" printf -- '- same-language stress row key에는 harness topology가 axis에 포함될 수 있으며, baseline 비교는 같은 topology 조건끼리 해석한다.\n' printf -- '- 절대 throughput/latency는 local 환경 기준값이며, baseline 비교는 같은 host/runtime/profile에서 실행한 결과끼리 수행한다.\n' printf -- '- `--fail-on-regression`을 지정하면 regression WARN도 non-zero exit로 처리한다.\n\n' printf '## 구성요소 요약\n\n' build_component_summary printf '\n## 측정 결과\n\n' printf '| Profile | Axis | Language | Transport | Payload(bytes) | Clients | Requests | Throughput(rps) | p50(ms) | p95(ms) | p99(ms) | Timeout | NonceMis | TypeMis | FIFOViol | PendLeak | QueueBacklog | GatewayBacklog | Gateway | Mem(MB) | 결과 |\n' printf '|---|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---|---:|---|\n' cat "$current_rows_file" printf '\n## Regression 비교\n\n' if [ -n "$baseline_file" ]; then printf -- '- baseline: `%s`\n' "${baseline_file#$repo_root/}" printf -- '- compared rows: %s\n' "$compared_count" printf -- '- missing baseline rows: %s\n' "$missing_count" printf -- '- warning rows: %s\n\n' "$warning_count" cat "$comparison_file" else printf -- '- SKIPPED: `--baseline `가 지정되지 않았다.\n' fi printf '\n## git status 요약\n\n' printf '```text\n' git -C "$repo_root" status --short 2>/dev/null || true printf '```\n\n' printf '## 구성요소 로그 tail\n\n' for i in "${!component_names[@]}"; do printf '### %s\n\n' "${component_names[$i]}" printf '```text\n' tail -n 30 "${component_logs[$i]}" 2>/dev/null || true printf '```\n\n' done } >"$record_file" printf '\n=== performance 측정 결과 ===\n' >&2 build_component_summary >&2 printf '\n전체 결과값: %s\n' "$overall_result" printf 'Regression 결과값: %s\n' "$regression_result" printf '결과 기록 파일: `%s`\n' "${record_file#$repo_root/}" case "$overall_result" in PASS) exit 0 ;; WARN) if [ "$fail_on_regression" -eq 1 ]; then exit 5 fi exit 0 ;; BLOCKED) exit 3 ;; INCOMPLETE) exit 4 ;; *) exit 1 ;; esac