Skip to content

Commit aaeba4d

Browse files
committed
Initial benchmark version
1 parent 2b5bab8 commit aaeba4d

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ yalc.lock
6767
/spec/dummy/.bsb.lock
6868
/spec/dummy/**/*.res.js
6969

70+
# Performance test results
71+
/bench_results
72+
7073
# Generated by ROR FS-based Registry
7174
generated
7275

spec/performance/bench.sh

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
#set -x # Uncomment for debugging commands
4+
5+
# Benchmark parameters
6+
TARGET="http://${BASE_URL:-localhost:3001}/${ROUTE:-server_side_hello_world_hooks}"
7+
# requests per second; if "max" will get maximum number of queries instead of a fixed rate
8+
RATE=${RATE:-50}
9+
# virtual users for k6
10+
VUS=${VUS:-100}
11+
DURATION_SEC=${DURATION_SEC:-10}
12+
DURATION="${DURATION_SEC}s"
13+
# Tools to run (comma-separated)
14+
TOOLS=${TOOLS:-fortio,vegeta,k6}
15+
16+
OUTDIR="bench_results"
17+
18+
# Precompute checks for each tool
19+
RUN_FORTIO=0
20+
RUN_VEGETA=0
21+
RUN_K6=0
22+
[[ ",$TOOLS," == *",fortio,"* ]] && RUN_FORTIO=1
23+
[[ ",$TOOLS," == *",vegeta,"* ]] && RUN_VEGETA=1
24+
[[ ",$TOOLS," == *",k6,"* ]] && RUN_K6=1
25+
26+
for cmd in ${TOOLS//,/ } jq column awk tee; do
27+
if ! command -v "$cmd" >/dev/null 2>&1; then
28+
echo "Error: required tool '$cmd' is not installed" >&2
29+
exit 1
30+
fi
31+
done
32+
33+
TIMEOUT_SEC=60
34+
START=$(date +%s)
35+
until curl -fsS "$TARGET" >/dev/null; do
36+
if (( $(date +%s) - START > TIMEOUT_SEC )); then
37+
echo "Error: Target $TARGET not responding within ${TIMEOUT_SEC}s" >&2
38+
exit 1
39+
fi
40+
sleep 1
41+
done
42+
43+
mkdir -p "$OUTDIR"
44+
45+
if [ "$RATE" = "max" ]; then
46+
FORTIO_ARGS=(-qps 0)
47+
VEGETA_ARGS=()
48+
K6_SCENARIOS="{
49+
max_rate: {
50+
executor: 'shared-iterations',
51+
vus: $VUS,
52+
iterations: $((VUS * DURATION_SEC * 10)),
53+
maxDuration: '$DURATION'
54+
}
55+
}"
56+
else
57+
FORTIO_ARGS=(-qps "$RATE" -uniform)
58+
VEGETA_ARGS=(-rate="$RATE")
59+
K6_SCENARIOS="{
60+
constant_rate: {
61+
executor: 'constant-arrival-rate',
62+
rate: $RATE,
63+
timeUnit: '1s',
64+
duration: '$DURATION',
65+
preAllocatedVUs: $VUS,
66+
maxVUs: $((VUS * 10))
67+
}
68+
}"
69+
fi
70+
71+
if (( RUN_FORTIO )); then
72+
echo "===> Fortio"
73+
# TODO https://github.com/fortio/fortio/wiki/FAQ#i-want-to-get-the-best-results-what-flags-should-i-pass
74+
fortio load "${FORTIO_ARGS[@]}" -t "$DURATION" -timeout 30s -json "$OUTDIR/fortio.json" "$TARGET" \
75+
| tee "$OUTDIR/fortio.txt"
76+
fi
77+
78+
if (( RUN_VEGETA )); then
79+
echo
80+
echo "===> Vegeta"
81+
echo "GET $TARGET" | vegeta attack "${VEGETA_ARGS[@]}" -duration="$DURATION" \
82+
| tee "$OUTDIR/vegeta.bin" \
83+
| vegeta report | tee "$OUTDIR/vegeta.txt"
84+
vegeta report -type=json "$OUTDIR/vegeta.bin" > "$OUTDIR/vegeta.json"
85+
fi
86+
87+
if (( RUN_K6 )); then
88+
echo
89+
echo "===> k6"
90+
cat <<EOF > "$OUTDIR/k6_test.js"
91+
import http from 'k6/http';
92+
import { check } from 'k6';
93+
94+
export const options = {
95+
scenarios: $K6_SCENARIOS,
96+
};
97+
98+
export default function () {
99+
check(http.get('$TARGET'), {
100+
'status=200': r => r.status === 200,
101+
// you can add more if needed:
102+
// 'status=500': r => r.status === 500,
103+
});
104+
}
105+
EOF
106+
107+
k6 run --summary-export="$OUTDIR/k6_summary.json" --summary-trend-stats "min,avg,med,max,p(90),p(99)" "$OUTDIR/k6_test.js" | tee "$OUTDIR/k6.txt"
108+
fi
109+
110+
echo
111+
echo "===> Parsing results and generating summary"
112+
113+
echo -e "Tool\tRPS\tp50(ms)\tp90(ms)\tp99(ms)\tStatus" > "$OUTDIR/summary.txt"
114+
115+
if (( RUN_FORTIO )); then
116+
FORTIO_RPS=$(jq '.ActualQPS' "$OUTDIR/fortio.json" | awk '{printf "%.2f", $1}')
117+
FORTIO_P50=$(jq '.DurationHistogram.Percentiles[] | select(.Percentile==50) | .Value * 1000' "$OUTDIR/fortio.json" | awk '{printf "%.2f", $1}')
118+
FORTIO_P90=$(jq '.DurationHistogram.Percentiles[] | select(.Percentile==90) | .Value * 1000' "$OUTDIR/fortio.json" | awk '{printf "%.2f", $1}')
119+
FORTIO_P99=$(jq '.DurationHistogram.Percentiles[] | select(.Percentile==99) | .Value * 1000' "$OUTDIR/fortio.json" | awk '{printf "%.2f", $1}')
120+
FORTIO_STATUS=$(jq -r '.RetCodes | to_entries | map("\(.key)=\(.value)") | join(",")' "$OUTDIR/fortio.json")
121+
echo -e "Fortio\t$FORTIO_RPS\t$FORTIO_P50\t$FORTIO_P90\t$FORTIO_P99\t$FORTIO_STATUS" >> "$OUTDIR/summary.txt"
122+
fi
123+
124+
if (( RUN_VEGETA )); then
125+
# .throughput is successful_reqs/total_period, .rate is all_requests/attack_period
126+
VEGETA_RPS=$(jq '.throughput' "$OUTDIR/vegeta.json" | awk '{printf "%.2f", $1}')
127+
VEGETA_P50=$(jq '.latencies["50th"] / 1000000' "$OUTDIR/vegeta.json" | awk '{printf "%.2f", $1}')
128+
VEGETA_P90=$(jq '.latencies["90th"] / 1000000' "$OUTDIR/vegeta.json" | awk '{printf "%.2f", $1}')
129+
VEGETA_P99=$(jq '.latencies["99th"] / 1000000' "$OUTDIR/vegeta.json" | awk '{printf "%.2f", $1}')
130+
VEGETA_STATUS=$(jq -r '.status_codes | to_entries | map("\(.key)=\(.value)") | join(",")' "$OUTDIR/vegeta.json")
131+
echo -e "Vegeta\t$VEGETA_RPS\t$VEGETA_P50\t$VEGETA_P90\t$VEGETA_P99\t$VEGETA_STATUS" >> "$OUTDIR/summary.txt"
132+
fi
133+
134+
if (( RUN_K6 )); then
135+
K6_RPS=$(jq '.metrics.iterations.rate' "$OUTDIR/k6_summary.json" | awk '{printf "%.2f", $1}')
136+
K6_P50=$(jq '.metrics.http_req_duration.med' "$OUTDIR/k6_summary.json" | awk '{printf "%.2f", $1}')
137+
K6_P90=$(jq '.metrics.http_req_duration["p(90)"]' "$OUTDIR/k6_summary.json" | awk '{printf "%.2f", $1}')
138+
K6_P99=$(jq '.metrics.http_req_duration["p(99)"]' "$OUTDIR/k6_summary.json" | awk '{printf "%.2f", $1}')
139+
# Status: compute successful vs failed requests
140+
K6_REQS_TOTAL=$(jq '.metrics.http_reqs.count' "$OUTDIR/k6_summary.json")
141+
K6_STATUS=$(jq -r '
142+
.root_group.checks
143+
| to_entries
144+
| map(.key[7:] + "=" + (.value.passes|tostring))
145+
| join(",")
146+
' "$OUTDIR/k6_summary.json")
147+
K6_REQS_KNOWN_STATUS=$(jq -r '
148+
.root_group.checks
149+
| to_entries
150+
| map(.value.passes)
151+
| add
152+
' "$OUTDIR/k6_summary.json")
153+
K6_REQS_OTHER=$(( K6_REQS_TOTAL - K6_REQS_KNOWN_STATUS ))
154+
if [ "$K6_REQS_OTHER" -gt 0 ]; then
155+
K6_STATUS="$K6_STATUS,other=$K6_REQS_OTHER"
156+
fi
157+
echo -e "k6\t$K6_RPS\t$K6_P50\t$K6_P90\t$K6_P99\t$K6_STATUS" >> "$OUTDIR/summary.txt"
158+
fi
159+
160+
echo
161+
echo "Summary saved to $OUTDIR/summary.txt"
162+
column -t -s $'\t' "$OUTDIR/summary.txt"

0 commit comments

Comments
 (0)