You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

run_locust_stress_test.sh 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #!/bin/bash
  2. # Run Dify SSE Stress Test using Locust
  3. set -e
  4. # Get the directory where this script is located
  5. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
  6. # Go to project root first, then to script dir
  7. PROJECT_ROOT="$( cd "${SCRIPT_DIR}/../.." && pwd )"
  8. cd "${PROJECT_ROOT}"
  9. STRESS_TEST_DIR="scripts/stress-test"
  10. # Colors for output
  11. RED='\033[0;31m'
  12. GREEN='\033[0;32m'
  13. YELLOW='\033[1;33m'
  14. BLUE='\033[0;34m'
  15. CYAN='\033[0;36m'
  16. NC='\033[0m' # No Color
  17. # Configuration
  18. TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
  19. REPORT_DIR="${STRESS_TEST_DIR}/reports"
  20. CSV_PREFIX="${REPORT_DIR}/locust_${TIMESTAMP}"
  21. HTML_REPORT="${REPORT_DIR}/locust_report_${TIMESTAMP}.html"
  22. SUMMARY_REPORT="${REPORT_DIR}/locust_summary_${TIMESTAMP}.txt"
  23. # Create reports directory if it doesn't exist
  24. mkdir -p "${REPORT_DIR}"
  25. echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
  26. echo -e "${BLUE}║ DIFY SSE WORKFLOW STRESS TEST (LOCUST) ║${NC}"
  27. echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
  28. echo
  29. # Check if services are running
  30. echo -e "${YELLOW}Checking services...${NC}"
  31. # Check Dify API
  32. if curl -s -f http://localhost:5001/health > /dev/null 2>&1; then
  33. echo -e "${GREEN}✓ Dify API is running${NC}"
  34. # Warn if running in debug mode (check for werkzeug in process)
  35. if ps aux | grep -v grep | grep -q "werkzeug.*5001\|flask.*run.*5001"; then
  36. echo -e "${YELLOW}⚠ WARNING: API appears to be running in debug mode (Flask development server)${NC}"
  37. echo -e "${YELLOW} This will give inaccurate benchmark results!${NC}"
  38. echo -e "${YELLOW} For accurate benchmarking, restart with Gunicorn:${NC}"
  39. echo -e "${CYAN} cd api && uv run gunicorn --bind 0.0.0.0:5001 --workers 4 --worker-class gevent app:app${NC}"
  40. echo
  41. echo -n "Continue anyway? (not recommended) [y/N]: "
  42. read -t 10 continue_debug || continue_debug="n"
  43. if [ "$continue_debug" != "y" ] && [ "$continue_debug" != "Y" ]; then
  44. echo -e "${RED}Benchmark cancelled. Please restart API with Gunicorn.${NC}"
  45. exit 1
  46. fi
  47. fi
  48. else
  49. echo -e "${RED}✗ Dify API is not running on port 5001${NC}"
  50. echo -e "${YELLOW} Start it with Gunicorn for accurate benchmarking:${NC}"
  51. echo -e "${CYAN} cd api && uv run gunicorn --bind 0.0.0.0:5001 --workers 4 --worker-class gevent app:app${NC}"
  52. exit 1
  53. fi
  54. # Check Mock OpenAI server
  55. if curl -s -f http://localhost:5004/v1/models > /dev/null 2>&1; then
  56. echo -e "${GREEN}✓ Mock OpenAI server is running${NC}"
  57. else
  58. echo -e "${RED}✗ Mock OpenAI server is not running on port 5004${NC}"
  59. echo -e "${YELLOW} Start it with: python scripts/stress-test/setup/mock_openai_server.py${NC}"
  60. exit 1
  61. fi
  62. # Check API token exists
  63. if [ ! -f "${STRESS_TEST_DIR}/setup/config/stress_test_state.json" ]; then
  64. echo -e "${RED}✗ Stress test configuration not found${NC}"
  65. echo -e "${YELLOW} Run setup first: python scripts/stress-test/setup_all.py${NC}"
  66. exit 1
  67. fi
  68. API_TOKEN=$(python3 -c "import json; state = json.load(open('${STRESS_TEST_DIR}/setup/config/stress_test_state.json')); print(state.get('api_key', {}).get('token', ''))" 2>/dev/null)
  69. if [ -z "$API_TOKEN" ]; then
  70. echo -e "${RED}✗ Failed to read API token from stress test state${NC}"
  71. exit 1
  72. fi
  73. echo -e "${GREEN}✓ API token found: ${API_TOKEN:0:10}...${NC}"
  74. echo
  75. echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}"
  76. echo -e "${CYAN} STRESS TEST PARAMETERS ${NC}"
  77. echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}"
  78. # Parse configuration
  79. USERS=$(grep "^users" ${STRESS_TEST_DIR}/locust.conf | cut -d'=' -f2 | tr -d ' ')
  80. SPAWN_RATE=$(grep "^spawn-rate" ${STRESS_TEST_DIR}/locust.conf | cut -d'=' -f2 | tr -d ' ')
  81. RUN_TIME=$(grep "^run-time" ${STRESS_TEST_DIR}/locust.conf | cut -d'=' -f2 | tr -d ' ')
  82. echo -e " ${YELLOW}Users:${NC} $USERS concurrent users"
  83. echo -e " ${YELLOW}Spawn Rate:${NC} $SPAWN_RATE users/second"
  84. echo -e " ${YELLOW}Duration:${NC} $RUN_TIME"
  85. echo -e " ${YELLOW}Mode:${NC} SSE Streaming"
  86. echo
  87. # Ask user for run mode
  88. echo -e "${YELLOW}Select run mode:${NC}"
  89. echo " 1) Headless (CLI only) - Default"
  90. echo " 2) Web UI (http://localhost:8089)"
  91. echo -n "Choice [1]: "
  92. read -t 10 choice || choice="1"
  93. echo
  94. # Use SSE stress test script
  95. LOCUST_SCRIPT="${STRESS_TEST_DIR}/sse_benchmark.py"
  96. # Prepare Locust command
  97. if [ "$choice" = "2" ]; then
  98. echo -e "${BLUE}Starting Locust with Web UI...${NC}"
  99. echo -e "${YELLOW}Access the web interface at: ${CYAN}http://localhost:8089${NC}"
  100. echo
  101. # Run with web UI
  102. uv --project api run locust \
  103. -f ${LOCUST_SCRIPT} \
  104. --host http://localhost:5001 \
  105. --web-port 8089
  106. else
  107. echo -e "${BLUE}Starting stress test in headless mode...${NC}"
  108. echo
  109. # Run in headless mode with CSV output
  110. uv --project api run locust \
  111. -f ${LOCUST_SCRIPT} \
  112. --host http://localhost:5001 \
  113. --users $USERS \
  114. --spawn-rate $SPAWN_RATE \
  115. --run-time $RUN_TIME \
  116. --headless \
  117. --print-stats \
  118. --csv=$CSV_PREFIX \
  119. --html=$HTML_REPORT \
  120. 2>&1 | tee $SUMMARY_REPORT
  121. echo
  122. echo -e "${GREEN}═══════════════════════════════════════════════════════════════${NC}"
  123. echo -e "${GREEN} STRESS TEST COMPLETE ${NC}"
  124. echo -e "${GREEN}═══════════════════════════════════════════════════════════════${NC}"
  125. echo
  126. echo -e "${BLUE}Reports generated:${NC}"
  127. echo -e " ${YELLOW}Summary:${NC} $SUMMARY_REPORT"
  128. echo -e " ${YELLOW}HTML Report:${NC} $HTML_REPORT"
  129. echo -e " ${YELLOW}CSV Stats:${NC} ${CSV_PREFIX}_stats.csv"
  130. echo -e " ${YELLOW}CSV History:${NC} ${CSV_PREFIX}_stats_history.csv"
  131. echo
  132. echo -e "${CYAN}View HTML report:${NC}"
  133. echo " open $HTML_REPORT # macOS"
  134. echo " xdg-open $HTML_REPORT # Linux"
  135. echo
  136. # Parse and display key metrics
  137. echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}"
  138. echo -e "${CYAN} KEY METRICS ${NC}"
  139. echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}"
  140. if [ -f "${CSV_PREFIX}_stats.csv" ]; then
  141. python3 - <<EOF
  142. import csv
  143. import sys
  144. csv_file = "${CSV_PREFIX}_stats.csv"
  145. try:
  146. with open(csv_file, 'r') as f:
  147. reader = csv.DictReader(f)
  148. rows = list(reader)
  149. # Find the aggregated row
  150. for row in rows:
  151. if row.get('Name') == 'Aggregated':
  152. print(f" Total Requests: {row.get('Request Count', 'N/A')}")
  153. print(f" Failure Rate: {row.get('Failure Count', '0')} failures")
  154. print(f" Median Response: {row.get('Median Response Time', 'N/A')} ms")
  155. print(f" 95%ile Response: {row.get('95%', 'N/A')} ms")
  156. print(f" 99%ile Response: {row.get('99%', 'N/A')} ms")
  157. print(f" RPS: {row.get('Requests/s', 'N/A')}")
  158. break
  159. # Show SSE-specific metrics
  160. print()
  161. print("SSE Streaming Metrics:")
  162. for row in rows:
  163. if 'Time to First Event' in row.get('Name', ''):
  164. print(f" Time to First Event: {row.get('Median Response Time', 'N/A')} ms (median)")
  165. elif 'Stream Duration' in row.get('Name', ''):
  166. print(f" Stream Duration: {row.get('Median Response Time', 'N/A')} ms (median)")
  167. except Exception as e:
  168. print(f"Could not parse metrics: {e}")
  169. EOF
  170. fi
  171. echo -e "${CYAN}═══════════════════════════════════════════════════════════════${NC}"
  172. fi