mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
refactor: ruff autofixes
This commit is contained in:
455 files changed
+12322
-14762
No files matched your search
@@ -32,23 +32,19 @@ async def create_test_projects(base_url, headers, count, prefix="perf_test_proje
|
||||
|
||||
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}
|
||||
)
|
||||
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 hasattr(result, "status_code"):
|
||||
if result.status_code == 201:
|
||||
print(f" Created project {i+1}/{count}")
|
||||
print(f" Created project {i + 1}/{count}")
|
||||
else:
|
||||
print(f" Failed to create project {i+1}: {result.status_code}")
|
||||
print(f" Failed to create project {i + 1}: {result.status_code}")
|
||||
else:
|
||||
print(f" Error creating project {i+1}: {result}")
|
||||
print(f" Error creating project {i + 1}: {result}")
|
||||
|
||||
|
||||
async def cleanup_test_projects(base_url, headers, prefix="perf_test_project"):
|
||||
@@ -62,7 +58,7 @@ async def cleanup_test_projects(base_url, headers, prefix="perf_test_project"):
|
||||
if response.status_code == 200:
|
||||
projects = response.json()
|
||||
|
||||
test_projects = [p for p in projects if p.get('name', '').startswith(prefix)]
|
||||
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
|
||||
@@ -72,21 +68,18 @@ async def cleanup_test_projects(base_url, headers, prefix="perf_test_project"):
|
||||
batch_tasks = []
|
||||
|
||||
for project in test_projects[batch_start:batch_end]:
|
||||
task = client.delete(
|
||||
f"{base_url}/projects/{project['project_id']}",
|
||||
headers=headers
|
||||
)
|
||||
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:
|
||||
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)")
|
||||
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}")
|
||||
@@ -95,11 +88,11 @@ async def cleanup_test_projects(base_url, headers, prefix="perf_test_project"):
|
||||
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"\n{'=' * 60}")
|
||||
print(f"GET /projects Performance Benchmark")
|
||||
print(f"{'='*60}")
|
||||
print(f"{'=' * 60}")
|
||||
print(f"Server: {base_url}")
|
||||
print(f"{'='*60}\n")
|
||||
print(f"{'=' * 60}\n")
|
||||
|
||||
if project_count > 0:
|
||||
await create_test_projects(base_url, headers, project_count, prefix)
|
||||
@@ -137,11 +130,11 @@ async def benchmark_get_projects(base_url, headers, project_count, iterations=10
|
||||
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)")
|
||||
print(f" Iteration {i + 1}: {elapsed_ms:.2f}ms ({len(projects)} projects)")
|
||||
else:
|
||||
print(f" Iteration {i+1}: ERROR {response.status_code}")
|
||||
print(f" Iteration {i + 1}: ERROR {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" Iteration {i+1}: Exception - {type(e).__name__}: {str(e)}")
|
||||
print(f" Iteration {i + 1}: Exception - {type(e).__name__}: {str(e)}")
|
||||
|
||||
# Cleanup test projects
|
||||
if project_count > 0:
|
||||
@@ -155,17 +148,17 @@ async def benchmark_get_projects(base_url, headers, project_count, iterations=10
|
||||
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}")
|
||||
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"{'=' * 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}")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
# Performance assessment
|
||||
if avg_time < 100:
|
||||
@@ -185,36 +178,43 @@ async def benchmark_get_projects(base_url, headers, project_count, iterations=10
|
||||
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")
|
||||
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
|
||||
"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')
|
||||
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()
|
||||
|
||||
@@ -227,8 +227,7 @@ async def main():
|
||||
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}
|
||||
f"{base_url}/access/users/login", data={"username": args.username, "password": args.password}
|
||||
)
|
||||
|
||||
if login_response.status_code != 200:
|
||||
@@ -274,13 +273,13 @@ async def main():
|
||||
|
||||
# Summary
|
||||
if len(results) > 1:
|
||||
print(f"\n{'='*60}")
|
||||
print(f"\n{'=' * 60}")
|
||||
print("Summary - Performance Scaling")
|
||||
print(f"{'='*60}")
|
||||
print(f"{'=' * 60}")
|
||||
print(f"{'Projects':<12} {'Avg (ms)':<12} {'Status':<12}")
|
||||
print(f"{'-'*40}")
|
||||
print(f"{'-' * 40}")
|
||||
for r in results:
|
||||
avg = r['avg_time_ms']
|
||||
avg = r["avg_time_ms"]
|
||||
if avg < 100:
|
||||
status = "EXCELLENT"
|
||||
elif avg < 500:
|
||||
@@ -290,7 +289,7 @@ async def main():
|
||||
else:
|
||||
status = "CRITICAL"
|
||||
print(f"{r['project_count']:<12} {avg:<12.2f} {status:<12}")
|
||||
print(f"{'='*60}\n")
|
||||
print(f"{'=' * 60}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -18,10 +18,7 @@ from typing import List, Optional
|
||||
import sys
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -41,8 +38,7 @@ class TelnetClient:
|
||||
try:
|
||||
log.debug(f"Client {self.client_id}: Connecting to {self.host}:{self.port}...")
|
||||
self.reader, self.writer = await asyncio.wait_for(
|
||||
asyncio.open_connection(self.host, self.port),
|
||||
timeout=5.0
|
||||
asyncio.open_connection(self.host, self.port), timeout=5.0
|
||||
)
|
||||
self.connected = True
|
||||
log.debug(f"Client {self.client_id}: Connected successfully")
|
||||
@@ -57,7 +53,7 @@ class TelnetClient:
|
||||
return False
|
||||
|
||||
try:
|
||||
self.writer.write(command.encode() + b'\r\n')
|
||||
self.writer.write(command.encode() + b"\r\n")
|
||||
await asyncio.wait_for(self.writer.drain(), timeout=2.0)
|
||||
log.debug(f"Client {self.client_id}: Sent command: {command.strip()}")
|
||||
return True
|
||||
@@ -73,7 +69,7 @@ class TelnetClient:
|
||||
try:
|
||||
data = await asyncio.wait_for(self.reader.read(1024), timeout=timeout)
|
||||
if data:
|
||||
response = data.decode('utf-8', errors='ignore')
|
||||
response = data.decode("utf-8", errors="ignore")
|
||||
log.debug(f"Client {self.client_id}: Received: {response[:50]}...")
|
||||
return response
|
||||
except asyncio.TimeoutError:
|
||||
@@ -121,7 +117,7 @@ async def rapid_fire_client(
|
||||
max_delay: float = 0.01,
|
||||
receive_before_disconnect: bool = False,
|
||||
immediate_disconnect: bool = True,
|
||||
device_type: str = "iou-l3"
|
||||
device_type: str = "iou-l3",
|
||||
):
|
||||
"""
|
||||
A client that rapidly connects, sends commands, and disconnects.
|
||||
@@ -220,13 +216,7 @@ async def rapid_fire_client(
|
||||
return success_count, fail_count
|
||||
|
||||
|
||||
async def long_lived_client(
|
||||
client_id: int,
|
||||
host: str,
|
||||
port: str,
|
||||
duration: float,
|
||||
send_interval: float = 1.0
|
||||
):
|
||||
async def long_lived_client(client_id: int, host: str, port: str, duration: float, send_interval: float = 1.0):
|
||||
"""
|
||||
A long-lived client that stays connected and periodically sends commands.
|
||||
|
||||
@@ -273,7 +263,7 @@ async def run_stress_test(
|
||||
long_lived_clients: int,
|
||||
iterations_per_client: int,
|
||||
test_duration: float,
|
||||
device_type: str = "iou-l3"
|
||||
device_type: str = "iou-l3",
|
||||
):
|
||||
"""
|
||||
Run the stress test with multiple concurrent clients.
|
||||
@@ -306,13 +296,7 @@ async def run_stress_test(
|
||||
# Start long-lived clients first (simulate web console users)
|
||||
for i in range(long_lived_clients):
|
||||
task = asyncio.create_task(
|
||||
long_lived_client(
|
||||
client_id=1000 + i,
|
||||
host=host,
|
||||
port=port,
|
||||
duration=test_duration,
|
||||
send_interval=2.0
|
||||
)
|
||||
long_lived_client(client_id=1000 + i, host=host, port=port, duration=test_duration, send_interval=2.0)
|
||||
)
|
||||
tasks.append(task)
|
||||
await asyncio.sleep(0.1) # Stagger connections
|
||||
@@ -332,10 +316,10 @@ async def run_stress_test(
|
||||
port=port,
|
||||
iterations=iterations_per_client,
|
||||
min_delay=0.001, # 1ms - very fast
|
||||
max_delay=0.01, # 10ms - still fast
|
||||
max_delay=0.01, # 10ms - still fast
|
||||
receive_before_disconnect=False, # Don't wait for response
|
||||
immediate_disconnect=True, # Abrupt close
|
||||
device_type=device_type
|
||||
device_type=device_type,
|
||||
)
|
||||
)
|
||||
rapid_tasks.append(task)
|
||||
@@ -393,62 +377,31 @@ Device Types:
|
||||
iou-l3 - Cisco IOS L3 router (uses show/run/write commands that trigger broadcast)
|
||||
vpcs - VPCS simulator (simple commands)
|
||||
generic - Generic device (basic test commands)
|
||||
"""
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--host',
|
||||
default='127.0.0.1',
|
||||
help='Telnet server host (default: 127.0.0.1)'
|
||||
)
|
||||
parser.add_argument("--host", default="127.0.0.1", help="Telnet server host (default: 127.0.0.1)")
|
||||
|
||||
parser.add_argument("--port", type=int, default=2000, help="Telnet server port (default: 2000)")
|
||||
|
||||
parser.add_argument(
|
||||
'--port',
|
||||
type=int,
|
||||
default=2000,
|
||||
help='Telnet server port (default: 2000)'
|
||||
"--rapid-clients", type=int, default=10, help="Number of rapid connect/disconnect clients (default: 10)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--rapid-clients',
|
||||
type=int,
|
||||
default=10,
|
||||
help='Number of rapid connect/disconnect clients (default: 10)'
|
||||
)
|
||||
parser.add_argument("--long-lived", type=int, default=2, help="Number of long-lived clients (default: 2)")
|
||||
|
||||
parser.add_argument("--iterations", type=int, default=50, help="Iterations per rapid client (default: 50)")
|
||||
|
||||
parser.add_argument("--duration", type=float, default=30.0, help="Test duration in seconds (default: 30.0)")
|
||||
|
||||
parser.add_argument(
|
||||
'--long-lived',
|
||||
type=int,
|
||||
default=2,
|
||||
help='Number of long-lived clients (default: 2)'
|
||||
"--device-type",
|
||||
default="iou-l3",
|
||||
choices=["iou-l3", "vpcs", "generic"],
|
||||
help="Device type for commands (default: iou-l3)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--iterations',
|
||||
type=int,
|
||||
default=50,
|
||||
help='Iterations per rapid client (default: 50)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--duration',
|
||||
type=float,
|
||||
default=30.0,
|
||||
help='Test duration in seconds (default: 30.0)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--device-type',
|
||||
default='iou-l3',
|
||||
choices=['iou-l3', 'vpcs', 'generic'],
|
||||
help='Device type for commands (default: iou-l3)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
help='Enable verbose logging'
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -456,19 +409,21 @@ Device Types:
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
|
||||
try:
|
||||
asyncio.run(run_stress_test(
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
rapid_clients=args.rapid_clients,
|
||||
long_lived_clients=args.long_lived,
|
||||
iterations_per_client=args.iterations,
|
||||
test_duration=args.duration,
|
||||
device_type=args.device_type
|
||||
))
|
||||
asyncio.run(
|
||||
run_stress_test(
|
||||
host=args.host,
|
||||
port=args.port,
|
||||
rapid_clients=args.rapid_clients,
|
||||
long_lived_clients=args.long_lived,
|
||||
iterations_per_client=args.iterations,
|
||||
test_duration=args.duration,
|
||||
device_type=args.device_type,
|
||||
)
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
log.info("Test interrupted by user")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user