#!/bin/bash # ============================================================================= # WP-00: Paper Dynasty Card Render & Upload Pipeline Benchmark # Phase 0 - Render Pipeline Optimization # # Usage: # ./scripts/benchmark_render.sh # Run full benchmark (dev API) # ./scripts/benchmark_render.sh --prod # Run against production API # ./scripts/benchmark_render.sh --quick # Connectivity check only # # Requirements: curl, bc # ============================================================================= # --- Configuration ----------------------------------------------------------- DEV_API="https://pddev.manticorum.com/api" PROD_API="https://pd.manticorum.com/api" API_URL="$DEV_API" # Player IDs in the 12000-13000 range (2005 Live cardset) # Mix of batters and pitchers across different teams PLAYER_IDS=(12785 12790 12800 12810 12820 12830 12840 12850 12860 12870) RESULTS_FILE="$(dirname "$0")/benchmark_results.txt" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') RUN_LABEL="benchmark-$(date +%s)" # --- Argument parsing --------------------------------------------------------- QUICK_MODE=false for arg in "$@"; do case "$arg" in --prod) API_URL="$PROD_API" ;; --quick) QUICK_MODE=true ;; --help|-h) echo "Usage: $0 [--prod] [--quick]" echo " --prod Use production API instead of dev" echo " --quick Connectivity check only (1 request)" exit 0 ;; esac done # --- Helpers ----------------------------------------------------------------- hr() { printf '%0.s-' {1..72}; echo; } # bc-based float arithmetic fadd() { echo "$1 + $2" | bc -l; } fdiv() { echo "scale=6; $1 / $2" | bc -l; } flt() { echo "$1 < $2" | bc -l; } # returns 1 if true fmt3() { printf "%.3f" "$1"; } # format to 3 decimal places # Print and simultaneously append to results file log() { echo "$@" | tee -a "$RESULTS_FILE"; } # Single card render with timing; sets LAST_HTTP, LAST_TIME, LAST_SIZE measure_card() { local player_id="$1" local card_type="${2:-batting}" local cache_bust="${RUN_LABEL}-${player_id}" local url="${API_URL}/v2/players/${player_id}/${card_type}card?d=${cache_bust}" # -s silent, -o discard body, -w write timing vars separated by | local result result=$(curl -s -o /dev/null \ -w "%{http_code}|%{time_total}|%{time_connect}|%{time_starttransfer}|%{size_download}" \ --max-time 30 \ "$url" 2>&1) LAST_HTTP=$(echo "$result" | cut -d'|' -f1) LAST_TIME=$(echo "$result" | cut -d'|' -f2) LAST_CONN=$(echo "$result" | cut -d'|' -f3) LAST_TTFB=$(echo "$result" | cut -d'|' -f4) LAST_SIZE=$(echo "$result" | cut -d'|' -f5) LAST_URL="$url" } # ============================================================================= # START # ============================================================================= # Truncate results file for this run and write header cat > "$RESULTS_FILE" << EOF Paper Dynasty Card Render Benchmark Run timestamp : $TIMESTAMP API target : $API_URL Cache-bust tag: $RUN_LABEL EOF echo "" >> "$RESULTS_FILE" echo "" log "==============================================================" log " Paper Dynasty Card Render Benchmark - WP-00 / Phase 0" log " $(date '+%Y-%m-%d %H:%M:%S')" log " API: $API_URL" log "==============================================================" echo "" # ============================================================================= # SECTION 1: Connectivity Check # ============================================================================= log "--- Section 1: Connectivity Check ---" log "" log "Sending single request to verify API is reachable..." log " Player : 12785 (batting card)" log " URL : ${API_URL}/v2/players/12785/battingcard?d=${RUN_LABEL}-probe" echo "" measure_card 12785 batting if [ "$LAST_HTTP" = "200" ]; then log " HTTP : $LAST_HTTP OK" log " Total : $(fmt3 $LAST_TIME)s" log " Connect: $(fmt3 $LAST_CONN)s" log " TTFB : $(fmt3 $LAST_TTFB)s" log " Size : ${LAST_SIZE} bytes ($(echo "scale=1; $LAST_SIZE/1024" | bc)KB)" log "" log " Connectivity: PASS" elif [ -z "$LAST_HTTP" ] || [ "$LAST_HTTP" = "000" ]; then log " ERROR: Could not reach $API_URL (no response / timeout)" log " Aborting benchmark." echo "" exit 1 else log " HTTP : $LAST_HTTP" log " WARNING: Unexpected status code. Continuing anyway." fi echo "" if [ "$QUICK_MODE" = true ]; then log "Quick mode: exiting after connectivity check." echo "" exit 0 fi # ============================================================================= # SECTION 2: Sequential Card Render Benchmark (10 cards) # ============================================================================= log "" hr log "--- Section 2: Sequential Card Render Benchmark ---" log "" log "Rendering ${#PLAYER_IDS[@]} cards sequentially with fresh cache busts." log "Each request forces a full server-side render (bypasses nginx cache)." log "" log "$(printf '%-8s %-10s %-10s %-10s %-10s %-8s' 'Player' 'HTTP' 'Total(s)' 'TTFB(s)' 'Connect(s)' 'Size(KB)')" log "$(printf '%0.s-' {1..62})" # Accumulators total_time="0" min_time="" max_time="" success_count=0 fail_count=0 all_times=() for pid in "${PLAYER_IDS[@]}"; do measure_card "$pid" batting size_kb=$(echo "scale=1; $LAST_SIZE/1024" | bc) row=$(printf '%-8s %-10s %-10s %-10s %-10s %-8s' \ "$pid" \ "$LAST_HTTP" \ "$(fmt3 $LAST_TIME)" \ "$(fmt3 $LAST_TTFB)" \ "$(fmt3 $LAST_CONN)" \ "$size_kb") if [ "$LAST_HTTP" = "200" ]; then log "$row" total_time=$(fadd "$total_time" "$LAST_TIME") all_times+=("$LAST_TIME") success_count=$((success_count + 1)) # Track min if [ -z "$min_time" ] || [ "$(flt $LAST_TIME $min_time)" = "1" ]; then min_time="$LAST_TIME" fi # Track max if [ -z "$max_time" ] || [ "$(flt $max_time $LAST_TIME)" = "1" ]; then max_time="$LAST_TIME" fi else log "$row << FAILED" fail_count=$((fail_count + 1)) fi done echo "" log "" log "--- Section 2: Results Summary ---" log "" if [ "$success_count" -gt 0 ]; then avg_time=$(fdiv "$total_time" "$success_count") log " Cards requested : ${#PLAYER_IDS[@]}" log " Successful : $success_count" log " Failed : $fail_count" log " Total wall time : $(fmt3 $total_time)s" log " Average per card : $(fmt3 $avg_time)s" log " Minimum : $(fmt3 $min_time)s" log " Maximum : $(fmt3 $max_time)s" log "" # Rough throughput estimate (sequential) cards_per_min=$(echo "scale=1; 60 / $avg_time" | bc) log " Sequential throughput: ~${cards_per_min} cards/min" # Estimate full cardset at ~500 players * 2 cards each = 1000 renders est_1000=$(echo "scale=0; (1000 * $avg_time) / 1" | bc) log " Est. full cardset (1000 renders, sequential): ~${est_1000}s (~$(echo "scale=1; $est_1000/60" | bc) min)" else log " No successful renders to summarize." fi # ============================================================================= # SECTION 3: Upload Pipeline Reference # ============================================================================= echo "" log "" hr log "--- Section 3: Upload Pipeline Benchmark Commands ---" log "" log "The upload pipeline (pd_cards/core/upload.py) fetches rendered PNG cards" log "and uploads them to S3. It uses a persistent aiohttp session with a 6s" log "timeout per card." log "" log "To time a dry-run batch of 20 cards:" log "" log " cd /mnt/NV2/Development/paper-dynasty/card-creation" log " time pd-cards upload s3 --cardset \"2005 Live\" --limit 20 --dry-run" log "" log "To time a real upload batch of 20 cards (writes to S3, updates DB URLs):" log "" log " time pd-cards upload s3 --cardset \"2005 Live\" --limit 20" log "" log "Notes:" log " - dry-run validates card URLs exist without uploading" log " - Remove --limit for full cardset run" log " - Pipeline is currently sequential (one card at a time per session)" log " - Each card: fetch PNG (~2-4s render) + S3 put (~0.1-0.5s) = ~2.5-4.5s/card" log " - Parallelism target (Phase 0 goal): 10-20 concurrent fetches via asyncio" log "" # ============================================================================= # SECTION 4: Before/After Comparison Template # ============================================================================= echo "" hr log "--- Section 4: Before/After Comparison Template ---" log "" log "Fill in after optimization work is complete." log "" log " Metric Before After Delta" log " $(printf '%0.s-' {1..64})" if [ "$success_count" -gt 0 ]; then log " Avg render time (s) $(fmt3 $avg_time) ___._____ ___._____" log " Min render time (s) $(fmt3 $min_time) ___._____ ___._____" log " Max render time (s) $(fmt3 $max_time) ___._____ ___._____" log " Sequential cards/min ${cards_per_min} ___.___ ___.___" else log " Avg render time (s) (no data) ___._____ ___._____" fi log " Upload batch (20 cards) ___._____s ___._____s ___._____s" log " Upload cards/min ___.___ ___.___ ___.___" log " Full cardset time (est) ___._____min ___._____min ___ min saved" log "" # ============================================================================= # DONE # ============================================================================= echo "" hr log "Benchmark complete." log "Results saved to: $RESULTS_FILE" log "" # Voice notify curl -s -X POST http://localhost:8888/notify \ -H 'Content-Type: application/json' \ -d "{\"message\":\"Benchmark complete. Average render time $(fmt3 ${avg_time:-0}) seconds per card\"}" \ > /dev/null 2>&1 || true