test: add hardware benchmark scripts and Mac results

Includes bench.sh, bench-ai.sh, bench-limits.sh and corresponding
result files for Mac hardware baseline.
This commit is contained in:
ashim-hq
2026-04-24 14:23:12 +08:00
parent 2f09618897
commit 9f9fbf1fd7
4 changed files with 839 additions and 0 deletions
+188
View File
@@ -0,0 +1,188 @@
#!/usr/bin/env bash
set -euo pipefail
SYSTEM="${1:?Usage: bench-ai.sh <system-name> <fixture-dir> [port] [gpu-mode]}"
FIXTURE_DIR="${2:?Usage: bench-ai.sh <system-name> <fixture-dir> [port] [gpu-mode]}"
PORT="${3:-1349}"
GPU_MODE="${4:-gpu}"
BASE_URL="http://localhost:${PORT}"
RESULTS_FILE="bench-ai-results-${SYSTEM}-${GPU_MODE}.jsonl"
CONTAINER_NAME="ashim"
log() { echo "[$(date +%H:%M:%S)] $*" >&2; }
get_token() {
curl -sf -X POST "${BASE_URL}/api/auth/login" \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])"
}
get_container_id() {
docker ps -q -f name="${CONTAINER_NAME}" | head -1
}
docker_mem_mb() {
local cid="$1"
docker stats "$cid" --no-stream --format "{{.MemUsage}}" 2>/dev/null | awk -F/ '{gsub(/[^0-9.]/, "", $1); if($1+0 > 0) print $1; else print 0}'
}
docker_cpu_pct() {
local cid="$1"
docker stats "$cid" --no-stream --format "{{.CPUPerc}}" 2>/dev/null | tr -d '%'
}
gpu_vram_mb() {
nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits 2>/dev/null || echo "0"
}
record() {
local tier="$1" tool="$2" variant="$3" time_s="$4" pass="$5" output_size="${6:-0}" mem_mb="${7:-0}" cpu_pct="${8:-0}" vram_mb="${9:-0}"
printf '{"system":"%s","tier":"%s","tool":"%s","variant":"%s","time_s":%s,"pass":%s,"output_size":%s,"mem_mb":%s,"cpu_pct":%s,"vram_mb":%s,"gpu_mode":"%s"}\n' \
"$SYSTEM" "$tier" "$tool" "$variant" "$time_s" "$pass" "$output_size" "$mem_mb" "$cpu_pct" "$vram_mb" "$GPU_MODE" >> "$RESULTS_FILE"
}
bench_ai_tool() {
local tool="$1" variant="$2" file="$3" settings="${4:-}" extra_args="${5:-}"
local cid time_s http_code mem_after cpu vram output_file pass output_size
cid=$(get_container_id)
output_file=$(mktemp)
local curl_args=(-s -X POST "${BASE_URL}/api/v1/tools/${tool}" -H "Authorization: Bearer ${TOKEN}")
if [ -n "$file" ] && [ "$file" != "NONE" ]; then
curl_args+=(-F "file=@${file}")
fi
if [ -n "$settings" ]; then
curl_args+=(-F "settings=${settings}")
fi
if [ -n "$extra_args" ]; then
eval "curl_args+=($extra_args)"
fi
curl_args+=(-o "$output_file" -w "%{http_code} %{time_total}")
local result
result=$(curl --max-time 300 "${curl_args[@]}" 2>/dev/null) || result="000 0.000"
http_code=$(echo "$result" | awk '{print $1}')
time_s=$(echo "$result" | awk '{print $2}')
mem_after=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
cpu=$(docker_cpu_pct "$cid" 2>/dev/null || echo "0")
vram=$(gpu_vram_mb)
output_size=$(stat -c%s "$output_file" 2>/dev/null || stat -f%z "$output_file" 2>/dev/null || echo "0")
if [ "$http_code" = "200" ]; then
pass="true"
else
pass="false"
fi
record "ai" "$tool" "$variant" "$time_s" "$pass" "$output_size" "$mem_after" "$cpu" "$vram"
log "ai/$tool/$variant: ${time_s}s HTTP:${http_code} mem:${mem_after}MB vram:${vram}MB pass:${pass}"
rm -f "$output_file"
}
F="${FIXTURE_DIR}"
P="${F}/content/portrait-color.jpg"
ISO="${F}/content/portrait-isolated.png"
J="${F}/test-100x100.jpg"
BW="${F}/content/portrait-bw.jpeg"
OCR="${F}/content/ocr-chat.jpeg"
OCRJP="${F}/content/ocr-japanese.png"
FACE="${F}/content/multi-face.webp"
HEAD="${F}/content/portrait-headshot.heic"
REDEYE="${F}/content/red-eye.jpg"
S="${F}/test-200x150.png"
L="${F}/content/stress-large.jpg"
> "$RESULTS_FILE"
log "=== Starting AI benchmarks on ${SYSTEM} (${GPU_MODE}) ==="
TOKEN=$(get_token)
log "Auth token obtained"
for run in 1 2 3; do
log "--- AI Run $run of 3 ---"
bench_ai_tool "remove-background" "portrait-r${run}" "$P" '{"backgroundType":"transparent"}'
bench_ai_tool "remove-background" "isolated-r${run}" "$ISO" '{"backgroundType":"color","backgroundColor":"#0000FF"}'
bench_ai_tool "upscale" "2x-small-r${run}" "$J" '{"scale":2}'
bench_ai_tool "upscale" "2x-large-r${run}" "$P" '{"scale":2}'
bench_ai_tool "upscale" "face-r${run}" "$P" '{"scale":2,"faceEnhance":true}'
bench_ai_tool "ocr" "fast-r${run}" "$OCR" '{"quality":"fast","language":"en"}'
bench_ai_tool "ocr" "best-r${run}" "$OCR" '{"quality":"best","language":"en"}'
bench_ai_tool "ocr" "japanese-r${run}" "$OCRJP" '{"quality":"balanced","language":"ja"}'
bench_ai_tool "blur-faces" "r${run}" "$FACE" '{"blurRadius":30,"sensitivity":0.5}'
bench_ai_tool "smart-crop" "face-r${run}" "$P" '{"mode":"face","width":400,"height":400}'
bench_ai_tool "colorize" "r${run}" "$BW" '{"intensity":1.0}'
bench_ai_tool "enhance-faces" "gfpgan-r${run}" "$P" '{"model":"gfpgan","strength":0.8}'
bench_ai_tool "enhance-faces" "codeformer-r${run}" "$P" '{"model":"codeformer","strength":0.7}'
bench_ai_tool "noise-removal" "quick-r${run}" "$S" '{"tier":"quick"}'
bench_ai_tool "noise-removal" "quality-r${run}" "$L" '{"tier":"quality"}'
bench_ai_tool "red-eye-removal" "r${run}" "$REDEYE" '{"sensitivity":50,"strength":80}'
bench_ai_tool "restore-photo" "full-r${run}" "$BW" '{"mode":"auto","scratchRemoval":true,"faceEnhancement":true,"colorize":true}'
bench_ai_tool "passport-photo" "r${run}" "$HEAD" ''
bench_ai_tool "content-aware-resize" "face-r${run}" "$P" '{"width":300,"protectFaces":true}'
done
log "=== TIER 3: AI Batch Processing ==="
for batch_size in 3 5; do
log "AI Batch ${batch_size} - remove-background"
output_file=$(mktemp)
cid=$(get_container_id)
curl_args=(-s -X POST "${BASE_URL}/api/v1/tools/remove-background" -H "Authorization: Bearer ${TOKEN}")
for i in $(seq 1 "$batch_size"); do
curl_args+=(-F "file=@${P}")
done
curl_args+=(-F 'settings={"backgroundType":"transparent"}')
curl_args+=(-o "$output_file" -w "%{http_code} %{time_total}")
result=$(curl --max-time 600 "${curl_args[@]}" 2>/dev/null) || result="000 0.000"
http_code=$(echo "$result" | awk '{print $1}')
time_s=$(echo "$result" | awk '{print $2}')
mem_after=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
vram=$(gpu_vram_mb)
pass=$( [ "$http_code" = "200" ] && echo "true" || echo "false" )
record "ai-batch" "remove-background" "b${batch_size}" "$time_s" "$pass" "0" "$mem_after" "0" "$vram"
log "ai-batch/remove-background/b${batch_size}: ${time_s}s HTTP:${http_code}"
rm -f "$output_file"
done
log "=== Sustained AI Load (10 cycles) ==="
cid=$(get_container_id)
mem_start=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
vram_start=$(gpu_vram_mb)
for i in $(seq 1 10); do
bench_ai_tool "remove-background" "sustained-${i}" "$P" '{"backgroundType":"transparent"}'
done
mem_end=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
vram_end=$(gpu_vram_mb)
printf '{"system":"%s","tier":"ai-sustained-summary","gpu_mode":"%s","mem_start_mb":%s,"mem_end_mb":%s,"vram_start_mb":%s,"vram_end_mb":%s}\n' \
"$SYSTEM" "$GPU_MODE" "$mem_start" "$mem_end" "$vram_start" "$vram_end" >> "$RESULTS_FILE"
log "=== ALL AI BENCHMARKS COMPLETE for ${SYSTEM} (${GPU_MODE}) ==="
log "Results in: ${RESULTS_FILE}"
wc -l "$RESULTS_FILE" | awk '{print $1 " AI benchmark records written"}'
+191
View File
@@ -0,0 +1,191 @@
#!/usr/bin/env bash
set -euo pipefail
SYSTEM="${1:?Usage: bench-limits.sh <system-name> <fixture-dir> <docker-image>}"
FIXTURE_DIR="${2:?}"
DOCKER_IMAGE="${3:-ashim:latest}"
PORT=13491
BASE_URL="http://localhost:${PORT}"
RESULTS_FILE="bench-limits-results-${SYSTEM}.jsonl"
CONTAINER_NAME="ashim-bench-limits"
log() { echo "[$(date +%H:%M:%S)] $*" >&2; }
> "$RESULTS_FILE"
cleanup() {
docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
}
start_container() {
local cpus="$1" memory="$2"
cleanup
log "Starting container: --cpus=${cpus} --memory=${memory}"
docker run --rm -d \
--cpus="$cpus" --memory="$memory" \
-p "${PORT}:1349" \
-e AUTH_ENABLED=false \
-e SKIP_MUST_CHANGE_PASSWORD=true \
--name "$CONTAINER_NAME" \
"$DOCKER_IMAGE" >/dev/null
log "Waiting for health..."
local attempts=0
while ! curl -sf "http://localhost:${PORT}/api/v1/health" >/dev/null 2>&1; do
sleep 2
attempts=$((attempts + 1))
if [ "$attempts" -gt 60 ]; then
log "Container failed to become healthy after 120s"
return 1
fi
done
log "Container healthy"
}
run_bench() {
local cpus="$1" memory="$2" tool="$3" variant="$4" file="$5" settings="${6:-}"
local output_file time_s http_code pass output_size mem_after
output_file=$(mktemp)
local curl_args=(-s --max-time 120 -X POST "${BASE_URL}/api/v1/tools/${tool}")
if [ -n "$file" ] && [ "$file" != "NONE" ]; then
curl_args+=(-F "file=@${file}")
fi
if [ -n "$settings" ]; then
curl_args+=(-F "settings=${settings}")
fi
curl_args+=(-o "$output_file" -w "%{http_code} %{time_total}")
local result
result=$(curl "${curl_args[@]}" 2>/dev/null) || result="000 0.000"
http_code=$(echo "$result" | awk '{print $1}')
time_s=$(echo "$result" | awk '{print $2}')
mem_after=$(docker stats "$CONTAINER_NAME" --no-stream --format "{{.MemUsage}}" 2>/dev/null | awk -F/ '{gsub(/[^0-9.]/, "", $1); if($1+0 > 0) print $1; else print 0}' || echo "0")
output_size=$(stat -c%s "$output_file" 2>/dev/null || stat -f%z "$output_file" 2>/dev/null || echo "0")
if [ "$http_code" = "200" ]; then
pass="true"
else
pass="false"
fi
printf '{"system":"%s","tier":"resource-limit","cpus":"%s","memory":"%s","tool":"%s","variant":"%s","time_s":%s,"pass":%s,"output_size":%s,"mem_mb":%s}\n' \
"$SYSTEM" "$cpus" "$memory" "$tool" "$variant" "$time_s" "$pass" "$output_size" "$mem_after" >> "$RESULTS_FILE"
log "cpus=${cpus} mem=${memory} ${tool}/${variant}: ${time_s}s HTTP:${http_code} pass:${pass}"
rm -f "$output_file"
}
run_batch_bench() {
local cpus="$1" memory="$2" count="$3"
local output_file time_s http_code pass mem_after
output_file=$(mktemp)
local curl_args=(-s --max-time 180 -X POST "${BASE_URL}/api/v1/tools/resize")
for i in $(seq 1 "$count"); do
curl_args+=(-F "file=@${F}/test-200x150.png")
done
curl_args+=(-F 'settings={"width":100}')
curl_args+=(-o "$output_file" -w "%{http_code} %{time_total}")
local result
result=$(curl "${curl_args[@]}" 2>/dev/null) || result="000 0.000"
http_code=$(echo "$result" | awk '{print $1}')
time_s=$(echo "$result" | awk '{print $2}')
mem_after=$(docker stats "$CONTAINER_NAME" --no-stream --format "{{.MemUsage}}" 2>/dev/null | awk -F/ '{gsub(/[^0-9.]/, "", $1); if($1+0 > 0) print $1; else print 0}' || echo "0")
if [ "$http_code" = "200" ]; then pass="true"; else pass="false"; fi
printf '{"system":"%s","tier":"resource-limit","cpus":"%s","memory":"%s","tool":"batch-resize","variant":"b%d","time_s":%s,"pass":%s,"output_size":0,"mem_mb":%s}\n' \
"$SYSTEM" "$cpus" "$memory" "$count" "$time_s" "$pass" "$mem_after" >> "$RESULTS_FILE"
log "cpus=${cpus} mem=${memory} batch-resize/b${count}: ${time_s}s HTTP:${http_code} pass:${pass}"
rm -f "$output_file"
}
F="${FIXTURE_DIR}"
L="${F}/content/stress-large.jpg"
log "=== Resource Limit Sweep on ${SYSTEM} ==="
configs=(
"1:512m"
"1:1g"
"1:2g"
"2:1g"
"2:2g"
"2:4g"
"4:2g"
"4:4g"
)
for config in "${configs[@]}"; do
cpus="${config%%:*}"
memory="${config##*:}"
if start_container "$cpus" "$memory"; then
sleep 2
run_bench "$cpus" "$memory" "resize" "large" "$L" '{"width":800,"fit":"cover"}'
run_bench "$cpus" "$memory" "compress" "targetSize" "$L" '{"mode":"targetSize","targetSizeKb":500}'
run_bench "$cpus" "$memory" "convert" "avif" "$L" '{"format":"avif","quality":50}'
run_batch_bench "$cpus" "$memory" 5
run_bench "$cpus" "$memory" "collage" "4img" "NONE" '' \
|| log "Collage test skipped (needs multi-file support)"
cleanup
else
log "FAILED to start container at cpus=${cpus} mem=${memory}"
printf '{"system":"%s","tier":"resource-limit","cpus":"%s","memory":"%s","tool":"startup","variant":"failed","time_s":0,"pass":false,"output_size":0,"mem_mb":0}\n' \
"$SYSTEM" "$cpus" "$memory" >> "$RESULTS_FILE"
cleanup
fi
done
log "=== Cold Start Timing ==="
for config in "1:512m" "2:2g" "4:4g"; do
cpus="${config%%:*}"
memory="${config##*:}"
cleanup
start_time=$(date +%s%N)
docker run --rm -d --cpus="$cpus" --memory="$memory" -p "${PORT}:1349" \
-e AUTH_ENABLED=false -e SKIP_MUST_CHANGE_PASSWORD=true \
--name "$CONTAINER_NAME" "$DOCKER_IMAGE" >/dev/null
attempts=0
while ! curl -sf "http://localhost:${PORT}/api/v1/health" >/dev/null 2>&1; do
sleep 0.5
attempts=$((attempts + 1))
if [ "$attempts" -gt 120 ]; then break; fi
done
end_time=$(date +%s%N)
startup_s=$(echo "scale=3; ($end_time - $start_time) / 1000000000" | bc 2>/dev/null || echo "0")
printf '{"system":"%s","tier":"cold-start","cpus":"%s","memory":"%s","startup_s":%s}\n' \
"$SYSTEM" "$cpus" "$memory" "$startup_s" >> "$RESULTS_FILE"
log "Cold start cpus=${cpus} mem=${memory}: ${startup_s}s"
cleanup
done
log "=== Resource Limit Sweep COMPLETE ==="
log "Results in: ${RESULTS_FILE}"
wc -l "$RESULTS_FILE" | awk '{print $1 " records written"}'
+94
View File
@@ -0,0 +1,94 @@
{"system":"mac","tier":"core","tool":"resize","variant":"small-r1","time_s":0.014626,"pass":true,"output_size":181,"mem_mb":279.6,"cpu_pct":0.11}
{"system":"mac","tier":"core","tool":"resize","variant":"large-r1","time_s":0.301508,"pass":true,"output_size":188,"mem_mb":356.1,"cpu_pct":0.11}
{"system":"mac","tier":"core","tool":"crop","variant":"small-r1","time_s":0.008267,"pass":true,"output_size":179,"mem_mb":356.1,"cpu_pct":0.04}
{"system":"mac","tier":"core","tool":"crop","variant":"large-r1","time_s":0.273358,"pass":true,"output_size":184,"mem_mb":425.7,"cpu_pct":0.10}
{"system":"mac","tier":"core","tool":"rotate","variant":"small-r1","time_s":0.014258,"pass":true,"output_size":181,"mem_mb":426,"cpu_pct":0.03}
{"system":"mac","tier":"core","tool":"rotate","variant":"large-r1","time_s":0.376651,"pass":true,"output_size":189,"mem_mb":545.7,"cpu_pct":0.12}
{"system":"mac","tier":"core","tool":"convert","variant":"jpg-webp-small-r1","time_s":0.006595,"pass":true,"output_size":175,"mem_mb":545.5,"cpu_pct":0.28}
{"system":"mac","tier":"core","tool":"convert","variant":"jpg-webp-large-r1","time_s":1.328183,"pass":true,"output_size":182,"mem_mb":532.2,"cpu_pct":0.14}
{"system":"mac","tier":"core","tool":"convert","variant":"jpg-avif-small-r1","time_s":0.010060,"pass":true,"output_size":175,"mem_mb":532.6,"cpu_pct":0.08}
{"system":"mac","tier":"core","tool":"convert","variant":"jpg-avif-large-r1","time_s":2.741221,"pass":true,"output_size":182,"mem_mb":603.7,"cpu_pct":0.11}
{"system":"mac","tier":"core","tool":"compress","variant":"quality-small-r1","time_s":0.020368,"pass":true,"output_size":183,"mem_mb":603.2,"cpu_pct":0.05}
{"system":"mac","tier":"core","tool":"compress","variant":"quality-large-r1","time_s":0.331297,"pass":true,"output_size":190,"mem_mb":615,"cpu_pct":0.08}
{"system":"mac","tier":"core","tool":"compress","variant":"targetSize-large-r1","time_s":0.427988,"pass":true,"output_size":190,"mem_mb":695.1,"cpu_pct":0.18}
{"system":"mac","tier":"core","tool":"strip-metadata","variant":"small-r1","time_s":0.009445,"pass":true,"output_size":191,"mem_mb":695.3,"cpu_pct":0.11}
{"system":"mac","tier":"core","tool":"strip-metadata","variant":"large-r1","time_s":0.972625,"pass":true,"output_size":197,"mem_mb":718.3,"cpu_pct":0.10}
{"system":"mac","tier":"core","tool":"edit-metadata","variant":"small-r1","time_s":0.182409,"pass":true,"output_size":177,"mem_mb":718.5,"cpu_pct":0.08}
{"system":"mac","tier":"core","tool":"edit-metadata","variant":"large-r1","time_s":0.218451,"pass":true,"output_size":182,"mem_mb":718.7,"cpu_pct":0.84}
{"system":"mac","tier":"core","tool":"color-adjustments","variant":"small-r1","time_s":0.007756,"pass":false,"output_size":40,"mem_mb":671.7,"cpu_pct":0.04}
{"system":"mac","tier":"core","tool":"color-adjustments","variant":"large-r1","time_s":0.003707,"pass":false,"output_size":40,"mem_mb":266.7,"cpu_pct":0.33}
{"system":"mac","tier":"core","tool":"sharpening","variant":"small-r1","time_s":0.018643,"pass":true,"output_size":185,"mem_mb":510.6,"cpu_pct":99.66}
{"system":"mac","tier":"core","tool":"sharpening","variant":"large-r1","time_s":0.744501,"pass":true,"output_size":193,"mem_mb":511,"cpu_pct":98.44}
{"system":"mac","tier":"core","tool":"watermark-text","variant":"small-r1","time_s":0.045615,"pass":true,"output_size":189,"mem_mb":511.8,"cpu_pct":99.62}
{"system":"mac","tier":"core","tool":"watermark-text","variant":"large-r1","time_s":0.597063,"pass":true,"output_size":197,"mem_mb":509.4,"cpu_pct":97.67}
{"system":"mac","tier":"core","tool":"compose","variant":"small-r1","time_s":0.002649,"pass":false,"output_size":37,"mem_mb":496.5,"cpu_pct":96.50}
{"system":"mac","tier":"core","tool":"compose","variant":"large-r1","time_s":0.073550,"pass":false,"output_size":37,"mem_mb":156.5,"cpu_pct":0.05}
{"system":"mac","tier":"core","tool":"collage","variant":"4img-small-r1","time_s":0.228542,"pass":true,"output_size":172,"mem_mb":157.3,"cpu_pct":0}
{"system":"mac","tier":"core","tool":"collage","variant":"4img-large-r1","time_s":0.803269,"pass":true,"output_size":177,"mem_mb":778.5,"cpu_pct":0.06}
{"system":"mac","tier":"core","tool":"stitch","variant":"3img-small-r1","time_s":0.015616,"pass":true,"output_size":169,"mem_mb":288.5,"cpu_pct":0.21}
{"system":"mac","tier":"core","tool":"stitch","variant":"3img-large-r1","time_s":0.643335,"pass":true,"output_size":176,"mem_mb":549.8,"cpu_pct":101.54}
{"system":"mac","tier":"core","tool":"split","variant":"small-r1","time_s":0.023203,"pass":true,"output_size":1134,"mem_mb":746.8,"cpu_pct":96.29}
{"system":"mac","tier":"core","tool":"split","variant":"large-r1","time_s":0.383536,"pass":true,"output_size":1101452,"mem_mb":772,"cpu_pct":102.62}
{"system":"mac","tier":"core","tool":"border","variant":"small-r1","time_s":0.032606,"pass":true,"output_size":182,"mem_mb":805.2,"cpu_pct":99.91}
{"system":"mac","tier":"core","tool":"border","variant":"large-r1","time_s":3.942523,"pass":true,"output_size":183,"mem_mb":519.7,"cpu_pct":0.53}
{"system":"mac","tier":"core","tool":"svg-to-raster","variant":"small-r1","time_s":0.152959,"pass":true,"output_size":176,"mem_mb":0,"cpu_pct":0}
{"system":"mac","tier":"core","tool":"vectorize","variant":"small-r1","time_s":0.010541,"pass":true,"output_size":174,"mem_mb":303.4,"cpu_pct":2.73}
{"system":"mac","tier":"core","tool":"vectorize","variant":"large-r1","time_s":0.457126,"pass":true,"output_size":182,"mem_mb":574.3,"cpu_pct":99.77}
{"system":"mac","tier":"core","tool":"gif-tools","variant":"optimize-r1","time_s":0.088603,"pass":true,"output_size":194,"mem_mb":827.6,"cpu_pct":103.66}
{"system":"mac","tier":"core","tool":"pdf-to-image","variant":"r1","time_s":0.372870,"pass":true,"output_size":521,"mem_mb":802.9,"cpu_pct":99.53}
{"system":"mac","tier":"core","tool":"optimize-for-web","variant":"large-r1","time_s":0.818123,"pass":true,"output_size":182,"mem_mb":835.9,"cpu_pct":100.16}
{"system":"mac","tier":"core","tool":"favicon","variant":"small-r1","time_s":0.034346,"pass":true,"output_size":2468,"mem_mb":861.8,"cpu_pct":98.31}
{"system":"mac","tier":"core","tool":"favicon","variant":"large-r1","time_s":0.051474,"pass":true,"output_size":736200,"mem_mb":490.1,"cpu_pct":6.14}
{"system":"mac","tier":"core","tool":"image-to-pdf","variant":"3img-r1","time_s":0.029216,"pass":true,"output_size":180,"mem_mb":0,"cpu_pct":0.00}
{"system":"mac","tier":"core","tool":"replace-color","variant":"small-r1","time_s":0.007426,"pass":true,"output_size":188,"mem_mb":834.7,"cpu_pct":0.11}
{"system":"mac","tier":"core","tool":"replace-color","variant":"large-r1","time_s":1.004593,"pass":true,"output_size":197,"mem_mb":839.8,"cpu_pct":198.06}
{"system":"mac","tier":"core","tool":"info","variant":"small-r1","time_s":0.014790,"pass":true,"output_size":537,"mem_mb":916.4,"cpu_pct":198.94}
{"system":"mac","tier":"core","tool":"info","variant":"large-r1","time_s":0.367496,"pass":true,"output_size":490,"mem_mb":616.5,"cpu_pct":0.65}
{"system":"mac","tier":"core","tool":"compare","variant":"small-r1","time_s":0.019049,"pass":true,"output_size":224,"mem_mb":0,"cpu_pct":0.00}
{"system":"mac","tier":"core","tool":"compare","variant":"large-r1","time_s":1.237066,"pass":true,"output_size":234,"mem_mb":888.1,"cpu_pct":0.12}
{"system":"mac","tier":"core","tool":"find-duplicates","variant":"5img-r1","time_s":0.031787,"pass":true,"output_size":3427,"mem_mb":378.2,"cpu_pct":0.14}
{"system":"mac","tier":"core","tool":"color-palette","variant":"small-r1","time_s":0.016019,"pass":true,"output_size":134,"mem_mb":860.2,"cpu_pct":198.97}
{"system":"mac","tier":"core","tool":"color-palette","variant":"large-r1","time_s":0.116914,"pass":true,"output_size":132,"mem_mb":936.1,"cpu_pct":0.05}
{"system":"mac","tier":"core","tool":"barcode-read","variant":"r1","time_s":0.136906,"pass":true,"output_size":79,"mem_mb":609.7,"cpu_pct":0}
{"system":"mac","tier":"core","tool":"image-to-base64","variant":"small-r1","time_s":0.005899,"pass":true,"output_size":615,"mem_mb":809.6,"cpu_pct":1.25}
{"system":"mac","tier":"core","tool":"image-to-base64","variant":"large-r1","time_s":1.250363,"pass":true,"output_size":1298304,"mem_mb":887.9,"cpu_pct":198.25}
{"system":"mac","tier":"core","tool":"content-aware-resize","variant":"small-r1","time_s":0.274548,"pass":true,"output_size":204,"mem_mb":979.4,"cpu_pct":171.83}
{"system":"mac","tier":"core","tool":"content-aware-resize","variant":"large-r1","time_s":120.532070,"pass":false,"output_size":188727,"mem_mb":0,"cpu_pct":0}
{"system":"mac","tier":"core","tool":"image-enhancement","variant":"small-r1","time_s":0.060936,"pass":true,"output_size":192,"mem_mb":839.1,"cpu_pct":0.16}
{"system":"mac","tier":"core","tool":"image-enhancement","variant":"large-r1","time_s":1.162208,"pass":true,"output_size":200,"mem_mb":871,"cpu_pct":0.12}
{"system":"mac","tier":"core","tool":"resize","variant":"small-r2","time_s":0.008022,"pass":true,"output_size":181,"mem_mb":870.5,"cpu_pct":0.12}
{"system":"mac","tier":"core","tool":"resize","variant":"large-r2","time_s":0.319399,"pass":true,"output_size":188,"mem_mb":836.4,"cpu_pct":0.06}
{"system":"mac","tier":"core","tool":"crop","variant":"small-r2","time_s":0.011819,"pass":true,"output_size":179,"mem_mb":836.4,"cpu_pct":0.05}
{"system":"mac","tier":"core","tool":"crop","variant":"large-r2","time_s":0.258094,"pass":true,"output_size":184,"mem_mb":836.6,"cpu_pct":0.15}
{"system":"mac","tier":"core","tool":"rotate","variant":"small-r2","time_s":0.009025,"pass":true,"output_size":181,"mem_mb":836.7,"cpu_pct":0.13}
{"system":"mac","tier":"core","tool":"rotate","variant":"large-r2","time_s":0.376644,"pass":true,"output_size":189,"mem_mb":836.9,"cpu_pct":0.13}
{"system":"mac","tier":"core","tool":"convert","variant":"jpg-webp-small-r2","time_s":0.009957,"pass":true,"output_size":175,"mem_mb":836.9,"cpu_pct":0.11}
{"system":"mac","tier":"core","tool":"convert","variant":"jpg-webp-large-r2","time_s":1.274019,"pass":true,"output_size":182,"mem_mb":851.5,"cpu_pct":0.15}
{"system":"mac","tier":"core","tool":"convert","variant":"jpg-avif-small-r2","time_s":0.011801,"pass":true,"output_size":175,"mem_mb":851.4,"cpu_pct":0.16}
{"system":"mac","tier":"core","tool":"convert","variant":"jpg-avif-large-r2","time_s":2.436089,"pass":true,"output_size":182,"mem_mb":889.2,"cpu_pct":0.12}
{"system":"mac","tier":"core","tool":"compress","variant":"quality-small-r2","time_s":0.012862,"pass":true,"output_size":183,"mem_mb":889.2,"cpu_pct":0.14}
{"system":"mac","tier":"core","tool":"compress","variant":"quality-large-r2","time_s":0.313211,"pass":true,"output_size":190,"mem_mb":854.5,"cpu_pct":0.13}
{"system":"mac","tier":"core","tool":"compress","variant":"targetSize-large-r2","time_s":0.421803,"pass":true,"output_size":190,"mem_mb":878.8,"cpu_pct":0.22}
{"system":"mac","tier":"core","tool":"strip-metadata","variant":"small-r2","time_s":0.008415,"pass":true,"output_size":191,"mem_mb":878.8,"cpu_pct":0.12}
{"system":"mac","tier":"core","tool":"strip-metadata","variant":"large-r2","time_s":0.965809,"pass":true,"output_size":197,"mem_mb":879.2,"cpu_pct":1.59}
{"system":"mac","tier":"core","tool":"edit-metadata","variant":"small-r2","time_s":0.160852,"pass":true,"output_size":177,"mem_mb":878.7,"cpu_pct":0.06}
{"system":"mac","tier":"core","tool":"edit-metadata","variant":"large-r2","time_s":0.220078,"pass":true,"output_size":182,"mem_mb":878.9,"cpu_pct":0.53}
{"system":"mac","tier":"core","tool":"color-adjustments","variant":"small-r2","time_s":0.004969,"pass":false,"output_size":40,"mem_mb":878.8,"cpu_pct":0.16}
{"system":"mac","tier":"core","tool":"color-adjustments","variant":"large-r2","time_s":0.002368,"pass":false,"output_size":40,"mem_mb":879.4,"cpu_pct":0.09}
{"system":"mac","tier":"core","tool":"sharpening","variant":"small-r2","time_s":0.020579,"pass":true,"output_size":185,"mem_mb":880,"cpu_pct":2.11}
{"system":"mac","tier":"core","tool":"sharpening","variant":"large-r2","time_s":0.715239,"pass":true,"output_size":193,"mem_mb":880.1,"cpu_pct":0.14}
{"system":"mac","tier":"core","tool":"watermark-text","variant":"small-r2","time_s":0.012788,"pass":true,"output_size":189,"mem_mb":880.1,"cpu_pct":0.12}
{"system":"mac","tier":"core","tool":"watermark-text","variant":"large-r2","time_s":0.578214,"pass":true,"output_size":197,"mem_mb":880.1,"cpu_pct":0.14}
{"system":"mac","tier":"core","tool":"compose","variant":"small-r2","time_s":0.002695,"pass":false,"output_size":37,"mem_mb":880.1,"cpu_pct":0.42}
{"system":"mac","tier":"core","tool":"compose","variant":"large-r2","time_s":0.072563,"pass":false,"output_size":37,"mem_mb":880.6,"cpu_pct":0.12}
{"system":"mac","tier":"core","tool":"collage","variant":"4img-small-r2","time_s":0.248801,"pass":true,"output_size":172,"mem_mb":886.2,"cpu_pct":1.74}
{"system":"mac","tier":"core","tool":"collage","variant":"4img-large-r2","time_s":0.763333,"pass":true,"output_size":177,"mem_mb":926.1,"cpu_pct":0.08}
{"system":"mac","tier":"core","tool":"stitch","variant":"3img-small-r2","time_s":0.012169,"pass":true,"output_size":169,"mem_mb":886.1,"cpu_pct":0.09}
{"system":"mac","tier":"core","tool":"stitch","variant":"3img-large-r2","time_s":0.542565,"pass":true,"output_size":176,"mem_mb":936.2,"cpu_pct":0.13}
{"system":"mac","tier":"core","tool":"split","variant":"small-r2","time_s":0.010531,"pass":true,"output_size":1134,"mem_mb":936.2,"cpu_pct":0.18}
{"system":"mac","tier":"core","tool":"split","variant":"large-r2","time_s":0.436432,"pass":true,"output_size":1101452,"mem_mb":936.2,"cpu_pct":0.07}
{"system":"mac","tier":"core","tool":"border","variant":"small-r2","time_s":0.037580,"pass":true,"output_size":182,"mem_mb":895.3,"cpu_pct":0.19}
{"system":"mac","tier":"core","tool":"border","variant":"large-r2","time_s":3.915338,"pass":true,"output_size":183,"mem_mb":898.5,"cpu_pct":0.07}
{"system":"mac","tier":"core","tool":"svg-to-raster","variant":"small-r2","time_s":0.166333,"pass":true,"output_size":176,"mem_mb":897.8,"cpu_pct":0.13}
{"system":"mac","tier":"core","tool":"vectorize","variant":"small-r2","time_s":0.010872,"pass":true,"output_size":174,"mem_mb":897.8,"cpu_pct":0.28}
{"system":"mac","tier":"core","tool":"vectorize","variant":"large-r2","time_s":0.412589,"pass":true,"output_size":182,"mem_mb":900.2,"cpu_pct":0.72}
+366
View File
@@ -0,0 +1,366 @@
#!/usr/bin/env bash
set -euo pipefail
SYSTEM="${1:?Usage: bench.sh <system-name> <fixture-dir> [port]}"
FIXTURE_DIR="${2:?Usage: bench.sh <system-name> <fixture-dir> [port]}"
PORT="${3:-1349}"
BASE_URL="http://localhost:${PORT}"
RESULTS_FILE="bench-results-${SYSTEM}.jsonl"
CONTAINER_NAME="ashim"
log() { echo "[$(date +%H:%M:%S)] $*" >&2; }
get_token() {
curl -sf -X POST "${BASE_URL}/api/auth/login" \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])"
}
get_container_id() {
docker ps -q -f name="${CONTAINER_NAME}" | head -1
}
docker_mem_mb() {
local cid="$1"
docker stats "$cid" --no-stream --format "{{.MemUsage}}" 2>/dev/null | awk -F/ '{gsub(/[^0-9.]/, "", $1); if($1+0 > 0) print $1; else print 0}'
}
docker_cpu_pct() {
local cid="$1"
docker stats "$cid" --no-stream --format "{{.CPUPerc}}" 2>/dev/null | tr -d '%'
}
record() {
local tier="$1" tool="$2" variant="$3" time_s="$4" pass="$5" output_size="${6:-0}" mem_mb="${7:-0}" cpu_pct="${8:-0}" extra="${9:-}"
printf '{"system":"%s","tier":"%s","tool":"%s","variant":"%s","time_s":%s,"pass":%s,"output_size":%s,"mem_mb":%s,"cpu_pct":%s%s}\n' \
"$SYSTEM" "$tier" "$tool" "$variant" "$time_s" "$pass" "$output_size" "$mem_mb" "$cpu_pct" "$extra" >> "$RESULTS_FILE"
}
bench_tool() {
local tier="$1" tool="$2" variant="$3" file="$4" settings="${5:-}" extra_args="${6:-}"
local cid time_s http_code mem_before mem_after cpu output_file pass output_size
cid=$(get_container_id)
mem_before=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
output_file=$(mktemp)
local timing_file=$(mktemp)
local curl_args=(-s -X POST "${BASE_URL}/api/v1/tools/${tool}" -H "Authorization: Bearer ${TOKEN}")
if [ -n "$file" ] && [ "$file" != "NONE" ]; then
curl_args+=(-F "file=@${file}")
fi
if [ -n "$settings" ]; then
curl_args+=(-F "settings=${settings}")
fi
if [ -n "$extra_args" ]; then
eval "curl_args+=($extra_args)"
fi
curl_args+=(-o "$output_file" -w "%{http_code} %{time_total}")
local result
result=$(curl "${curl_args[@]}" 2>/dev/null) || result="000 0.000"
http_code=$(echo "$result" | awk '{print $1}')
time_s=$(echo "$result" | awk '{print $2}')
mem_after=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
cpu=$(docker_cpu_pct "$cid" 2>/dev/null || echo "0")
output_size=$(stat -c%s "$output_file" 2>/dev/null || stat -f%z "$output_file" 2>/dev/null || echo "0")
if [ "$http_code" = "200" ]; then
pass="true"
else
pass="false"
fi
record "$tier" "$tool" "$variant" "$time_s" "$pass" "$output_size" "$mem_after" "$cpu"
log "$tier/$tool/$variant: ${time_s}s HTTP:${http_code} mem:${mem_after}MB pass:${pass}"
rm -f "$output_file" "$timing_file"
}
bench_tool_multifile() {
local tier="$1" tool="$2" variant="$3" settings="${4:-}"
shift 4
local files=("$@")
local cid time_s http_code mem_after cpu output_file pass output_size
cid=$(get_container_id)
output_file=$(mktemp)
local curl_args=(-s -X POST "${BASE_URL}/api/v1/tools/${tool}" -H "Authorization: Bearer ${TOKEN}")
for f in "${files[@]}"; do
curl_args+=(-F "file=@${f}")
done
if [ -n "$settings" ]; then
curl_args+=(-F "settings=${settings}")
fi
curl_args+=(-o "$output_file" -w "%{http_code} %{time_total}")
local result
result=$(curl "${curl_args[@]}" 2>/dev/null) || result="000 0.000"
http_code=$(echo "$result" | awk '{print $1}')
time_s=$(echo "$result" | awk '{print $2}')
mem_after=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
cpu=$(docker_cpu_pct "$cid" 2>/dev/null || echo "0")
output_size=$(stat -c%s "$output_file" 2>/dev/null || stat -f%z "$output_file" 2>/dev/null || echo "0")
if [ "$http_code" = "200" ]; then
pass="true"
else
pass="false"
fi
record "$tier" "$tool" "$variant" "$time_s" "$pass" "$output_size" "$mem_after" "$cpu"
log "$tier/$tool/$variant: ${time_s}s HTTP:${http_code} pass:${pass}"
rm -f "$output_file"
}
run_n_times() {
local n="$1"
shift
local times=()
for i in $(seq 1 "$n"); do
"$@"
done
}
F="${FIXTURE_DIR}"
S="${F}/test-200x150.png"
J="${F}/test-100x100.jpg"
L="${F}/content/stress-large.jpg"
P="${F}/content/portrait-color.jpg"
BW="${F}/content/portrait-bw.jpeg"
SVG="${F}/content/svg-logo.svg"
GIF="${F}/content/animated-simpsons.gif"
PDF="${F}/test-3page.pdf"
EXIF="${F}/test-with-exif.jpg"
FACE="${F}/content/multi-face.webp"
OCR="${F}/content/ocr-chat.jpeg"
OCRJP="${F}/content/ocr-japanese.png"
ISO="${F}/content/portrait-isolated.png"
HEAD="${F}/content/portrait-headshot.heic"
REDEYE="${F}/content/red-eye.jpg"
BARCODE="${F}/content/barcode.avif"
echo "[]" > /dev/null
> "$RESULTS_FILE"
log "=== Starting benchmarks on ${SYSTEM} ==="
TOKEN=$(get_token)
log "Auth token obtained"
log "=== TIER 1: Core Tool Benchmarks ==="
for run in 1 2 3; do
log "--- Run $run of 3 ---"
bench_tool "core" "resize" "small-r${run}" "$S" '{"width":100,"fit":"cover"}'
bench_tool "core" "resize" "large-r${run}" "$L" '{"width":800,"fit":"cover"}'
bench_tool "core" "crop" "small-r${run}" "$S" '{"left":10,"top":10,"width":100,"height":100}'
bench_tool "core" "crop" "large-r${run}" "$L" '{"left":10,"top":10,"width":100,"height":100}'
bench_tool "core" "rotate" "small-r${run}" "$S" '{"angle":90}'
bench_tool "core" "rotate" "large-r${run}" "$L" '{"angle":90}'
bench_tool "core" "convert" "jpg-webp-small-r${run}" "$J" '{"format":"webp","quality":80}'
bench_tool "core" "convert" "jpg-webp-large-r${run}" "$L" '{"format":"webp","quality":80}'
bench_tool "core" "convert" "jpg-avif-small-r${run}" "$J" '{"format":"avif","quality":50}'
bench_tool "core" "convert" "jpg-avif-large-r${run}" "$L" '{"format":"avif","quality":50}'
bench_tool "core" "compress" "quality-small-r${run}" "$S" '{"mode":"quality","quality":60}'
bench_tool "core" "compress" "quality-large-r${run}" "$L" '{"mode":"quality","quality":60}'
bench_tool "core" "compress" "targetSize-large-r${run}" "$L" '{"mode":"targetSize","targetSizeKb":500}'
bench_tool "core" "strip-metadata" "small-r${run}" "$EXIF" '{"stripAll":true}'
bench_tool "core" "strip-metadata" "large-r${run}" "$L" '{"stripAll":true}'
bench_tool "core" "edit-metadata" "small-r${run}" "$EXIF" '{"title":"Bench","clearGps":true}'
bench_tool "core" "edit-metadata" "large-r${run}" "$L" '{"title":"Bench","clearGps":true}'
bench_tool "core" "color-adjustments" "small-r${run}" "$S" '{"brightness":20,"contrast":10,"effect":"grayscale"}'
bench_tool "core" "color-adjustments" "large-r${run}" "$L" '{"brightness":20,"contrast":10,"effect":"grayscale"}'
bench_tool "core" "sharpening" "small-r${run}" "$S" '{"method":"adaptive","sigma":1.5}'
bench_tool "core" "sharpening" "large-r${run}" "$L" '{"method":"adaptive","sigma":1.5}'
bench_tool "core" "watermark-text" "small-r${run}" "$S" '{"text":"BENCHMARK","position":"tiled"}'
bench_tool "core" "watermark-text" "large-r${run}" "$L" '{"text":"BENCHMARK","position":"tiled"}'
bench_tool_multifile "core" "compose" "small-r${run}" '{"blendMode":"overlay"}' "$S" "$J"
bench_tool_multifile "core" "compose" "large-r${run}" '{"blendMode":"overlay"}' "$L" "$P"
bench_tool_multifile "core" "collage" "4img-small-r${run}" '{"templateId":"4-grid"}' "$S" "$J" "${F}/test-50x50.webp" "${F}/test-100x100.svg"
bench_tool_multifile "core" "collage" "4img-large-r${run}" '{"templateId":"4-grid"}' "$L" "$P" "$BW" "${F}/content/watermark.jpg"
bench_tool_multifile "core" "stitch" "3img-small-r${run}" '{"direction":"horizontal"}' "$S" "$J" "${F}/test-50x50.webp"
bench_tool_multifile "core" "stitch" "3img-large-r${run}" '{"direction":"horizontal"}' "$L" "$P" "$BW"
bench_tool "core" "split" "small-r${run}" "$S" '{"columns":2,"rows":2}'
bench_tool "core" "split" "large-r${run}" "$L" '{"columns":2,"rows":2}'
bench_tool "core" "border" "small-r${run}" "$S" '{"borderWidth":20,"cornerRadius":10,"shadow":true}'
bench_tool "core" "border" "large-r${run}" "$L" '{"borderWidth":20,"cornerRadius":10,"shadow":true}'
bench_tool "core" "svg-to-raster" "small-r${run}" "${F}/test-100x100.svg" '{"width":2000,"dpi":300}'
bench_tool "core" "vectorize" "small-r${run}" "$S" '{"colorMode":"color"}'
bench_tool "core" "vectorize" "large-r${run}" "$P" '{"colorMode":"color"}'
bench_tool "core" "gif-tools" "optimize-r${run}" "$GIF" '{"mode":"optimize","colors":64}'
bench_tool "core" "pdf-to-image" "r${run}" "$PDF" '{"format":"png","dpi":300}'
bench_tool "core" "optimize-for-web" "large-r${run}" "$L" '{"format":"webp","quality":80,"maxWidth":1920}'
bench_tool "core" "favicon" "small-r${run}" "$S" ''
bench_tool "core" "favicon" "large-r${run}" "$P" ''
bench_tool_multifile "core" "image-to-pdf" "3img-r${run}" '{"pageSize":"A4"}' "$S" "$J" "${F}/test-50x50.webp"
bench_tool "core" "replace-color" "small-r${run}" "$S" '{"sourceColor":"#FFFFFF","targetColor":"#FF0000","tolerance":30}'
bench_tool "core" "replace-color" "large-r${run}" "$L" '{"sourceColor":"#FFFFFF","targetColor":"#FF0000","tolerance":30}'
bench_tool "core" "info" "small-r${run}" "$S" ''
bench_tool "core" "info" "large-r${run}" "$L" ''
bench_tool_multifile "core" "compare" "small-r${run}" '' "$S" "$S"
bench_tool_multifile "core" "compare" "large-r${run}" '' "$L" "$L"
bench_tool_multifile "core" "find-duplicates" "5img-r${run}" '{"threshold":5}' "$S" "$J" "${F}/test-50x50.webp" "$S" "$J"
bench_tool "core" "color-palette" "small-r${run}" "$P" ''
bench_tool "core" "color-palette" "large-r${run}" "$L" ''
bench_tool "core" "barcode-read" "r${run}" "$BARCODE" '{"tryHarder":true}'
bench_tool "core" "image-to-base64" "small-r${run}" "$S" '{"outputFormat":"webp"}'
bench_tool "core" "image-to-base64" "large-r${run}" "$L" '{"outputFormat":"webp"}'
bench_tool "core" "content-aware-resize" "small-r${run}" "$S" '{"width":100}'
bench_tool "core" "content-aware-resize" "large-r${run}" "$L" '{"width":100}'
bench_tool "core" "image-enhancement" "small-r${run}" "$S" '{"mode":"auto","intensity":50}'
bench_tool "core" "image-enhancement" "large-r${run}" "$L" '{"mode":"auto","intensity":50}'
done
log "=== TIER 5: Format Decode Benchmarks ==="
for fmt_file in "${F}/formats/"*; do
fname=$(basename "$fmt_file")
for run in 1 2 3; do
bench_tool "format" "resize" "fmt-${fname}-r${run}" "$fmt_file" '{"width":200}'
done
done
log "=== TIER 6: Concurrent Load Benchmarks ==="
for concurrency in 1 3 5 10 20; do
log "Concurrency: ${concurrency}"
local_results=$(mktemp)
for i in $(seq 1 "$concurrency"); do
(
result=$(curl -s -X POST "${BASE_URL}/api/v1/tools/resize" \
-H "Authorization: Bearer ${TOKEN}" \
-F "file=@${L}" \
-F 'settings={"width":800}' \
-o /dev/null -w "%{http_code} %{time_total}")
echo "$result"
) >> "$local_results" &
done
wait
cid=$(get_container_id)
mem_after=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
times=()
errors=0
while IFS= read -r line; do
code=$(echo "$line" | awk '{print $1}')
t=$(echo "$line" | awk '{print $2}')
times+=("$t")
if [ "$code" != "200" ]; then
errors=$((errors + 1))
fi
done < "$local_results"
sorted=$(printf '%s\n' "${times[@]}" | sort -n)
count=${#times[@]}
avg=$(printf '%s\n' "${times[@]}" | awk '{s+=$1} END {printf "%.3f", s/NR}')
p95_idx=$(( (count * 95 / 100) ))
[ "$p95_idx" -ge "$count" ] && p95_idx=$((count - 1))
p95=$(echo "$sorted" | sed -n "$((p95_idx + 1))p")
max=$(echo "$sorted" | tail -1)
min=$(echo "$sorted" | head -1)
printf '{"system":"%s","tier":"concurrent","tool":"resize","variant":"c%d","concurrency":%d,"avg_s":%s,"p95_s":%s,"max_s":%s,"min_s":%s,"errors":%d,"mem_mb":%s}\n' \
"$SYSTEM" "$concurrency" "$concurrency" "$avg" "${p95:-0}" "${max:-0}" "${min:-0}" "$errors" "$mem_after" >> "$RESULTS_FILE"
log "concurrent/c${concurrency}: avg=${avg}s p95=${p95}s max=${max}s errors=${errors} mem=${mem_after}MB"
rm -f "$local_results"
done
log "=== TIER 7: Sustained Load (50 sequential resizes) ==="
cid=$(get_container_id)
mem_start=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
for i in $(seq 1 50); do
bench_tool "sustained" "resize" "seq-${i}" "$L" '{"width":800}'
done
mem_end=$(docker_mem_mb "$cid" 2>/dev/null || echo "0")
mem_delta=$(echo "$mem_end - $mem_start" | bc 2>/dev/null || echo "0")
printf '{"system":"%s","tier":"sustained-summary","test":"50-resizes","mem_start_mb":%s,"mem_end_mb":%s,"mem_delta_mb":%s}\n' \
"$SYSTEM" "$mem_start" "$mem_end" "$mem_delta" >> "$RESULTS_FILE"
log "Sustained: start=${mem_start}MB end=${mem_end}MB delta=${mem_delta}MB"
log "=== TIER 3: Batch Processing ==="
for batch_size in 3 5 10; do
files=()
for i in $(seq 1 "$batch_size"); do
files+=("$S")
done
bench_tool_multifile "batch" "resize" "small-b${batch_size}" '{"width":100}' "${files[@]}"
done
for batch_size in 3 5; do
files=()
for i in $(seq 1 "$batch_size"); do
files+=("$L")
done
bench_tool_multifile "batch" "resize" "large-b${batch_size}" '{"width":800}' "${files[@]}"
done
bench_tool_multifile "batch" "convert" "mixed-5" '{"format":"webp","quality":80}' \
"$J" "$S" "${F}/test-50x50.webp" "${F}/test-200x150.heic" "${F}/formats/sample.avif"
log "=== TIER 4: Pipeline Benchmarks ==="
bench_tool "pipeline" "resize" "1step" "$L" '{"width":800}'
log "=== Container Cold Start ==="
# Record current time as baseline
log "Cold start measurement requires container restart - skipping in automated run"
log "=== ALL BENCHMARKS COMPLETE for ${SYSTEM} ==="
log "Results in: ${RESULTS_FILE}"
wc -l "$RESULTS_FILE" | awk '{print $1 " benchmark records written"}'