mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-09-28 23:00:12 +03:00
297 lines
11 KiB
Python
297 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Benchmark test for GET /projects performance on a running GNS3 server
|
|
|
|
This test connects to an already running GNS3 server and measures
|
|
the response time of the GET /projects endpoint.
|
|
"""
|
|
|
|
import asyncio
|
|
import time
|
|
import argparse
|
|
import sys
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
|
|
# Add parent directory to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
|
|
|
|
async def create_test_projects(base_url, headers, count, prefix="perf_test_project"):
|
|
"""Create specified number of test projects"""
|
|
print(f"Creating {count} test projects...")
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
batch_size = 50
|
|
|
|
for batch_start in range(0, count, batch_size):
|
|
batch_end = min(batch_start + batch_size, count)
|
|
batch_tasks = []
|
|
|
|
for i in range(batch_start, batch_end):
|
|
name = f"{prefix}_{i}_{uuid.uuid4().hex[:8]}"
|
|
task = client.post(f"{base_url}/projects", headers=headers, json={"name": name})
|
|
batch_tasks.append(task)
|
|
|
|
results = await asyncio.gather(*batch_tasks, return_exceptions=True)
|
|
|
|
for i, result in enumerate(results, batch_start):
|
|
if hasattr(result, "status_code"):
|
|
if result.status_code == 201:
|
|
print(f" Created project {i + 1}/{count}")
|
|
else:
|
|
print(f" Failed to create project {i + 1}: {result.status_code}")
|
|
else:
|
|
print(f" Error creating project {i + 1}: {result}")
|
|
|
|
|
|
async def cleanup_test_projects(base_url, headers, prefix="perf_test_project"):
|
|
"""Clean up test projects"""
|
|
print("Cleaning up test projects...")
|
|
cleanup_start = time.time()
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
response = await client.get(f"{base_url}/projects", headers=headers)
|
|
if response.status_code == 200:
|
|
projects = response.json()
|
|
|
|
test_projects = [p for p in projects if p.get("name", "").startswith(prefix)]
|
|
print(f" Found {len(test_projects)} test projects to delete")
|
|
|
|
batch_size = 50
|
|
total_deleted = 0
|
|
for batch_start in range(0, len(test_projects), batch_size):
|
|
batch_end = min(batch_start + batch_size, len(test_projects))
|
|
batch_tasks = []
|
|
|
|
for project in test_projects[batch_start:batch_end]:
|
|
task = client.delete(f"{base_url}/projects/{project['project_id']}", headers=headers)
|
|
batch_tasks.append(task)
|
|
|
|
results = await asyncio.gather(*batch_tasks, return_exceptions=True)
|
|
|
|
for i, result in enumerate(results, batch_start):
|
|
project = test_projects[i]
|
|
if hasattr(result, "status_code") and result.status_code == 204:
|
|
total_deleted += 1
|
|
|
|
elapsed = time.time() - cleanup_start
|
|
print(f" Deleted {total_deleted} projects in {elapsed:.1f}s ({total_deleted / elapsed:.0f} projects/sec)")
|
|
|
|
except Exception as e:
|
|
print(f"Error during cleanup: {type(e).__name__}: {e}")
|
|
|
|
|
|
async def benchmark_get_projects(base_url, headers, project_count, iterations=10, prefix="perf_test_project"):
|
|
"""Benchmark GET /projects endpoint"""
|
|
|
|
print(f"\n{'=' * 60}")
|
|
print(f"GET /projects Performance Benchmark")
|
|
print(f"{'=' * 60}")
|
|
print(f"Server: {base_url}")
|
|
print(f"{'=' * 60}\n")
|
|
|
|
if project_count > 0:
|
|
await create_test_projects(base_url, headers, project_count, prefix)
|
|
|
|
# Warm-up run
|
|
print("Performing warm-up run...")
|
|
try:
|
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
|
response = await client.get(f"{base_url}/projects", headers=headers)
|
|
print(f" Response status: {response.status_code}")
|
|
if response.status_code != 200:
|
|
print(f"Error during warm-up: HTTP {response.status_code}")
|
|
print(f"Response text: {response.text[:200]}")
|
|
return
|
|
actual_count = len(response.json())
|
|
print(f" Warm-up successful: {actual_count} projects")
|
|
except Exception as e:
|
|
print(f"Error during warm-up: {type(e).__name__}: {e}")
|
|
return
|
|
|
|
# Benchmark runs
|
|
response_times = []
|
|
project_counts = []
|
|
|
|
print(f"\nRunning {iterations} benchmark iterations...")
|
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
|
for i in range(iterations):
|
|
start_time = time.time()
|
|
try:
|
|
response = await client.get(f"{base_url}/projects", headers=headers)
|
|
end_time = time.time()
|
|
|
|
if response.status_code == 200:
|
|
elapsed_ms = (end_time - start_time) * 1000
|
|
projects = response.json()
|
|
response_times.append(elapsed_ms)
|
|
project_counts.append(len(projects))
|
|
print(f" Iteration {i + 1}: {elapsed_ms:.2f}ms ({len(projects)} projects)")
|
|
else:
|
|
print(f" Iteration {i + 1}: ERROR {response.status_code}")
|
|
except Exception as e:
|
|
print(f" Iteration {i + 1}: Exception - {type(e).__name__}: {str(e)}")
|
|
|
|
# Cleanup test projects
|
|
if project_count > 0:
|
|
await cleanup_test_projects(base_url, headers, prefix)
|
|
|
|
# Calculate statistics
|
|
if response_times:
|
|
avg_time = sum(response_times) / len(response_times)
|
|
min_time = min(response_times)
|
|
max_time = max(response_times)
|
|
median_time = sorted(response_times)[len(response_times) // 2]
|
|
avg_project_count = sum(project_counts) / len(project_counts) if project_counts else 0
|
|
|
|
print(f"\n{'=' * 60}")
|
|
if project_count > 0:
|
|
print(f"Results for {project_count} projects (created specifically):")
|
|
else:
|
|
print(f"Results for existing projects (~{avg_project_count:.0f} per request):")
|
|
print(f"{'=' * 60}")
|
|
print(f"Average: {avg_time:.2f}ms")
|
|
print(f"Median: {median_time:.2f}ms")
|
|
print(f"Min: {min_time:.2f}ms")
|
|
print(f"Max: {max_time:.2f}ms")
|
|
print(f"{'=' * 60}")
|
|
|
|
# Performance assessment
|
|
if avg_time < 100:
|
|
status = "EXCELLENT"
|
|
elif avg_time < 500:
|
|
status = "ACCEPTABLE"
|
|
elif avg_time < 2000:
|
|
status = "SLOW"
|
|
else:
|
|
status = "CRITICAL"
|
|
|
|
print(f"Status: {status}")
|
|
|
|
# Throughput: projects returned per second
|
|
if project_counts and sum(response_times) > 0:
|
|
total_projects = sum(project_counts)
|
|
total_seconds = sum(response_times) / 1000
|
|
projects_per_second = total_projects / total_seconds
|
|
print(f"Throughput: {projects_per_second:.1f} projects/sec")
|
|
print(f"{'=' * 60}\n")
|
|
|
|
return {
|
|
"project_count": project_count,
|
|
"avg_time_ms": avg_time,
|
|
"median_time_ms": median_time,
|
|
"min_time_ms": min_time,
|
|
"max_time_ms": max_time,
|
|
"projects_per_second": projects_per_second if project_counts and sum(response_times) > 0 else 0,
|
|
}
|
|
|
|
|
|
async def main():
|
|
parser = argparse.ArgumentParser(description="Benchmark GET /projects performance on running server")
|
|
parser.add_argument("--host", type=str, default="127.0.0.1", help="GNS3 server host")
|
|
parser.add_argument("--port", type=int, default=3080, help="GNS3 server port")
|
|
parser.add_argument("--username", type=str, required=True, help="Username for authentication")
|
|
parser.add_argument("--password", type=str, required=True, help="Password for authentication")
|
|
parser.add_argument(
|
|
"--project-counts",
|
|
type=int,
|
|
nargs="+",
|
|
default=[10, 50, 100],
|
|
help="Numbers of projects to test (default: 10 50 100)",
|
|
)
|
|
parser.add_argument("--iterations", type=int, default=10, help="Number of iterations per test (default: 10)")
|
|
parser.add_argument("--full-scale", action="store_true", help="Run full scale test: 10, 50, 100, 200, 500 projects")
|
|
parser.add_argument(
|
|
"--no-cleanup", action="store_true", help="Do not create/cleanup test projects (use existing projects)"
|
|
)
|
|
parser.add_argument(
|
|
"--prefix",
|
|
type=str,
|
|
default="perf_test_project",
|
|
help="Project name prefix for test/cleanup (default: perf_test_project)",
|
|
)
|
|
parser.add_argument("--cleanup-only", action="store_true", help="Only clean up test projects, do not run benchmark")
|
|
|
|
args = parser.parse_args()
|
|
|
|
base_url = f"http://{args.host}:{args.port}/v3"
|
|
|
|
# Login to get JWT token
|
|
print(f"Connecting to {base_url}")
|
|
print(f"Authenticating as {args.username}...")
|
|
|
|
try:
|
|
async with httpx.AsyncClient() as client:
|
|
login_response = await client.post(
|
|
f"{base_url}/access/users/login", data={"username": args.username, "password": args.password}
|
|
)
|
|
|
|
if login_response.status_code != 200:
|
|
print(f"Login failed: {login_response.status_code}")
|
|
print(f"Response: {login_response.text}")
|
|
sys.exit(1)
|
|
|
|
token_data = login_response.json()
|
|
access_token = token_data.get("access_token")
|
|
headers = {"Authorization": f"Bearer {access_token}"}
|
|
print("Authentication successful")
|
|
|
|
except Exception as e:
|
|
print(f"Error connecting to server: {e}")
|
|
print("Make sure GNS3 server is running and accessible")
|
|
sys.exit(1)
|
|
|
|
# Determine test scope
|
|
if args.full_scale:
|
|
project_counts = [10, 50, 100, 200, 500]
|
|
else:
|
|
project_counts = args.project_counts
|
|
|
|
# If cleanup-only flag, just clean up and exit
|
|
if args.cleanup_only:
|
|
await cleanup_test_projects(base_url, headers, args.prefix)
|
|
return
|
|
|
|
# If no-cleanup flag, test with existing projects as-is
|
|
if args.no_cleanup:
|
|
project_counts = [0]
|
|
args.iterations = 20
|
|
|
|
results = []
|
|
|
|
for count in project_counts:
|
|
try:
|
|
result = await benchmark_get_projects(base_url, headers, count, args.iterations, args.prefix)
|
|
if result:
|
|
results.append(result)
|
|
except Exception as e:
|
|
print(f"Error testing {count} projects: {e}")
|
|
|
|
# Summary
|
|
if len(results) > 1:
|
|
print(f"\n{'=' * 60}")
|
|
print("Summary - Performance Scaling")
|
|
print(f"{'=' * 60}")
|
|
print(f"{'Projects':<12} {'Avg (ms)':<12} {'Status':<12}")
|
|
print(f"{'-' * 40}")
|
|
for r in results:
|
|
avg = r["avg_time_ms"]
|
|
if avg < 100:
|
|
status = "EXCELLENT"
|
|
elif avg < 500:
|
|
status = "ACCEPTABLE"
|
|
elif avg < 2000:
|
|
status = "SLOW"
|
|
else:
|
|
status = "CRITICAL"
|
|
print(f"{r['project_count']:<12} {avg:<12.2f} {status:<12}")
|
|
print(f"{'=' * 60}\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|