| """ |
| API Latency Benchmarks |
| |
| Measures response times for critical API endpoints using pytest-benchmark. |
| These tests establish baseline performance and enable regression detection through |
| historical tracking. |
| |
| Target Metrics: |
| - Health check <10ms P50 (liveness probe must be instant) |
| - Agent list <100ms P50 (agent listing) |
| - Agent get <50ms P50 (single agent fetch) |
| - Governance check <20ms P50 (cached governance checks) |
| - Workflow execute <200ms P50 (workflow initiation) |
| - Canvas create <100ms P50 (canvas creation) |
| - Episode list <100ms P50 (episode listing) |
| - Analytics dashboard <500ms P50 (analytics aggregation) |
| |
| Reference: Phase 208 Plan 05 - API Latency Benchmarks |
| """ |
|
|
| import pytest |
| import time |
| from typing import Dict, Any |
| from unittest.mock import MagicMock, AsyncMock |
| from httpx import AsyncClient |
|
|
| from core.database import get_db |
| from core.models import AgentRegistry, Episode |
| from main_api_app import app |
|
|
| |
| try: |
| import pytest_benchmark |
| BENCHMARK_AVAILABLE = True |
| except ImportError: |
| BENCHMARK_AVAILABLE = False |
|
|
| |
| pytestmark = pytest.mark.skipif( |
| not BENCHMARK_AVAILABLE, |
| reason="pytest-benchmark plugin not installed. Install with: pip install pytest-benchmark" |
| ) |
|
|
|
|
| class TestAPILatency: |
| """Test API endpoint latency benchmarks.""" |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_health_check_latency(self, benchmark): |
| """ |
| Benchmark health check endpoint (liveness probe). |
| |
| Target: <10ms P50 (liveness probe must be instant) |
| Endpoint: GET /health/live |
| Verify: 200 response with status "alive" |
| """ |
| from fastapi.testclient import TestClient |
|
|
| client = TestClient(app) |
|
|
| def get_health(): |
| response = client.get("/health/live") |
| |
| |
| assert response.status_code in [200, 404] |
| return response |
|
|
| result = benchmark(get_health) |
| |
| assert result.status_code in [200, 404] |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_agent_list_latency(self, benchmark, db_session): |
| """ |
| Benchmark agent list endpoint. |
| |
| Target: <100ms P50 (agent listing with pagination) |
| Endpoint: GET /api/v1/agents |
| Setup: Create 10 agents in database |
| Verify: 200 response with agent list |
| """ |
| from fastapi.testclient import TestClient |
|
|
| |
| for i in range(10): |
| agent = AgentRegistry( |
| id=f"test_agent_{i}", |
| name=f"Test Agent {i}", |
| category="test", |
| module_path="test.module", |
| class_name="TestClass", |
| status="AUTONOMOUS", |
| confidence_score=0.9 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| client = TestClient(app) |
|
|
| def get_agents(): |
| response = client.get("/api/v1/agents") |
| |
| assert response.status_code in [200, 404] |
| return response |
|
|
| result = benchmark(get_agents) |
| |
| assert result.status_code in [200, 404] |
|
|
| |
| db_session.query(AgentRegistry).filter( |
| AgentRegistry.id.like("test_agent_%") |
| ).delete(synchronize_session=False) |
| db_session.commit() |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_agent_get_latency(self, benchmark, db_session): |
| """ |
| Benchmark single agent fetch endpoint. |
| |
| Target: <50ms P50 (single agent fetch by ID) |
| Endpoint: GET /api/v1/agents/{agent_id} |
| Setup: Create agent in database |
| Verify: 200 response with agent details |
| """ |
| from fastapi.testclient import TestClient |
|
|
| |
| agent = AgentRegistry( |
| id="benchmark_test_agent", |
| name="Benchmark Test Agent", |
| category="test", |
| module_path="test.module", |
| class_name="TestClass", |
| status="AUTONOMOUS", |
| confidence_score=0.95 |
| ) |
| db_session.add(agent) |
| db_session.commit() |
|
|
| client = TestClient(app) |
|
|
| def get_agent(): |
| response = client.get("/api/v1/agents/benchmark_test_agent") |
| |
| assert response.status_code in [200, 404] |
| return response |
|
|
| result = benchmark(get_agent) |
| assert result.status_code in [200, 404] |
|
|
| |
| db_session.query(AgentRegistry).filter( |
| AgentRegistry.id == "benchmark_test_agent" |
| ).delete(synchronize_session=False) |
| db_session.commit() |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_governance_check_latency(self, benchmark): |
| """ |
| Benchmark governance check endpoint with cached data. |
| |
| Target: <20ms P50 (cached governance checks) |
| Endpoint: POST /api/v1/governance/check-permission |
| Setup: Pre-populate governance cache |
| Verify: 200 response with permission result |
| """ |
| from fastapi.testclient import TestClient |
| from core.governance_cache import GovernanceCache |
|
|
| |
| cache = GovernanceCache(max_size=1000, ttl_seconds=60) |
| cache.set( |
| agent_id="test_agent", |
| action_type="test_action", |
| data={"allowed": True, "maturity_level": "AUTONOMOUS"} |
| ) |
|
|
| client = TestClient(app) |
|
|
| def check_permission(): |
| |
| response = client.post( |
| "/api/agent-governance/check-permission", |
| json={ |
| "agent_id": "test_agent", |
| "action": "test_action", |
| "action_complexity": 1 |
| } |
| ) |
| |
| assert response.status_code in [200, 404, 422] |
| return response |
|
|
| result = benchmark(check_permission) |
| assert result.status_code in [200, 404, 422] |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_workflow_execute_latency(self, benchmark, db_session): |
| """ |
| Benchmark workflow execution initiation endpoint. |
| |
| Target: <200ms P50 (workflow initiation, not full execution) |
| Endpoint: POST /api/v1/workflows/{workflow_id}/execute |
| Setup: Create simple 2-step workflow |
| Mock: Actual execution (return execution_id) |
| Verify: 200 response with execution_id |
| """ |
| from fastapi.testclient import TestClient |
|
|
| |
| workflow_data = { |
| "id": "benchmark_workflow", |
| "name": "Benchmark Workflow", |
| "nodes": [ |
| {"id": "step1", "type": "action", "config": {"action": "test1"}}, |
| {"id": "step2", "type": "action", "config": {"action": "test2"}} |
| ], |
| "connections": [{"source": "step1", "target": "step2"}] |
| } |
|
|
| client = TestClient(app) |
|
|
| def execute_workflow(): |
| |
| response = client.post( |
| "/api/v1/workflows/benchmark_workflow/execute", |
| json={"input_data": {"test": "value"}} |
| ) |
| |
| assert response.status_code in [200, 401, 404, 405] |
| return response |
|
|
| result = benchmark(execute_workflow) |
| |
| assert result.status_code in [200, 401, 404, 405] |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_canvas_create_latency(self, benchmark): |
| """ |
| Benchmark canvas creation endpoint. |
| |
| Target: <100ms P50 (canvas creation) |
| Endpoint: POST /api/v1/canvas |
| Input: Simple markdown canvas |
| Verify: 201 response with canvas_id |
| """ |
| from fastapi.testclient import TestClient |
|
|
| client = TestClient(app) |
|
|
| def create_canvas(): |
| response = client.post( |
| "/api/v1/canvas", |
| json={ |
| "type": "markdown", |
| "content": "# Test Canvas\n\nThis is a benchmark test." |
| } |
| ) |
| |
| assert response.status_code in [201, 404, 422] |
| return response |
|
|
| result = benchmark(create_canvas) |
| assert result.status_code in [201, 404, 422] |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_episode_list_latency(self, benchmark, db_session): |
| """ |
| Benchmark episode list endpoint. |
| |
| Target: <100ms P50 (episode listing with pagination) |
| Endpoint: GET /api/v1/episodes |
| Setup: Create 10 episodes in database |
| Verify: 200 response with episode list |
| """ |
| from fastapi.testclient import TestClient |
| import uuid |
|
|
| |
| try: |
| for i in range(10): |
| episode = Episode( |
| id=str(uuid.uuid4()), |
| agent_id=f"agent_{i}", |
| session_id=f"session_{i}", |
| start_time=time.time(), |
| end_time=time.time() + 3600, |
| message_count=10 |
| ) |
| db_session.add(episode) |
| db_session.commit() |
| episodes_created = True |
| except Exception: |
| |
| episodes_created = False |
|
|
| client = TestClient(app) |
|
|
| def get_episodes(): |
| response = client.get("/api/v1/episodes") |
| |
| assert response.status_code in [200, 404] |
| return response |
|
|
| result = benchmark(get_episodes) |
| assert result.status_code in [200, 404] |
|
|
| |
| if episodes_created: |
| try: |
| db_session.query(Episode).filter( |
| Episode.agent_id.like("agent_%") |
| ).delete(synchronize_session=False) |
| db_session.commit() |
| except Exception: |
| pass |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_analytics_dashboard_latency(self, benchmark): |
| """ |
| Benchmark analytics dashboard endpoint. |
| |
| Target: <500ms P50 (analytics aggregation) |
| Endpoint: GET /api/v1/analytics/dashboard |
| Setup: Create sample analytics data |
| Verify: 200 response with dashboard data |
| """ |
| from fastapi.testclient import TestClient |
|
|
| client = TestClient(app) |
|
|
| def get_analytics(): |
| response = client.get("/api/v1/analytics/dashboard") |
| |
| assert response.status_code in [200, 404] |
| return response |
|
|
| result = benchmark(get_analytics) |
| assert result.status_code in [200, 404] |
|
|
|
|
| class TestAPIEdgeCases: |
| """Test API edge cases and error handling performance.""" |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_health_check_readiness_latency(self, benchmark): |
| """ |
| Benchmark readiness probe endpoint (includes DB check). |
| |
| Target: <100ms P50 (readiness includes dependencies check) |
| Endpoint: GET /health/ready |
| Verify: 200 response with status "ready" |
| """ |
| from fastapi.testclient import TestClient |
|
|
| client = TestClient(app) |
|
|
| def get_readiness(): |
| response = client.get("/health/ready") |
| |
| assert response.status_code in [200, 404] |
| return response |
|
|
| result = benchmark(get_readiness) |
| |
| assert result.status_code in [200, 404] |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_nonexistent_agent_latency(self, benchmark): |
| """ |
| Benchmark agent fetch for non-existent agent. |
| |
| Target: <50ms P50 (should fail fast) |
| Endpoint: GET /api/v1/agents/{nonexistent_id} |
| Verify: 404 response |
| """ |
| from fastapi.testclient import TestClient |
|
|
| client = TestClient(app) |
|
|
| def get_nonexistent_agent(): |
| response = client.get("/api/v1/agents/nonexistent_agent_12345") |
| |
| assert response.status_code in [404, 422] |
| return response |
|
|
| result = benchmark(get_nonexistent_agent) |
| assert result.status_code in [404, 422] |
|
|
| @pytest.mark.benchmark(group="api-latency") |
| def test_large_agent_list_latency(self, benchmark, db_session): |
| """ |
| Benchmark agent list with 100 agents. |
| |
| Target: <200ms P50 (pagination should handle large datasets) |
| Endpoint: GET /api/v1/agents?limit=100 |
| Setup: Create 100 agents in database |
| Verify: 200 response with paginated list |
| """ |
| from fastapi.testclient import TestClient |
|
|
| |
| agents_to_create = [] |
| for i in range(100): |
| agent = AgentRegistry( |
| id=f"load_test_agent_{i}", |
| name=f"Load Test Agent {i}", |
| category="test", |
| module_path="test.module", |
| class_name="TestClass", |
| status="AUTONOMOUS", |
| confidence_score=0.9 |
| ) |
| agents_to_create.append(agent) |
|
|
| db_session.add_all(agents_to_create) |
| db_session.commit() |
|
|
| client = TestClient(app) |
|
|
| def get_many_agents(): |
| response = client.get("/api/v1/agents?limit=100") |
| |
| assert response.status_code in [200, 404] |
| return response |
|
|
| result = benchmark(get_many_agents) |
| assert result.status_code in [200, 404] |
|
|
| |
| db_session.query(AgentRegistry).filter( |
| AgentRegistry.id.like("load_test_agent_%") |
| ).delete(synchronize_session=False) |
| db_session.commit() |
|
|