aegis-stack 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aegis-stack might be problematic. Click here for more details.
- aegis/__init__.py +5 -0
- aegis/__main__.py +374 -0
- aegis/core/CLAUDE.md +365 -0
- aegis/core/__init__.py +6 -0
- aegis/core/components.py +115 -0
- aegis/core/dependency_resolver.py +119 -0
- aegis/core/template_generator.py +163 -0
- aegis/templates/CLAUDE.md +306 -0
- aegis/templates/cookiecutter-aegis-project/cookiecutter.json +27 -0
- aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +172 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +70 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +127 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +211 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py +321 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py +638 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +134 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +247 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/tasks.py.j2 +596 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +133 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +190 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/theme.py +46 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py +687 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py +138 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +48 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +36 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +526 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +32 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +279 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +60 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py +67 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +85 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +61 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +661 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +269 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1105 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +169 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +195 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +414 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +156 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +104 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +239 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +76 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +81 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +376 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +633 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +665 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +602 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +96 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +224 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +50 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
- aegis_stack-0.1.0.dist-info/METADATA +114 -0
- aegis_stack-0.1.0.dist-info/RECORD +103 -0
- aegis_stack-0.1.0.dist-info/WHEEL +4 -0
- aegis_stack-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException
|
|
4
|
+
from starlette import status
|
|
5
|
+
|
|
6
|
+
from app.services.system import (
|
|
7
|
+
DetailedHealthResponse,
|
|
8
|
+
HealthResponse,
|
|
9
|
+
get_system_status,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.get("/", response_model=HealthResponse)
|
|
16
|
+
async def health_check() -> HealthResponse:
|
|
17
|
+
"""
|
|
18
|
+
Quick health check endpoint.
|
|
19
|
+
|
|
20
|
+
Returns basic healthy/unhealthy status for load balancers and monitoring.
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
system_status = await get_system_status()
|
|
24
|
+
return HealthResponse(
|
|
25
|
+
healthy=system_status.overall_healthy,
|
|
26
|
+
status="healthy" if system_status.overall_healthy else "unhealthy",
|
|
27
|
+
components=system_status.components,
|
|
28
|
+
timestamp=system_status.timestamp.isoformat(),
|
|
29
|
+
)
|
|
30
|
+
except Exception:
|
|
31
|
+
# If health checks fail completely, consider unhealthy
|
|
32
|
+
return HealthResponse(
|
|
33
|
+
healthy=False,
|
|
34
|
+
status="unhealthy",
|
|
35
|
+
components={},
|
|
36
|
+
timestamp="",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.get("/detailed", response_model=DetailedHealthResponse)
|
|
41
|
+
async def detailed_health() -> DetailedHealthResponse:
|
|
42
|
+
"""
|
|
43
|
+
Detailed health check with component information.
|
|
44
|
+
|
|
45
|
+
Returns comprehensive system status including individual component health,
|
|
46
|
+
system metrics, and diagnostic information.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
system_status = await get_system_status()
|
|
50
|
+
|
|
51
|
+
# Always return 200 OK - service is available even if components are unhealthy
|
|
52
|
+
return DetailedHealthResponse(
|
|
53
|
+
healthy=system_status.overall_healthy,
|
|
54
|
+
status="healthy" if system_status.overall_healthy else "unhealthy",
|
|
55
|
+
service="{{ cookiecutter.project_name }}",
|
|
56
|
+
version="0.1.0",
|
|
57
|
+
components=system_status.components,
|
|
58
|
+
system_info=system_status.system_info,
|
|
59
|
+
timestamp=system_status.timestamp.isoformat(),
|
|
60
|
+
healthy_components=system_status.healthy_components,
|
|
61
|
+
unhealthy_components=system_status.unhealthy_components,
|
|
62
|
+
health_percentage=system_status.health_percentage,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
except HTTPException:
|
|
66
|
+
# Re-raise HTTP exceptions from unexpected errors
|
|
67
|
+
raise
|
|
68
|
+
except Exception as e:
|
|
69
|
+
# Handle unexpected errors
|
|
70
|
+
raise HTTPException(
|
|
71
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
72
|
+
detail={
|
|
73
|
+
"message": "Health check failed",
|
|
74
|
+
"error": str(e),
|
|
75
|
+
"status": "unhealthy",
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@router.get("/dashboard")
|
|
81
|
+
async def system_dashboard() -> dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
System dashboard endpoint optimized for frontend consumption.
|
|
84
|
+
|
|
85
|
+
Returns system status with additional dashboard metadata like
|
|
86
|
+
alert counts, trend data, and formatted display information.
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
system_status = await get_system_status()
|
|
90
|
+
|
|
91
|
+
# TODO: Implement alert tracking when alert management is enhanced
|
|
92
|
+
recent_alerts = 0
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
"status": "healthy" if system_status.overall_healthy else "unhealthy",
|
|
96
|
+
"service": "{{ cookiecutter.project_name }}",
|
|
97
|
+
"version": "0.1.0",
|
|
98
|
+
"dashboard_data": {
|
|
99
|
+
"overall_status": {
|
|
100
|
+
"healthy": system_status.overall_healthy,
|
|
101
|
+
"percentage": system_status.health_percentage,
|
|
102
|
+
"status_text": (
|
|
103
|
+
"System Healthy"
|
|
104
|
+
if system_status.overall_healthy
|
|
105
|
+
else "Issues Detected"
|
|
106
|
+
),
|
|
107
|
+
},
|
|
108
|
+
"components": {
|
|
109
|
+
name: {
|
|
110
|
+
"name": name,
|
|
111
|
+
"healthy": component.healthy,
|
|
112
|
+
"message": component.message,
|
|
113
|
+
"response_time_ms": component.response_time_ms,
|
|
114
|
+
"metadata": component.metadata,
|
|
115
|
+
}
|
|
116
|
+
for name, component in system_status.components.items()
|
|
117
|
+
},
|
|
118
|
+
"summary": {
|
|
119
|
+
"total_components": len(system_status.components),
|
|
120
|
+
"healthy_components": len(system_status.healthy_components),
|
|
121
|
+
"unhealthy_components": len(system_status.unhealthy_components),
|
|
122
|
+
"recent_alerts": recent_alerts,
|
|
123
|
+
},
|
|
124
|
+
"system_info": system_status.system_info,
|
|
125
|
+
"timestamp": system_status.timestamp.isoformat(),
|
|
126
|
+
"last_updated": system_status.timestamp.strftime("%H:%M:%S"),
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
raise HTTPException(
|
|
132
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
133
|
+
detail={"message": "Dashboard data unavailable", "error": str(e)},
|
|
134
|
+
)
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
{%- if cookiecutter.include_worker == "yes" %}
|
|
2
|
+
"""
|
|
3
|
+
API models for background tasks and responses.
|
|
4
|
+
|
|
5
|
+
This module contains all Pydantic models used by the API layer for
|
|
6
|
+
task management, status tracking, and response formatting.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Dict, Any, Optional
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
from app.components.worker.constants import LoadTestTypes
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TaskRequest(BaseModel):
|
|
18
|
+
"""Request model for enqueueing a background task."""
|
|
19
|
+
|
|
20
|
+
task_name: str = Field(..., description="Name of the task function to execute")
|
|
21
|
+
queue_type: str = Field(
|
|
22
|
+
"system", description="Functional queue type: media or system"
|
|
23
|
+
)
|
|
24
|
+
args: list[Any] = Field(
|
|
25
|
+
default_factory=list, description="Positional arguments for the task"
|
|
26
|
+
)
|
|
27
|
+
kwargs: Dict[str, Any] = Field(
|
|
28
|
+
default_factory=dict, description="Keyword arguments for the task"
|
|
29
|
+
)
|
|
30
|
+
delay_seconds: Optional[int] = Field(
|
|
31
|
+
None, description="Delay task execution by this many seconds"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TaskResponse(BaseModel):
|
|
36
|
+
"""Response model for task enqueue operations."""
|
|
37
|
+
|
|
38
|
+
task_id: str
|
|
39
|
+
task_name: str
|
|
40
|
+
queue_type: str
|
|
41
|
+
queued_at: datetime
|
|
42
|
+
estimated_start: Optional[datetime] = None
|
|
43
|
+
message: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TaskListResponse(BaseModel):
|
|
47
|
+
"""Response model for listing available tasks."""
|
|
48
|
+
|
|
49
|
+
available_tasks: list[str]
|
|
50
|
+
total_count: int
|
|
51
|
+
queues: Dict[str, list[str]]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TaskStatusResponse(BaseModel):
|
|
55
|
+
"""Response model for task status checks."""
|
|
56
|
+
|
|
57
|
+
task_id: str
|
|
58
|
+
status: str = Field(
|
|
59
|
+
...,
|
|
60
|
+
description="Task status: queued, in_progress, complete, failed, unknown",
|
|
61
|
+
)
|
|
62
|
+
enqueue_time: Optional[datetime] = Field(
|
|
63
|
+
None, description="When the task was enqueued"
|
|
64
|
+
)
|
|
65
|
+
start_time: Optional[datetime] = Field(
|
|
66
|
+
None, description="When the task started processing"
|
|
67
|
+
)
|
|
68
|
+
finish_time: Optional[datetime] = Field(None, description="When the task completed")
|
|
69
|
+
result_available: bool = Field(
|
|
70
|
+
False, description="Whether task result is available"
|
|
71
|
+
)
|
|
72
|
+
error: Optional[str] = Field(None, description="Error message if task failed")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TaskResultResponse(BaseModel):
|
|
76
|
+
"""Response model for completed task results."""
|
|
77
|
+
|
|
78
|
+
task_id: str
|
|
79
|
+
status: str = Field(..., description="Task completion status")
|
|
80
|
+
result: Any = Field(..., description="The actual task result data")
|
|
81
|
+
enqueue_time: Optional[datetime] = Field(
|
|
82
|
+
None, description="When the task was enqueued"
|
|
83
|
+
)
|
|
84
|
+
start_time: Optional[datetime] = Field(
|
|
85
|
+
None, description="When the task started processing"
|
|
86
|
+
)
|
|
87
|
+
finish_time: Optional[datetime] = Field(None, description="When the task completed")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class LoadTestRequest(BaseModel):
|
|
91
|
+
"""Request model for orchestrating load tests that measure queue throughput."""
|
|
92
|
+
|
|
93
|
+
num_tasks: int = Field(
|
|
94
|
+
100,
|
|
95
|
+
description="Number of tasks to spawn for the load test",
|
|
96
|
+
ge=10,
|
|
97
|
+
le=10000
|
|
98
|
+
)
|
|
99
|
+
task_type: LoadTestTypes = Field(
|
|
100
|
+
LoadTestTypes.CPU_INTENSIVE,
|
|
101
|
+
description="Type of worker task to spawn for load testing",
|
|
102
|
+
)
|
|
103
|
+
batch_size: int = Field(
|
|
104
|
+
10,
|
|
105
|
+
description="How many tasks to send concurrently per batch",
|
|
106
|
+
ge=1,
|
|
107
|
+
le=100
|
|
108
|
+
)
|
|
109
|
+
delay_ms: int = Field(
|
|
110
|
+
0,
|
|
111
|
+
description="Delay between batches in milliseconds",
|
|
112
|
+
ge=0,
|
|
113
|
+
le=5000
|
|
114
|
+
)
|
|
115
|
+
target_queue: str = Field(
|
|
116
|
+
"load_test",
|
|
117
|
+
description="Which queue to test (system, media, or load_test)",
|
|
118
|
+
pattern="^(system|media|load_test)$"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
class Config:
|
|
122
|
+
schema_extra = {
|
|
123
|
+
"examples": [
|
|
124
|
+
{
|
|
125
|
+
"description": (
|
|
126
|
+
"Quick CPU load test - fibonacci calculations and operations"
|
|
127
|
+
),
|
|
128
|
+
"num_tasks": 50,
|
|
129
|
+
"task_type": "cpu_intensive",
|
|
130
|
+
"batch_size": 10,
|
|
131
|
+
"delay_ms": 0,
|
|
132
|
+
"target_queue": "system",
|
|
133
|
+
"expected_work": (
|
|
134
|
+
"Computational: fibonacci, prime checking, math operations"
|
|
135
|
+
),
|
|
136
|
+
"expected_metrics": [
|
|
137
|
+
"fibonacci_result",
|
|
138
|
+
"math_operations_sum",
|
|
139
|
+
"operations_per_ms",
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"description": (
|
|
144
|
+
"I/O stress test - async operations and concurrent handling"
|
|
145
|
+
),
|
|
146
|
+
"num_tasks": 200,
|
|
147
|
+
"task_type": "io_simulation",
|
|
148
|
+
"batch_size": 20,
|
|
149
|
+
"delay_ms": 50,
|
|
150
|
+
"target_queue": "system",
|
|
151
|
+
"expected_work": (
|
|
152
|
+
"Async I/O: network delays, concurrent operations, files"
|
|
153
|
+
),
|
|
154
|
+
"expected_metrics": [
|
|
155
|
+
"concurrent_operations",
|
|
156
|
+
"io_efficiency_percent",
|
|
157
|
+
"concurrency_benefit",
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"description": (
|
|
162
|
+
"Memory allocation test - data structures and GC pressure"
|
|
163
|
+
),
|
|
164
|
+
"num_tasks": 500,
|
|
165
|
+
"task_type": "memory_operations",
|
|
166
|
+
"batch_size": 25,
|
|
167
|
+
"delay_ms": 0,
|
|
168
|
+
"target_queue": "media",
|
|
169
|
+
"expected_work": (
|
|
170
|
+
"Memory: allocation patterns, data manipulation, cleanup"
|
|
171
|
+
),
|
|
172
|
+
"expected_metrics": [
|
|
173
|
+
"estimated_peak_memory_mb",
|
|
174
|
+
"memory_throughput_mb_per_sec",
|
|
175
|
+
"memory_operations_count",
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"description": (
|
|
180
|
+
"Failure resilience test - ~20% random failures for errors"
|
|
181
|
+
),
|
|
182
|
+
"num_tasks": 100,
|
|
183
|
+
"task_type": "test_task_failure",
|
|
184
|
+
"batch_size": 15,
|
|
185
|
+
"delay_ms": 0,
|
|
186
|
+
"target_queue": "system",
|
|
187
|
+
"expected_work": (
|
|
188
|
+
"Failure testing: random errors, recovery patterns, resilience"
|
|
189
|
+
),
|
|
190
|
+
"expected_metrics": [
|
|
191
|
+
"failure_roll",
|
|
192
|
+
"failure_threshold",
|
|
193
|
+
"would_have_failed",
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class LoadTestStatus(BaseModel):
|
|
201
|
+
"""Status model for monitoring active load tests."""
|
|
202
|
+
|
|
203
|
+
test_id: str = Field(..., description="Unique load test identifier")
|
|
204
|
+
status: str = Field(..., description="Test status: running, completed, failed")
|
|
205
|
+
tasks_sent: int = Field(..., description="Number of tasks sent to queue")
|
|
206
|
+
tasks_completed: int = Field(..., description="Number of tasks completed")
|
|
207
|
+
tasks_failed: int = Field(0, description="Number of tasks that failed")
|
|
208
|
+
elapsed_seconds: float = Field(..., description="Time elapsed since test start")
|
|
209
|
+
current_throughput: float = Field(..., description="Current tasks per second")
|
|
210
|
+
estimated_completion: Optional[datetime] = Field(
|
|
211
|
+
None, description="Estimated completion time"
|
|
212
|
+
)
|
|
213
|
+
queue_depth: int = Field(0, description="Current queue depth")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class LoadTestResults(BaseModel):
|
|
217
|
+
"""Final results model for completed load tests."""
|
|
218
|
+
|
|
219
|
+
test_id: str = Field(..., description="Unique load test identifier")
|
|
220
|
+
status: str = Field(..., description="Final test status")
|
|
221
|
+
configuration: LoadTestRequest = Field(..., description="Test configuration used")
|
|
222
|
+
|
|
223
|
+
# Task metrics
|
|
224
|
+
tasks_sent: int = Field(..., description="Total tasks sent")
|
|
225
|
+
tasks_completed: int = Field(..., description="Total tasks completed")
|
|
226
|
+
tasks_failed: int = Field(..., description="Total tasks failed")
|
|
227
|
+
|
|
228
|
+
# Timing metrics
|
|
229
|
+
start_time: datetime = Field(..., description="Test start time")
|
|
230
|
+
end_time: Optional[datetime] = Field(None, description="Test end time")
|
|
231
|
+
total_duration_seconds: float = Field(..., description="Total test duration")
|
|
232
|
+
|
|
233
|
+
# Performance metrics
|
|
234
|
+
overall_throughput: float = Field(..., description="Overall tasks per second")
|
|
235
|
+
peak_throughput: float = Field(..., description="Peak throughput achieved")
|
|
236
|
+
failure_rate: float = Field(..., description="Percentage of tasks that failed")
|
|
237
|
+
|
|
238
|
+
# Queue metrics
|
|
239
|
+
final_queue_metrics: Dict[str, Any] = Field(
|
|
240
|
+
..., description="Final arq queue health data"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Summary
|
|
244
|
+
performance_summary: str = Field(
|
|
245
|
+
..., description="Human readable performance summary"
|
|
246
|
+
)
|
|
247
|
+
{%- endif %}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
|
|
3
|
+
from app.components.backend.api import health
|
|
4
|
+
{%- if cookiecutter.include_worker == "yes" %}
|
|
5
|
+
from app.components.backend.api import tasks
|
|
6
|
+
{%- endif %}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def include_routers(app: FastAPI) -> None:
|
|
10
|
+
"""Include all API routers in the FastAPI app"""
|
|
11
|
+
app.include_router(health.router, prefix="/health", tags=["health"])
|
|
12
|
+
{%- if cookiecutter.include_worker == "yes" %}
|
|
13
|
+
app.include_router(tasks.router, prefix="/api/v1", tags=["tasks"])
|
|
14
|
+
{%- endif %}
|