Spaces:
Running
Running
chore(deploy): sync backend from GitHub Actions
Browse files
app.py
CHANGED
|
@@ -3,6 +3,8 @@ import subprocess
|
|
| 3 |
import io
|
| 4 |
import csv
|
| 5 |
import asyncio
|
|
|
|
|
|
|
| 6 |
from typing import Dict, List, Optional, Tuple, Union, BinaryIO
|
| 7 |
# Load environment variables from .env if present
|
| 8 |
try:
|
|
@@ -13,7 +15,7 @@ except ImportError:
|
|
| 13 |
|
| 14 |
import numpy as np
|
| 15 |
from fastapi import Depends, FastAPI, File, Form, HTTPException, UploadFile, Request
|
| 16 |
-
from fastapi.responses import HTMLResponse
|
| 17 |
from fastapi.middleware.cors import CORSMiddleware
|
| 18 |
from slowapi import Limiter, _rate_limit_exceeded_handler
|
| 19 |
from slowapi.util import get_remote_address
|
|
@@ -150,7 +152,60 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|
| 150 |
|
| 151 |
app.add_middleware(SecurityHeadersMiddleware)
|
| 152 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
# --- Globals: DB pool e Redis client ---
|
|
|
|
| 154 |
db_pool = None
|
| 155 |
redis_conn = None
|
| 156 |
vision_warmup = {
|
|
|
|
| 3 |
import io
|
| 4 |
import csv
|
| 5 |
import asyncio
|
| 6 |
+
import time
|
| 7 |
+
from collections import defaultdict
|
| 8 |
from typing import Dict, List, Optional, Tuple, Union, BinaryIO
|
| 9 |
# Load environment variables from .env if present
|
| 10 |
try:
|
|
|
|
| 15 |
|
| 16 |
import numpy as np
|
| 17 |
from fastapi import Depends, FastAPI, File, Form, HTTPException, UploadFile, Request
|
| 18 |
+
from fastapi.responses import HTMLResponse, PlainTextResponse
|
| 19 |
from fastapi.middleware.cors import CORSMiddleware
|
| 20 |
from slowapi import Limiter, _rate_limit_exceeded_handler
|
| 21 |
from slowapi.util import get_remote_address
|
|
|
|
| 152 |
|
| 153 |
app.add_middleware(SecurityHeadersMiddleware)
|
| 154 |
|
| 155 |
+
_request_metrics = defaultdict(int)
|
| 156 |
+
_request_latency_ms = defaultdict(float)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
class RequestMetricsMiddleware(BaseHTTPMiddleware):
|
| 160 |
+
async def dispatch(self, request: Request, call_next):
|
| 161 |
+
started = time.perf_counter()
|
| 162 |
+
try:
|
| 163 |
+
response = await call_next(request)
|
| 164 |
+
except Exception:
|
| 165 |
+
_request_metrics[(request.method, request.url.path, "500")] += 1
|
| 166 |
+
raise
|
| 167 |
+
elapsed_ms = (time.perf_counter() - started) * 1000.0
|
| 168 |
+
key = (request.method, request.url.path, str(response.status_code))
|
| 169 |
+
_request_metrics[key] += 1
|
| 170 |
+
_request_latency_ms[(request.method, request.url.path)] += elapsed_ms
|
| 171 |
+
response.headers["X-Response-Time-Ms"] = f"{elapsed_ms:.1f}"
|
| 172 |
+
return response
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
app.add_middleware(RequestMetricsMiddleware)
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
@app.get("/metrics", include_in_schema=False)
|
| 179 |
+
def metrics():
|
| 180 |
+
lines = [
|
| 181 |
+
"# HELP animalmind_requests_total Total HTTP requests by method, path and status.",
|
| 182 |
+
"# TYPE animalmind_requests_total counter",
|
| 183 |
+
]
|
| 184 |
+
for (method, path, status), count in sorted(_request_metrics.items()):
|
| 185 |
+
lines.append(
|
| 186 |
+
f'animalmind_requests_total{{method="{method}",path="{path}",status="{status}"}} {count}'
|
| 187 |
+
)
|
| 188 |
+
lines.extend([
|
| 189 |
+
"# HELP animalmind_request_latency_ms_sum Cumulative request latency in milliseconds.",
|
| 190 |
+
"# TYPE animalmind_request_latency_ms_sum counter",
|
| 191 |
+
])
|
| 192 |
+
for (method, path), total_ms in sorted(_request_latency_ms.items()):
|
| 193 |
+
lines.append(
|
| 194 |
+
f'animalmind_request_latency_ms_sum{{method="{method}",path="{path}"}} {total_ms:.3f}'
|
| 195 |
+
)
|
| 196 |
+
lines.extend([
|
| 197 |
+
"# HELP animalmind_sse_clients Current SSE subscribers.",
|
| 198 |
+
"# TYPE animalmind_sse_clients gauge",
|
| 199 |
+
f"animalmind_sse_clients {len(_sse_subscribers)}",
|
| 200 |
+
"# HELP animalmind_warmup_ready Whether the visual model warm-up completed.",
|
| 201 |
+
"# TYPE animalmind_warmup_ready gauge",
|
| 202 |
+
f"animalmind_warmup_ready {1 if _vit_model is not None else 0}",
|
| 203 |
+
])
|
| 204 |
+
return PlainTextResponse("\\n".join(lines) + "\\n", media_type="text/plain; version=0.0.4")
|
| 205 |
+
|
| 206 |
+
|
| 207 |
# --- Globals: DB pool e Redis client ---
|
| 208 |
+
|
| 209 |
db_pool = None
|
| 210 |
redis_conn = None
|
| 211 |
vision_warmup = {
|