Files
foxhunt/scripts/python/testing/concurrent_connection_test.py
jgrusewski 433af5c25d chore: Major codebase cleanup - remove deprecated files and organize structure
- Docker: Delete 23 deprecated Dockerfiles, fix CI/CD to use Dockerfile.foxhunt-build
- Config: Remove 36 .env files, keep 4 essential, delete config/environments/
- Docs: Archive 614 Wave D files to docs/archive/wave_d/, 95% reduction in root
- Scripts: Delete 56 deprecated scripts, keep 58 production-critical (49% reduction)
- Python: Organize 37 scripts into scripts/python/ subdirectories, delete ml/python/
- Build: Remove 1GB artifacts, delete old venvs, clean Python cache from git
- Migrations: Delete deprecated directory (4,432 lines), remove duplicate database/migrations/
- Infrastructure: Delete deployment/ (61 files), docs/scripts/ (8 files)

Total impact: ~2,500 files cleaned, 750MB+ space freed, zero production impact
All deleted scripts backed up to archives. runpod/ and tests/runpod/ preserved.
data_acquisition_service retained per user request.
2025-10-30 01:02:34 +01:00

229 lines
8.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Concurrent Connection Load Test for Foxhunt HFT Trading System
Tests system behavior under 10, 50, 100, and 200+ concurrent gRPC connections
"""
import asyncio
import time
import statistics
from dataclasses import dataclass
from typing import List, Optional
import grpc
import sys
from concurrent.futures import ThreadPoolExecutor
# JWT token for authentication (same as used in previous tests)
JWT_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0X3VzZXIiLCJleHAiOjk5OTk5OTk5OTksImp0aSI6InRlc3RfdXNlciIsInJvbGVzIjpbInRyYWRlciJdLCJwZXJtaXNzaW9ucyI6WyJ0cmFkZSJdfQ.kHXiGFA0JjGmW66SiFRTUvzd2S6mOGN8pDfr9VJ8mAM"
@dataclass
class ConnectionMetrics:
"""Metrics for a single connection attempt"""
connection_time_ms: float
request_latency_ms: float
success: bool
error_message: Optional[str] = None
@dataclass
class LoadTestResult:
"""Aggregated results for a load test run"""
concurrent_connections: int
total_requests: int
successful_requests: int
failed_requests: int
connection_time_p50: float
connection_time_p95: float
connection_time_p99: float
latency_p50: float
latency_p95: float
latency_p99: float
error_rate: float
total_duration_secs: float
throughput_rps: float
async def execute_single_connection(endpoint: str, connection_id: int) -> ConnectionMetrics:
"""Execute a single connection and request to test connection behavior"""
connection_start = time.time()
try:
# Create gRPC channel with connection settings
channel = grpc.aio.insecure_channel(
endpoint,
options=[
('grpc.max_receive_message_length', 100 * 1024 * 1024),
('grpc.max_send_message_length', 100 * 1024 * 1024),
('grpc.keepalive_time_ms', 30000),
('grpc.keepalive_timeout_ms', 20000),
('grpc.http2.max_pings_without_data', 0),
('grpc.keepalive_permit_without_calls', 1),
]
)
connection_time = (time.time() - connection_start) * 1000 # ms
# Execute a simple unary RPC call to test the connection
request_start = time.time()
# Simple channel state check
await channel.channel_ready()
request_latency = (time.time() - request_start) * 1000 # ms
await channel.close()
return ConnectionMetrics(
connection_time_ms=connection_time,
request_latency_ms=request_latency,
success=True,
error_message=None
)
except Exception as e:
connection_time = (time.time() - connection_start) * 1000
return ConnectionMetrics(
connection_time_ms=connection_time,
request_latency_ms=0,
success=False,
error_message=str(e)
)
async def run_concurrent_load_test(endpoint: str, concurrent_connections: int) -> LoadTestResult:
"""Run load test with specified number of concurrent connections"""
print(f"\n{'='*60}")
print(f"Testing with {concurrent_connections} concurrent connections")
print(f"{'='*60}")
test_start = time.time()
# Create concurrent tasks
tasks = [
execute_single_connection(endpoint, i)
for i in range(concurrent_connections)
]
# Execute all tasks concurrently
metrics = await asyncio.gather(*tasks)
total_duration = time.time() - test_start
# Calculate statistics
connection_times = [m.connection_time_ms for m in metrics]
latencies = [m.request_latency_ms for m in metrics if m.success]
successful = sum(1 for m in metrics if m.success)
failed = len(metrics) - successful
error_rate = (failed / len(metrics)) * 100.0
def percentile(values: List[float], p: float) -> float:
if not values:
return 0.0
sorted_values = sorted(values)
idx = int((len(sorted_values) - 1) * p)
return sorted_values[idx]
conn_p50 = percentile(connection_times, 0.50)
conn_p95 = percentile(connection_times, 0.95)
conn_p99 = percentile(connection_times, 0.99)
lat_p50 = percentile(latencies, 0.50) if latencies else 0
lat_p95 = percentile(latencies, 0.95) if latencies else 0
lat_p99 = percentile(latencies, 0.99) if latencies else 0
throughput = successful / total_duration if total_duration > 0 else 0
return LoadTestResult(
concurrent_connections=concurrent_connections,
total_requests=len(metrics),
successful_requests=successful,
failed_requests=failed,
connection_time_p50=conn_p50,
connection_time_p95=conn_p95,
connection_time_p99=conn_p99,
latency_p50=lat_p50,
latency_p95=lat_p95,
latency_p99=lat_p99,
error_rate=error_rate,
total_duration_secs=total_duration,
throughput_rps=throughput
)
def print_result(result: LoadTestResult):
"""Print detailed results for a test run"""
success_pct = (result.successful_requests / result.total_requests) * 100.0
print(f"\nResults for {result.concurrent_connections} concurrent connections:")
print(f" Total Requests: {result.total_requests}")
print(f" Successful: {result.successful_requests} ({success_pct:.1f}%)")
print(f" Failed: {result.failed_requests} ({result.error_rate:.1f}%)")
print(f" Duration: {result.total_duration_secs:.2f}s")
print(f" Throughput: {result.throughput_rps:.2f} conn/s")
print(f"\n Connection Times:")
print(f" P50: {result.connection_time_p50:.2f}ms")
print(f" P95: {result.connection_time_p95:.2f}ms")
print(f" P99: {result.connection_time_p99:.2f}ms")
print(f"\n Request Latencies:")
print(f" P50: {result.latency_p50:.2f}ms")
print(f" P95: {result.latency_p95:.2f}ms")
print(f" P99: {result.latency_p99:.2f}ms")
async def main():
"""Main test execution function"""
print("\n" + ""*70 + "")
print("│ Foxhunt HFT Trading System - Concurrent Connection Load Test │")
print("" + ""*70 + "")
# Test against Trading Service directly (port 50052)
endpoint = "localhost:50052"
test_levels = [10, 50, 100, 200]
all_results = []
for connections in test_levels:
result = await run_concurrent_load_test(endpoint, connections)
print_result(result)
all_results.append(result)
# Add delay between tests
if connections < 200:
print("\nWaiting 10 seconds before next test...")
await asyncio.sleep(10)
# Print summary table
print("\n\n" + ""*100 + "")
print("│ SUMMARY TABLE - ALL TEST LEVELS" + " "*50 + "")
print("" + ""*100 + "")
print("│ Connections │ Success │ Error % │ Conn P99 │ Lat P50 │ Lat P95 │ Lat P99 │ Throughput │")
print("" + ""*100 + "")
for result in all_results:
success_pct = (result.successful_requests / result.total_requests) * 100.0
print(f"{result.concurrent_connections:11d}{success_pct:6.1f}% │ {result.error_rate:6.1f}% │ "
f"{result.connection_time_p99:7.1f}ms │ {result.latency_p50:6.1f}ms │ "
f"{result.latency_p95:6.1f}ms │ {result.latency_p99:6.1f}ms │ "
f"{result.throughput_rps:8.1f} c/s │")
print("" + ""*100 + "")
# Determine PASS/FAIL based on success criteria
result_100 = next((r for r in all_results if r.concurrent_connections == 100), None)
if result_100:
pass_criteria = (
result_100.error_rate < 1.0 and
result_100.latency_p99 < 100
)
if pass_criteria:
print("\n✅ TEST PASSED - System successfully handled 100+ concurrent connections")
print(" - Error rate < 1%")
print(" - P99 latency < 100ms")
else:
print("\n❌ TEST FAILED - System did not meet success criteria")
print(f" - Error rate: {result_100.error_rate:.2f}% (required: <1%)")
print(f" - P99 latency: {result_100.latency_p99:.2f}ms (required: <100ms)")
return all_results
if __name__ == "__main__":
asyncio.run(main())