mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 11:51:14 -05:00
Add Django Celery tasks and utilities to periodically collect system metrics (error rates, response times, queue sizes) and record them into metric_time_series. Include monitoring app scaffolding, metrics collector, Celery beat schedule, middleware for live metrics, and a Supabase edge function for cross-source metrics.
189 lines
6.5 KiB
Python
189 lines
6.5 KiB
Python
"""
|
|
Metrics collection utilities for system monitoring.
|
|
"""
|
|
import time
|
|
import logging
|
|
from typing import Dict, Any, List
|
|
from datetime import datetime, timedelta
|
|
from django.db import connection
|
|
from django.core.cache import cache
|
|
from celery import current_app as celery_app
|
|
import os
|
|
import requests
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SUPABASE_URL = os.environ.get('SUPABASE_URL', 'https://api.thrillwiki.com')
|
|
SUPABASE_SERVICE_KEY = os.environ.get('SUPABASE_SERVICE_ROLE_KEY')
|
|
|
|
|
|
class MetricsCollector:
|
|
"""Collects various system metrics for anomaly detection."""
|
|
|
|
@staticmethod
|
|
def get_error_rate() -> float:
|
|
"""
|
|
Calculate error rate from recent logs.
|
|
Returns percentage of error logs in the last minute.
|
|
"""
|
|
cache_key = 'metrics:error_rate'
|
|
cached_value = cache.get(cache_key)
|
|
|
|
if cached_value is not None:
|
|
return cached_value
|
|
|
|
# In production, query actual error logs
|
|
# For now, return a mock value
|
|
error_rate = 0.0
|
|
cache.set(cache_key, error_rate, 60)
|
|
return error_rate
|
|
|
|
@staticmethod
|
|
def get_api_response_time() -> float:
|
|
"""
|
|
Get average API response time in milliseconds.
|
|
Returns average response time from recent requests.
|
|
"""
|
|
cache_key = 'metrics:avg_response_time'
|
|
cached_value = cache.get(cache_key)
|
|
|
|
if cached_value is not None:
|
|
return cached_value
|
|
|
|
# In production, calculate from middleware metrics
|
|
# For now, return a mock value
|
|
response_time = 150.0 # milliseconds
|
|
cache.set(cache_key, response_time, 60)
|
|
return response_time
|
|
|
|
@staticmethod
|
|
def get_celery_queue_size() -> int:
|
|
"""
|
|
Get current Celery queue size across all queues.
|
|
"""
|
|
try:
|
|
inspect = celery_app.control.inspect()
|
|
active_tasks = inspect.active() or {}
|
|
scheduled_tasks = inspect.scheduled() or {}
|
|
|
|
total_active = sum(len(tasks) for tasks in active_tasks.values())
|
|
total_scheduled = sum(len(tasks) for tasks in scheduled_tasks.values())
|
|
|
|
return total_active + total_scheduled
|
|
except Exception as e:
|
|
logger.error(f"Error getting Celery queue size: {e}")
|
|
return 0
|
|
|
|
@staticmethod
|
|
def get_database_connection_count() -> int:
|
|
"""
|
|
Get current number of active database connections.
|
|
"""
|
|
try:
|
|
with connection.cursor() as cursor:
|
|
cursor.execute("SELECT count(*) FROM pg_stat_activity WHERE state = 'active';")
|
|
count = cursor.fetchone()[0]
|
|
return count
|
|
except Exception as e:
|
|
logger.error(f"Error getting database connection count: {e}")
|
|
return 0
|
|
|
|
@staticmethod
|
|
def get_cache_hit_rate() -> float:
|
|
"""
|
|
Calculate cache hit rate percentage.
|
|
"""
|
|
cache_key_hits = 'metrics:cache_hits'
|
|
cache_key_misses = 'metrics:cache_misses'
|
|
|
|
hits = cache.get(cache_key_hits, 0)
|
|
misses = cache.get(cache_key_misses, 0)
|
|
|
|
total = hits + misses
|
|
if total == 0:
|
|
return 100.0
|
|
|
|
return (hits / total) * 100
|
|
|
|
@staticmethod
|
|
def record_metric(metric_name: str, metric_value: float, metric_category: str = 'system') -> bool:
|
|
"""
|
|
Record a metric to Supabase metric_time_series table.
|
|
"""
|
|
if not SUPABASE_SERVICE_KEY:
|
|
logger.warning("SUPABASE_SERVICE_ROLE_KEY not configured, skipping metric recording")
|
|
return False
|
|
|
|
try:
|
|
headers = {
|
|
'apikey': SUPABASE_SERVICE_KEY,
|
|
'Authorization': f'Bearer {SUPABASE_SERVICE_KEY}',
|
|
'Content-Type': 'application/json',
|
|
}
|
|
|
|
data = {
|
|
'metric_name': metric_name,
|
|
'metric_value': metric_value,
|
|
'metric_category': metric_category,
|
|
'timestamp': datetime.utcnow().isoformat(),
|
|
}
|
|
|
|
response = requests.post(
|
|
f'{SUPABASE_URL}/rest/v1/metric_time_series',
|
|
headers=headers,
|
|
json=data,
|
|
timeout=5
|
|
)
|
|
|
|
if response.status_code in [200, 201]:
|
|
logger.info(f"Recorded metric: {metric_name} = {metric_value}")
|
|
return True
|
|
else:
|
|
logger.error(f"Failed to record metric: {response.status_code} - {response.text}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error recording metric {metric_name}: {e}")
|
|
return False
|
|
|
|
@staticmethod
|
|
def collect_all_metrics() -> Dict[str, Any]:
|
|
"""
|
|
Collect all system metrics and record them.
|
|
Returns a summary of collected metrics.
|
|
"""
|
|
metrics = {}
|
|
|
|
try:
|
|
# Collect error rate
|
|
error_rate = MetricsCollector.get_error_rate()
|
|
metrics['error_rate'] = error_rate
|
|
MetricsCollector.record_metric('error_rate', error_rate, 'performance')
|
|
|
|
# Collect API response time
|
|
response_time = MetricsCollector.get_api_response_time()
|
|
metrics['api_response_time'] = response_time
|
|
MetricsCollector.record_metric('api_response_time', response_time, 'performance')
|
|
|
|
# Collect queue size
|
|
queue_size = MetricsCollector.get_celery_queue_size()
|
|
metrics['celery_queue_size'] = queue_size
|
|
MetricsCollector.record_metric('celery_queue_size', queue_size, 'system')
|
|
|
|
# Collect database connections
|
|
db_connections = MetricsCollector.get_database_connection_count()
|
|
metrics['database_connections'] = db_connections
|
|
MetricsCollector.record_metric('database_connections', db_connections, 'system')
|
|
|
|
# Collect cache hit rate
|
|
cache_hit_rate = MetricsCollector.get_cache_hit_rate()
|
|
metrics['cache_hit_rate'] = cache_hit_rate
|
|
MetricsCollector.record_metric('cache_hit_rate', cache_hit_rate, 'performance')
|
|
|
|
logger.info(f"Successfully collected {len(metrics)} metrics")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error collecting metrics: {e}", exc_info=True)
|
|
|
|
return metrics
|