Files
rustfs/scripts/test/rustfs_performance_testing.sh
hector d48dda5bdc ci: add RustFS 4x4 performance test workflow and scripts (#6752)
* ci: add RustFS 4x4 performance test workflow and scripts

* ci: run performance test on dedicated pf-testing runner
2026-08-27 22:24:57 +08:00

240 lines
8.8 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
#
# rustfs-performance-testing.sh
# RustFS 对象存储压测脚本(固定版):测试方法 + 执行 + 结果解析
#
# 测试方法
# 1) 方法:GET / PUT / MIXEDwarp 默认混合负载 45% GET + 55% PUT
# 2) 对象尺寸:1KiB 4KiB 16KiB 128KiB 1MiB 4MiB 8MiB 16MiB 32MiB 64MiB
# 3) 并发:64;单轮时长:5m;轮间 sleep60sGET 对象数:2500
# 4) 顺序:GET 全部尺寸 -> PUT 全部尺寸 -> MIXED 全部尺寸
# 5) 结果解析:每轮结束后自动解析 warp 输出,写入
# summary.tsv(机器可读)与 summary.mdMarkdown 汇总表)
#
# 依赖:warp >= v1.6MinIO warp),bashawk/sed/grep
# 说明:warp v1.6.1 的 put 不支持 --objects,脚本已自动处理(仅 get/mixed 传该参数)
#
# 环境变量覆盖(不传时使用固定默认值):
# WARP_HOST WARP_ACCESS_KEY WARP_SECRET_KEY WARP_BUCKET
# WARP_CONCURRENCY WARP_DURATION WARP_GET_OBJECTS WARP_SLEEP_BETWEEN_ROUNDS
# WARP_RESULT_DIR
# WARP_METHODS WARP_SIZES # 手动指定方法/尺寸(逗号或空格分隔),不传则全量
set -u -o pipefail
HOST="${WARP_HOST:-rustfs-node1:9000,rustfs-node2:9000,rustfs-node3:9000,rustfs-node4:9000}"
ACCESS_KEY="${WARP_ACCESS_KEY:-rustfs@test}"
SECRET_KEY="${WARP_SECRET_KEY:-rustfs@test}"
BUCKET="${WARP_BUCKET:-warp-benchmark-bucket}"
CONCURRENCY="${WARP_CONCURRENCY:-64}"
DURATION="${WARP_DURATION:-5m}"
GET_OBJECTS="${WARP_GET_OBJECTS:-2500}"
SLEEP_BETWEEN_ROUNDS="${WARP_SLEEP_BETWEEN_ROUNDS:-60}"
RESULT_DIR="${WARP_RESULT_DIR:-$(pwd)/warp-bench-results-$(date +%Y%m%d-%H%M%S)}"
if [ -n "${WARP_SIZES:-}" ] && [ "${WARP_SIZES}" != "all" ] && [ "${WARP_SIZES}" != "ALL" ]; then
read -r -a SIZES <<<"${WARP_SIZES//,/ }"
else
SIZES=(1KiB 4KiB 16KiB 128KiB 1MiB 4MiB 8MiB 16MiB 32MiB 64MiB)
fi
if [ -n "${WARP_METHODS:-}" ] && [ "${WARP_METHODS}" != "all" ] && [ "${WARP_METHODS}" != "ALL" ]; then
read -r -a METHODS <<<"${WARP_METHODS//,/ }"
else
METHODS=(get put mixed)
fi
TOTAL_ROUNDS=$(( ${#METHODS[@]} * ${#SIZES[@]} ))
ROUND=0
# --parse-only <result-dir>:只解析已有结果目录(${method}_${size}.txt),不执行压测
if [[ "${1:-}" == "--parse-only" && -n "${2:-}" ]]; then
RESULT_DIR="$2"
fi
LOG_FILE="${RESULT_DIR}/master.log"
SUMMARY_TSV="${RESULT_DIR}/summary.tsv"
SUMMARY_MD="${RESULT_DIR}/summary.md"
if [[ "${1:-}" != "--parse-only" ]] && ! command -v warp >/dev/null 2>&1; then
echo "错误:未找到 warp 命令,请先安装 MinIO warp。" >&2
exit 1
fi
log() {
echo "$(date -u '+%Y-%m-%dT%H:%M:%SZ') $*" | tee -a "${LOG_FILE}"
}
# ---- 结果解析 ----
# 提取指定 sectionGET/PUT/Total 等)的 Average / Reqs / TTFB 原始行
section_lines() {
awk -v sec="$2" '
/^Report: / { cur = $2; sub(/\.$/, "", cur) }
cur == sec && /^ *\* Average:/ { avg = $0 }
cur == sec && /^ *\* Reqs:/ { reqs = $0 }
cur == sec && /^ *\* TTFB:/ { ttfb = $0 }
END {
if (avg != "") print avg
if (reqs != "") print reqs
if (ttfb != "") print ttfb
}
' "$1"
}
# 从统计行中取字段:tp objs avg p50 p90 p99 ttfb_avg ttfb_p99 ttfb_worst
field() {
case "$2" in
tp) echo "$1" | sed -n 's/^ *\* Average: \(.*\), \([0-9.]*\) obj\/s.*/\1/p' ;;
objs) echo "$1" | sed -n 's/^ *\* Average: .*, \([0-9.]*\) obj\/s.*/\1/p' ;;
avg) echo "$1" | sed -n 's/^ *\* Reqs: Avg: \([^,]*\),.*/\1/p' ;;
p50) echo "$1" | sed -n 's/^ *\* Reqs: Avg: [^,]*, 50%: \([^,]*\),.*/\1/p' ;;
p90) echo "$1" | sed -n 's/^ *\* Reqs: Avg: [^,]*, 50%: [^,]*, 90%: \([^,]*\),.*/\1/p' ;;
p99) echo "$1" | sed -n 's/^ *\* Reqs: Avg: [^,]*, 50%: [^,]*, 90%: [^,]*, 99%: \([^,]*\),.*/\1/p' ;;
ttfb_avg) echo "$1" | sed -n 's/^ *\* TTFB: Avg: \([^,]*\),.*/\1/p' ;;
ttfb_p99) echo "$1" | sed -n 's/^ *\* TTFB: .*99th: \([^,]*\),.*/\1/p' ;;
ttfb_worst) echo "$1" | sed -n 's/^ *\* TTFB: .*Worst: \([^ ]*\).*/\1/p' ;;
*) echo "" ;;
esac
}
# 解析一轮输出,追加一行到 summary.tsv
parse_round() {
local method="$1" size="$2" file="$3"
local line
if [[ "${method}" == "mixed" ]]; then
local total get put
total=$(section_lines "$file" Total)
get=$(section_lines "$file" GET)
put=$(section_lines "$file" PUT)
line=$(printf 'mixed\t%s\t%s\t%s\t%s\t%s' \
"$size" \
"$(field "${total}" tp)" \
"$(field "${total}" objs)" \
"$(field "${get}" avg)" \
"$(field "${put}" avg)")
else
local sec
sec=$(printf '%s' "${method}" | tr '[:lower:]' '[:upper:]')
local stats
stats=$(section_lines "$file" "${sec}")
line=$(printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s' \
"${method}" "${size}" \
"$(field "${stats}" tp)" \
"$(field "${stats}" objs)" \
"$(field "${stats}" avg)" \
"$(field "${stats}" p50)" \
"$(field "${stats}" p90)" \
"$(field "${stats}" p99)" \
"$(field "${stats}" ttfb_avg)" \
"$(field "${stats}" ttfb_p99)" \
"$(field "${stats}" ttfb_worst)")
fi
printf '%s\n' "${line}" >> "${SUMMARY_TSV}"
}
# 汇总 summary.tsv -> summary.mdMarkdown 表格)
gen_summary_md() {
{
echo "# RustFS 性能压测结果"
echo ""
echo "- 日期:$(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "- 目标:${HOST}"
echo "- 并发:${CONCURRENCY};单轮:${DURATION}sleep${SLEEP_BETWEEN_ROUNDS}sGET objects${GET_OBJECTS}"
echo "- 方法:GET / PUT / MIXEDwarp 默认混合负载);尺寸:${SIZES[*]}"
echo ""
} > "${SUMMARY_MD}"
for m in get put; do
{
echo "## $(printf '%s' "$m" | tr '[:lower:]' '[:upper:]') 结果"
echo ""
echo "| 对象尺寸 | 平均吞吐 | 平均 obj/s | Avg Latency | P50 | P90 | P99 | TTFB Avg | TTFB P99 | TTFB 最差 |"
echo "|----------|----------|-----------|-------------|-----|-----|-----|----------|----------|-----------|"
} >> "${SUMMARY_MD}"
while IFS=$'\t' read -r method size tp objs avg p50 p90 p99 ttfb_avg ttfb_p99 ttfb_worst; do
[[ "${method}" == "${m}" ]] && \
echo "| ${size} | ${tp} | ${objs} | ${avg} | ${p50} | ${p90} | ${p99} | ${ttfb_avg} | ${ttfb_p99} | ${ttfb_worst} |" >> "${SUMMARY_MD}"
done < "${SUMMARY_TSV}"
echo "" >> "${SUMMARY_MD}"
done
{
echo "## MIXED 结果(Total 口径)"
echo ""
echo "| 对象尺寸 | Total 平均吞吐 | Total 平均 obj/s | Mixed-GET Avg | Mixed-PUT Avg |"
echo "|----------|---------------|------------------|----------------|----------------|"
} >> "${SUMMARY_MD}"
while IFS=$'\t' read -r method size tp objs gavg pavg rest; do
[[ "${method}" == "mixed" ]] && \
echo "| ${size} | ${tp} | ${objs} | ${gavg} | ${pavg} |" >> "${SUMMARY_MD}"
done < "${SUMMARY_TSV}"
echo "" >> "${SUMMARY_MD}"
}
# ---- 主流程 ----
mkdir -p "${RESULT_DIR}"
log "CONFIG host=${HOST} bucket=${BUCKET} concurrency=${CONCURRENCY} duration=${DURATION} get_objects=${GET_OBJECTS} sleep_between_rounds=${SLEEP_BETWEEN_ROUNDS}s"
printf 'method\tsize\tthroughput\tobj_per_s\treq_avg\treq_p50\treq_p90\treq_p99\tttfb_avg\tttfb_p99\tttfb_worst\n' > "${SUMMARY_TSV}"
if [[ "${1:-}" == "--parse-only" ]]; then
for method in "${METHODS[@]}"; do
for size in "${SIZES[@]}"; do
outfile="${RESULT_DIR}/${method}_${size}.txt"
if [[ -s "${outfile}" ]]; then
parse_round "${method}" "${size}" "${outfile}"
fi
done
done
gen_summary_md
echo "parsed from ${RESULT_DIR}"
echo ""
cat "${SUMMARY_MD}"
exit 0
fi
for method in "${METHODS[@]}"; do
for size in "${SIZES[@]}"; do
ROUND=$((ROUND + 1))
outfile="${RESULT_DIR}/${method}_${size}.txt"
log "START round=${ROUND}/${TOTAL_ROUNDS} method=${method} size=${size} concurrency=${CONCURRENCY} duration=${DURATION}"
extra_args=()
if [[ "${method}" != "put" ]]; then
extra_args=(--objects "${GET_OBJECTS}")
fi
start_epoch=$(date +%s)
warp "${method}" \
--host "${HOST}" \
--access-key "${ACCESS_KEY}" \
--secret-key "${SECRET_KEY}" \
--bucket "${BUCKET}" \
--concurrent "${CONCURRENCY}" \
--duration "${DURATION}" \
--obj.size "${size}" \
"${extra_args[@]}" \
--no-color 2>&1 | tee "${outfile}"
rc=${PIPESTATUS[0]}
end_epoch=$(date +%s)
if [[ ${rc} -eq 0 ]]; then
parse_round "${method}" "${size}" "${outfile}"
log "END round=${ROUND}/${TOTAL_ROUNDS} method=${method} size=${size} rc=${rc} elapsed=$((end_epoch - start_epoch))s parsed=ok"
else
log "END round=${ROUND}/${TOTAL_ROUNDS} method=${method} size=${size} rc=${rc} elapsed=$((end_epoch - start_epoch))s parsed=skipped"
fi
if [[ ${ROUND} -lt ${TOTAL_ROUNDS} ]]; then
log "SLEEP ${SLEEP_BETWEEN_ROUNDS}s before next round"
sleep "${SLEEP_BETWEEN_ROUNDS}"
fi
done
done
gen_summary_md
log "ALL_ROUNDS_COMPLETE summary_tsv=${SUMMARY_TSV} summary_md=${SUMMARY_MD}"
echo ""
echo "==== 结果汇总 ===="
cat "${SUMMARY_MD}"