yams/tests/benchmarks/run_multi_client_optimization_loop.sh

485 lines
16 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
# Runs phase 2 (throughput-first) then phase 1 (stability-first)
# and stores machine-readable artifacts for tuning/regression loops.
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
BENCH_BIN_DEFAULT="$ROOT_DIR/builddir-nosan/tests/benchmarks/multi_client_ingestion_bench"
BENCH_BIN="${YAMS_MULTI_CLIENT_BENCH_BIN:-$BENCH_BIN_DEFAULT}"
DATA_DIR_SOURCE="${YAMS_BENCH_DATA_DIR:-}"
DATA_DIR="$DATA_DIR_SOURCE"
DATA_DIR_MODE="${YAMS_BENCH_DATA_DIR_MODE:-snapshot}"
RUN_TAG="${YAMS_OPTIMIZATION_TAG:-}"
BASELINE_JSON="${YAMS_MULTI_CLIENT_BASELINE:-$ROOT_DIR/tests/benchmarks/baseline/multi_client.baseline.json}"
FAIL_ON_REGRESSION="${YAMS_FAIL_ON_REGRESSION:-0}"
TMP_WORK_DIR=""
while [[ $# -gt 0 ]]; do
case "$1" in
--bench-bin)
BENCH_BIN="$2"
shift 2
;;
--data-dir)
DATA_DIR_SOURCE="$2"
DATA_DIR="$2"
shift 2
;;
--data-dir-mode)
DATA_DIR_MODE="$2"
shift 2
;;
--tag)
RUN_TAG="$2"
shift 2
;;
--baseline)
BASELINE_JSON="$2"
shift 2
;;
--fail-on-regression)
FAIL_ON_REGRESSION="1"
shift
;;
*)
echo "Unknown arg: $1" >&2
exit 2
;;
esac
done
cleanup() {
if [[ -n "$TMP_WORK_DIR" && -d "$TMP_WORK_DIR" ]]; then
rm -rf "$TMP_WORK_DIR"
fi
}
trap cleanup EXIT
if [[ ! -x "$BENCH_BIN" ]]; then
echo "Benchmark binary not executable: $BENCH_BIN" >&2
echo "Build target first (example): meson compile -C builddir-nosan bench_multi_client" >&2
exit 1
fi
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
if [[ -n "$RUN_TAG" ]]; then
RUN_DIR="$ROOT_DIR/bench_results/optimization_loop/${STAMP}_${RUN_TAG}"
else
RUN_DIR="$ROOT_DIR/bench_results/optimization_loop/${STAMP}"
fi
mkdir -p "$RUN_DIR"
RUN_ID="${STAMP}${RUN_TAG:+_${RUN_TAG}}"
GIT_SHA="$(git -C "$ROOT_DIR" rev-parse --short HEAD)"
CASE_MANIFEST="$RUN_DIR/cases.manifest.jsonl"
RUN_MANIFEST="$RUN_DIR/run_manifest.json"
: > "$CASE_MANIFEST"
prepare_data_dir() {
if [[ -z "$DATA_DIR_SOURCE" ]]; then
DATA_DIR=""
return
fi
if [[ ! -d "$DATA_DIR_SOURCE" ]]; then
echo "Data directory does not exist: $DATA_DIR_SOURCE" >&2
exit 1
fi
case "$DATA_DIR_MODE" in
direct)
DATA_DIR="$DATA_DIR_SOURCE"
echo "Using data dir directly: $DATA_DIR"
;;
snapshot)
TMP_WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/yams_opt_loop_data.XXXXXX")"
DATA_DIR="$TMP_WORK_DIR/data"
mkdir -p "$DATA_DIR"
echo "Creating isolated data dir snapshot from: $DATA_DIR_SOURCE"
if command -v rsync >/dev/null 2>&1; then
rsync -a \
--exclude='.yams-lock' \
--exclude='*.sock' \
--exclude='*.pid' \
"$DATA_DIR_SOURCE/" "$DATA_DIR/"
else
cp -R "$DATA_DIR_SOURCE/." "$DATA_DIR/"
rm -f "$DATA_DIR/.yams-lock" "$DATA_DIR"/*.sock "$DATA_DIR"/*.pid
fi
echo "Using isolated snapshot data dir: $DATA_DIR"
;;
*)
echo "Invalid --data-dir-mode: $DATA_DIR_MODE (expected: direct|snapshot)" >&2
exit 2
;;
esac
}
prepare_data_dir
PHASE2_OUT="$RUN_DIR/phase2_throughput_first.jsonl"
PHASE1_OUT="$RUN_DIR/phase1_stability_first.jsonl"
: > "$PHASE2_OUT"
: > "$PHASE1_OUT"
write_metadata() {
local metadata_file="$1"
local phase_label="$2"
local tuning_profile="$3"
local usage_profile="$4"
local use_mcp="$5"
{
echo "{"
echo " \"phase\": \"$phase_label\","
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\","
echo " \"git_sha\": \"$GIT_SHA\","
echo " \"run_id\": \"$RUN_ID\","
echo " \"bench_bin\": \"$BENCH_BIN\","
echo " \"data_dir_source\": \"$DATA_DIR_SOURCE\","
echo " \"data_dir_effective\": \"$DATA_DIR\","
echo " \"data_dir_mode\": \"$DATA_DIR_MODE\","
echo " \"env\": {"
echo " \"YAMS_TUNING_PROFILE\": \"$tuning_profile\","
echo " \"YAMS_BENCH_USAGE_PROFILE\": \"$usage_profile\","
echo " \"YAMS_BENCH_USE_MCP\": \"$use_mcp\","
echo " \"YAMS_IO_THREADS\": \"${YAMS_IO_THREADS:-}\","
echo " \"YAMS_MAX_ACTIVE_CONN\": \"${YAMS_MAX_ACTIVE_CONN:-}\","
echo " \"YAMS_SERVER_MAX_INFLIGHT\": \"${YAMS_SERVER_MAX_INFLIGHT:-}\","
echo " \"YAMS_IPC_TIMEOUT_MS\": \"${YAMS_IPC_TIMEOUT_MS:-}\","
echo " \"YAMS_MAX_IDLE_TIMEOUTS\": \"${YAMS_MAX_IDLE_TIMEOUTS:-}\","
echo " \"YAMS_CONNECTION_LIFETIME_S\": \"${YAMS_CONNECTION_LIFETIME_S:-}\","
echo " \"YAMS_BENCH_OPS_PER_CLIENT\": \"${YAMS_BENCH_OPS_PER_CLIENT:-}\","
echo " \"YAMS_BENCH_OP_TIMEOUT_S\": \"${YAMS_BENCH_OP_TIMEOUT_S:-}\","
echo " \"YAMS_BENCH_SEARCH_CLIENTS\": \"${YAMS_BENCH_SEARCH_CLIENTS:-}\","
echo " \"YAMS_BENCH_LIST_CLIENTS\": \"${YAMS_BENCH_LIST_CLIENTS:-}\","
echo " \"YAMS_BENCH_GREP_CLIENTS\": \"${YAMS_BENCH_GREP_CLIENTS:-}\","
echo " \"YAMS_BENCH_STATUS_GET_CLIENTS\": \"${YAMS_BENCH_STATUS_GET_CLIENTS:-}\","
echo " \"YAMS_BENCH_LIST_INFLIGHT_CAP\": \"${YAMS_BENCH_LIST_INFLIGHT_CAP:-}\","
echo " \"YAMS_BENCH_MCP_POOL_SIZE\": \"${YAMS_BENCH_MCP_POOL_SIZE:-}\","
echo " \"YAMS_BENCH_MCP_SESSION_CHURN_EVERY_N\": \"${YAMS_BENCH_MCP_SESSION_CHURN_EVERY_N:-}\","
echo " \"YAMS_BENCH_CASE_TIMEOUT_S\": \"${YAMS_BENCH_CASE_TIMEOUT_S:-}\","
echo " \"YAMS_BENCH_CONTINUE_ON_CASE_FAILURE\": \"${YAMS_BENCH_CONTINUE_ON_CASE_FAILURE:-}\""
echo " }"
echo "}"
} > "$metadata_file"
}
run_case() {
local output_file="$1"
local test_filter="$2"
local tuning_profile="$3"
local usage_profile="$4"
local use_mcp="$5"
local phase_label="$6"
local transport_label="daemon_ipc"
if [[ "$use_mcp" == "1" ]]; then
transport_label="mcp_query"
fi
local case_timeout_s="${YAMS_BENCH_CASE_TIMEOUT_S:-0}"
if [[ "$case_timeout_s" =~ ^[0-9]+$ ]] && [[ "$case_timeout_s" -gt 0 ]]; then
YAMS_BENCH_OUTPUT="$output_file" \
YAMS_TUNING_PROFILE="$tuning_profile" \
YAMS_BENCH_USAGE_PROFILE="$usage_profile" \
YAMS_BENCH_USE_MCP="$use_mcp" \
YAMS_BENCH_RUN_ID="$RUN_ID" \
YAMS_BENCH_GIT_SHA="$GIT_SHA" \
YAMS_BENCH_PHASE="$phase_label" \
YAMS_BENCH_CASE="$test_filter" \
YAMS_BENCH_TRANSPORT="$transport_label" \
python3 - "$BENCH_BIN" "$test_filter" "$case_timeout_s" <<'PY'
import os
import subprocess
import sys
bench_bin = sys.argv[1]
test_filter = sys.argv[2]
timeout_s = int(sys.argv[3])
cmd = [bench_bin, "--allow-running-no-tests", test_filter]
try:
completed = subprocess.run(cmd, env=os.environ.copy(), timeout=timeout_s)
sys.exit(completed.returncode)
except subprocess.TimeoutExpired:
print(
f"Benchmark case timed out after {timeout_s}s: {test_filter}",
file=sys.stderr,
)
sys.exit(124)
PY
return $?
fi
YAMS_BENCH_OUTPUT="$output_file" \
YAMS_TUNING_PROFILE="$tuning_profile" \
YAMS_BENCH_USAGE_PROFILE="$usage_profile" \
YAMS_BENCH_USE_MCP="$use_mcp" \
YAMS_BENCH_RUN_ID="$RUN_ID" \
YAMS_BENCH_GIT_SHA="$GIT_SHA" \
YAMS_BENCH_PHASE="$phase_label" \
YAMS_BENCH_CASE="$test_filter" \
YAMS_BENCH_TRANSPORT="$transport_label" \
"$BENCH_BIN" --allow-running-no-tests "$test_filter"
}
append_case_manifest() {
local phase_name="$1"
local output_file="$2"
local test_filter="$3"
local use_mcp="$4"
local status="$5"
local exit_code="$6"
local usage_profile="$7"
python3 - "$CASE_MANIFEST" "$phase_name" "$output_file" "$test_filter" "$use_mcp" \
"$status" "$exit_code" "$usage_profile" "$RUN_ID" "$GIT_SHA" <<'PY'
import json
import sys
from datetime import datetime, timezone
manifest_path = sys.argv[1]
phase_name = sys.argv[2]
output_file = sys.argv[3]
test_filter = sys.argv[4]
use_mcp = sys.argv[5] == "1"
status = sys.argv[6]
exit_code = int(sys.argv[7])
usage_profile = sys.argv[8]
run_id = sys.argv[9]
git_sha = sys.argv[10]
entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"run_id": run_id,
"git_sha": git_sha,
"phase": phase_name,
"test_filter": test_filter,
"transport": "mcp_query" if use_mcp else "daemon_ipc",
"usage_profile": usage_profile,
"output_jsonl": output_file,
"status": status,
"exit_code": exit_code,
}
with open(manifest_path, "a", encoding="utf-8") as f:
f.write(json.dumps(entry, sort_keys=True))
f.write("\n")
PY
}
apply_phase2_profile() {
# Throughput-first (tuned via sweep 20260302: IO=16 and INF=128 top cluster)
export YAMS_TUNING_PROFILE="aggressive"
export YAMS_IO_THREADS="16"
export YAMS_MAX_ACTIVE_CONN="2048"
export YAMS_SERVER_MAX_INFLIGHT="128"
export YAMS_IPC_TIMEOUT_MS="15000"
export YAMS_MAX_IDLE_TIMEOUTS="12"
export YAMS_CONNECTION_LIFETIME_S="0"
}
apply_phase1_profile() {
# Stability-first (tuned via sweep 20260302: IO=10 safe uplift, keep conservative INF)
export YAMS_TUNING_PROFILE="balanced"
export YAMS_IO_THREADS="10"
export YAMS_MAX_ACTIVE_CONN="2048"
export YAMS_SERVER_MAX_INFLIGHT="64"
export YAMS_IPC_TIMEOUT_MS="30000"
export YAMS_MAX_IDLE_TIMEOUTS="20"
export YAMS_CONNECTION_LIFETIME_S="1800"
}
run_phase() {
local phase_name="$1"
local output_file="$2"
local usage_profile="$3"
local continue_on_case_failure="${YAMS_BENCH_CONTINUE_ON_CASE_FAILURE:-1}"
run_case_checked() {
local case_filter="$1"
local case_transport="$2"
local case_usage_profile="${3:-$usage_profile}"
local rc=0
local status="ok"
if run_case "$output_file" "$case_filter" "$YAMS_TUNING_PROFILE" "$case_usage_profile" \
"$case_transport" "$phase_name"; then
rc=0
status="ok"
else
rc=$?
status="failed"
echo "Case failed (rc=$rc): $case_filter (transport=$case_transport)" >&2
if [[ "$continue_on_case_failure" != "1" ]]; then
append_case_manifest "$phase_name" "$output_file" "$case_filter" "$case_transport" \
"$status" "$rc" "$case_usage_profile"
exit "$rc"
fi
fi
append_case_manifest "$phase_name" "$output_file" "$case_filter" "$case_transport" \
"$status" "$rc" "$case_usage_profile"
if [[ "$status" == "failed" && "$continue_on_case_failure" == "1" ]]; then
return 0
fi
return "$rc"
}
echo "=== $phase_name ==="
# Connection contention run (always available)
run_case_checked "Multi-client ingestion: connection contention" "0"
# Large corpus read-path run (requires existing corpus)
if [[ -n "$DATA_DIR" && -d "$DATA_DIR" ]]; then
export YAMS_BENCH_DATA_DIR="$DATA_DIR"
# daemon IPC path
run_case_checked "Multi-client ingestion: large corpus reads" "0"
# MCP path
run_case_checked "Multi-client ingestion: large corpus reads" "1"
# External-agent-like churn profile (MCP path only)
run_case_checked "Multi-client ingestion: large corpus reads" "1" "external_agent_churn"
else
echo "Skipping large corpus reads (set --data-dir or YAMS_BENCH_DATA_DIR)"
fi
}
cd "$ROOT_DIR"
apply_phase2_profile
write_metadata "$RUN_DIR/phase2_throughput_first.meta.json" "phase2_throughput_first" \
"$YAMS_TUNING_PROFILE" "extension_direct" "0_or_1"
run_phase "phase2_throughput_first" "$PHASE2_OUT" "extension_direct"
apply_phase1_profile
write_metadata "$RUN_DIR/phase1_stability_first.meta.json" "phase1_stability_first" \
"$YAMS_TUNING_PROFILE" "mixed" "0_or_1"
run_phase "phase1_stability_first" "$PHASE1_OUT" "mixed"
python3 "$ROOT_DIR/tests/scripts/summarize_multi_client_jsonl.py" \
--input "$PHASE2_OUT" \
--output "$RUN_DIR/phase2_throughput_first.summary.json"
python3 "$ROOT_DIR/tests/scripts/summarize_multi_client_jsonl.py" \
--input "$PHASE1_OUT" \
--output "$RUN_DIR/phase1_stability_first.summary.json"
python3 "$ROOT_DIR/tests/scripts/summarize_multi_client_jsonl.py" \
--input "$PHASE2_OUT" \
--input "$PHASE1_OUT" \
--output "$RUN_DIR/combined.summary.json"
python3 "$ROOT_DIR/tests/scripts/check_multi_client_regression.py" \
"$RUN_DIR/phase1_stability_first.summary.json" \
"$RUN_DIR/phase2_throughput_first.summary.json" \
--output "$RUN_DIR/phase1_vs_phase2.regression.json"
# --- Baseline regression check ---
if [[ -f "$BASELINE_JSON" ]]; then
echo ""
echo "=== Baseline regression check ==="
echo " current: $RUN_DIR/combined.summary.json"
echo " baseline: $BASELINE_JSON"
FAIL_FLAG=""
[[ "$FAIL_ON_REGRESSION" == "1" ]] && FAIL_FLAG="--fail-on-regression"
python3 "$ROOT_DIR/tests/scripts/check_multi_client_regression.py" \
"$RUN_DIR/combined.summary.json" \
"$BASELINE_JSON" \
--output "$RUN_DIR/vs_baseline.regression.json" \
$FAIL_FLAG
BASELINE_RC=$?
if [[ $BASELINE_RC -ne 0 ]]; then
echo "WARNING: Baseline regression check failed (exit $BASELINE_RC)"
fi
else
echo "No baseline file found at $BASELINE_JSON — skipping baseline regression check"
fi
python3 - "$RUN_MANIFEST" "$RUN_ID" "$GIT_SHA" "$RUN_DIR" "$CASE_MANIFEST" <<'PY'
import json
import pathlib
import sys
from datetime import datetime, timezone
manifest_path = pathlib.Path(sys.argv[1])
run_id = sys.argv[2]
git_sha = sys.argv[3]
run_dir = pathlib.Path(sys.argv[4])
case_manifest_path = pathlib.Path(sys.argv[5])
def _read_json(path: pathlib.Path):
if not path.exists():
return None
with path.open("r", encoding="utf-8") as f:
return json.load(f)
def _read_jsonl(path: pathlib.Path):
rows = []
if not path.exists():
return rows
with path.open("r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
rows.append(json.loads(line))
return rows
artifacts = {
"phase2_jsonl": str(run_dir / "phase2_throughput_first.jsonl"),
"phase1_jsonl": str(run_dir / "phase1_stability_first.jsonl"),
"phase2_summary": str(run_dir / "phase2_throughput_first.summary.json"),
"phase1_summary": str(run_dir / "phase1_stability_first.summary.json"),
"combined_summary": str(run_dir / "combined.summary.json"),
"phase_compare_regression": str(run_dir / "phase1_vs_phase2.regression.json"),
"baseline_regression": str(run_dir / "vs_baseline.regression.json"),
"phase2_metadata": str(run_dir / "phase2_throughput_first.meta.json"),
"phase1_metadata": str(run_dir / "phase1_stability_first.meta.json"),
"case_manifest": str(case_manifest_path),
}
combined = _read_json(pathlib.Path(artifacts["combined_summary"])) or {}
phase_compare = _read_json(pathlib.Path(artifacts["phase_compare_regression"])) or {}
baseline_compare = _read_json(pathlib.Path(artifacts["baseline_regression"])) or {}
case_rows = _read_jsonl(case_manifest_path)
run_manifest = {
"schema_version": "multi_client_run_manifest_v1",
"generated_at": datetime.now(timezone.utc).isoformat(),
"run_id": run_id,
"git_sha": git_sha,
"artifacts": artifacts,
"case_count": len(case_rows),
"failed_cases": [row for row in case_rows if row.get("status") != "ok"],
"summary_snapshot": {
"record_count": combined.get("record_count", 0),
"connection_contention_throughput_mean": (
combined.get("connection_contention", {})
.get("throughput_ops_per_sec", {})
.get("mean", 0.0)
),
"connection_contention_fail_rate_mean": (
combined.get("connection_contention", {})
.get("fail_rate", {})
.get("mean", 0.0)
),
"phase1_vs_phase2_has_regression": phase_compare.get("has_regression", False),
"vs_baseline_has_regression": baseline_compare.get("has_regression", False),
},
}
with manifest_path.open("w", encoding="utf-8") as f:
json.dump(run_manifest, f, indent=2)
PY
echo ""
echo "Artifacts saved to: $RUN_DIR"