Files
Agent-System-Dev/tests/load/collaboration/run_load.sh
T
weichen f705f99b8f Add U7 two-process acceptance, k6 load harness and release checklist
Why: collaboration storage U7 (proposal sections 3/5/7) requires two-process
HTTP-level acceptance over shared PG, a reproducible load-test entry honoring
the load contract, and an auditable pre-release checklist; capacity windows and
dual-host runs need user-scheduled resources, so the deliverable here is the
verifiable entry, not capacity claims.
Evidence: make test green (composite); make test-multi-instance green locally
against an isolated PG (five scenarios incl. crash-takeover and restart
invariants); run_load.sh --profile smoke end-to-end green (SSE probe p95~18ms,
zero seq duplicates, stack auto-recycled); backend pinned to feature branch
with the three cross-instance fixes.
Impact: new tests/integration/multi-instance (CI test-pg job step), new
tests/load/collaboration (k6 + SSE probe + run_load.sh), release checklist at
docs/release-multi-instance.md; no runtime behavior change in Dev repo itself.
Refs: backend PR #164; task card feature/u7-load-fault-release.
2026-10-12 02:31:35 +00:00

141 lines
6.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 协作存储 U7 压测入口(方案 §3 负载合同 / §5 U7)。
#
# 起一个隔离栈(backend mock 单实例 + 隔离 PG)→ 预建引导账号 → k6 阶梯
# → SSE 可见延迟探针 → 数据不变量对账 → 结果落 results/<ts>/ → 回收。
# 绝不打生产服务:默认自建 PG 实例(svc.sh);--dsn 复用已有隔离实例。
#
# 用法:
# tests/load/collaboration/run_load.sh [--profile smoke|ladder|sustained60m|sustained2h]
# [--users N] [--dsn <PG DSN>] [--port <app port>]
# [--keep] [--skip-k6] [--skip-probe] [--help]
# 依赖:bash4、go(构建 backend/探针)、psql(对账)、curl;
# k6 可选(缺失时跳过 k6 段并给出安装指引,探针与对账照跑)。
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
HERE="$ROOT/tests/load/collaboration"
PROFILE=smoke USERS=30 APP_PORT=18980 PG_PORT=18981 KEEP=0 SKIP_K6=0 SKIP_PROBE=0
DSN=""
while [[ $# -gt 0 ]]; do
case "$1" in
--profile) PROFILE="$2"; shift 2 ;;
--users) USERS="$2"; shift 2 ;;
--dsn) DSN="$2"; shift 2 ;;
--port) APP_PORT="$2"; shift 2 ;;
--keep) KEEP=1; shift ;;
--skip-k6) SKIP_K6=1; shift ;;
--skip-probe) SKIP_PROBE=1; shift ;;
-h|--help) sed -n '2,16p' "$0"; exit 0 ;;
*) echo "unknown arg: $1" >&2; exit 2 ;;
esac
done
TS="$(date +%Y%m%d-%H%M%S)"
RESULTS="$HERE/results/$TS"
mkdir -p "$RESULTS"
OWNED_PG=0
APP_PID=""
cleanup() {
[[ -n "$APP_PID" ]] && kill "$APP_PID" 2>/dev/null || true
if [[ $OWNED_PG -eq 1 && $KEEP -eq 0 ]]; then
"$ROOT/scripts/svc.sh" u7load stop >/dev/null 2>&1 || true
"$ROOT/scripts/svc.sh" rm u7load --purge >/dev/null 2>&1 || true
fi
[[ $KEEP -eq 1 ]] && echo "[load] --keep:栈保留(PG=u7load:$PG_PORT,app=127.0.0.1:$APP_PORT,结果 $RESULTS)"
}
trap cleanup EXIT
# ---- 1) PG:自建隔离实例(或复用 --dsn) ----
if [[ -z "$DSN" ]]; then
echo "[load] 创建隔离 PG 实例 u7load(端口 $PG_PORT)"
"$ROOT/scripts/svc.sh" db create u7load --port "$PG_PORT" >/dev/null
OWNED_PG=1
"$ROOT/scripts/svc.sh" u7load start >/dev/null
DSN="$("$ROOT/scripts/svc.sh" db dsn u7load --reveal)"
fi
echo "[load] PG DSN 已就绪(密码不回显)"
# ---- 2) 构建 backend 与探针 ----
echo "[load] 构建 backend(worktree 当前源码)与 SSE 探针"
BIN="$HERE/.build/agent-system"
mkdir -p "$HERE/.build"
( cd "$ROOT/modules/backend" && CGO_ENABLED=0 go build -o "$BIN" ./cmd/agent-system )
( cd "$HERE/sse_probe" && go build -o "$HERE/.build/sse_probe" . )
# ---- 3) 压测库 + 引导账号(CLI 直连,终态口令避免首登改密分支) ----
# DSN 规范化:库名换 u7load_app、query 补 sslmode=disable(密码不回显)。
ADMIN_DSN="$(python3 - "$DSN" <<'PY'
import sys
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
u = urlsplit(sys.argv[1])
q = dict(parse_qsl(u.query))
q.setdefault("sslmode", "disable")
print(urlunsplit((u.scheme, u.netloc, "/" + (u.path.lstrip("/") or "postgres"), urlencode(q), "")))
PY
)"
psql "$ADMIN_DSN" -v ON_ERROR_STOP=1 -c "DROP DATABASE IF EXISTS u7load_app WITH (FORCE)" -c "CREATE DATABASE u7load_app" >/dev/null
APP_DSN="$(python3 - "$ADMIN_DSN" <<'PY'
import sys
from urllib.parse import urlsplit, urlunsplit
u = urlsplit(sys.argv[1])
print(urlunsplit((u.scheme, u.netloc, "/u7load_app", u.query, "")))
PY
)"
"$BIN" user create --dsn "$APP_DSN" --admin --owner load_admin --password load-admin-pass-Aa1 >/dev/null
"$BIN" user create --dsn "$APP_DSN" --owner probe_user --password probe-user-pass-Aa9 >/dev/null
# ---- 4) 起被测实例(mock、单实例首期;执行原语关闭——压测不打执行面) ----
echo "[load] 启动被测实例 127.0.0.1:$APP_PORT(mock / tier=team / auth=on)"
APP_LOG="$RESULTS/app.log"
"$BIN" serve --dsn "$APP_DSN" --addr "127.0.0.1:$APP_PORT" --tier team --auth on --mock \
--instance u7load --disable-exec-tools >"$APP_LOG" 2>&1 &
APP_PID=$!
# auth=on 时 /api/health 同样要求鉴权:200/401 都视为已就绪(服务在正确应答)。
app_ready() { code=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:$APP_PORT/api/health"); [[ "$code" == 200 || "$code" == 401 ]]; }
for _ in $(seq 1 60); do app_ready && break; sleep 0.5; done
app_ready || { echo "[load] app 未就绪,日志 $APP_LOG" >&2; exit 1; }
BASE="http://127.0.0.1:$APP_PORT"
# ---- 5) k6 阶梯 ----
if [[ $SKIP_K6 -eq 1 ]]; then
echo "[load] --skip-k6:跳过 k6 段"
elif ! command -v k6 >/dev/null 2>&1; then
cat >"$RESULTS/k6-skipped.txt" <<'EOF'
k6 未安装:本段跳过(探针与对账照跑)。
安装(本机 linuxbrew):brew install k6
安装后重跑:tests/load/collaboration/run_load.sh --profile <同参数>
EOF
echo "[load] k6 未安装,跳过(见 $RESULTS/k6-skipped.txt)"
else
echo "[load] k6 profile=$PROFILE users=$USERS"
( cd "$HERE" && BASE_URL="$BASE" ADMIN_OWNER=load_admin ADMIN_PASS=load-admin-pass-Aa1 \
USERS="$USERS" PROFILE="$PROFILE" \
k6 run --summary-export "$RESULTS/k6-summary.json" k6/collab_api.js ) | tee "$RESULTS/k6-output.txt"
fi
# ---- 6) SSE 可见延迟探针 ----
if [[ $SKIP_PROBE -eq 0 ]]; then
echo "[load] SSE 可见延迟探针(60s,10 会话)"
"$HERE/.build/sse_probe" -base "$BASE" -dur 60s -convs 10 -out "$RESULTS/sse-probe.json"
fi
# ---- 7) 数据不变量对账(方案 §3:零违反) ----
echo "[load] 数据不变量对账"
psql "$APP_DSN" -v ON_ERROR_STOP=1 \
-c "SELECT 'users' k, COUNT(*) v FROM users UNION ALL SELECT 'projects', COUNT(*) FROM projects UNION ALL SELECT 'conversations', COUNT(*) FROM conversations UNION ALL SELECT 'messages', COUNT(*) FROM messages UNION ALL SELECT 'events', COUNT(*) FROM events UNION ALL SELECT 'runs', COUNT(*) FROM runs" \
-c "SELECT 'dup_seq_per_conv' k, COUNT(*) v FROM (SELECT conversation_id, seq FROM events GROUP BY 1,2 HAVING COUNT(*)>1) d" \
-c "SELECT 'nonterminal_orphan_runs' k, COUNT(*) v FROM runs WHERE status IN ('pending','running','waiting_hitl')" \
| tee "$RESULTS/invariants.txt"
DUP=$(psql "$APP_DSN" -tAc "SELECT COUNT(*) FROM (SELECT conversation_id, seq FROM events GROUP BY 1,2 HAVING COUNT(*)>1) d")
if [[ "$DUP" != "0" ]]; then
echo "[load] 对账失败:events 每会话 seq 出现重复($DUP 组)" >&2
exit 1
fi
echo "[load] 对账通过:无重复 seq;孤儿 run 报告见 invariants.txt(探针正常收口应为 0)"
echo "[load] 完成。结果目录:$RESULTS"