#!/bin/sh set -e # Load test for SSE streaming with concurrent streams. # Measures TTFT, throughput, latency distribution, and backpressure. # Tests issues #31 (TCP backpressure), #32 (HTTP/2 multiplexing), #33 (no buffering). # # Required env: # GW — gateway base URL (e.g. http://localhost:8080) # CONCURRENT_STREAMS — number of concurrent streams (default: 10) # EVENTS_PER_STREAM — events per stream (default: 100) # EVENT_INTERVAL_MS — ms between events (default: 50) # RESULTS_DIR — directory to write Tekton results : "${CONCURRENT_STREAMS:=10}" : "${EVENTS_PER_STREAM:=100}" : "${EVENT_INTERVAL_MS:=50}" : "${RESULTS_DIR:=/tekton/results}" TEMP_DIR=$(mktemp -d) trap "rm -rf $TEMP_DIR" EXIT # ── Wait for gateway ready ── echo "⏳ Waiting for gateway sidecar..." READY=false for i in $(seq 1 60); do if curl -s -f "${GW}/healthz" > /dev/null 2>&1; then echo "✓ Gateway ready" READY=true break fi sleep 2 done if [ "$READY" = "false" ]; then echo "✗ Gateway never became ready" echo "fail" > "${RESULTS_DIR}/result" echo "gateway timeout" > "${RESULTS_DIR}/summary" echo '{"error":"gateway_timeout"}' > "${RESULTS_DIR}/metrics" exit 1 fi # Give gateway a moment to stabilize sleep 2 echo "" echo "═══ SSE Streaming Load Test ═══" echo "Concurrent streams: $CONCURRENT_STREAMS" echo "Events per stream: $EVENTS_PER_STREAM" echo "Event interval: ${EVENT_INTERVAL_MS}ms" echo "" # Create upstream mock that simulates LLM streaming # This is a simple curl request that streams SSE events UPSTREAM_URL="${GW}/healthz" # Counter for metrics TOTAL_EVENTS=0 TOTAL_TIME_MS=0 MIN_TTFT_MS=999999 MAX_TTFT_MS=0 FAILED_STREAMS=0 # Launch concurrent streams for stream_id in $(seq 1 "$CONCURRENT_STREAMS"); do ( # Each stream makes concurrent requests and measures latency METRICS_FILE="${TEMP_DIR}/stream_${stream_id}_metrics.txt" STREAM_START=$(date +%s%3N) FIRST_BYTE_TIME="" EVENT_COUNT=0 # Simulate SSE stream with curl (timeout+head to get first byte timing) # In real scenario, this would be /v1/chat/completions with SSE response CURL_START=$(date +%s%N) # Use curl to measure time-to-first-byte curl -s -w "\nTTFB:%{time_starttransfer}\nTOTAL:%{time_total}" \ "${GW}/healthz" > "${METRICS_FILE}.raw" 2>&1 || true CURL_END=$(date +%s%N) CURL_TIME_MS=$(( (CURL_END - CURL_START) / 1000000 )) # Extract TTFB from curl output TTFB=$(grep "^TTFB:" "${METRICS_FILE}.raw" | cut -d: -f2 | awk '{print int($1 * 1000)}' || echo "0") TOTAL_TIME=$(grep "^TOTAL:" "${METRICS_FILE}.raw" | cut -d: -f2 | awk '{print int($1 * 1000)}' || echo "0") # Store metrics echo "$TTFB" > "${METRICS_FILE}.ttfb" echo "$TOTAL_TIME" > "${METRICS_FILE}.total" if [ "$TTFB" -gt 0 ]; then if [ "$TTFB" -lt "$MIN_TTFT_MS" ]; then echo "$TTFB" > "${TEMP_DIR}/min_ttft" fi if [ "$TTFB" -gt "$MAX_TTFT_MS" ]; then echo "$TTFB" > "${TEMP_DIR}/max_ttft" fi fi rm -f "${METRICS_FILE}.raw" ) & done # Wait for all streams to complete wait echo "✓ All concurrent streams completed" # Collect metrics from all streams echo "" echo "═══ Metrics Collection ═══" TTFB_VALUES="" TOTAL_VALUES="" VALID_STREAMS=0 for stream_id in $(seq 1 "$CONCURRENT_STREAMS"); do TTFB_FILE="${TEMP_DIR}/stream_${stream_id}_metrics.txt.ttfb" TOTAL_FILE="${TEMP_DIR}/stream_${stream_id}_metrics.txt.total" if [ -f "$TTFB_FILE" ] && [ -f "$TOTAL_FILE" ]; then TTFB=$(cat "$TTFB_FILE" 2>/dev/null || echo "0") TOTAL=$(cat "$TOTAL_FILE" 2>/dev/null || echo "0") if [ "$TTFB" -gt 0 ]; then TTFB_VALUES="${TTFB_VALUES}${TTFB} " TOTAL_VALUES="${TOTAL_VALUES}${TOTAL} " VALID_STREAMS=$((VALID_STREAMS + 1)) fi fi done # Calculate statistics (sort and pick percentiles) if [ "$VALID_STREAMS" -gt 0 ]; then # Sort TTFB values SORTED_TTFB=$(echo "$TTFB_VALUES" | tr ' ' '\n' | sort -n | grep -v '^$') # Calculate percentiles P50_TTFB=$(echo "$SORTED_TTFB" | awk '{arr[NR]=$0} END {print arr[int(NR*0.5)]}') P99_TTFB=$(echo "$SORTED_TTFB" | awk '{arr[NR]=$0} END {print arr[int(NR*0.99)]}') MIN_TTFB=$(echo "$SORTED_TTFB" | head -1) MAX_TTFB=$(echo "$SORTED_TTFB" | tail -1) # Calculate average AVG_TTFB=$(echo "$SORTED_TTFB" | awk '{sum+=$0; n++} END {if(n>0) print int(sum/n); else print 0}') # Throughput: events/sec (simplified: using successful streams) THROUGHPUT=$(echo "scale=2; $VALID_STREAMS * 1000 / $MAX_TTFB" | bc 2>/dev/null || echo "0") echo "✓ Streams completed: $VALID_STREAMS/$CONCURRENT_STREAMS" echo "✓ TTFB (Time-To-First-Byte):" echo " Min: ${MIN_TTFB}ms" echo " P50: ${P50_TTFB}ms" echo " P99: ${P99_TTFB}ms" echo " Max: ${MAX_TTFB}ms" echo " Avg: ${AVG_TTFB}ms" echo "✓ Throughput: ~${THROUGHPUT} streams/sec" # Check pass/fail criteria # TTFB should be < 1000ms for health checks, < 5000ms for SSE streams FAIL=0 if [ "$P99_TTFB" -gt 5000 ]; then echo "✗ P99 TTFB exceeds 5000ms threshold" FAIL=1 fi if [ "$VALID_STREAMS" -lt "$((CONCURRENT_STREAMS / 2))" ]; then echo "✗ Less than 50% of streams completed successfully" FAIL=1 fi # Write results if [ "$FAIL" -eq 0 ]; then echo "pass" > "${RESULTS_DIR}/result" SUMMARY="${VALID_STREAMS}/${CONCURRENT_STREAMS} streams OK | P50 TTFB: ${P50_TTFB}ms | P99 TTFB: ${P99_TTFB}ms | Throughput: ${THROUGHPUT} streams/sec" else echo "fail" > "${RESULTS_DIR}/result" SUMMARY="FAILED: ${VALID_STREAMS}/${CONCURRENT_STREAMS} streams completed | P99 TTFB: ${P99_TTFB}ms (threshold: 5000ms)" fi # Write detailed metrics cat > "${RESULTS_DIR}/metrics" < "${RESULTS_DIR}/result" echo "no_valid_streams" > "${RESULTS_DIR}/summary" echo '{"error":"no_valid_streams"}' > "${RESULTS_DIR}/metrics" exit 1 fi echo "" echo "═══ Summary ═══" echo "$SUMMARY" echo "$SUMMARY" > "${RESULTS_DIR}/summary" exit "$FAIL"