""" Performance Tracking System for MarkiTect This module provides historical performance tracking, trend analysis, and performance index calculation for monitoring system performance over time. """ import sqlite3 import json from datetime import datetime from pathlib import Path from typing import Dict, List, Optional, Any from dataclasses import dataclass @dataclass class PerformanceSnapshot: """A complete performance measurement snapshot.""" timestamp: str git_commit: Optional[str] system_info: Dict[str, Any] template_ops_per_sec: float database_ops_per_sec: float ingestion_ops_per_sec: float memory_usage_mb: float performance_index: float notes: str = "" class PerformanceTracker: """Manager for historical performance tracking and analysis.""" def __init__(self, db_path: str): """Initialize performance tracker with database path.""" self.db_path = db_path self.initialize_tracking_database() def initialize_tracking_database(self) -> None: """Initialize SQLite database for performance tracking.""" # Ensure directory exists db_dir = Path(self.db_path).parent if not db_dir.exists(): db_dir.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Create performance_snapshots table cursor.execute(''' CREATE TABLE IF NOT EXISTS performance_snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, git_commit TEXT, system_info TEXT, -- JSON template_ops_per_sec REAL NOT NULL, database_ops_per_sec REAL NOT NULL, ingestion_ops_per_sec REAL NOT NULL, memory_usage_mb REAL NOT NULL, performance_index REAL NOT NULL, notes TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') # Create performance_trends table for aggregated data cursor.execute(''' CREATE TABLE IF NOT EXISTS performance_trends ( id INTEGER PRIMARY KEY AUTOINCREMENT, period_start TEXT NOT NULL, period_end TEXT NOT NULL, avg_performance_index REAL NOT NULL, min_performance_index REAL NOT NULL, max_performance_index REAL NOT NULL, trend_direction TEXT, -- 'improving', 'degrading', 'stable' snapshot_count INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') conn.commit() conn.close() def calculate_performance_index(self, template_ops: float, database_ops: float, ingestion_ops: float, memory_mb: float) -> float: """ Calculate a normalized performance index (0-100 scale). Higher values indicate better performance. The index is calculated as: - Template performance (40%): normalized to baseline of 1000 ops/sec - Database performance (30%): normalized to baseline of 100 ops/sec - Ingestion performance (20%): normalized to baseline of 1000 ops/sec - Memory efficiency (10%): inversely weighted, baseline 50MB Returns: Performance index value (0-100, higher is better) """ # Define baseline values for normalization template_baseline = 1000.0 database_baseline = 100.0 ingestion_baseline = 1000.0 memory_baseline = 50.0 # Calculate component scores (capped at 100 for each) template_score = min(100.0, (template_ops / template_baseline) * 100.0) * 0.40 database_score = min(100.0, (database_ops / database_baseline) * 100.0) * 0.30 ingestion_score = min(100.0, (ingestion_ops / ingestion_baseline) * 100.0) * 0.20 # Memory score is inverse - lower memory usage is better memory_score = min(100.0, (memory_baseline / max(memory_mb, 1.0)) * 100.0) * 0.10 performance_index = template_score + database_score + ingestion_score + memory_score return round(performance_index, 2) def get_system_info(self) -> Dict[str, Any]: """Collect system information for context.""" import platform import sys try: import psutil memory_total = psutil.virtual_memory().total / (1024 * 1024 * 1024) # GB cpu_count = psutil.cpu_count() except ImportError: memory_total = "unknown" cpu_count = "unknown" return { "platform": platform.platform(), "python_version": sys.version, "cpu_count": cpu_count, "memory_total_gb": memory_total, "markitect_version": "dev" # Could be extracted from __version__ } def get_git_commit(self) -> Optional[str]: """Get current git commit hash if available.""" try: import subprocess result = subprocess.run( ['git', 'rev-parse', 'HEAD'], capture_output=True, text=True, cwd=Path(__file__).parent.parent ) if result.returncode == 0: return result.stdout.strip()[:12] # Short commit hash except Exception: pass return None def store_performance_snapshot(self, template_ops: float, database_ops: float, ingestion_ops: float, memory_mb: float, notes: str = "") -> int: """ Store a performance snapshot in the database. Returns: The ID of the stored snapshot """ performance_index = self.calculate_performance_index( template_ops, database_ops, ingestion_ops, memory_mb ) snapshot = PerformanceSnapshot( timestamp=datetime.now().isoformat(), git_commit=self.get_git_commit(), system_info=self.get_system_info(), template_ops_per_sec=template_ops, database_ops_per_sec=database_ops, ingestion_ops_per_sec=ingestion_ops, memory_usage_mb=memory_mb, performance_index=performance_index, notes=notes ) conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(''' INSERT INTO performance_snapshots (timestamp, git_commit, system_info, template_ops_per_sec, database_ops_per_sec, ingestion_ops_per_sec, memory_usage_mb, performance_index, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( snapshot.timestamp, snapshot.git_commit, json.dumps(snapshot.system_info), snapshot.template_ops_per_sec, snapshot.database_ops_per_sec, snapshot.ingestion_ops_per_sec, snapshot.memory_usage_mb, snapshot.performance_index, snapshot.notes )) snapshot_id = cursor.lastrowid conn.commit() conn.close() return snapshot_id def get_performance_history(self, limit: int = 50) -> List[PerformanceSnapshot]: """Get recent performance history.""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(''' SELECT timestamp, git_commit, system_info, template_ops_per_sec, database_ops_per_sec, ingestion_ops_per_sec, memory_usage_mb, performance_index, notes FROM performance_snapshots ORDER BY created_at DESC LIMIT ? ''', (limit,)) snapshots = [] for row in cursor.fetchall(): snapshots.append(PerformanceSnapshot( timestamp=row[0], git_commit=row[1], system_info=json.loads(row[2]) if row[2] else {}, template_ops_per_sec=row[3], database_ops_per_sec=row[4], ingestion_ops_per_sec=row[5], memory_usage_mb=row[6], performance_index=row[7], notes=row[8] or "" )) conn.close() return snapshots def analyze_performance_trend(self, days: int = 30) -> Dict[str, Any]: """Analyze performance trends over specified period.""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Get recent snapshots cursor.execute(''' SELECT performance_index, timestamp, template_ops_per_sec, database_ops_per_sec, ingestion_ops_per_sec FROM performance_snapshots WHERE datetime(timestamp) > datetime('now', '-{} days') ORDER BY timestamp ASC '''.format(days)) rows = cursor.fetchall() conn.close() if len(rows) < 2: return { "trend": "insufficient_data", "message": "Need at least 2 snapshots for trend analysis" } # Calculate trends indices = [row[0] for row in rows] first_half = indices[:len(indices)//2] second_half = indices[len(indices)//2:] first_avg = sum(first_half) / len(first_half) second_avg = sum(second_half) / len(second_half) trend_change = second_avg - first_avg trend_percent = (trend_change / first_avg) * 100 if first_avg > 0 else 0 if abs(trend_percent) < 2: trend_direction = "stable" elif trend_percent > 0: trend_direction = "improving" else: trend_direction = "degrading" return { "trend": trend_direction, "trend_change_points": round(trend_change, 2), "trend_change_percent": round(trend_percent, 2), "current_index": indices[-1], "period_min": min(indices), "period_max": max(indices), "period_avg": round(sum(indices) / len(indices), 2), "snapshot_count": len(indices), "analysis_period_days": days } def get_performance_summary(self) -> Dict[str, Any]: """Get comprehensive performance summary.""" history = self.get_performance_history(limit=10) trend_analysis = self.analyze_performance_trend(days=30) if not history: return {"status": "no_data", "message": "No performance data available"} latest = history[0] return { "latest_snapshot": { "performance_index": latest.performance_index, "timestamp": latest.timestamp, "git_commit": latest.git_commit, "template_ops_per_sec": latest.template_ops_per_sec, "database_ops_per_sec": latest.database_ops_per_sec, "ingestion_ops_per_sec": latest.ingestion_ops_per_sec, "memory_usage_mb": latest.memory_usage_mb }, "trend_analysis": trend_analysis, "history_count": len(history) }