proto-socket/agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_performance.sh
toki d2cee0ab9b feat: large payload follow-up optimization
- typescript: communicator nonce handling and WS client improvements
- typescript: performance benchmark and test updates
- agent-ops: update test matrix scripts for performance/stress
- agent-roadmap: update milestone documentation
- agent-task: add kotlin tcp parallel optimization task
2026-06-14 16:46:44 +09:00

424 lines
14 KiB
Bash
Executable file

#!/usr/bin/env bash
set -u
usage() {
cat <<'USAGE'
Usage: run_performance.sh [--quick|--full] [--no-cross]
[--baseline <record.md>] [--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/<baseline>.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 <record.md>`가 지정되지 않았다.\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