Refactor: PHASE 8: Testing & Integration
This commit is contained in:
parent
af34f4fd08
commit
9e8c6804a7
32 changed files with 17128 additions and 0 deletions
596
tests/performance/test_websocket_performance.py
Normal file
596
tests/performance/test_websocket_performance.py
Normal file
|
@ -0,0 +1,596 @@
|
|||
"""
|
||||
Performance tests for WebSocket communication and message processing.
|
||||
|
||||
These tests benchmark WebSocket throughput, latency, and concurrent
|
||||
connection handling to ensure scalability requirements are met.
|
||||
"""
|
||||
import pytest
|
||||
import asyncio
|
||||
import time
|
||||
import statistics
|
||||
import json
|
||||
from unittest.mock import Mock, AsyncMock
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import psutil
|
||||
|
||||
from detector_worker.communication.websocket_handler import WebSocketHandler
|
||||
from detector_worker.communication.message_processor import MessageProcessor
|
||||
from detector_worker.communication.websocket_handler import ConnectionManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def performance_config():
|
||||
"""Configuration for performance tests."""
|
||||
return {
|
||||
"max_message_latency_ms": 10,
|
||||
"min_throughput_msgs_per_sec": 1000,
|
||||
"max_concurrent_connections": 100,
|
||||
"max_memory_per_connection_kb": 100
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_websocket():
|
||||
"""Create mock WebSocket for performance testing."""
|
||||
websocket = Mock()
|
||||
websocket.accept = AsyncMock()
|
||||
websocket.send_json = AsyncMock()
|
||||
websocket.send_text = AsyncMock()
|
||||
websocket.receive_json = AsyncMock()
|
||||
websocket.receive_text = AsyncMock()
|
||||
websocket.close = AsyncMock()
|
||||
websocket.ping = AsyncMock()
|
||||
return websocket
|
||||
|
||||
|
||||
class TestWebSocketMessagePerformance:
|
||||
"""Test WebSocket message processing performance."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_message_processing_throughput(self, performance_config):
|
||||
"""Test message processing throughput."""
|
||||
|
||||
message_processor = MessageProcessor()
|
||||
|
||||
# Simple state request message
|
||||
test_message = {"type": "requestState"}
|
||||
client_id = "perf_client"
|
||||
|
||||
# Warm up
|
||||
for _ in range(10):
|
||||
await message_processor.process_message(test_message, client_id)
|
||||
|
||||
# Benchmark throughput
|
||||
num_messages = 10000
|
||||
start_time = time.perf_counter()
|
||||
|
||||
for _ in range(num_messages):
|
||||
await message_processor.process_message(test_message, client_id)
|
||||
|
||||
end_time = time.perf_counter()
|
||||
total_time = end_time - start_time
|
||||
throughput = num_messages / total_time
|
||||
|
||||
print(f"\nMessage Processing Throughput:")
|
||||
print(f"Messages processed: {num_messages}")
|
||||
print(f"Total time: {total_time:.2f} seconds")
|
||||
print(f"Throughput: {throughput:.0f} messages/second")
|
||||
|
||||
assert throughput >= performance_config["min_throughput_msgs_per_sec"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_message_processing_latency(self, performance_config):
|
||||
"""Test individual message processing latency."""
|
||||
|
||||
message_processor = MessageProcessor()
|
||||
|
||||
test_messages = [
|
||||
{"type": "requestState"},
|
||||
{"type": "setSessionId", "payload": {"sessionId": "test", "displayId": "display"}},
|
||||
{"type": "patchSession", "payload": {"sessionId": "test", "data": {"test": "value"}}}
|
||||
]
|
||||
|
||||
client_id = "latency_client"
|
||||
|
||||
# Benchmark individual message latency
|
||||
all_latencies = []
|
||||
|
||||
for message_type, test_message in enumerate(test_messages):
|
||||
latencies = []
|
||||
|
||||
for _ in range(1000):
|
||||
start_time = time.perf_counter()
|
||||
await message_processor.process_message(test_message, client_id)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
latency_ms = (end_time - start_time) * 1000
|
||||
latencies.append(latency_ms)
|
||||
|
||||
avg_latency = statistics.mean(latencies)
|
||||
max_latency = max(latencies)
|
||||
p95_latency = statistics.quantiles(latencies, n=20)[18] # 95th percentile
|
||||
|
||||
all_latencies.extend(latencies)
|
||||
|
||||
print(f"\nMessage Type: {test_message['type']}")
|
||||
print(f"Average latency: {avg_latency:.3f} ms")
|
||||
print(f"Max latency: {max_latency:.3f} ms")
|
||||
print(f"95th percentile: {p95_latency:.3f} ms")
|
||||
|
||||
assert avg_latency < performance_config["max_message_latency_ms"]
|
||||
assert p95_latency < performance_config["max_message_latency_ms"] * 2
|
||||
|
||||
# Overall statistics
|
||||
overall_avg = statistics.mean(all_latencies)
|
||||
overall_p95 = statistics.quantiles(all_latencies, n=20)[18]
|
||||
|
||||
print(f"\nOverall Message Latency:")
|
||||
print(f"Average latency: {overall_avg:.3f} ms")
|
||||
print(f"95th percentile: {overall_p95:.3f} ms")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_concurrent_message_processing(self, performance_config):
|
||||
"""Test concurrent message processing performance."""
|
||||
|
||||
message_processor = MessageProcessor()
|
||||
|
||||
async def process_messages_batch(client_id, num_messages):
|
||||
"""Process a batch of messages for one client."""
|
||||
test_message = {"type": "requestState"}
|
||||
latencies = []
|
||||
|
||||
for _ in range(num_messages):
|
||||
start_time = time.perf_counter()
|
||||
await message_processor.process_message(test_message, client_id)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
latency_ms = (end_time - start_time) * 1000
|
||||
latencies.append(latency_ms)
|
||||
|
||||
return latencies
|
||||
|
||||
# Run concurrent processing
|
||||
num_clients = 50
|
||||
messages_per_client = 100
|
||||
|
||||
start_time = time.perf_counter()
|
||||
|
||||
tasks = [
|
||||
process_messages_batch(f"client_{i}", messages_per_client)
|
||||
for i in range(num_clients)
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
end_time = time.perf_counter()
|
||||
total_time = end_time - start_time
|
||||
|
||||
# Analyze results
|
||||
all_latencies = [latency for client_latencies in results for latency in client_latencies]
|
||||
total_messages = len(all_latencies)
|
||||
avg_latency = statistics.mean(all_latencies)
|
||||
throughput = total_messages / total_time
|
||||
|
||||
print(f"\nConcurrent Message Processing:")
|
||||
print(f"Clients: {num_clients}")
|
||||
print(f"Total messages: {total_messages}")
|
||||
print(f"Total time: {total_time:.2f} seconds")
|
||||
print(f"Throughput: {throughput:.0f} messages/second")
|
||||
print(f"Average latency: {avg_latency:.3f} ms")
|
||||
|
||||
assert throughput >= performance_config["min_throughput_msgs_per_sec"] / 2 # Reduced due to concurrency overhead
|
||||
assert avg_latency < performance_config["max_message_latency_ms"] * 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_large_message_performance(self):
|
||||
"""Test performance with large messages."""
|
||||
|
||||
message_processor = MessageProcessor()
|
||||
|
||||
# Create large message (simulating detection results)
|
||||
large_payload = {
|
||||
"detections": [
|
||||
{
|
||||
"class": f"object_{i}",
|
||||
"confidence": 0.9,
|
||||
"bbox": [i*10, i*10, (i+1)*10, (i+1)*10],
|
||||
"metadata": {
|
||||
"feature_vector": [float(j) for j in range(100)],
|
||||
"description": "x" * 500 # Large text field
|
||||
}
|
||||
}
|
||||
for i in range(50) # 50 detections
|
||||
],
|
||||
"camera_info": {
|
||||
"resolution": [1920, 1080],
|
||||
"settings": {"brightness": 50, "contrast": 75},
|
||||
"history": [{"timestamp": i, "event": f"event_{i}"} for i in range(100)]
|
||||
}
|
||||
}
|
||||
|
||||
large_message = {
|
||||
"type": "imageDetection",
|
||||
"payload": large_payload
|
||||
}
|
||||
|
||||
client_id = "large_msg_client"
|
||||
|
||||
# Measure message size
|
||||
message_size_bytes = len(json.dumps(large_message))
|
||||
print(f"\nLarge Message Performance:")
|
||||
print(f"Message size: {message_size_bytes / 1024:.1f} KB")
|
||||
|
||||
# Benchmark large message processing
|
||||
processing_times = []
|
||||
num_iterations = 100
|
||||
|
||||
for _ in range(num_iterations):
|
||||
start_time = time.perf_counter()
|
||||
await message_processor.process_message(large_message, client_id)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
processing_time_ms = (end_time - start_time) * 1000
|
||||
processing_times.append(processing_time_ms)
|
||||
|
||||
avg_processing_time = statistics.mean(processing_times)
|
||||
max_processing_time = max(processing_times)
|
||||
|
||||
print(f"Average processing time: {avg_processing_time:.2f} ms")
|
||||
print(f"Max processing time: {max_processing_time:.2f} ms")
|
||||
|
||||
# Large messages should still be processed reasonably quickly
|
||||
assert avg_processing_time < 100 # Less than 100ms for large messages
|
||||
assert max_processing_time < 500 # Less than 500ms max
|
||||
|
||||
|
||||
class TestConnectionManagerPerformance:
|
||||
"""Test connection manager performance."""
|
||||
|
||||
def test_connection_creation_performance(self, performance_config, mock_websocket):
|
||||
"""Test connection creation and management performance."""
|
||||
|
||||
connection_manager = ConnectionManager()
|
||||
|
||||
# Benchmark connection creation
|
||||
creation_times = []
|
||||
num_connections = 1000
|
||||
|
||||
for i in range(num_connections):
|
||||
start_time = time.perf_counter()
|
||||
connection_manager._create_connection(mock_websocket, f"client_{i}")
|
||||
end_time = time.perf_counter()
|
||||
|
||||
creation_time_ms = (end_time - start_time) * 1000
|
||||
creation_times.append(creation_time_ms)
|
||||
|
||||
avg_creation_time = statistics.mean(creation_times)
|
||||
max_creation_time = max(creation_times)
|
||||
|
||||
print(f"\nConnection Creation Performance:")
|
||||
print(f"Connections created: {num_connections}")
|
||||
print(f"Average creation time: {avg_creation_time:.3f} ms")
|
||||
print(f"Max creation time: {max_creation_time:.3f} ms")
|
||||
|
||||
# Connection creation should be very fast
|
||||
assert avg_creation_time < 1.0 # Less than 1ms average
|
||||
assert max_creation_time < 10.0 # Less than 10ms max
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_broadcast_performance(self, mock_websocket):
|
||||
"""Test broadcast message performance."""
|
||||
|
||||
connection_manager = ConnectionManager()
|
||||
|
||||
# Create many mock connections
|
||||
num_connections = 1000
|
||||
mock_websockets = []
|
||||
|
||||
for i in range(num_connections):
|
||||
ws = Mock()
|
||||
ws.send_json = AsyncMock()
|
||||
ws.send_text = AsyncMock()
|
||||
mock_websockets.append(ws)
|
||||
|
||||
# Add to connection manager
|
||||
connection = connection_manager._create_connection(ws, f"client_{i}")
|
||||
connection.is_connected = True
|
||||
connection_manager.connections[f"client_{i}"] = connection
|
||||
|
||||
# Test broadcast performance
|
||||
test_message = {"type": "broadcast", "data": "test message"}
|
||||
|
||||
broadcast_times = []
|
||||
num_broadcasts = 100
|
||||
|
||||
for _ in range(num_broadcasts):
|
||||
start_time = time.perf_counter()
|
||||
await connection_manager.broadcast(test_message)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
broadcast_time_ms = (end_time - start_time) * 1000
|
||||
broadcast_times.append(broadcast_time_ms)
|
||||
|
||||
avg_broadcast_time = statistics.mean(broadcast_times)
|
||||
max_broadcast_time = max(broadcast_times)
|
||||
|
||||
print(f"\nBroadcast Performance:")
|
||||
print(f"Connections: {num_connections}")
|
||||
print(f"Broadcasts: {num_broadcasts}")
|
||||
print(f"Average broadcast time: {avg_broadcast_time:.2f} ms")
|
||||
print(f"Max broadcast time: {max_broadcast_time:.2f} ms")
|
||||
|
||||
# Broadcast should scale reasonably
|
||||
assert avg_broadcast_time < 50 # Less than 50ms for 1000 connections
|
||||
|
||||
# Verify all connections received the message
|
||||
for ws in mock_websockets:
|
||||
assert ws.send_json.call_count == num_broadcasts
|
||||
|
||||
def test_subscription_management_performance(self):
|
||||
"""Test subscription management performance."""
|
||||
|
||||
connection_manager = ConnectionManager()
|
||||
|
||||
# Test subscription operations performance
|
||||
num_operations = 10000
|
||||
|
||||
# Add subscriptions
|
||||
add_times = []
|
||||
for i in range(num_operations):
|
||||
client_id = f"client_{i % 100}" # 100 unique clients
|
||||
subscription_id = f"camera_{i % 50}" # 50 unique cameras
|
||||
|
||||
start_time = time.perf_counter()
|
||||
connection_manager.add_subscription(client_id, subscription_id)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
add_time_ms = (end_time - start_time) * 1000
|
||||
add_times.append(add_time_ms)
|
||||
|
||||
# Query subscriptions
|
||||
query_times = []
|
||||
for i in range(1000):
|
||||
client_id = f"client_{i % 100}"
|
||||
|
||||
start_time = time.perf_counter()
|
||||
subscriptions = connection_manager.get_client_subscriptions(client_id)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
query_time_ms = (end_time - start_time) * 1000
|
||||
query_times.append(query_time_ms)
|
||||
|
||||
# Remove subscriptions
|
||||
remove_times = []
|
||||
for i in range(num_operations):
|
||||
client_id = f"client_{i % 100}"
|
||||
subscription_id = f"camera_{i % 50}"
|
||||
|
||||
start_time = time.perf_counter()
|
||||
connection_manager.remove_subscription(client_id, subscription_id)
|
||||
end_time = time.perf_counter()
|
||||
|
||||
remove_time_ms = (end_time - start_time) * 1000
|
||||
remove_times.append(remove_time_ms)
|
||||
|
||||
# Analyze results
|
||||
avg_add_time = statistics.mean(add_times)
|
||||
avg_query_time = statistics.mean(query_times)
|
||||
avg_remove_time = statistics.mean(remove_times)
|
||||
|
||||
print(f"\nSubscription Management Performance:")
|
||||
print(f"Average add time: {avg_add_time:.4f} ms")
|
||||
print(f"Average query time: {avg_query_time:.4f} ms")
|
||||
print(f"Average remove time: {avg_remove_time:.4f} ms")
|
||||
|
||||
# Should be very fast operations
|
||||
assert avg_add_time < 0.1
|
||||
assert avg_query_time < 0.1
|
||||
assert avg_remove_time < 0.1
|
||||
|
||||
|
||||
class TestWebSocketHandlerPerformance:
|
||||
"""Test complete WebSocket handler performance."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_concurrent_connections_performance(self, performance_config):
|
||||
"""Test performance with many concurrent connections."""
|
||||
|
||||
message_processor = MessageProcessor()
|
||||
websocket_handler = WebSocketHandler(message_processor)
|
||||
|
||||
async def simulate_client_session(client_id, num_messages=50):
|
||||
"""Simulate a client WebSocket session."""
|
||||
mock_ws = Mock()
|
||||
mock_ws.accept = AsyncMock()
|
||||
mock_ws.send_json = AsyncMock()
|
||||
mock_ws.receive_json = AsyncMock()
|
||||
|
||||
# Simulate message sequence
|
||||
messages = [
|
||||
{"type": "requestState"} for _ in range(num_messages)
|
||||
]
|
||||
messages.append(asyncio.CancelledError()) # Disconnect
|
||||
|
||||
mock_ws.receive_json.side_effect = messages
|
||||
|
||||
processing_times = []
|
||||
try:
|
||||
await websocket_handler.handle_websocket(mock_ws, client_id)
|
||||
except asyncio.CancelledError:
|
||||
pass # Expected disconnect
|
||||
|
||||
return len(messages) - 1 # Exclude the disconnect
|
||||
|
||||
# Test concurrent connections
|
||||
num_concurrent_clients = 100
|
||||
messages_per_client = 25
|
||||
|
||||
start_time = time.perf_counter()
|
||||
|
||||
tasks = [
|
||||
simulate_client_session(f"perf_client_{i}", messages_per_client)
|
||||
for i in range(num_concurrent_clients)
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
end_time = time.perf_counter()
|
||||
total_time = end_time - start_time
|
||||
|
||||
# Analyze results
|
||||
successful_clients = len([r for r in results if not isinstance(r, Exception)])
|
||||
total_messages = sum(r for r in results if isinstance(r, int))
|
||||
|
||||
print(f"\nConcurrent Connections Performance:")
|
||||
print(f"Concurrent clients: {num_concurrent_clients}")
|
||||
print(f"Successful clients: {successful_clients}")
|
||||
print(f"Total messages: {total_messages}")
|
||||
print(f"Total time: {total_time:.2f} seconds")
|
||||
print(f"Messages per second: {total_messages / total_time:.0f}")
|
||||
|
||||
assert successful_clients >= num_concurrent_clients * 0.95 # 95% success rate
|
||||
assert total_messages / total_time >= 1000 # At least 1000 msg/sec throughput
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_memory_usage_under_load(self, performance_config):
|
||||
"""Test memory usage under high connection load."""
|
||||
|
||||
message_processor = MessageProcessor()
|
||||
websocket_handler = WebSocketHandler(message_processor)
|
||||
|
||||
# Measure initial memory
|
||||
initial_memory = psutil.Process().memory_info().rss / 1024 / 1024 # MB
|
||||
|
||||
# Create many connections
|
||||
num_connections = 500
|
||||
connections = []
|
||||
|
||||
for i in range(num_connections):
|
||||
mock_ws = Mock()
|
||||
mock_ws.accept = AsyncMock()
|
||||
mock_ws.send_json = AsyncMock()
|
||||
|
||||
connection = websocket_handler.connection_manager._create_connection(
|
||||
mock_ws, f"mem_test_client_{i}"
|
||||
)
|
||||
connection.is_connected = True
|
||||
websocket_handler.connection_manager.connections[f"mem_test_client_{i}"] = connection
|
||||
connections.append(connection)
|
||||
|
||||
# Measure memory after connections
|
||||
after_connections_memory = psutil.Process().memory_info().rss / 1024 / 1024
|
||||
memory_per_connection = (after_connections_memory - initial_memory) / num_connections * 1024 # KB
|
||||
|
||||
# Simulate some activity
|
||||
test_message = {"type": "broadcast", "data": "test"}
|
||||
for _ in range(10):
|
||||
await websocket_handler.connection_manager.broadcast(test_message)
|
||||
|
||||
# Measure memory after activity
|
||||
after_activity_memory = psutil.Process().memory_info().rss / 1024 / 1024
|
||||
|
||||
print(f"\nMemory Usage Under Load:")
|
||||
print(f"Initial memory: {initial_memory:.1f} MB")
|
||||
print(f"After {num_connections} connections: {after_connections_memory:.1f} MB")
|
||||
print(f"After activity: {after_activity_memory:.1f} MB")
|
||||
print(f"Memory per connection: {memory_per_connection:.1f} KB")
|
||||
|
||||
# Memory usage should be reasonable
|
||||
assert memory_per_connection < performance_config["max_memory_per_connection_kb"]
|
||||
|
||||
# Clean up
|
||||
websocket_handler.connection_manager.connections.clear()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_heartbeat_performance(self):
|
||||
"""Test heartbeat mechanism performance."""
|
||||
|
||||
message_processor = MessageProcessor()
|
||||
websocket_handler = WebSocketHandler(message_processor, {"heartbeat_interval": 0.1})
|
||||
|
||||
# Create connections with mock WebSockets
|
||||
num_connections = 100
|
||||
mock_websockets = []
|
||||
|
||||
for i in range(num_connections):
|
||||
mock_ws = Mock()
|
||||
mock_ws.ping = AsyncMock()
|
||||
mock_websockets.append(mock_ws)
|
||||
|
||||
connection = websocket_handler.connection_manager._create_connection(
|
||||
mock_ws, f"heartbeat_client_{i}"
|
||||
)
|
||||
connection.is_connected = True
|
||||
websocket_handler.connection_manager.connections[f"heartbeat_client_{i}"] = connection
|
||||
|
||||
# Start heartbeat task
|
||||
heartbeat_task = asyncio.create_task(websocket_handler._heartbeat_loop())
|
||||
|
||||
# Let it run for several heartbeat cycles
|
||||
start_time = time.perf_counter()
|
||||
await asyncio.sleep(0.5) # 5 heartbeat cycles
|
||||
end_time = time.perf_counter()
|
||||
|
||||
# Cancel heartbeat
|
||||
heartbeat_task.cancel()
|
||||
|
||||
try:
|
||||
await heartbeat_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
# Analyze heartbeat performance
|
||||
elapsed_time = end_time - start_time
|
||||
expected_pings = int(elapsed_time / 0.1) * num_connections
|
||||
|
||||
actual_pings = sum(ws.ping.call_count for ws in mock_websockets)
|
||||
ping_efficiency = actual_pings / expected_pings if expected_pings > 0 else 0
|
||||
|
||||
print(f"\nHeartbeat Performance:")
|
||||
print(f"Connections: {num_connections}")
|
||||
print(f"Elapsed time: {elapsed_time:.2f} seconds")
|
||||
print(f"Expected pings: {expected_pings}")
|
||||
print(f"Actual pings: {actual_pings}")
|
||||
print(f"Ping efficiency: {ping_efficiency:.2%}")
|
||||
|
||||
# Should achieve reasonable ping efficiency
|
||||
assert ping_efficiency > 0.8 # At least 80% efficiency
|
||||
|
||||
# Clean up
|
||||
websocket_handler.connection_manager.connections.clear()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_error_handling_performance(self):
|
||||
"""Test performance impact of error handling."""
|
||||
|
||||
message_processor = MessageProcessor()
|
||||
websocket_handler = WebSocketHandler(message_processor)
|
||||
|
||||
# Create messages that will cause errors
|
||||
error_messages = [
|
||||
{"invalid": "message"}, # Missing type
|
||||
{"type": "unknown_type"}, # Unknown type
|
||||
{"type": "subscribe"}, # Missing payload
|
||||
]
|
||||
|
||||
valid_message = {"type": "requestState"}
|
||||
|
||||
# Mix error messages with valid ones
|
||||
test_sequence = (error_messages + [valid_message]) * 250 # 1000 total messages
|
||||
|
||||
start_time = time.perf_counter()
|
||||
|
||||
for message in test_sequence:
|
||||
await message_processor.process_message(message, "error_perf_client")
|
||||
|
||||
end_time = time.perf_counter()
|
||||
total_time = end_time - start_time
|
||||
throughput = len(test_sequence) / total_time
|
||||
|
||||
print(f"\nError Handling Performance:")
|
||||
print(f"Total messages (with errors): {len(test_sequence)}")
|
||||
print(f"Total time: {total_time:.2f} seconds")
|
||||
print(f"Throughput: {throughput:.0f} messages/second")
|
||||
|
||||
# Error handling shouldn't significantly impact performance
|
||||
assert throughput > 500 # Should still process > 500 msg/sec with errors
|
Loading…
Add table
Add a link
Reference in a new issue