docs: 联网搜索服务架构方案全套(plan-final/design-arch/选型决策/整合导览/MCP文档/部署预设/联调手册) bench: 5 方案 + 代理 + 站点矩阵本机实测工程(无密钥) 部署目标:primary mgr1 先行测试(待批准后执行)
240 lines
8.6 KiB
Bash
Executable file
240 lines
8.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# phases: up | wait | inspect | idle | t1 | t5 | down | summarize
|
|
set -euo pipefail
|
|
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy NO_PROXY no_proxy
|
|
|
|
ROOT="$(cd "$(dirname "$0")" && pwd)"
|
|
cd "$ROOT"
|
|
NAME="bench-s3b-searxng-cn"
|
|
COMPOSE=(docker compose -p bench-s3b-searxng-cn -f "$ROOT/compose.yml")
|
|
RAW="$ROOT/raw"
|
|
SAMPLES="$ROOT/samples"
|
|
mkdir -p "$RAW/bodies" "$SAMPLES"
|
|
LOG="$RAW/commands.log"
|
|
phase="${1:-}"
|
|
|
|
log() {
|
|
local ts
|
|
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
echo "[$ts] $*" | tee -a "$LOG"
|
|
}
|
|
|
|
wait_ready() {
|
|
local i code
|
|
for i in $(seq 1 90); do
|
|
set +e
|
|
code=$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 2 --max-time 3 -A 'onesvm-bench/s3b' "http://127.0.0.1:18880/" || true)
|
|
set -e
|
|
if [ "$code" = "200" ] || [ "$code" = "302" ] || [ "$code" = "403" ]; then
|
|
# 403 can happen on / without format; try /healthz
|
|
set +e
|
|
hz=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 -A 'onesvm-bench/s3b' "http://127.0.0.1:18880/healthz" || true)
|
|
set -e
|
|
log "ready http=/=>$code healthz=$hz after ${i}s"
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
log "ERROR: searxng not ready"
|
|
docker logs "$NAME" 2>&1 | tail -80 | tee -a "$LOG" || true
|
|
return 1
|
|
}
|
|
|
|
inspect_image() {
|
|
python3 - <<PY
|
|
import json, subprocess, pathlib
|
|
raw = pathlib.Path("$RAW")
|
|
meta = {}
|
|
mp = raw / "meta.json"
|
|
if mp.exists():
|
|
meta = json.loads(mp.read_text())
|
|
out = subprocess.check_output([
|
|
"docker","inspect","--format",
|
|
"{{.Name}}\t{{.Image}}\t{{json .Config.Image}}",
|
|
"$NAME"
|
|
], text=True).strip()
|
|
arch = subprocess.check_output(["docker","image","inspect","--format","{{.Architecture}}","docker.io/searxng/searxng:2026.8.29-d226b78bc"], text=True).strip()
|
|
digests = subprocess.check_output(["docker","image","inspect","--format","{{json .RepoDigests}}","docker.io/searxng/searxng:2026.8.29-d226b78bc"], text=True).strip()
|
|
id_ = subprocess.check_output(["docker","image","inspect","--format","{{.Id}}","docker.io/searxng/searxng:2026.8.29-d226b78bc"], text=True).strip()
|
|
meta["image"] = {
|
|
"ref": "docker.io/searxng/searxng:2026.8.29-d226b78bc",
|
|
"digest": (json.loads(digests) or [id_])[0],
|
|
"id": id_,
|
|
"architecture": arch,
|
|
"inspect_line": out,
|
|
}
|
|
meta.setdefault("commands", []).append({"ts": __import__("datetime").datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), "cmd": "docker inspect $NAME + image"})
|
|
mp.write_text(json.dumps(meta, ensure_ascii=False, indent=2))
|
|
print(json.dumps(meta["image"], indent=2))
|
|
PY
|
|
}
|
|
|
|
case "$phase" in
|
|
up)
|
|
log "compose up $NAME"
|
|
"${COMPOSE[@]}" up -d --no-build
|
|
wait_ready
|
|
inspect_image
|
|
;;
|
|
wait)
|
|
wait_ready
|
|
;;
|
|
inspect)
|
|
inspect_image
|
|
;;
|
|
idle)
|
|
log "idle sample 5min interval=5 container=$NAME"
|
|
STATS_DIR="$RAW" bash "$ROOT/lib/sample_stats.sh" searxng-cn-idle 5 300 "$NAME"
|
|
log "idle done lines=$(wc -l < "$RAW/searxng-cn-idle.tsv")"
|
|
;;
|
|
t1)
|
|
log "T1 x5 + session RSS around request 1"
|
|
: > "$RAW/t1.jsonl"
|
|
STATS_DIR="$RAW" bash "$ROOT/lib/sample_stats.sh" searxng-cn-session 1 0 "$NAME" &
|
|
spid=$!
|
|
sleep 10
|
|
pick_url=""
|
|
pick_title=""
|
|
pick_cjk=""
|
|
for i in 1 2 3 4 5; do
|
|
log "T1 request $i"
|
|
BODY_OUT="$RAW/bodies/t1-${i}.json" RAW_DIR="$RAW" bash "$ROOT/request.sh" t1 "$i" > "$RAW/t1-${i}.metrics.json"
|
|
verdict=$(python3 "$ROOT/eval_t1.py" "$RAW/bodies/t1-${i}.json")
|
|
python3 - "$RAW/t1-${i}.metrics.json" "$verdict" "$RAW/t1.jsonl" <<'PY'
|
|
import json, sys
|
|
m = json.loads(open(sys.argv[1], encoding="utf-8").read())
|
|
v = json.loads(sys.argv[2])
|
|
m.update(v)
|
|
open(sys.argv[3], "a", encoding="utf-8").write(json.dumps(m, ensure_ascii=False) + "\n")
|
|
print(json.dumps({"idx": m.get("idx"), "http": m.get("http_code"), "assert_ok": m.get("assert_ok"), "n": m.get("n_results"), "engines": m.get("engines"), "t_total": m.get("t_total")}, ensure_ascii=False))
|
|
PY
|
|
if [ -z "$pick_url" ]; then
|
|
pick_url=$(python3 -c "import json; d=json.load(open('$RAW/bodies/t1-${i}.json',encoding='utf-8'));
|
|
import sys
|
|
sys.path.insert(0,'$ROOT')
|
|
from eval_t1 import eval_body
|
|
v=eval_body(open('$RAW/bodies/t1-${i}.json',encoding='utf-8').read());
|
|
print(v.get('t2_url') or '')")
|
|
pick_title=$(python3 -c "from eval_t1 import eval_body; v=eval_body(open('$RAW/bodies/t1-${i}.json',encoding='utf-8').read()); print(v.get('t2_title') or '')")
|
|
pick_cjk=$(python3 -c "from eval_t1 import eval_body; v=eval_body(open('$RAW/bodies/t1-${i}.json',encoding='utf-8').read()); print(v.get('t2_title_cjk4') or '')")
|
|
fi
|
|
if [ "$i" -eq 1 ]; then
|
|
sleep 10
|
|
kill "$spid" 2>/dev/null || true
|
|
wait "$spid" 2>/dev/null || true
|
|
fi
|
|
done
|
|
kill "$spid" 2>/dev/null || true
|
|
wait "$spid" 2>/dev/null || true
|
|
if [ -z "$pick_url" ]; then
|
|
pick_url="https://www.gov.cn/zhengce/2021-03/13/content_5592681.htm"
|
|
pick_title="国民经济和社会发展第十四个五年规划和2035年远景目标纲要"
|
|
pick_cjk="国民经社"
|
|
source="fallback"
|
|
else
|
|
source="t1"
|
|
fi
|
|
python3 - "$RAW/meta.json" "$pick_url" "$pick_title" "$pick_cjk" "$source" <<'PY'
|
|
import json, sys, pathlib
|
|
p = pathlib.Path(sys.argv[1])
|
|
meta = json.loads(p.read_text()) if p.exists() else {}
|
|
meta["t2_pick"] = {"url": sys.argv[2], "title": sys.argv[3], "title_cjk4": sys.argv[4], "source": sys.argv[5]}
|
|
p.write_text(json.dumps(meta, ensure_ascii=False, indent=2))
|
|
print(json.dumps(meta["t2_pick"], ensure_ascii=False, indent=2))
|
|
PY
|
|
python3 - <<PY
|
|
import json, pathlib
|
|
root = pathlib.Path("$SAMPLES")
|
|
raw = pathlib.Path("$RAW/bodies")
|
|
n = 0
|
|
for i in (1,2,3):
|
|
src = raw / f"t1-{i}.json"
|
|
if not src.exists():
|
|
continue
|
|
data = json.loads(src.read_text(encoding="utf-8"))
|
|
slim = {
|
|
"query": data.get("query"),
|
|
"number_of_results": data.get("number_of_results"),
|
|
"unresponsive_engines": data.get("unresponsive_engines"),
|
|
"results": []
|
|
}
|
|
for item in (data.get("results") or [])[:8]:
|
|
slim["results"].append({
|
|
"title": (item.get("title") or "")[:160],
|
|
"url": (item.get("url") or "")[:220],
|
|
"content": (item.get("content") or "")[:240],
|
|
"engine": item.get("engine"),
|
|
})
|
|
(root / f"t1-{i}.excerpt.json").write_text(json.dumps(slim, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
n += 1
|
|
print(f"samples={n}")
|
|
PY
|
|
log "T1 done pick_url=$pick_url source=$source"
|
|
;;
|
|
t5)
|
|
log "T5 burst 60 concurrent"
|
|
: > "$RAW/t5.jsonl"
|
|
STATS_DIR="$RAW" bash "$ROOT/lib/sample_stats.sh" searxng-cn-t5 1 0 "$NAME" &
|
|
spid=$!
|
|
t5start=$(date +%s)
|
|
seq 1 60 | xargs -P 60 -I{} bash -c '
|
|
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy NO_PROXY no_proxy
|
|
ROOT="'"$ROOT"'"
|
|
RAW="'"$RAW"'"
|
|
i="$1"
|
|
BODY_OUT="$RAW/bodies/t5-${i}.json" RAW_DIR="$RAW" bash "$ROOT/request.sh" t5-once "$i" > "$RAW/t5-${i}.metrics.json"
|
|
' _ {}
|
|
t5end=$(date +%s)
|
|
log "T5 curls finished wall=$((t5end-t5start))s; cooling 15s"
|
|
sleep 15
|
|
kill "$spid" 2>/dev/null || true
|
|
wait "$spid" 2>/dev/null || true
|
|
python3 - <<PY
|
|
import json, pathlib, subprocess
|
|
root = pathlib.Path("$ROOT")
|
|
raw = pathlib.Path("$RAW")
|
|
out = raw / "t5.jsonl"
|
|
lines = []
|
|
for i in range(1, 61):
|
|
mp = raw / f"t5-{i}.metrics.json"
|
|
bp = raw / "bodies" / f"t5-{i}.json"
|
|
if not mp.exists():
|
|
continue
|
|
m = json.loads(mp.read_text(encoding="utf-8"))
|
|
if bp.exists():
|
|
v = json.loads(subprocess.check_output(["python3", str(root/"eval_t1.py"), str(bp)], text=True))
|
|
m.update(v)
|
|
else:
|
|
m["assert_ok"] = False
|
|
m["fail_class"] = "timeout"
|
|
m["note"] = "no_body"
|
|
lines.append(m)
|
|
out.write_text("".join(json.dumps(x, ensure_ascii=False)+"\n" for x in lines), encoding="utf-8")
|
|
ok = sum(1 for x in lines if x.get("assert_ok"))
|
|
print(f"t5 n={len(lines)} ok={ok}")
|
|
PY
|
|
oom=$(docker inspect --format '{{.State.OOMKilled}} {{.State.Status}}' "$NAME" 2>/dev/null || echo "unknown unknown")
|
|
log "T5 oom/status=$oom"
|
|
python3 - "$RAW/meta.json" "$oom" <<'PY'
|
|
import json, sys, pathlib
|
|
p = pathlib.Path(sys.argv[1])
|
|
meta = json.loads(p.read_text()) if p.exists() else {}
|
|
parts = sys.argv[2].split()
|
|
meta["oom"] = parts[0].lower() == "true"
|
|
meta["container_status_after_t5"] = sys.argv[2]
|
|
p.write_text(json.dumps(meta, ensure_ascii=False, indent=2))
|
|
PY
|
|
;;
|
|
down)
|
|
log "compose down"
|
|
"${COMPOSE[@]}" down --remove-orphans || true
|
|
docker rm -f "$NAME" 2>/dev/null || true
|
|
;;
|
|
summarize)
|
|
python3 "$ROOT/summarize.py" "$ROOT"
|
|
;;
|
|
*)
|
|
echo "usage: $0 up|wait|inspect|idle|t1|t5|down|summarize" >&2
|
|
exit 2
|
|
;;
|
|
esac
|