Files
ldb/test-redis/run_bench.hash.sh
2026-03-07 07:29:32 +00:00

438 lines
12 KiB
Bash
Executable File
Raw Permalink 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
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT_DIR=$(cd "$SCRIPT_DIR/.." && pwd)
cd "$ROOT_DIR"
TS=$(date +%Y%m%d_%H%M%S)
RUN_TIME=$(date '+%Y-%m-%d %H:%M:%S')
OUT_DIR="$ROOT_DIR/test-redis/results"
mkdir -p "$OUT_DIR"
DETAIL_CSV="$OUT_DIR/hash_bench_fair_detail_${TS}.csv"
SUMMARY_CSV="$OUT_DIR/hash_bench_fair_summary_${TS}.csv"
KV_LOG_DIR="/tmp/kv_bench_hash_fair_${TS}"
mkdir -p "$KV_LOG_DIR"
CONFIG_XML="$ROOT_DIR/config/config.xml"
README_MD="$ROOT_DIR/test-redis/README.md"
ROUNDS=${ROUNDS:-5}
RETRIES=${RETRIES:-3}
REQ=${REQ:-1000000}
PIPE=${PIPE:-128}
KEYSPACE=${KEYSPACE:-1000000}
VSIZE=${VSIZE:-32}
SEED=${SEED:-12345}
ALLOC=${ALLOC:-mypool}
KV_HOST=127.0.0.1
KV_PORT=${KV_PORT:-8888}
SET_CMD=${SET_CMD:-RSET}
GET_CMD=${GET_CMD:-RGET}
ORIG_CONFIG_BACKUP=$(mktemp "/tmp/kvstore_config_backup_${TS}.XXXXXX")
cp "$CONFIG_XML" "$ORIG_CONFIG_BACKUP"
KV_PID=""
printf "strategy,persistence,oplog_sync,allocator,mode,round,qps,avg_us,elapsed_s,key_prefix,seed,requests,pipeline,keyspace,value_size\n" > "$DETAIL_CSV"
printf "strategy,persistence,oplog_sync,allocator,mode,round_qps,round_avg_us,round_elapsed_s,avg_qps,avg_avg_us,avg_elapsed_s\n" > "$SUMMARY_CSV"
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "missing required command: $cmd" >&2
exit 1
fi
}
ensure_binaries() {
if [[ ! -x "$ROOT_DIR/kvstore" || ! -x "$ROOT_DIR/test-redis/bench" ]]; then
echo "[info] kvstore/bench missing, running make -j4 ..."
make -j4
fi
}
set_config() {
local ptype="$1"
local oplog_sync="$2"
local pdir="$3"
local alloc="$4"
python3 - "$CONFIG_XML" "$ptype" "$oplog_sync" "$pdir" "$alloc" "$KV_PORT" <<'PY'
import sys
import xml.etree.ElementTree as ET
path, ptype, oplog_sync, pdir, alloc, kv_port = sys.argv[1:]
tree = ET.parse(path)
root = tree.getroot()
server = root.find("server")
if server is not None:
ip = server.find("ip")
port = server.find("port")
mode = server.find("mode")
replica = server.find("replica")
if ip is not None:
ip.text = "127.0.0.1"
if port is not None:
port.text = kv_port
if mode is not None:
mode.text = "master"
if replica is not None:
replica.text = "disable"
persistence = root.find("persistence")
if persistence is not None:
t = persistence.find("type")
d = persistence.find("dir")
s = persistence.find("oplog_sync")
if t is not None:
t.text = ptype
if d is not None:
d.text = pdir
if s is not None:
s.text = oplog_sync
memory = root.find("memory")
if memory is not None:
a = memory.find("allocator")
leak = memory.find("leakage")
if a is not None:
a.text = alloc
if leak is not None:
leak.text = "disable"
tree.write(path, encoding="UTF-8", xml_declaration=True)
PY
}
wait_port_open() {
local port="$1"
for _ in $(seq 1 200); do
if ss -ltn | rg -q ":${port}\\b"; then
return 0
fi
sleep 0.1
done
return 1
}
wait_port_close() {
local port="$1"
for _ in $(seq 1 200); do
if ! ss -ltn | rg -q ":${port}\\b"; then
return 0
fi
sleep 0.1
done
return 1
}
assert_port_free() {
local port="$1"
if ss -ltn | rg -q ":${port}\\b"; then
echo "port ${port} is already in use, cannot start kvstore" >&2
exit 1
fi
}
extract_metric() {
local line="$1"
local key="$2"
echo "$line" | sed -E "s/.*${key}=([0-9]+(\\.[0-9]+)?).*/\\1/"
}
run_bench_capture() {
local mode="$1"
local key_prefix="$2"
local seed="$3"
local verify="$4"
local cmd=(
./test-redis/bench
--host "$KV_HOST"
--port "$KV_PORT"
--mode "$mode"
--set-cmd "$SET_CMD"
--get-cmd "$GET_CMD"
--requests "$REQ"
--pipeline "$PIPE"
--keyspace "$KEYSPACE"
--value-size "$VSIZE"
--seed "$seed"
--key-prefix "$key_prefix"
)
if [[ "$verify" == "1" ]]; then
cmd+=(--verify-get)
fi
local out
out=$("${cmd[@]}")
echo "$out" >&2
local line
line=$(echo "$out" | rg "\\[result\\]" | tail -n1)
if [[ -z "$line" ]]; then
echo "missing [result] line in benchmark output" >&2
return 1
fi
local qps avg elapsed
qps=$(extract_metric "$line" "qps")
avg=$(extract_metric "$line" "avg")
elapsed=$(extract_metric "$line" "elapsed")
if [[ -z "$qps" || -z "$avg" || -z "$elapsed" ]]; then
echo "failed to parse benchmark metrics: $line" >&2
return 1
fi
echo "$qps,$avg,$elapsed"
}
start_kv() {
local label="$1"
assert_port_free "$KV_PORT"
./kvstore >"$KV_LOG_DIR/${label}.log" 2>&1 &
KV_PID=$!
if ! wait_port_open "$KV_PORT"; then
echo "kvstore start failed for ${label}" >&2
return 1
fi
}
stop_kv() {
if [[ -n "${KV_PID:-}" ]] && kill -0 "$KV_PID" >/dev/null 2>&1; then
kill "$KV_PID" >/dev/null 2>&1 || true
wait "$KV_PID" >/dev/null 2>&1 || true
fi
KV_PID=""
wait_port_close "$KV_PORT" || true
}
cleanup() {
stop_kv
if [[ -f "$ORIG_CONFIG_BACKUP" ]]; then
cp "$ORIG_CONFIG_BACKUP" "$CONFIG_XML"
rm -f "$ORIG_CONFIG_BACKUP"
fi
}
trap cleanup EXIT
case_params() {
local strategy="$1"
case "$strategy" in
persist_no)
echo "incremental,none"
;;
persist_everysec)
echo "incremental,every_sec"
;;
nopersist)
echo "none,none"
;;
*)
echo "unknown strategy: $strategy" >&2
return 1
;;
esac
}
run_one_case_round() {
local strategy="$1"
local round="$2"
local params ptype oplog_sync
local pdir_rel pdir_abs
local key_prefix seed
local m qps avg elapsed
local set_qps set_avg set_elapsed
local get_qps get_avg get_elapsed
local attempt ok
params=$(case_params "$strategy")
IFS=',' read -r ptype oplog_sync <<< "$params"
key_prefix="bench:${TS}:round:${round}:"
seed=$((SEED + round))
for attempt in $(seq 1 "$RETRIES"); do
pdir_rel="data/${strategy}_${TS}_r${round}_a${attempt}"
pdir_abs="$ROOT_DIR/${pdir_rel}"
rm -rf "$pdir_abs"
mkdir -p "$pdir_abs"
set_config "$ptype" "$oplog_sync" "$pdir_rel" "$ALLOC"
start_kv "${strategy}_r${round}_a${attempt}"
ok=1
if ! m=$(run_bench_capture "set" "$key_prefix" "$seed" 0); then
ok=0
else
IFS=',' read -r set_qps set_avg set_elapsed <<< "$m"
fi
if [[ "$ok" == "1" ]]; then
if ! m=$(run_bench_capture "get" "$key_prefix" "$seed" 1); then
ok=0
else
IFS=',' read -r get_qps get_avg get_elapsed <<< "$m"
fi
fi
stop_kv
if [[ "$ok" == "1" ]]; then
printf "%s,%s,%s,%s,set,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$ptype" "$oplog_sync" "$ALLOC" "$round" \
"$set_qps" "$set_avg" "$set_elapsed" "$key_prefix" "$seed" \
"$REQ" "$PIPE" "$KEYSPACE" "$VSIZE" >> "$DETAIL_CSV"
printf "%s,%s,%s,%s,get,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$ptype" "$oplog_sync" "$ALLOC" "$round" \
"$get_qps" "$get_avg" "$get_elapsed" "$key_prefix" "$seed" \
"$REQ" "$PIPE" "$KEYSPACE" "$VSIZE" >> "$DETAIL_CSV"
return 0
fi
echo "[warn] retry $attempt/$RETRIES failed: strategy=$strategy round=$round" >&2
sleep 0.2
done
echo "[error] all retries failed: strategy=$strategy round=$round" >&2
return 1
}
append_readme_results() {
python3 - "$DETAIL_CSV" "$SUMMARY_CSV" "$README_MD" "$RUN_TIME" "$ROUNDS" "$REQ" "$PIPE" "$KEYSPACE" "$VSIZE" "$ALLOC" "$SET_CMD" "$GET_CMD" <<'PY'
import csv
import os
import sys
from collections import defaultdict
detail_csv, summary_csv, readme_path, run_time, rounds, req, pipeline, keyspace, value_size, alloc, set_cmd, get_cmd = sys.argv[1:]
rounds_i = int(rounds)
group = defaultdict(list)
meta = {}
with open(detail_csv, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for r in reader:
key = (r["strategy"], r["mode"])
group[key].append(r)
meta[r["strategy"]] = (r["persistence"], r["oplog_sync"], r["allocator"])
def f2(x):
return f"{float(x):.2f}"
with open(summary_csv, "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow([
"strategy","persistence","oplog_sync","allocator","mode",
"round_qps","round_avg_us","round_elapsed_s",
"avg_qps","avg_avg_us","avg_elapsed_s"
])
for strategy in ["persist_no", "persist_everysec", "nopersist"]:
for mode in ["set", "get"]:
rows = sorted(group[(strategy, mode)], key=lambda x: int(x["round"]))
qps = [float(r["qps"]) for r in rows]
avg_us = [float(r["avg_us"]) for r in rows]
elapsed = [float(r["elapsed_s"]) for r in rows]
persistence, oplog_sync, allocator = meta[strategy]
w.writerow([
strategy, persistence, oplog_sync, allocator, mode,
"|".join(f2(v) for v in qps),
"|".join(f2(v) for v in avg_us),
"|".join(f2(v) for v in elapsed),
f2(sum(qps) / len(qps)),
f2(sum(avg_us) / len(avg_us)),
f2(sum(elapsed) / len(elapsed)),
])
rows = []
with open(summary_csv, newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
def by_mode(mode):
ordered = ["persist_no", "persist_everysec", "nopersist"]
m = [r for r in rows if r["mode"] == mode]
m.sort(key=lambda r: ordered.index(r["strategy"]))
return m
round_headers = [f"Round{i}" for i in range(1, rounds_i + 1)]
detail_rel = os.path.relpath(detail_csv, os.path.dirname(readme_path))
summary_rel = os.path.relpath(summary_csv, os.path.dirname(readme_path))
lines = []
lines.append("")
lines.append(f"## run_bench.hash.sh {rounds_i}轮均值复测({run_time}")
lines.append("")
lines.append(f"- 轮次:{rounds_i} 轮(每种情况)")
lines.append("- 策略persist(no), persist(everysec), nopersist")
lines.append(f"- 命令:{set_cmd}/{get_cmd}GET 保持 prefill + GET")
lines.append("- 公平性:同一 case 同一轮的 SET/GET 使用相同 key_prefix 与 seedcase 顺序按轮次轮转")
lines.append(f"- 参数requests={req} pipeline={pipeline} keyspace={keyspace} value-size={value_size} allocator={alloc}")
lines.append(f"- 明细数据:`{detail_rel}`")
lines.append(f"- 汇总数据:`{summary_rel}`")
lines.append("")
for mode in ["set", "get"]:
lines.append(f"### kvstore{set_cmd}/{get_cmd} ({mode})")
lines.append("")
header = "| 场景 | persistence | oplog_sync | " + " | ".join(round_headers) + " | 均值QPS | 均值avg(us/op) | 均值elapsed(s) |"
sep = "|---|---|---|" + "|".join(["---:"] * len(round_headers)) + "|---:|---:|---:|"
lines.append(header)
lines.append(sep)
for r in by_mode(mode):
qps_rounds = r["round_qps"].split("|")
lines.append(
"| {} | {} | {} | {} | {} | {} | {} |".format(
r["strategy"],
r["persistence"],
r["oplog_sync"],
" | ".join(qps_rounds),
r["avg_qps"],
r["avg_avg_us"],
r["avg_elapsed_s"],
)
)
lines.append("")
with open(readme_path, "a", encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")
PY
}
main() {
require_cmd python3
require_cmd rg
require_cmd ss
ensure_binaries
for round in $(seq 1 "$ROUNDS"); do
case $((round % 3)) in
1)
order=(persist_no persist_everysec nopersist)
;;
2)
order=(persist_everysec nopersist persist_no)
;;
0)
order=(nopersist persist_no persist_everysec)
;;
esac
for strategy in "${order[@]}"; do
run_one_case_round "$strategy" "$round"
done
done
append_readme_results
echo "DETAIL_CSV=$DETAIL_CSV"
echo "SUMMARY_CSV=$SUMMARY_CSV"
echo "README_UPDATED=$README_MD"
}
main "$@"