Files
ldb/test-redis/run_hash_bench.sh
2026-03-05 08:45:23 +00:00

518 lines
16 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
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_detail_${TS}.csv"
SUMMARY_CSV="$OUT_DIR/hash_bench_summary_${TS}.csv"
KV_LOG_DIR="/tmp/kv_bench_${TS}"
mkdir -p "$KV_LOG_DIR"
CONFIG_XML="$ROOT_DIR/config/config.xml"
README_MD="$ROOT_DIR/test-redis/README.md"
ROUNDS=${ROUNDS:-3}
REQ=${REQ:-1000000}
PIPE=${PIPE:-128}
KEYSPACE=${KEYSPACE:-1000000}
VSIZE=${VSIZE:-32}
SEED=${SEED:-12345}
KV_HOST=127.0.0.1
KV_PORT=${KV_PORT:-8888}
REDIS_HOST=127.0.0.1
REDIS_PORTS=(6381 6382 6383 6384 6385)
ORIG_CONFIG_BACKUP=$(mktemp "/tmp/kvstore_config_backup_${TS}.XXXXXX")
cp "$CONFIG_XML" "$ORIG_CONFIG_BACKUP"
KV_PID=""
printf "target,strategy,persistence,allocator,cmd_pair,mode,round,qps,avg_us,elapsed_s\n" > "$DETAIL_CSV"
printf "target,strategy,persistence,allocator,cmd_pair,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 alloc="$2"
local pdir="$3"
python3 - "$CONFIG_XML" "$ptype" "$alloc" "$pdir" "$KV_PORT" <<'PY'
import sys
import xml.etree.ElementTree as ET
path, ptype, alloc, pdir, kv_port = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
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")
if t is not None:
t.text = ptype
if d is not None:
d.text = pdir
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"
local svc="$2"
if ss -ltn | rg -q ":${port}\\b"; then
echo "port ${port} is already in use, cannot start ${svc}" >&2
exit 1
fi
}
extract_metric() {
local line="$1"
local key="$2"
echo "$line" | sed -E "s/.*${key}=([0-9]+(\\.[0-9]+)?).*/\\1/"
}
float_add() {
awk -v a="$1" -v b="$2" 'BEGIN { printf "%.6f", a + b }'
}
float_div() {
awk -v a="$1" -v b="$2" 'BEGIN { if (b == 0) printf "0"; else printf "%.6f", a / b }'
}
join_pipe() {
local IFS='|'
echo "$*"
}
run_bench_capture() {
local host="$1"
local port="$2"
local mode="$3"
local set_cmd="$4"
local get_cmd="$5"
local key_prefix="$6"
local seed="$7"
local verify="$8"
local cmd=(
./test-redis/bench
--host "$host"
--port "$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"
./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
}
start_redis() {
local strategy="$1"
local round="$2"
local port="$3"
local save_rule="$4"
local appendonly="$5"
local appendfsync="$6"
local rdir="/tmp/redis_${strategy}_${TS}_r${round}"
local conf="$rdir/redis.conf"
rm -rf "$rdir"
mkdir -p "$rdir"
assert_port_free "$port" "redis(${strategy})"
cat > "$conf" <<EOF
bind 127.0.0.1
port $port
daemonize yes
pidfile $rdir/redis.pid
dir $rdir
dbfilename dump.rdb
appendfilename appendonly.aof
save $save_rule
appendonly $appendonly
appendfsync $appendfsync
logfile $rdir/redis.log
EOF
redis-server "$conf"
if ! wait_port_open "$port"; then
echo "redis start failed for ${strategy}, check $rdir/redis.log" >&2
return 1
fi
}
stop_redis_port() {
local port="$1"
if ss -ltn | rg -q ":${port}\\b"; then
redis-cli -h "$REDIS_HOST" -p "$port" shutdown nosave >/dev/null 2>&1 || true
wait_port_close "$port" || true
fi
}
cleanup() {
stop_kv
for port in "${REDIS_PORTS[@]}"; do
stop_redis_port "$port"
done
if [[ -f "$ORIG_CONFIG_BACKUP" ]]; then
cp "$ORIG_CONFIG_BACKUP" "$CONFIG_XML"
rm -f "$ORIG_CONFIG_BACKUP"
fi
}
trap cleanup EXIT
run_kv_case() {
local strategy="$1"
local persistence="$2"
local alloc="$3"
local -a set_qps_list=() set_avg_list=() set_elapsed_list=()
local -a get_qps_list=() get_avg_list=() get_elapsed_list=()
local set_qps_sum="0" set_avg_sum="0" set_elapsed_sum="0"
local get_qps_sum="0" get_avg_sum="0" get_elapsed_sum="0"
for round in $(seq 1 "$ROUNDS"); do
local pdir_rel="data/${strategy}_${TS}_r${round}"
local pdir_abs="$ROOT_DIR/${pdir_rel}"
rm -rf "$pdir_abs"
mkdir -p "$pdir_abs"
set_config "$persistence" "$alloc" "$pdir_rel"
start_kv "${strategy}_r${round}"
local m qps avg elapsed
m=$(run_bench_capture "$KV_HOST" "$KV_PORT" "set" "RSET" "RGET" "bench:${TS}:kv:${strategy}:r${round}:set:" "$((SEED + round * 10 + 1))" 0)
IFS=',' read -r qps avg elapsed <<< "$m"
printf "kvstore,%s,%s,%s,RSET/RGET,set,%s,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
set_qps_list+=("$qps")
set_avg_list+=("$avg")
set_elapsed_list+=("$elapsed")
set_qps_sum=$(float_add "$set_qps_sum" "$qps")
set_avg_sum=$(float_add "$set_avg_sum" "$avg")
set_elapsed_sum=$(float_add "$set_elapsed_sum" "$elapsed")
m=$(run_bench_capture "$KV_HOST" "$KV_PORT" "get" "RSET" "RGET" "bench:${TS}:kv:${strategy}:r${round}:get:" "$((SEED + round * 10 + 2))" 1)
IFS=',' read -r qps avg elapsed <<< "$m"
printf "kvstore,%s,%s,%s,RSET/RGET,get,%s,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
get_qps_list+=("$qps")
get_avg_list+=("$avg")
get_elapsed_list+=("$elapsed")
get_qps_sum=$(float_add "$get_qps_sum" "$qps")
get_avg_sum=$(float_add "$get_avg_sum" "$avg")
get_elapsed_sum=$(float_add "$get_elapsed_sum" "$elapsed")
stop_kv
done
local set_avg_qps set_avg_avg set_avg_elapsed
local get_avg_qps get_avg_avg get_avg_elapsed
set_avg_qps=$(float_div "$set_qps_sum" "$ROUNDS")
set_avg_avg=$(float_div "$set_avg_sum" "$ROUNDS")
set_avg_elapsed=$(float_div "$set_elapsed_sum" "$ROUNDS")
get_avg_qps=$(float_div "$get_qps_sum" "$ROUNDS")
get_avg_avg=$(float_div "$get_avg_sum" "$ROUNDS")
get_avg_elapsed=$(float_div "$get_elapsed_sum" "$ROUNDS")
printf "kvstore,%s,%s,%s,RSET/RGET,set,avg,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$DETAIL_CSV"
printf "kvstore,%s,%s,%s,RSET/RGET,get,avg,%s,%s,%s\n" "$strategy" "$persistence" "$alloc" "$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$DETAIL_CSV"
printf "kvstore,%s,%s,%s,RSET/RGET,set,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$persistence" "$alloc" \
"$(join_pipe "${set_qps_list[@]}")" \
"$(join_pipe "${set_avg_list[@]}")" \
"$(join_pipe "${set_elapsed_list[@]}")" \
"$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$SUMMARY_CSV"
printf "kvstore,%s,%s,%s,RSET/RGET,get,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$persistence" "$alloc" \
"$(join_pipe "${get_qps_list[@]}")" \
"$(join_pipe "${get_avg_list[@]}")" \
"$(join_pipe "${get_elapsed_list[@]}")" \
"$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$SUMMARY_CSV"
}
run_redis_case() {
local strategy="$1"
local persistence="$2"
local port="$3"
local save_rule="$4"
local appendonly="$5"
local appendfsync="$6"
local -a set_qps_list=() set_avg_list=() set_elapsed_list=()
local -a get_qps_list=() get_avg_list=() get_elapsed_list=()
local set_qps_sum="0" set_avg_sum="0" set_elapsed_sum="0"
local get_qps_sum="0" get_avg_sum="0" get_elapsed_sum="0"
for round in $(seq 1 "$ROUNDS"); do
start_redis "$strategy" "$round" "$port" "$save_rule" "$appendonly" "$appendfsync"
local m qps avg elapsed
m=$(run_bench_capture "$REDIS_HOST" "$port" "set" "SET" "GET" "bench:${TS}:redis:${strategy}:r${round}:set:" "$((SEED + round * 100 + 1))" 0)
IFS=',' read -r qps avg elapsed <<< "$m"
printf "redis,%s,%s,-,SET/GET,set,%s,%s,%s,%s\n" "$strategy" "$persistence" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
set_qps_list+=("$qps")
set_avg_list+=("$avg")
set_elapsed_list+=("$elapsed")
set_qps_sum=$(float_add "$set_qps_sum" "$qps")
set_avg_sum=$(float_add "$set_avg_sum" "$avg")
set_elapsed_sum=$(float_add "$set_elapsed_sum" "$elapsed")
m=$(run_bench_capture "$REDIS_HOST" "$port" "get" "SET" "GET" "bench:${TS}:redis:${strategy}:r${round}:get:" "$((SEED + round * 100 + 2))" 1)
IFS=',' read -r qps avg elapsed <<< "$m"
printf "redis,%s,%s,-,SET/GET,get,%s,%s,%s,%s\n" "$strategy" "$persistence" "$round" "$qps" "$avg" "$elapsed" >> "$DETAIL_CSV"
get_qps_list+=("$qps")
get_avg_list+=("$avg")
get_elapsed_list+=("$elapsed")
get_qps_sum=$(float_add "$get_qps_sum" "$qps")
get_avg_sum=$(float_add "$get_avg_sum" "$avg")
get_elapsed_sum=$(float_add "$get_elapsed_sum" "$elapsed")
stop_redis_port "$port"
done
local set_avg_qps set_avg_avg set_avg_elapsed
local get_avg_qps get_avg_avg get_avg_elapsed
set_avg_qps=$(float_div "$set_qps_sum" "$ROUNDS")
set_avg_avg=$(float_div "$set_avg_sum" "$ROUNDS")
set_avg_elapsed=$(float_div "$set_elapsed_sum" "$ROUNDS")
get_avg_qps=$(float_div "$get_qps_sum" "$ROUNDS")
get_avg_avg=$(float_div "$get_avg_sum" "$ROUNDS")
get_avg_elapsed=$(float_div "$get_elapsed_sum" "$ROUNDS")
printf "redis,%s,%s,-,SET/GET,set,avg,%s,%s,%s\n" "$strategy" "$persistence" "$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$DETAIL_CSV"
printf "redis,%s,%s,-,SET/GET,get,avg,%s,%s,%s\n" "$strategy" "$persistence" "$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$DETAIL_CSV"
printf "redis,%s,%s,-,SET/GET,set,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$persistence" \
"$(join_pipe "${set_qps_list[@]}")" \
"$(join_pipe "${set_avg_list[@]}")" \
"$(join_pipe "${set_elapsed_list[@]}")" \
"$set_avg_qps" "$set_avg_avg" "$set_avg_elapsed" >> "$SUMMARY_CSV"
printf "redis,%s,%s,-,SET/GET,get,%s,%s,%s,%s,%s,%s\n" \
"$strategy" "$persistence" \
"$(join_pipe "${get_qps_list[@]}")" \
"$(join_pipe "${get_avg_list[@]}")" \
"$(join_pipe "${get_elapsed_list[@]}")" \
"$get_avg_qps" "$get_avg_avg" "$get_avg_elapsed" >> "$SUMMARY_CSV"
}
append_readme_results() {
python3 - "$SUMMARY_CSV" "$README_MD" "$RUN_TIME" "$ROUNDS" "$REQ" "$PIPE" "$KEYSPACE" "$VSIZE" "$DETAIL_CSV" "$SUMMARY_CSV" <<'PY'
import csv
import os
import sys
summary_csv, readme_path, run_time, rounds, req, pipeline, keyspace, value_size, detail_csv, summary_path = sys.argv[1:]
rounds_i = int(rounds)
def split_rounds(v):
if not v:
return []
return v.split("|")
def fmt(v):
try:
return f"{float(v):.2f}"
except Exception:
return v
rows = []
with open(summary_csv, newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
kv_rows = [r for r in rows if r["target"] == "kvstore"]
redis_rows = [r for r in rows if r["target"] == "redis"]
lines = []
lines.append("")
lines.append(f"## run_hash_bench.sh 三轮均值复测({run_time}")
lines.append("")
lines.append(f"- 轮次:{rounds_i} 轮(取均值)")
lines.append(f"- 参数requests={req} pipeline={pipeline} keyspace={keyspace} value-size={value_size}")
lines.append(f"- 明细数据:`{os.path.relpath(detail_csv, os.path.dirname(readme_path))}`")
lines.append(f"- 汇总数据:`{os.path.relpath(summary_path, os.path.dirname(readme_path))}`")
lines.append("")
round_headers = [f"Round{i}" for i in range(1, rounds_i + 1)]
def render_table(title, items, scene_fn):
lines.append(f"### {title}")
lines.append("")
header = "| 场景 | 模式 | " + " | ".join(round_headers) + " | 均值QPS | 均值avg(us/op) | 均值elapsed(s) |"
sep = "|---|---:|" + "|".join(["---:"] * len(round_headers)) + "|---:|---:|---:|"
lines.append(header)
lines.append(sep)
for row in items:
rounds_qps = split_rounds(row["round_qps"])
while len(rounds_qps) < rounds_i:
rounds_qps.append("-")
rounds_qps = rounds_qps[:rounds_i]
scene = scene_fn(row)
lines.append(
"| {} | {} | {} | {} | {} | {} |".format(
scene,
row["mode"],
" | ".join(rounds_qps),
fmt(row["avg_qps"]),
fmt(row["avg_avg_us"]),
fmt(row["avg_elapsed_s"]),
)
)
lines.append("")
render_table(
"kvstoreRSET/RGET持久化 × allocator",
kv_rows,
lambda r: f"{r['strategy']} ({r['persistence']}, {r['allocator']})",
)
render_table(
"RedisSET/GET各持久化模式",
redis_rows,
lambda r: f"{r['strategy']} ({r['persistence']})",
)
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
require_cmd redis-server
require_cmd redis-cli
ensure_binaries
run_kv_case "persist_mypool" "incremental" "mypool"
run_kv_case "nopersist_mypool" "none" "mypool"
run_kv_case "persist_malloc" "incremental" "malloc"
run_kv_case "nopersist_malloc" "none" "malloc"
run_redis_case "none" "none" 6381 "\"\"" "no" "everysec"
run_redis_case "rdb_default" "rdb_default" 6382 "900 1 300 10 60 10000" "no" "everysec"
run_redis_case "aof_no" "aof_no" 6383 "\"\"" "yes" "no"
run_redis_case "aof_everysec" "aof_everysec" 6384 "\"\"" "yes" "everysec"
run_redis_case "aof_always" "aof_always" 6385 "\"\"" "yes" "always"
append_readme_results
echo "DETAIL_CSV=$DETAIL_CSV"
echo "SUMMARY_CSV=$SUMMARY_CSV"
echo "README_UPDATED=$README_MD"
}
main "$@"