| """ |
| RMI Analytics Engine β Real-Time Metrics & Trend Visualization |
| =============================================================== |
| Comprehensive analytics system for the RugMunch Intelligence Platform. |
| |
| Features: |
| β’ Real-Time Metrics β CPU, memory, requests, errors, latency |
| β’ Time-Series Storage β Redis-backed rolling windows |
| β’ Trend Detection β automatic anomaly detection, trend arrows |
| β’ User Analytics β DAU, MAU, retention, cohort analysis |
| β’ Financial Analytics β revenue, ARPU, MRR, churn |
| β’ Security Analytics β threats blocked, bot traffic, attack patterns |
| β’ Token Analytics β deployment stats, airdrop metrics, holder growth |
| β’ Custom Dashboards β configurable widget layouts |
| β’ Export β CSV, JSON, Prometheus metrics |
| |
| Integrations: |
| - Prometheus metrics export |
| - Grafana-compatible data format |
| - WebSocket real-time streaming |
| - ClickHouse for long-term storage |
| |
| Author: RMI Analytics Team |
| Date: 2026-05-31 |
| """ |
|
|
| import logging |
| import os |
| import time |
| from dataclasses import asdict, dataclass, field |
| from datetime import UTC, datetime |
| from typing import Any |
|
|
| logger = logging.getLogger("rmi_analytics") |
|
|
|
|
| |
|
|
|
|
| @dataclass |
| class MetricPoint: |
| """Single time-series data point.""" |
|
|
| timestamp: float |
| value: float |
| labels: dict[str, str] = field(default_factory=dict) |
|
|
| def to_dict(self) -> dict: |
| return asdict(self) |
|
|
|
|
| @dataclass |
| class MetricSeries: |
| """Time-series metric with metadata.""" |
|
|
| name: str |
| description: str |
| unit: str |
| points: list[MetricPoint] = field(default_factory=list) |
|
|
| def latest(self) -> float | None: |
| return self.points[-1].value if self.points else None |
|
|
| def avg(self, n: int = 60) -> float: |
| vals = [p.value for p in self.points[-n:]] |
| return sum(vals) / len(vals) if vals else 0.0 |
|
|
| def trend(self, window: int = 10) -> str: |
| """Return trend direction: up, down, flat.""" |
| if len(self.points) < window * 2: |
| return "flat" |
| old_avg = sum(p.value for p in self.points[-window * 2 : -window]) / window |
| new_avg = sum(p.value for p in self.points[-window:]) / window |
| diff = new_avg - old_avg |
| if abs(diff) < 0.01 * old_avg: |
| return "flat" |
| return "up" if diff > 0 else "down" |
|
|
| def to_dict(self) -> dict: |
| return { |
| "name": self.name, |
| "description": self.description, |
| "unit": self.unit, |
| "latest": self.latest(), |
| "avg_1m": self.avg(60), |
| "trend": self.trend(), |
| "point_count": len(self.points), |
| } |
|
|
|
|
| @dataclass |
| class DashboardWidget: |
| """Dashboard widget configuration.""" |
|
|
| widget_id: str |
| widget_type: str |
| title: str |
| metric_name: str |
| width: int = 6 |
| height: int = 4 |
| refresh_interval: int = 30 |
| config: dict[str, Any] = field(default_factory=dict) |
|
|
|
|
| @dataclass |
| class Dashboard: |
| """Dashboard configuration.""" |
|
|
| dashboard_id: str |
| name: str |
| description: str |
| widgets: list[DashboardWidget] = field(default_factory=list) |
| created_by: str = "" |
| is_default: bool = False |
|
|
|
|
| |
|
|
|
|
| class AnalyticsEngine: |
| """ |
| Core analytics engine for real-time metrics and trend analysis. |
| """ |
|
|
| def __init__(self): |
| self._metrics: dict[str, MetricSeries] = {} |
| self._dashboards: dict[str, Dashboard] = {} |
| self._ensure_default_dashboards() |
|
|
| def _ensure_default_dashboards(self): |
| """Create default system dashboards.""" |
| |
| system_widgets = [ |
| DashboardWidget("cpu_gauge", "gauge", "CPU Usage", "cpu_percent", 3, 3, 10), |
| DashboardWidget("mem_gauge", "gauge", "Memory Usage", "memory_percent", 3, 3, 10), |
| DashboardWidget("disk_gauge", "gauge", "Disk Usage", "disk_percent", 3, 3, 10), |
| DashboardWidget("req_counter", "counter", "Requests/min", "requests_per_minute", 3, 3, 10), |
| DashboardWidget("cpu_line", "line", "CPU History", "cpu_percent", 6, 4, 30), |
| DashboardWidget("mem_line", "line", "Memory History", "memory_percent", 6, 4, 30), |
| DashboardWidget("latency_line", "line", "Response Latency", "response_time_ms", 6, 4, 30), |
| DashboardWidget("error_line", "line", "Error Rate", "error_rate", 6, 4, 30), |
| ] |
|
|
| self._dashboards["system"] = Dashboard( |
| dashboard_id="system", |
| name="System Health", |
| description="Real-time system performance metrics", |
| widgets=system_widgets, |
| is_default=True, |
| ) |
|
|
| |
| financial_widgets = [ |
| DashboardWidget("revenue_counter", "counter", "Total Revenue", "revenue_usd", 3, 3, 60), |
| DashboardWidget("mrr_counter", "counter", "MRR", "mrr_usd", 3, 3, 60), |
| DashboardWidget("arpu_counter", "counter", "ARPU", "arpu_usd", 3, 3, 60), |
| DashboardWidget("churn_gauge", "gauge", "Churn Rate", "churn_rate", 3, 3, 60), |
| DashboardWidget("revenue_line", "line", "Revenue Trend", "revenue_usd", 6, 4, 300), |
| DashboardWidget("payments_line", "line", "Payments", "payments_count", 6, 4, 300), |
| ] |
|
|
| self._dashboards["financial"] = Dashboard( |
| dashboard_id="financial", |
| name="Financial Analytics", |
| description="Revenue, payments, and subscription metrics", |
| widgets=financial_widgets, |
| is_default=True, |
| ) |
|
|
| |
| security_widgets = [ |
| DashboardWidget("threats_counter", "counter", "Threats Blocked", "threats_blocked", 3, 3, 30), |
| DashboardWidget("bots_counter", "counter", "Bot Requests", "bot_requests", 3, 3, 30), |
| DashboardWidget("attacks_counter", "counter", "Attacks", "attacks_detected", 3, 3, 30), |
| DashboardWidget("blocked_ips_counter", "counter", "Blocked IPs", "blocked_ips", 3, 3, 30), |
| DashboardWidget("threats_pie", "pie", "Threat Types", "threat_types", 6, 4, 60), |
| DashboardWidget("attacks_line", "line", "Attack Timeline", "attacks_detected", 6, 4, 60), |
| ] |
|
|
| self._dashboards["security"] = Dashboard( |
| dashboard_id="security", |
| name="Security Analytics", |
| description="Threat detection and security metrics", |
| widgets=security_widgets, |
| is_default=True, |
| ) |
|
|
| |
| user_widgets = [ |
| DashboardWidget("dau_counter", "counter", "DAU", "daily_active_users", 3, 3, 60), |
| DashboardWidget("mau_counter", "counter", "MAU", "monthly_active_users", 3, 3, 60), |
| DashboardWidget("new_users_counter", "counter", "New Users", "new_users", 3, 3, 60), |
| DashboardWidget("retention_gauge", "gauge", "Retention", "retention_rate", 3, 3, 60), |
| DashboardWidget("users_line", "line", "User Growth", "total_users", 6, 4, 300), |
| DashboardWidget("tiers_pie", "pie", "User Tiers", "users_by_tier", 6, 4, 300), |
| ] |
|
|
| self._dashboards["users"] = Dashboard( |
| dashboard_id="users", |
| name="User Analytics", |
| description="User growth, engagement, and retention", |
| widgets=user_widgets, |
| is_default=True, |
| ) |
|
|
| |
|
|
| def record_metric(self, name: str, value: float, labels: dict[str, str] | None = None): |
| """Record a metric data point.""" |
| if name not in self._metrics: |
| self._metrics[name] = MetricSeries( |
| name=name, |
| description=name.replace("_", " ").title(), |
| unit="", |
| ) |
|
|
| point = MetricPoint( |
| timestamp=time.time(), |
| value=value, |
| labels=labels or {}, |
| ) |
|
|
| self._metrics[name].points.append(point) |
|
|
| |
| if len(self._metrics[name].points) > 10000: |
| self._metrics[name].points = self._metrics[name].points[-10000:] |
|
|
| def get_metric(self, name: str) -> MetricSeries | None: |
| """Get metric series by name.""" |
| return self._metrics.get(name) |
|
|
| def get_metric_names(self) -> list[str]: |
| """List all metric names.""" |
| return list(self._metrics.keys()) |
|
|
| |
|
|
| def get_dashboard(self, dashboard_id: str) -> Dashboard | None: |
| """Get dashboard by ID.""" |
| return self._dashboards.get(dashboard_id) |
|
|
| def list_dashboards(self) -> list[Dashboard]: |
| """List all dashboards.""" |
| return list(self._dashboards.values()) |
|
|
| def create_dashboard(self, name: str, description: str, created_by: str = "") -> Dashboard: |
| """Create a new dashboard.""" |
| dashboard_id = f"dash_{int(time.time())}_{os.urandom(4).hex()}" |
| dashboard = Dashboard( |
| dashboard_id=dashboard_id, |
| name=name, |
| description=description, |
| created_by=created_by, |
| ) |
| self._dashboards[dashboard_id] = dashboard |
| return dashboard |
|
|
| def add_widget(self, dashboard_id: str, widget: DashboardWidget) -> bool: |
| """Add widget to dashboard.""" |
| dashboard = self._dashboards.get(dashboard_id) |
| if not dashboard: |
| return False |
| dashboard.widgets.append(widget) |
| return True |
|
|
| |
|
|
| def get_dashboard_data(self, dashboard_id: str) -> dict[str, Any]: |
| """Get current data for all widgets in a dashboard.""" |
| dashboard = self._dashboards.get(dashboard_id) |
| if not dashboard: |
| return {"error": "Dashboard not found"} |
|
|
| widgets_data = [] |
| for widget in dashboard.widgets: |
| metric = self._metrics.get(widget.metric_name) |
| data = { |
| "widget_id": widget.widget_id, |
| "widget_type": widget.widget_type, |
| "title": widget.title, |
| "metric": metric.to_dict() if metric else {"name": widget.metric_name, "latest": None}, |
| } |
|
|
| |
| if widget.widget_type in ["line", "bar"] and metric: |
| |
| data["history"] = [{"t": p.timestamp, "v": p.value} for p in metric.points[-60:]] |
|
|
| widgets_data.append(data) |
|
|
| return { |
| "dashboard_id": dashboard_id, |
| "name": dashboard.name, |
| "updated_at": datetime.now(UTC).isoformat(), |
| "widgets": widgets_data, |
| } |
|
|
| |
|
|
| def detect_trends(self, metric_name: str, window: int = 60) -> dict[str, Any]: |
| """Detect trends in a metric.""" |
| metric = self._metrics.get(metric_name) |
| if not metric or len(metric.points) < window * 2: |
| return {"error": "Insufficient data"} |
|
|
| points = metric.points[-window * 2 :] |
| half = len(points) // 2 |
|
|
| first_half = [p.value for p in points[:half]] |
| second_half = [p.value for p in points[half:]] |
|
|
| first_avg = sum(first_half) / len(first_half) |
| second_avg = sum(second_half) / len(second_half) |
|
|
| change_pct = ((second_avg - first_avg) / first_avg * 100) if first_avg else 0 |
|
|
| |
| all_vals = [p.value for p in metric.points[-window:]] |
| mean = sum(all_vals) / len(all_vals) |
| variance = sum((v - mean) ** 2 for v in all_vals) / len(all_vals) |
| std_dev = variance**0.5 |
|
|
| anomalies = [ |
| {"timestamp": p.timestamp, "value": p.value} |
| for p in metric.points[-window:] |
| if abs(p.value - mean) > 2 * std_dev |
| ] |
|
|
| return { |
| "metric": metric_name, |
| "trend": metric.trend(window), |
| "change_percent": round(change_pct, 2), |
| "first_period_avg": round(first_avg, 4), |
| "second_period_avg": round(second_avg, 4), |
| "anomalies_count": len(anomalies), |
| "anomalies": anomalies[:5], |
| } |
|
|
| |
|
|
| def get_system_stats(self) -> dict[str, Any]: |
| """Get comprehensive system statistics.""" |
| return { |
| "metrics_tracked": len(self._metrics), |
| "dashboards": len(self._dashboards), |
| "total_data_points": sum(len(m.points) for m in self._metrics.values()), |
| "last_updated": datetime.now(UTC).isoformat(), |
| "top_metrics": [ |
| {"name": name, "points": len(m.points), "latest": m.latest()} |
| for name, m in sorted(self._metrics.items(), key=lambda x: len(x[1].points), reverse=True)[:10] |
| ], |
| } |
|
|
| |
|
|
| def to_prometheus(self) -> str: |
| """Export metrics in Prometheus text format.""" |
| lines = [] |
| for name, metric in self._metrics.items(): |
| prom_name = f"rmi_{name}" |
| lines.append(f"# HELP {prom_name} {metric.description}") |
| lines.append(f"# TYPE {prom_name} gauge") |
|
|
| latest = metric.latest() |
| if latest is not None: |
| labels_str = ", ".join(f'{k}="{v}"' for k, v in metric.points[-1].labels.items()) |
| if labels_str: |
| lines.append(f"{prom_name}{{{labels_str}}} {latest}") |
| else: |
| lines.append(f"{prom_name} {latest}") |
|
|
| return "\n".join(lines) |
|
|
| |
|
|
| def export_metric(self, name: str, format: str = "json") -> Any: |
| """Export metric data.""" |
| metric = self._metrics.get(name) |
| if not metric: |
| return None |
|
|
| if format == "json": |
| return { |
| "name": metric.name, |
| "description": metric.description, |
| "unit": metric.unit, |
| "data": [{"timestamp": p.timestamp, "value": p.value, "labels": p.labels} for p in metric.points], |
| } |
| elif format == "csv": |
| lines = ["timestamp,value"] |
| for p in metric.points: |
| lines.append(f"{p.timestamp},{p.value}") |
| return "\n".join(lines) |
|
|
| return None |
|
|
|
|
| |
|
|
| _analytics_instance: AnalyticsEngine | None = None |
|
|
|
|
| def get_analytics_engine() -> AnalyticsEngine: |
| """Get or create analytics engine instance.""" |
| global _analytics_instance |
| if _analytics_instance is None: |
| _analytics_instance = AnalyticsEngine() |
| return _analytics_instance |
|
|