test: Migrate load test benchmark scripts to anyio
Remove unused redis container
This commit is contained in:
@@ -14,19 +14,12 @@ services:
|
|||||||
- MYSQL_DATABASE=nextcloud
|
- MYSQL_DATABASE=nextcloud
|
||||||
- MYSQL_USER=nextcloud
|
- MYSQL_USER=nextcloud
|
||||||
|
|
||||||
# Note: Redis is an external service. You can find more information about the configuration here:
|
|
||||||
# https://hub.docker.com/_/redis
|
|
||||||
redis:
|
|
||||||
image: docker.io/library/redis:alpine@sha256:59b6e694653476de2c992937ebe1c64182af4728e54bb49e9b7a6c26614d8933
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
app:
|
app:
|
||||||
image: docker.io/library/nextcloud:32.0.0@sha256:3e70e4dfe882ef44738fdc30d9896fb07c12febb27c4a1177e3d63dc0004a0b4
|
image: docker.io/library/nextcloud:32.0.0@sha256:3e70e4dfe882ef44738fdc30d9896fb07c12febb27c4a1177e3d63dc0004a0b4
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 0.0.0.0:8080:80
|
- 0.0.0.0:8080:80
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
|
||||||
- db
|
- db
|
||||||
volumes:
|
volumes:
|
||||||
- nextcloud:/var/www/html
|
- nextcloud:/var/www/html
|
||||||
|
|||||||
+58
-125
@@ -854,11 +854,9 @@ async def test_users_setup(anyio_backend, nc_client: NextcloudClient):
|
|||||||
"""
|
"""
|
||||||
Create test users for multi-user OAuth testing.
|
Create test users for multi-user OAuth testing.
|
||||||
|
|
||||||
Creates four test users:
|
Creates two test users to reduce CI resource usage:
|
||||||
- alice: Owner role, creates resources
|
- alice: Owner role, creates resources
|
||||||
- bob: Viewer role, read-only access
|
- bob: Viewer role, read-only access
|
||||||
- charlie: Editor role, can edit (in 'editors' group)
|
|
||||||
- diana: No-access role, no shares
|
|
||||||
"""
|
"""
|
||||||
test_user_configs = {
|
test_user_configs = {
|
||||||
"alice": {
|
"alice": {
|
||||||
@@ -873,50 +871,12 @@ async def test_users_setup(anyio_backend, nc_client: NextcloudClient):
|
|||||||
"display_name": "Bob Viewer",
|
"display_name": "Bob Viewer",
|
||||||
"groups": [],
|
"groups": [],
|
||||||
},
|
},
|
||||||
"charlie": {
|
|
||||||
"password": "CharlieSecurePass789!",
|
|
||||||
"email": "charlie@example.com",
|
|
||||||
"display_name": "Charlie Editor",
|
|
||||||
"groups": ["editors"],
|
|
||||||
},
|
|
||||||
"diana": {
|
|
||||||
"password": "DianaSecurePass012!",
|
|
||||||
"email": "diana@example.com",
|
|
||||||
"display_name": "Diana NoAccess",
|
|
||||||
"groups": [],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Creating test users for multi-user OAuth testing...")
|
logger.info("Creating test users for multi-user OAuth testing...")
|
||||||
created_users = []
|
created_users = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create the 'editors' group first (charlie needs it)
|
|
||||||
try:
|
|
||||||
# Use admin nc_client to create the group via User API
|
|
||||||
# First, try to create it (will fail if exists, but that's okay)
|
|
||||||
async with httpx.AsyncClient() as http_client:
|
|
||||||
base_url = str(nc_client._client.base_url)
|
|
||||||
# Get password from environment since nc_client doesn't expose it
|
|
||||||
password = os.getenv("NEXTCLOUD_PASSWORD")
|
|
||||||
response = await http_client.post(
|
|
||||||
f"{base_url}/ocs/v2.php/cloud/groups",
|
|
||||||
auth=(nc_client.username, password),
|
|
||||||
headers={"OCS-APIRequest": "true", "Accept": "application/json"},
|
|
||||||
data={"groupid": "editors"},
|
|
||||||
)
|
|
||||||
if response.status_code in [
|
|
||||||
200,
|
|
||||||
409,
|
|
||||||
]: # 200 = created, 409 = already exists
|
|
||||||
logger.info("Editors group ready")
|
|
||||||
else:
|
|
||||||
logger.warning(
|
|
||||||
f"Group creation returned {response.status_code}: {response.text}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Error creating editors group (may already exist): {e}")
|
|
||||||
|
|
||||||
# Create each test user
|
# Create each test user
|
||||||
for username, config in test_user_configs.items():
|
for username, config in test_user_configs.items():
|
||||||
try:
|
try:
|
||||||
@@ -929,14 +889,6 @@ async def test_users_setup(anyio_backend, nc_client: NextcloudClient):
|
|||||||
logger.info(f"Created test user: {username}")
|
logger.info(f"Created test user: {username}")
|
||||||
created_users.append(username)
|
created_users.append(username)
|
||||||
|
|
||||||
# Add user to groups if specified
|
|
||||||
for group in config["groups"]:
|
|
||||||
try:
|
|
||||||
await nc_client.users.add_user_to_group(username, group)
|
|
||||||
logger.info(f"Added {username} to group {group}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Error adding {username} to group {group}: {e}")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# User might already exist, that's okay
|
# User might already exist, that's okay
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -1094,7 +1046,7 @@ async def _get_oauth_token_for_user(
|
|||||||
return access_token
|
return access_token
|
||||||
|
|
||||||
|
|
||||||
# Parallel token retrieval fixture - fetches all OAuth tokens concurrently
|
# OAuth token retrieval fixture - parallel locally, sequential in CI
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
async def all_oauth_tokens(
|
async def all_oauth_tokens(
|
||||||
anyio_backend,
|
anyio_backend,
|
||||||
@@ -1104,13 +1056,13 @@ async def all_oauth_tokens(
|
|||||||
oauth_callback_server,
|
oauth_callback_server,
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Fetch OAuth tokens for all test users in parallel for speed.
|
Fetch OAuth tokens for all test users.
|
||||||
|
|
||||||
|
In CI (GitHub Actions), fetches sequentially to reduce load on Nextcloud.
|
||||||
|
Locally, fetches in parallel for speed.
|
||||||
|
|
||||||
Returns a dict mapping username to OAuth access token.
|
Returns a dict mapping username to OAuth access token.
|
||||||
This is significantly faster than fetching tokens sequentially.
|
Uses the real callback server with state parameters for reliable token acquisition.
|
||||||
|
|
||||||
Now uses the real callback server with state parameters for reliable
|
|
||||||
concurrent token acquisition without race conditions.
|
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
@@ -1119,47 +1071,68 @@ async def all_oauth_tokens(
|
|||||||
auth_states, callback_url = oauth_callback_server
|
auth_states, callback_url = oauth_callback_server
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
logger.info("Fetching OAuth tokens for all users in parallel...")
|
is_ci = os.getenv("GITHUB_ACTIONS") == "true"
|
||||||
|
mode = "sequentially" if is_ci else "in parallel"
|
||||||
|
logger.info(f"Fetching OAuth tokens for all users {mode} (CI={is_ci})...")
|
||||||
logger.info(f"Using callback server at {callback_url} with state-based correlation")
|
logger.info(f"Using callback server at {callback_url} with state-based correlation")
|
||||||
|
|
||||||
async def get_token_with_delay(username: str, config: dict, delay: float):
|
|
||||||
"""Get token for a user after a small delay to stagger requests."""
|
|
||||||
if delay > 0:
|
|
||||||
await asyncio.sleep(delay)
|
|
||||||
return await _get_oauth_token_for_user(
|
|
||||||
browser,
|
|
||||||
shared_oauth_client_credentials,
|
|
||||||
auth_states,
|
|
||||||
username,
|
|
||||||
config["password"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create tasks for all users with staggered starts (0.5s apart)
|
|
||||||
tasks = {
|
|
||||||
username: get_token_with_delay(username, config, idx * 0.5)
|
|
||||||
for idx, (username, config) in enumerate(test_users_setup.items())
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run all token fetches concurrently
|
|
||||||
results = await asyncio.gather(*tasks.values(), return_exceptions=True)
|
|
||||||
|
|
||||||
# Build result dict, handling any errors
|
|
||||||
tokens = {}
|
tokens = {}
|
||||||
for username, result in zip(tasks.keys(), results):
|
|
||||||
if isinstance(result, Exception):
|
if is_ci:
|
||||||
logger.error(f"Failed to get OAuth token for {username}: {result}")
|
# Sequential execution in CI to reduce Nextcloud load
|
||||||
raise result
|
logger.info("Running in CI: using sequential OAuth token acquisition")
|
||||||
tokens[username] = result
|
for username, config in test_users_setup.items():
|
||||||
|
logger.info(f"Fetching OAuth token for {username}...")
|
||||||
|
tokens[username] = await _get_oauth_token_for_user(
|
||||||
|
browser,
|
||||||
|
shared_oauth_client_credentials,
|
||||||
|
auth_states,
|
||||||
|
username,
|
||||||
|
config["password"],
|
||||||
|
)
|
||||||
|
# Add delay between users to give Nextcloud breathing room
|
||||||
|
await asyncio.sleep(1.0)
|
||||||
|
else:
|
||||||
|
# Parallel execution locally for speed
|
||||||
|
logger.info("Running locally: using parallel OAuth token acquisition")
|
||||||
|
|
||||||
|
async def get_token_with_delay(username: str, config: dict, delay: float):
|
||||||
|
"""Get token for a user after a small delay to stagger requests."""
|
||||||
|
if delay > 0:
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
return await _get_oauth_token_for_user(
|
||||||
|
browser,
|
||||||
|
shared_oauth_client_credentials,
|
||||||
|
auth_states,
|
||||||
|
username,
|
||||||
|
config["password"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create tasks for all users with staggered starts (0.5s apart)
|
||||||
|
tasks = {
|
||||||
|
username: get_token_with_delay(username, config, idx * 0.5)
|
||||||
|
for idx, (username, config) in enumerate(test_users_setup.items())
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run all token fetches concurrently
|
||||||
|
results = await asyncio.gather(*tasks.values(), return_exceptions=True)
|
||||||
|
|
||||||
|
# Build result dict, handling any errors
|
||||||
|
for username, result in zip(tasks.keys(), results):
|
||||||
|
if isinstance(result, Exception):
|
||||||
|
logger.error(f"Failed to get OAuth token for {username}: {result}")
|
||||||
|
raise result
|
||||||
|
tokens[username] = result
|
||||||
|
|
||||||
elapsed = time.time() - start_time
|
elapsed = time.time() - start_time
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Successfully fetched {len(tokens)} OAuth tokens in parallel "
|
f"Successfully fetched {len(tokens)} OAuth tokens {mode} "
|
||||||
f"in {elapsed:.1f}s (~{elapsed / len(tokens):.1f}s per user)"
|
f"in {elapsed:.1f}s (~{elapsed / len(tokens):.1f}s per user)"
|
||||||
)
|
)
|
||||||
return tokens
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
# Session-scoped OAuth token fixtures - now use the parallel fixture
|
# Session-scoped OAuth token fixtures
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
async def alice_oauth_token(anyio_backend, all_oauth_tokens) -> str:
|
async def alice_oauth_token(anyio_backend, all_oauth_tokens) -> str:
|
||||||
"""OAuth token for alice (cached for session). Uses shared OAuth client."""
|
"""OAuth token for alice (cached for session). Uses shared OAuth client."""
|
||||||
@@ -1172,18 +1145,6 @@ async def bob_oauth_token(anyio_backend, all_oauth_tokens) -> str:
|
|||||||
return all_oauth_tokens["bob"]
|
return all_oauth_tokens["bob"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
async def charlie_oauth_token(anyio_backend, all_oauth_tokens) -> str:
|
|
||||||
"""OAuth token for charlie (cached for session). Uses shared OAuth client."""
|
|
||||||
return all_oauth_tokens["charlie"]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
async def diana_oauth_token(anyio_backend, all_oauth_tokens) -> str:
|
|
||||||
"""OAuth token for diana (cached for session). Uses shared OAuth client."""
|
|
||||||
return all_oauth_tokens["diana"]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
async def alice_mcp_client(
|
async def alice_mcp_client(
|
||||||
anyio_backend,
|
anyio_backend,
|
||||||
@@ -1211,34 +1172,6 @@ async def bob_mcp_client(
|
|||||||
yield session
|
yield session
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
async def charlie_mcp_client(
|
|
||||||
anyio_backend,
|
|
||||||
charlie_oauth_token: str,
|
|
||||||
) -> AsyncGenerator[ClientSession, Any]:
|
|
||||||
"""MCP client authenticated as charlie (editor role, in 'editors' group)."""
|
|
||||||
async for session in create_mcp_client_session(
|
|
||||||
url="http://127.0.0.1:8001/mcp",
|
|
||||||
token=charlie_oauth_token,
|
|
||||||
client_name="Charlie MCP",
|
|
||||||
):
|
|
||||||
yield session
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
async def diana_mcp_client(
|
|
||||||
anyio_backend,
|
|
||||||
diana_oauth_token: str,
|
|
||||||
) -> AsyncGenerator[ClientSession, Any]:
|
|
||||||
"""MCP client authenticated as diana (no-access role)."""
|
|
||||||
async for session in create_mcp_client_session(
|
|
||||||
url="http://127.0.0.1:8001/mcp",
|
|
||||||
token=diana_oauth_token,
|
|
||||||
client_name="Diana MCP",
|
|
||||||
):
|
|
||||||
yield session
|
|
||||||
|
|
||||||
|
|
||||||
# Test user/group fixtures for clean test isolation
|
# Test user/group fixtures for clean test isolation
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
async def test_user(nc_client: NextcloudClient):
|
async def test_user(nc_client: NextcloudClient):
|
||||||
|
|||||||
+15
-21
@@ -7,7 +7,6 @@ Usage:
|
|||||||
uv run python -m tests.load.benchmark -c 50 -d 300 --output results.json
|
uv run python -m tests.load.benchmark -c 50 -d 300 --output results.json
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import signal
|
import signal
|
||||||
@@ -254,7 +253,7 @@ async def wait_for_mcp_server(url: str, max_attempts: int = 10) -> bool:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if attempt < max_attempts:
|
if attempt < max_attempts:
|
||||||
logger.debug(f"Attempt {attempt}/{max_attempts}: {e}")
|
logger.debug(f"Attempt {attempt}/{max_attempts}: {e}")
|
||||||
await asyncio.sleep(2)
|
await anyio.sleep(2)
|
||||||
else:
|
else:
|
||||||
logger.error(f"MCP server not ready after {max_attempts} attempts")
|
logger.error(f"MCP server not ready after {max_attempts} attempts")
|
||||||
return False
|
return False
|
||||||
@@ -267,7 +266,7 @@ async def benchmark_worker(
|
|||||||
url: str,
|
url: str,
|
||||||
duration: float,
|
duration: float,
|
||||||
metrics: BenchmarkMetrics,
|
metrics: BenchmarkMetrics,
|
||||||
stop_event: asyncio.Event,
|
stop_event: anyio.Event,
|
||||||
):
|
):
|
||||||
"""Single worker that runs operations for the specified duration."""
|
"""Single worker that runs operations for the specified duration."""
|
||||||
logger.info(f"Worker {worker_id} starting...")
|
logger.info(f"Worker {worker_id} starting...")
|
||||||
@@ -293,7 +292,7 @@ async def benchmark_worker(
|
|||||||
operation_count += 1
|
operation_count += 1
|
||||||
|
|
||||||
# Small delay to prevent overwhelming the server
|
# Small delay to prevent overwhelming the server
|
||||||
await asyncio.sleep(0.01)
|
await anyio.sleep(0.01)
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
await ops.cleanup()
|
await ops.cleanup()
|
||||||
@@ -312,7 +311,7 @@ async def run_benchmark(
|
|||||||
) -> BenchmarkMetrics:
|
) -> BenchmarkMetrics:
|
||||||
"""Run the benchmark with specified parameters."""
|
"""Run the benchmark with specified parameters."""
|
||||||
metrics = BenchmarkMetrics()
|
metrics = BenchmarkMetrics()
|
||||||
stop_event = asyncio.Event()
|
stop_event = anyio.Event()
|
||||||
|
|
||||||
# Setup signal handlers for graceful shutdown
|
# Setup signal handlers for graceful shutdown
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
@@ -331,27 +330,22 @@ async def run_benchmark(
|
|||||||
# Warmup period
|
# Warmup period
|
||||||
if warmup > 0:
|
if warmup > 0:
|
||||||
print("Warming up...")
|
print("Warming up...")
|
||||||
await asyncio.sleep(warmup)
|
await anyio.sleep(warmup)
|
||||||
|
|
||||||
# Start metrics collection
|
# Start metrics collection
|
||||||
metrics.start()
|
metrics.start()
|
||||||
|
|
||||||
# Create and run workers
|
# Create and run workers using anyio task groups
|
||||||
workers = [
|
async with anyio.create_task_group() as tg:
|
||||||
benchmark_worker(i, url, duration, metrics, stop_event)
|
# Start all workers
|
||||||
for i in range(concurrency)
|
for i in range(concurrency):
|
||||||
]
|
tg.start_soon(benchmark_worker, i, url, duration, metrics, stop_event)
|
||||||
|
|
||||||
# Show progress
|
# Show progress
|
||||||
progress_task = asyncio.create_task(show_progress(duration, metrics, stop_event))
|
tg.start_soon(show_progress, duration, metrics, stop_event)
|
||||||
|
|
||||||
# Wait for all workers to complete
|
# Stop metrics (tasks already completed when task group exits)
|
||||||
await asyncio.gather(*workers, return_exceptions=True)
|
|
||||||
|
|
||||||
# Stop metrics and progress
|
|
||||||
metrics.stop()
|
metrics.stop()
|
||||||
stop_event.set()
|
|
||||||
await progress_task
|
|
||||||
|
|
||||||
return metrics
|
return metrics
|
||||||
|
|
||||||
@@ -359,7 +353,7 @@ async def run_benchmark(
|
|||||||
async def show_progress(
|
async def show_progress(
|
||||||
duration: float,
|
duration: float,
|
||||||
metrics: BenchmarkMetrics,
|
metrics: BenchmarkMetrics,
|
||||||
stop_event: asyncio.Event,
|
stop_event: anyio.Event,
|
||||||
):
|
):
|
||||||
"""Show real-time progress during benchmark."""
|
"""Show real-time progress during benchmark."""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -387,7 +381,7 @@ async def show_progress(
|
|||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
await asyncio.sleep(0.5)
|
await anyio.sleep(0.5)
|
||||||
|
|
||||||
print() # New line after progress
|
print() # New line after progress
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ Usage:
|
|||||||
uv run python -m tests.load.oauth_benchmark -u 10 -d 300 --workload sharing
|
uv run python -m tests.load.oauth_benchmark -u 10 -d 300 --workload sharing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -223,7 +222,7 @@ async def oauth_benchmark_worker(
|
|||||||
workload: MixedOAuthWorkload,
|
workload: MixedOAuthWorkload,
|
||||||
duration: float,
|
duration: float,
|
||||||
metrics: OAuthBenchmarkMetrics,
|
metrics: OAuthBenchmarkMetrics,
|
||||||
stop_event: asyncio.Event,
|
stop_event: anyio.Event,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Single worker executing operations for one user.
|
Single worker executing operations for one user.
|
||||||
@@ -258,13 +257,13 @@ async def oauth_benchmark_worker(
|
|||||||
operation_count += 1
|
operation_count += 1
|
||||||
|
|
||||||
# Small delay to prevent overwhelming the server
|
# Small delay to prevent overwhelming the server
|
||||||
await asyncio.sleep(0.05)
|
await anyio.sleep(0.05)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Worker for {user_wrapper.username} completed {operation_count} operations"
|
f"Worker for {user_wrapper.username} completed {operation_count} operations"
|
||||||
)
|
)
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except anyio.get_cancelled_exc_class():
|
||||||
# Handle task cancellation gracefully (e.g., during benchmark shutdown)
|
# Handle task cancellation gracefully (e.g., during benchmark shutdown)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Worker for {user_wrapper.username} was cancelled "
|
f"Worker for {user_wrapper.username} was cancelled "
|
||||||
@@ -278,7 +277,7 @@ async def oauth_benchmark_worker(
|
|||||||
async def show_progress(
|
async def show_progress(
|
||||||
duration: float,
|
duration: float,
|
||||||
metrics: OAuthBenchmarkMetrics,
|
metrics: OAuthBenchmarkMetrics,
|
||||||
stop_event: asyncio.Event,
|
stop_event: anyio.Event,
|
||||||
):
|
):
|
||||||
"""Show real-time progress during benchmark."""
|
"""Show real-time progress during benchmark."""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@@ -306,7 +305,7 @@ async def show_progress(
|
|||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
await asyncio.sleep(0.5)
|
await anyio.sleep(0.5)
|
||||||
|
|
||||||
print() # New line after progress
|
print() # New line after progress
|
||||||
|
|
||||||
@@ -338,7 +337,7 @@ async def run_oauth_benchmark(
|
|||||||
OAuthBenchmarkMetrics with results
|
OAuthBenchmarkMetrics with results
|
||||||
"""
|
"""
|
||||||
metrics = OAuthBenchmarkMetrics()
|
metrics = OAuthBenchmarkMetrics()
|
||||||
stop_event = asyncio.Event()
|
stop_event = anyio.Event()
|
||||||
created_users: list[str] = []
|
created_users: list[str] = []
|
||||||
callback_server: OAuthCallbackServer | None = None
|
callback_server: OAuthCallbackServer | None = None
|
||||||
user_pool: OAuthUserPool | None = None
|
user_pool: OAuthUserPool | None = None
|
||||||
@@ -437,12 +436,23 @@ async def run_oauth_benchmark(
|
|||||||
browser = await browser_launcher.launch(headless=not headed)
|
browser = await browser_launcher.launch(headless=not headed)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create all users concurrently
|
# Create all users concurrently using anyio task groups
|
||||||
tasks = [
|
results = []
|
||||||
create_user_task(i, browser, callback_server.auth_states)
|
|
||||||
for i in range(num_users)
|
async def run_and_collect(i: int):
|
||||||
]
|
"""Wrapper to collect results from tasks."""
|
||||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
try:
|
||||||
|
result = await create_user_task(
|
||||||
|
i, browser, callback_server.auth_states
|
||||||
|
)
|
||||||
|
results.append(result)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"User creation task failed: {e}")
|
||||||
|
results.append(e)
|
||||||
|
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for i in range(num_users):
|
||||||
|
tg.start_soon(run_and_collect, i)
|
||||||
|
|
||||||
# Process results
|
# Process results
|
||||||
for result in results:
|
for result in results:
|
||||||
@@ -484,13 +494,21 @@ async def run_oauth_benchmark(
|
|||||||
logger.error(f"Failed to create session for {username}: {e}")
|
logger.error(f"Failed to create session for {username}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Create all sessions concurrently
|
# Create all sessions concurrently using anyio task groups
|
||||||
session_tasks = [
|
session_results = []
|
||||||
create_session_task(username) for username in created_users
|
|
||||||
]
|
async def run_and_collect_session(username: str):
|
||||||
session_results = await asyncio.gather(
|
"""Wrapper to collect session results from tasks."""
|
||||||
*session_tasks, return_exceptions=True
|
try:
|
||||||
)
|
result = await create_session_task(username)
|
||||||
|
session_results.append(result)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Session creation task failed: {e}")
|
||||||
|
session_results.append(e)
|
||||||
|
|
||||||
|
async with anyio.create_task_group() as tg:
|
||||||
|
for username in created_users:
|
||||||
|
tg.start_soon(run_and_collect_session, username)
|
||||||
|
|
||||||
# Process results
|
# Process results
|
||||||
for result in session_results:
|
for result in session_results:
|
||||||
@@ -508,7 +526,7 @@ async def run_oauth_benchmark(
|
|||||||
# Warmup period
|
# Warmup period
|
||||||
if warmup > 0:
|
if warmup > 0:
|
||||||
print(f"Warmup period: {warmup}s...")
|
print(f"Warmup period: {warmup}s...")
|
||||||
await asyncio.sleep(warmup)
|
await anyio.sleep(warmup)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Start benchmark
|
# Start benchmark
|
||||||
@@ -518,21 +536,26 @@ async def run_oauth_benchmark(
|
|||||||
|
|
||||||
metrics.start()
|
metrics.start()
|
||||||
|
|
||||||
# Create workload and workers
|
# Create workload and workers using anyio task groups
|
||||||
workload = MixedOAuthWorkload(user_wrappers)
|
workload = MixedOAuthWorkload(user_wrappers)
|
||||||
workers = [
|
|
||||||
oauth_benchmark_worker(wrapper, workload, duration, metrics, stop_event)
|
|
||||||
for wrapper in user_wrappers
|
|
||||||
]
|
|
||||||
|
|
||||||
# Run workers with progress display
|
# Run workers with progress display
|
||||||
progress_task = asyncio.create_task(
|
async with anyio.create_task_group() as tg:
|
||||||
show_progress(duration, metrics, stop_event)
|
# Start all workers
|
||||||
)
|
for wrapper in user_wrappers:
|
||||||
await asyncio.gather(*workers, return_exceptions=True)
|
tg.start_soon(
|
||||||
stop_event.set()
|
oauth_benchmark_worker,
|
||||||
await progress_task
|
wrapper,
|
||||||
|
workload,
|
||||||
|
duration,
|
||||||
|
metrics,
|
||||||
|
stop_event,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Show progress
|
||||||
|
tg.start_soon(show_progress, duration, metrics, stop_event)
|
||||||
|
|
||||||
|
# Tasks already completed when task group exits
|
||||||
metrics.stop()
|
metrics.stop()
|
||||||
|
|
||||||
print(f"\n{'=' * 80}")
|
print(f"\n{'=' * 80}")
|
||||||
|
|||||||
Reference in New Issue
Block a user