exercises

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

generate_traffic.sh (21803B)


      1 #!/bin/bash
      2 # Realistic E-Commerce Traffic Generator for Chapter 6
      3 #
      4 # Simulates realistic user behavior patterns:
      5 # - User sessions (30s-5min) with multiple concurrent users
      6 # - Product popularity distribution (price-based)
      7 # - User personas: Window Shoppers, Buyers, Power Buyers, Bots
      8 # - Realistic timing patterns and cart abandonment
      9 # - Clustered error scenarios (payment outages, stock depletion)
     10 #
     11 # Prerequisites:
     12 #   - The Chapter 6 docker stack is up:    docker compose up -d
     13 #   - bash, curl, jq are available
     14 #   - The gateway responds at $GATEWAY_URL/health (default: http://localhost:4200)
     15 #
     16 # Usage:
     17 #   ./generate_traffic.sh                   # Run for 2 minutes (default)
     18 #   ./generate_traffic.sh --pv              # Also print Prometheus verification queries at the end
     19 #   ./generate_traffic.sh -h | --help       # Show this help and exit
     20 #   DURATION=300 ./generate_traffic.sh      # Run for 5 minutes
     21 #   GATEWAY_URL=http://otelmart:4200 ./generate_traffic.sh
     22 #                                           # Hit the gateway by container name (run from inside docker)
     23 #
     24 # Environment variables:
     25 #   GATEWAY_URL  Base URL of the otelmart gateway. Default: http://localhost:4200
     26 #   DURATION     How long to run, in seconds.       Default: 120
     27 #
     28 # Running on Windows (no native bash):
     29 #   Use the alpine/curl image on the same docker network as the stack:
     30 #
     31 #     docker run --rm --network chapter_6_app-network \
     32 #       -v ${PWD}/scripts:/scripts \
     33 #       -e GATEWAY_URL=http://otelmart:4200 \
     34 #       -e DURATION=120 \
     35 #       alpine/curl:latest \
     36 #       sh -c "apk add --no-cache jq bash >/dev/null && bash /scripts/generate_traffic.sh"
     37 #
     38 # After the run, point Prometheus / Grafana at http://localhost:9090 and try the
     39 # example PromQL queries from Chapter 6 (sections 6.2, 6.4, 6.6).
     40 
     41 set -e
     42 
     43 # Configuration
     44 GATEWAY_URL="${GATEWAY_URL:-http://localhost:4200}"
     45 DURATION="${DURATION:-120}"  # Default: 2 minutes
     46 VERIFY_PROMETHEUS=false
     47 
     48 print_help() {
     49     sed -n '2,40p' "$0" | sed 's/^# \{0,1\}//'
     50 }
     51 
     52 # Parse arguments
     53 while [[ $# -gt 0 ]]; do
     54     case $1 in
     55         --pv|--prometheus-verify)
     56             VERIFY_PROMETHEUS=true
     57             shift
     58             ;;
     59         -h|--help)
     60             print_help
     61             exit 0
     62             ;;
     63         *)
     64             echo "Unknown option: $1"
     65             echo "Usage: $0 [--pv] [-h|--help]"
     66             echo "Run '$0 --help' for full instructions."
     67             exit 1
     68             ;;
     69     esac
     70 done
     71 
     72 # Colors for output
     73 RED='\033[0;31m'
     74 GREEN='\033[0;32m'
     75 YELLOW='\033[1;33m'
     76 BLUE='\033[0;34m'
     77 CYAN='\033[0;36m'
     78 NC='\033[0m' # No Color
     79 
     80 echo "========================================="
     81 echo "OpenTel E-Commerce Traffic Generator"
     82 echo "========================================="
     83 echo "Gateway URL: $GATEWAY_URL"
     84 echo "Duration: ${DURATION}s"
     85 echo "Prometheus Verification: $VERIFY_PROMETHEUS"
     86 echo ""
     87 
     88 # Check if gateway is reachable
     89 echo "Checking service health..."
     90 if ! curl -s -f "$GATEWAY_URL/health" > /dev/null 2>&1; then
     91     echo -e "${RED}ERROR: Gateway service not reachable at $GATEWAY_URL${NC}"
     92     echo "Make sure the services are running: docker-compose up"
     93     exit 1
     94 fi
     95 
     96 echo -e "${GREEN}✓ Gateway is healthy${NC}"
     97 echo ""
     98 
     99 # Counters
    100 TOTAL_REQUESTS=0
    101 SUCCESS_COUNT=0
    102 ERROR_404_COUNT=0
    103 ERROR_409_COUNT=0
    104 ERROR_422_COUNT=0
    105 ERROR_500_COUNT=0
    106 ORDER_SUCCESS_COUNT=0
    107 ORDER_FAILURE_COUNT=0
    108 SESSION_COUNT=0
    109 
    110 # Fetch products once
    111 echo "Fetching products..."
    112 PRODUCTS_JSON=$(curl -s "$GATEWAY_URL/api/products?page_size=50")
    113 
    114 if [ -z "$PRODUCTS_JSON" ] || [ "$PRODUCTS_JSON" = "null" ]; then
    115     echo -e "${RED}ERROR: Failed to fetch products from gateway${NC}"
    116     exit 1
    117 fi
    118 
    119 # Parse products into arrays
    120 PRODUCT_UUIDS=($(echo "$PRODUCTS_JSON" | jq -r '.products[].eid'))
    121 PRODUCT_NAMES=($(echo "$PRODUCTS_JSON" | jq -r '.products[].product_name'))
    122 PRODUCT_PRICES=($(echo "$PRODUCTS_JSON" | jq -r '.products[].final_price'))
    123 
    124 if [ ${#PRODUCT_UUIDS[@]} -eq 0 ]; then
    125     echo -e "${RED}ERROR: No products found. Make sure the database is populated.${NC}"
    126     exit 1
    127 fi
    128 
    129 echo -e "${GREEN}✓ Found ${#PRODUCT_UUIDS[@]} products${NC}"
    130 
    131 # Create product popularity index (price-based: expensive = more popular)
    132 # Sort products by price descending, top 20% are "popular"
    133 POPULAR_COUNT=$((${#PRODUCT_UUIDS[@]} / 5))
    134 if [ $POPULAR_COUNT -lt 3 ]; then
    135     POPULAR_COUNT=3
    136 fi
    137 
    138 # Create popularity tiers
    139 POPULAR_PRODUCTS=()
    140 REGULAR_PRODUCTS=()
    141 
    142 for i in "${!PRODUCT_PRICES[@]}"; do
    143     if [ $i -lt $POPULAR_COUNT ]; then
    144         POPULAR_PRODUCTS+=("$i")
    145     else
    146         REGULAR_PRODUCTS+=("$i")
    147     fi
    148 done
    149 
    150 echo -e "${CYAN}✓ Product popularity: ${#POPULAR_PRODUCTS[@]} popular, ${#REGULAR_PRODUCTS[@]} regular${NC}"
    151 echo ""
    152 
    153 # Helper: Get product with popularity bias
    154 get_product_by_popularity() {
    155     # 80% chance to select popular product, 20% regular
    156     if [ $((RANDOM % 100)) -lt 80 ] && [ ${#POPULAR_PRODUCTS[@]} -gt 0 ]; then
    157         local idx=${POPULAR_PRODUCTS[$((RANDOM % ${#POPULAR_PRODUCTS[@]}))]}
    158     else
    159         local idx=${REGULAR_PRODUCTS[$((RANDOM % ${#REGULAR_PRODUCTS[@]}))]}
    160     fi
    161     echo "${PRODUCT_UUIDS[$idx]}|${PRODUCT_NAMES[$idx]}|${PRODUCT_PRICES[$idx]}"
    162 }
    163 
    164 # Helper: Random delay with activity context
    165 think_time() {
    166     local activity=$1
    167     case $activity in
    168         "quick_scan")
    169             sleep $(awk 'BEGIN{srand(); print 1+rand()*2}')  # 1-3s
    170             ;;
    171         "product_view")
    172             sleep $(awk 'BEGIN{srand(); print 5+rand()*10}')  # 5-15s
    173             ;;
    174         "decision")
    175             sleep $(awk 'BEGIN{srand(); print 2+rand()*2}')  # 2-4s
    176             ;;
    177         "checkout")
    178             sleep $(awk 'BEGIN{srand(); print 10+rand()*20}')  # 10-30s
    179             ;;
    180         "rapid")
    181             sleep $(awk 'BEGIN{srand(); print 0.1+rand()*0.4}')  # 0.1-0.5s (bot)
    182             ;;
    183         *)
    184             sleep $(awk 'BEGIN{srand(); print 0.5+rand()*1.5}')  # 0.5-2s default
    185             ;;
    186     esac
    187 }
    188 
    189 # User Persona: Window Shopper (50% of users)
    190 # Browses 5-10 products, checks stock, abandons cart
    191 simulate_window_shopper() {
    192     local session_id=$1
    193     local browse_count=$((5 + RANDOM % 6))  # 5-10 products
    194     
    195     echo -e "${BLUE}[Session $session_id] Window Shopper: browsing $browse_count products${NC}"
    196     
    197     # Browse product list
    198     curl -s "$GATEWAY_URL/api/products?page_size=20" > /dev/null
    199     TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
    200     think_time "quick_scan"
    201     
    202     # View individual products
    203     for i in $(seq 1 $browse_count); do
    204         local product_info=$(get_product_by_popularity)
    205         local uuid=$(echo "$product_info" | cut -d'|' -f1)
    206         
    207         curl -s "$GATEWAY_URL/api/products/$uuid" > /dev/null
    208         TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
    209         think_time "product_view"
    210         
    211         # 30% chance to check stock (showing intent)
    212         if [ $((RANDOM % 100)) -lt 30 ]; then
    213             curl -s "$GATEWAY_URL/api/inventory/$uuid/stock" > /dev/null
    214             TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
    215             think_time "decision"
    216         fi
    217     done
    218     
    219     echo -e "${YELLOW}[Session $session_id] Window Shopper: abandoned (no purchase)${NC}"
    220 }
    221 
    222 # User Persona: Casual Buyer (30% of users)
    223 # Browses 2-5 products, completes purchase
    224 simulate_casual_buyer() {
    225     local session_id=$1
    226     local browse_count=$((2 + RANDOM % 4))  # 2-5 products
    227     
    228     echo -e "${BLUE}[Session $session_id] Casual Buyer: browsing $browse_count products${NC}"
    229     
    230     # Browse products
    231     for i in $(seq 1 $browse_count); do
    232         local product_info=$(get_product_by_popularity)
    233         local uuid=$(echo "$product_info" | cut -d'|' -f1)
    234         
    235         curl -s "$GATEWAY_URL/api/products/$uuid" > /dev/null
    236         TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
    237         think_time "product_view"
    238     done
    239     
    240     # Check stock before purchase
    241     local product_info=$(get_product_by_popularity)
    242     local uuid=$(echo "$product_info" | cut -d'|' -f1)
    243     local name=$(echo "$product_info" | cut -d'|' -f2)
    244     local price=$(echo "$product_info" | cut -d'|' -f3)
    245     
    246     curl -s "$GATEWAY_URL/api/inventory/$uuid/stock" > /dev/null
    247     TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
    248     think_time "decision"
    249     
    250     # Create order
    251     think_time "checkout"
    252     create_order "$uuid" "$name" "$price" 1 "$session_id"
    253 }
    254 
    255 # User Persona: Power Buyer (15% of users)
    256 # Quick browse, multi-item orders
    257 simulate_power_buyer() {
    258     local session_id=$1
    259     local item_count=$((2 + RANDOM % 4))  # 2-5 items
    260     
    261     echo -e "${BLUE}[Session $session_id] Power Buyer: purchasing $item_count items${NC}"
    262     
    263     # Quick product check
    264     curl -s "$GATEWAY_URL/api/products?page_size=10" > /dev/null
    265     TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
    266     think_time "quick_scan"
    267     
    268     # Create multi-item order
    269     local product_info=$(get_product_by_popularity)
    270     local uuid=$(echo "$product_info" | cut -d'|' -f1)
    271     local name=$(echo "$product_info" | cut -d'|' -f2)
    272     local price=$(echo "$product_info" | cut -d'|' -f3)
    273     
    274     think_time "checkout"
    275     create_order "$uuid" "$name" "$price" "$item_count" "$session_id"
    276 }
    277 
    278 # User Persona: Bot/Scraper (5% of users)
    279 # Rapid requests, high error rate
    280 simulate_bot() {
    281     local session_id=$1
    282     local request_count=$((10 + RANDOM % 20))  # 10-30 rapid requests
    283     
    284     echo -e "${CYAN}[Session $session_id] Bot: scraping $request_count pages${NC}"
    285     
    286     for i in $(seq 1 $request_count); do
    287         # Mix of valid and invalid requests
    288         if [ $((RANDOM % 100)) -lt 30 ]; then
    289             # 30% invalid requests (404s)
    290             curl -s "$GATEWAY_URL/api/products/00000000-0000-0000-0000-000000000000" > /dev/null 2>&1
    291             ERROR_404_COUNT=$((ERROR_404_COUNT + 1))
    292         else
    293             # Valid product requests
    294             curl -s "$GATEWAY_URL/api/products?page_size=50" > /dev/null
    295         fi
    296         TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
    297         think_time "rapid"
    298     done
    299 }
    300 
    301 # Create order with realistic error handling
    302 create_order() {
    303     local uuid=$1
    304     local name=$2
    305     local price=$3
    306     local quantity=$4
    307     local session_id=$5
    308     
    309     local response=$(curl -s -w "\n%{http_code}" -X POST "$GATEWAY_URL/api/orders" \
    310         -H "Content-Type: application/json" \
    311         -d "{
    312             \"customer_email\": \"user${session_id}@example.com\",
    313             \"items\": [{
    314                 \"product_uuid\": \"$uuid\",
    315                 \"product_name\": \"$name\",
    316                 \"quantity\": $quantity,
    317                 \"unit_price\": \"$price\"
    318             }],
    319             \"shipping_address\": {
    320                 \"first_name\": \"User\",
    321                 \"last_name\": \"${session_id}\",
    322                 \"address_line1\": \"123 Main St\",
    323                 \"city\": \"San Francisco\",
    324                 \"state\": \"CA\",
    325                 \"postal_code\": \"94105\",
    326                 \"country\": \"US\"
    327             },
    328             \"payment\": {
    329                 \"payment_method\": \"credit_card\",
    330                 \"card_last4\": \"4242\",
    331                 \"card_brand\": \"visa\"
    332             }
    333         }")
    334     
    335     local http_code=$(echo "$response" | tail -n1)
    336     TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
    337     
    338     if [ "$http_code" = "201" ] || [ "$http_code" = "200" ]; then
    339         SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
    340         ORDER_SUCCESS_COUNT=$((ORDER_SUCCESS_COUNT + 1))
    341         echo -e "${GREEN}[Session $session_id] ✓ Order created ($quantity items)${NC}"
    342     elif [ "$http_code" = "409" ]; then
    343         ERROR_409_COUNT=$((ERROR_409_COUNT + 1))
    344         ORDER_FAILURE_COUNT=$((ORDER_FAILURE_COUNT + 1))
    345         echo -e "${YELLOW}[Session $session_id] ⚠ Order failed: Insufficient stock (409)${NC}"
    346     else
    347         ORDER_FAILURE_COUNT=$((ORDER_FAILURE_COUNT + 1))
    348         echo -e "${RED}[Session $session_id] ✗ Order failed: HTTP $http_code${NC}"
    349     fi
    350 }
    351 
    352 # Simulate payment gateway outage (500 errors)
    353 simulate_payment_outage() {
    354     echo -e "${RED}[INCIDENT] Payment gateway outage (30s)${NC}"
    355     local outage_end=$(($(date +%s) + 30))
    356     
    357     while [ $(date +%s) -lt $outage_end ]; do
    358         # Generate validation errors (simulating gateway timeout)
    359         local product_info=$(get_product_by_popularity)
    360         local uuid=$(echo "$product_info" | cut -d'|' -f1)
    361         local name=$(echo "$product_info" | cut -d'|' -f2)
    362         local price=$(echo "$product_info" | cut -d'|' -f3)
    363         
    364         curl -s -X POST "$GATEWAY_URL/api/orders" \
    365             -H "Content-Type: application/json" \
    366             -d "{
    367                 \"customer_email\": \"outage@example.com\",
    368                 \"items\": [{
    369                     \"product_uuid\": \"$uuid\",
    370                     \"product_name\": \"$name\",
    371                     \"quantity\": 1,
    372                     \"unit_price\": \"$price\"
    373                 }],
    374                 \"shipping_address\": {
    375                     \"first_name\": \"Test\",
    376                     \"last_name\": \"User\",
    377                     \"address_line1\": \"123 Main St\",
    378                     \"city\": \"San Francisco\",
    379                     \"state\": \"CA\",
    380                     \"postal_code\": \"94105\",
    381                     \"country\": \"INVALID\"
    382                 },
    383                 \"payment\": {
    384                     \"payment_method\": \"credit_card\",
    385                     \"card_last4\": \"4242\",
    386                     \"card_brand\": \"visa\"
    387                 }
    388             }" > /dev/null 2>&1
    389         
    390         TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
    391         ERROR_422_COUNT=$((ERROR_422_COUNT + 1))
    392         sleep 0.5
    393     done
    394     
    395     echo -e "${GREEN}[INCIDENT] Payment gateway recovered${NC}"
    396 }
    397 
    398 # Exhaust stock on popular products
    399 exhaust_popular_stock() {
    400     echo -e "${YELLOW}[INCIDENT] Stock depletion wave on popular products${NC}"
    401     
    402     for i in {1..3}; do
    403         if [ ${#POPULAR_PRODUCTS[@]} -gt $i ]; then
    404             local idx=${POPULAR_PRODUCTS[$i]}
    405             local uuid="${PRODUCT_UUIDS[$idx]}"
    406             local name="${PRODUCT_NAMES[$idx]}"
    407             local price="${PRODUCT_PRICES[$idx]}"
    408             
    409             # Attempt large orders to exhaust stock
    410             for j in {1..5}; do
    411                 create_order "$uuid" "$name" "$price" 10 "stock_depletion_$i"
    412                 sleep 0.2
    413             done
    414         fi
    415     done
    416 }
    417 
    418 # Main traffic generation loop
    419 echo "========================================="
    420 echo "Generating realistic traffic for ${DURATION}s..."
    421 echo "========================================="
    422 echo ""
    423 
    424 START_TIME=$(date +%s)
    425 END_TIME=$((START_TIME + DURATION))
    426 
    427 # Background session spawner
    428 spawn_user_session() {
    429     local persona=$1
    430     SESSION_COUNT=$((SESSION_COUNT + 1))
    431     local session_id=$SESSION_COUNT
    432     
    433     (
    434         case $persona in
    435             "window_shopper")
    436                 simulate_window_shopper $session_id
    437                 ;;
    438             "casual_buyer")
    439                 simulate_casual_buyer $session_id
    440                 ;;
    441             "power_buyer")
    442                 simulate_power_buyer $session_id
    443                 ;;
    444             "bot")
    445                 simulate_bot $session_id
    446                 ;;
    447         esac
    448     ) &
    449 }
    450 
    451 PHASE=1
    452 INCIDENT_TRIGGERED=false
    453 
    454 while [ $(date +%s) -lt $END_TIME ]; do
    455     CURRENT_TIME=$(date +%s)
    456     ELAPSED=$((CURRENT_TIME - START_TIME))
    457     REMAINING=$((END_TIME - CURRENT_TIME))
    458     
    459     # Progress indicator
    460     if [ $((ELAPSED % 15)) -eq 0 ] && [ $ELAPSED -gt 0 ]; then
    461         echo -e "${BLUE}[${ELAPSED}s/${DURATION}s] Phase $PHASE - Sessions: $SESSION_COUNT, Requests: $TOTAL_REQUESTS, Orders: $ORDER_SUCCESS_COUNT${NC}"
    462     fi
    463     
    464     # Phase 1 (0-30s): Morning Traffic - Low Volume
    465     if [ $ELAPSED -lt 30 ]; then
    466         PHASE=1
    467         # Spawn 1-2 concurrent sessions
    468         persona_roll=$((RANDOM % 100))
    469         if [ $persona_roll -lt 70 ]; then
    470             spawn_user_session "window_shopper"
    471         elif [ $persona_roll -lt 90 ]; then
    472             spawn_user_session "casual_buyer"
    473         else
    474             spawn_user_session "power_buyer"
    475         fi
    476         sleep 2
    477         
    478     # Phase 2 (30-60s): Lunch Rush - Burst Traffic
    479     elif [ $ELAPSED -lt 60 ]; then
    480         PHASE=2
    481         # Spawn 3-5 concurrent sessions
    482         for i in {1..3}; do
    483             persona_roll=$((RANDOM % 100))
    484             if [ $persona_roll -lt 50 ]; then
    485                 spawn_user_session "window_shopper"
    486             elif [ $persona_roll -lt 90 ]; then
    487                 spawn_user_session "casual_buyer"
    488             else
    489                 spawn_user_session "power_buyer"
    490             fi
    491         done
    492         sleep 1
    493         
    494     # Phase 3 (60-90s): Afternoon - Incidents
    495     elif [ $ELAPSED -lt 90 ]; then
    496         PHASE=3
    497         
    498         # Trigger payment outage once
    499         if [ $ELAPSED -eq 65 ] && [ "$INCIDENT_TRIGGERED" = false ]; then
    500             simulate_payment_outage &
    501             INCIDENT_TRIGGERED=true
    502         fi
    503         
    504         # Trigger stock depletion
    505         if [ $ELAPSED -eq 75 ]; then
    506             exhaust_popular_stock &
    507         fi
    508         
    509         # Normal traffic with bots
    510         persona_roll=$((RANDOM % 100))
    511         if [ $persona_roll -lt 50 ]; then
    512             spawn_user_session "window_shopper"
    513         elif [ $persona_roll -lt 75 ]; then
    514             spawn_user_session "casual_buyer"
    515         elif [ $persona_roll -lt 90 ]; then
    516             spawn_user_session "power_buyer"
    517         else
    518             spawn_user_session "bot"
    519         fi
    520         sleep 1.5
    521         
    522     # Phase 4 (90-120s): Evening Peak - Sustained High Traffic
    523     else
    524         PHASE=4
    525         # Spawn 2-4 concurrent sessions with higher buyer ratio
    526         for i in {1..2}; do
    527             persona_roll=$((RANDOM % 100))
    528             if [ $persona_roll -lt 40 ]; then
    529                 spawn_user_session "window_shopper"
    530             elif [ $persona_roll -lt 85 ]; then
    531                 spawn_user_session "casual_buyer"
    532             else
    533                 spawn_user_session "power_buyer"
    534             fi
    535         done
    536         sleep 1
    537     fi
    538 done
    539 
    540 # Wait for all background sessions to complete
    541 echo ""
    542 echo "Waiting for active sessions to complete..."
    543 wait
    544 
    545 echo ""
    546 echo "========================================="
    547 echo "Traffic Generation Complete!"
    548 echo "========================================="
    549 echo "Duration: ${DURATION}s"
    550 echo "Total Sessions: $SESSION_COUNT"
    551 echo "Total Requests: $TOTAL_REQUESTS"
    552 echo "Successful Responses: $SUCCESS_COUNT"
    553 echo ""
    554 echo "Error Breakdown:"
    555 echo "  404 (Not Found): $ERROR_404_COUNT"
    556 echo "  409 (Conflict/Stock): $ERROR_409_COUNT"
    557 echo "  422 (Validation): $ERROR_422_COUNT"
    558 echo ""
    559 echo "Order Statistics:"
    560 echo "  Successful Orders: $ORDER_SUCCESS_COUNT"
    561 echo "  Failed Orders: $ORDER_FAILURE_COUNT"
    562 if [ $((ORDER_SUCCESS_COUNT + ORDER_FAILURE_COUNT)) -gt 0 ]; then
    563     echo "  Success Rate: $(awk "BEGIN {printf \"%.1f%%\", ($ORDER_SUCCESS_COUNT/($ORDER_SUCCESS_COUNT+$ORDER_FAILURE_COUNT))*100}")"
    564 fi
    565 echo ""
    566 
    567 # Optional: Verify metrics in Prometheus
    568 if [ "$VERIFY_PROMETHEUS" = true ]; then
    569     echo "========================================="
    570     echo "Verifying Metrics in Prometheus"
    571     echo "========================================="
    572     
    573     PROM_URL="http://localhost:9090"
    574     
    575     echo "Checking Prometheus availability..."
    576     if ! curl -s -f "$PROM_URL/-/healthy" > /dev/null 2>&1; then
    577         echo -e "${YELLOW}⚠ Prometheus not reachable at $PROM_URL${NC}"
    578         echo "Skipping verification."
    579     else
    580         echo -e "${GREEN}✓ Prometheus is healthy${NC}"
    581         echo ""
    582         
    583         echo "Querying metrics..."
    584         
    585         # HTTP Server Metrics (RED)
    586         HTTP_METRICS=$(curl -s "$PROM_URL/api/v1/query?query=http_server_request_duration_seconds_count" | jq -r '.data.result | length')
    587         echo "  HTTP Server Metrics: $HTTP_METRICS series"
    588         
    589         # Order Metrics
    590         ORDER_ATTEMPTS=$(curl -s "$PROM_URL/api/v1/query?query=orders_checkout_attempts_total" | jq -r '.data.result[0].value[1] // "0"')
    591         echo "  Order Attempts: $ORDER_ATTEMPTS"
    592         
    593         # Funnel Metrics
    594         FUNNEL_STARTED=$(curl -s "$PROM_URL/api/v1/query?query=checkout_funnel_started_total" | jq -r '.data.result[0].value[1] // "0"')
    595         FUNNEL_COMPLETED=$(curl -s "$PROM_URL/api/v1/query?query=checkout_funnel_completed_total" | jq -r '.data.result[0].value[1] // "0"')
    596         echo "  Funnel Started: $FUNNEL_STARTED"
    597         echo "  Funnel Completed: $FUNNEL_COMPLETED"
    598         
    599         if [ "$FUNNEL_STARTED" != "0" ]; then
    600             CONVERSION=$(awk "BEGIN {printf \"%.1f%%\", ($FUNNEL_COMPLETED/$FUNNEL_STARTED)*100}")
    601             echo "  Conversion Rate: $CONVERSION"
    602         fi
    603         
    604         # Inventory Metrics
    605         INV_ATTEMPTS=$(curl -s "$PROM_URL/api/v1/query?query=inventory_reservation_attempts_total" | jq -r '.data.result[0].value[1] // "0"')
    606         echo "  Inventory Reservations: $INV_ATTEMPTS"
    607         
    608         # Gateway Metrics
    609         GATEWAY_METRICS=$(curl -s "$PROM_URL/api/v1/query?query=http_client_request_duration_seconds_count" | jq -r '.data.result | length')
    610         echo "  Gateway Upstream Metrics: $GATEWAY_METRICS series"
    611         
    612         echo ""
    613         echo -e "${GREEN}✓ Metrics verification complete${NC}"
    614     fi
    615     echo ""
    616 fi
    617 
    618 echo "View metrics in Prometheus: http://localhost:9090"
    619 echo "View dashboards in Grafana: http://localhost:3000"
    620 echo ""
    621 echo "Example PromQL queries:"
    622 echo "  Request rate: rate(http_server_request_duration_seconds_count[5m])"
    623 echo "  Error rate: (sum(rate(http_server_request_duration_seconds_count{http_response_status_code=~\"4..|5..\"}[5m])) / sum(rate(http_server_request_duration_seconds_count[5m])) * 100) or vector(0)"
    624 echo "  P95 latency: histogram_quantile(0.95, rate(http_server_request_duration_seconds_bucket[5m]))"
    625 echo "  Conversion rate: ((rate(checkout_funnel_completed_total[5m]) / rate(checkout_funnel_started_total[5m])) * 100) or vector(0)"
    626 echo ""