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 ""