Spaces:
Sleeping
Sleeping
| """ | |
| Monitoring endpoints for token usage, performance metrics, and health checks. | |
| Endpoints: | |
| - GET /metrics/tokens - Token usage statistics | |
| - GET /metrics/tokens/recent - Recent token usage records | |
| - GET /metrics/performance - Performance metrics | |
| - GET /metrics/health - Detailed health status | |
| """ | |
| from fastapi import APIRouter, Depends, Query | |
| from datetime import datetime, timedelta | |
| from typing import Optional | |
| from app.utils.token_tracker import token_tracker | |
| from app.utils.logging_config import get_owner_id | |
| from app.conversation.store import conversation_store | |
| from app.config.settings import settings | |
| import psutil | |
| import os | |
| router = APIRouter(prefix="/metrics", tags=["monitoring"]) | |
| async def get_token_stats( | |
| owner_id: Optional[str] = Query(None), | |
| ): | |
| """ | |
| Get token usage statistics. | |
| **Query Parameters:** | |
| - `owner_id` (optional): Filter stats for specific user (defaults to current user from header) | |
| **Headers:** | |
| - `owner-id`: User identifier (required) | |
| **Response:** | |
| ```json | |
| { | |
| "total_requests": 150, | |
| "total_tokens": 45000, | |
| "total_cost_usd": 0.85, | |
| "average_tokens_per_request": 300, | |
| "average_cost_per_request": 0.0057, | |
| "by_feature": { | |
| "text_triage": { | |
| "requests": 100, | |
| "tokens": 30000, | |
| "cost_usd": 0.60 | |
| } | |
| }, | |
| "by_status": { | |
| "success": { | |
| "count": 148, | |
| "total_tokens": 44000, | |
| "total_cost_usd": 0.84 | |
| } | |
| } | |
| } | |
| ``` | |
| """ | |
| # Get user from context (set by middleware) | |
| current_user = get_owner_id() | |
| # Allow query param to override (for admin viewing other users) | |
| target_user = owner_id or current_user | |
| if not target_user: | |
| return { | |
| "error": "owner-id header required", | |
| "total_requests": 0, | |
| "total_tokens": 0, | |
| "total_cost_usd": 0 | |
| } | |
| return token_tracker.get_stats(target_user) | |
| async def get_recent_token_records(limit: int = Query(50, le=500)): | |
| """ | |
| Get recent token usage records. | |
| **Query Parameters:** | |
| - `limit` (optional): Number of records to return, max 500 (default: 50) | |
| **Headers:** | |
| - `owner-id`: User identifier (required) | |
| **Response:** | |
| ```json | |
| [ | |
| { | |
| "timestamp": "2026-01-25T12:30:45.123456", | |
| "feature": "text_triage", | |
| "input_tokens": 250, | |
| "output_tokens": 150, | |
| "total_tokens": 400, | |
| "cost_usd": 0.007, | |
| "owner_id": "user_123", | |
| "model": "gemini-1.5-flash", | |
| "status": "success", | |
| "latency_ms": 2345.6 | |
| } | |
| ] | |
| ``` | |
| """ | |
| # Get user from context | |
| current_user = get_owner_id() | |
| if not current_user: | |
| return {"error": "owner-id header required", "records": []} | |
| records = token_tracker.get_recent_records(limit) | |
| # Filter to only show user's own records | |
| filtered = [r for r in records if r["owner_id"] == current_user] | |
| return filtered | |
| async def get_performance_metrics(): | |
| """ | |
| Get system performance metrics. | |
| **Response:** | |
| ```json | |
| { | |
| "cpu_percent": 12.5, | |
| "memory_percent": 45.2, | |
| "memory_mb": 1234, | |
| "disk_percent": 67.8, | |
| "process_threads": 8, | |
| "uptime_hours": 24.5 | |
| } | |
| ``` | |
| """ | |
| process = psutil.Process(os.getpid()) | |
| return { | |
| "timestamp": datetime.utcnow().isoformat(), | |
| "cpu_percent": process.cpu_percent(interval=1), | |
| "memory_mb": process.memory_info().rss / 1024 / 1024, | |
| "memory_percent": process.memory_percent(), | |
| "disk_percent": psutil.disk_usage('/').percent, | |
| "process_threads": process.num_threads(), | |
| "conversation_store_size": len(conversation_store.conversations), | |
| "total_token_records": len(token_tracker.records) | |
| } | |
| async def get_detailed_health(): | |
| """ | |
| Get detailed health status with all subsystems. | |
| **Response:** | |
| ```json | |
| { | |
| "status": "healthy", | |
| "timestamp": "2026-01-25T12:30:45.123456", | |
| "components": { | |
| "api": { | |
| "status": "healthy", | |
| "response_time_ms": 1.2 | |
| }, | |
| "conversation_store": { | |
| "status": "healthy", | |
| "conversations": 45, | |
| "memory_mb": 12.5 | |
| }, | |
| "logging": { | |
| "status": "healthy", | |
| "records": 1234 | |
| }, | |
| "rate_limiter": { | |
| "status": "healthy", | |
| "active_users": 12 | |
| } | |
| }, | |
| "uptime": { | |
| "hours": 24.5, | |
| "requests_processed": 5000, | |
| "errors": 2 | |
| } | |
| } | |
| ``` | |
| """ | |
| process = psutil.Process(os.getpid()) | |
| return { | |
| "status": "healthy", | |
| "timestamp": datetime.utcnow().isoformat(), | |
| "components": { | |
| "api": { | |
| "status": "healthy", | |
| "version": settings.api_version | |
| }, | |
| "conversation_store": { | |
| "status": "healthy", | |
| "conversations": len(conversation_store.conversations), | |
| "conversations_by_user": len(set( | |
| state.user_id for state in conversation_store.conversations.values() | |
| )), | |
| "stats": conversation_store.get_stats() | |
| }, | |
| "token_tracking": { | |
| "status": "healthy", | |
| "records": len(token_tracker.records), | |
| "current_month_cost_usd": token_tracker.get_stats().get("total_cost_usd", 0) | |
| }, | |
| "performance": { | |
| "cpu_percent": process.cpu_percent(interval=0.5), | |
| "memory_percent": process.memory_percent(), | |
| "memory_mb": process.memory_info().rss / 1024 / 1024, | |
| "disk_percent": psutil.disk_usage('/').percent | |
| } | |
| } | |
| } | |
| async def export_token_records( | |
| owner_id: Optional[str] = Query(None) | |
| ): | |
| """ | |
| Export token records as CSV. | |
| **Query Parameters:** | |
| - `owner_id` (optional): Filter export for specific user (defaults to current user from header) | |
| **Headers:** | |
| - `owner-id`: User identifier (required if owner_id not provided) | |
| **Returns:** CSV file download | |
| """ | |
| import tempfile | |
| from fastapi.responses import FileResponse | |
| current_user = get_owner_id() | |
| target_user = owner_id or current_user | |
| if not target_user: | |
| return {"error": "owner-id header required"} | |
| # Create temporary file | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False) as f: | |
| temp_path = f.name | |
| token_tracker.export_csv(temp_path, target_user) | |
| return FileResponse( | |
| path=temp_path, | |
| filename=f"token_usage_{target_user}_{datetime.utcnow().strftime('%Y%m%d')}.csv", | |
| media_type="text/csv" | |
| ) |