package stats import ( "database/sql" "log" ) // CreateStatsTables creates the necessary tables for stats collection func CreateStatsTables(db *sql.DB) error { // Time-series stats table _, err := db.Exec(` CREATE TABLE IF NOT EXISTS stats_timeseries ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, component TEXT NOT NULL, metric TEXT NOT NULL, value REAL NOT NULL, metadata TEXT, INDEX idx_component_metric_time (component, metric, timestamp) ) `) if err != nil { return err } // Performance metrics table _, err = db.Exec(` CREATE TABLE IF NOT EXISTS performance_metrics ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, endpoint TEXT NOT NULL, method TEXT NOT NULL, response_time_ms REAL NOT NULL, status_code INTEGER NOT NULL, bytes_sent INTEGER DEFAULT 0, bytes_received INTEGER DEFAULT 0, user_agent TEXT, ip_address TEXT, INDEX idx_endpoint_time (endpoint, timestamp), INDEX idx_status_time (status_code, timestamp) ) `) if err != nil { return err } // Bandwidth tracking table _, err = db.Exec(` CREATE TABLE IF NOT EXISTS bandwidth_stats ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, torrent_hash TEXT NOT NULL, bytes_served INTEGER NOT NULL, bytes_from_peers INTEGER DEFAULT 0, peer_count INTEGER DEFAULT 0, source TEXT NOT NULL, -- 'webseed', 'tracker', 'dht' INDEX idx_torrent_time (torrent_hash, timestamp), INDEX idx_source_time (source, timestamp) ) `) if err != nil { return err } // System metrics table _, err = db.Exec(` CREATE TABLE IF NOT EXISTS system_metrics ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, cpu_percent REAL NOT NULL, memory_bytes INTEGER NOT NULL, goroutines INTEGER NOT NULL, heap_objects INTEGER NOT NULL, gc_cycles INTEGER NOT NULL, disk_usage INTEGER DEFAULT 0 ) `) if err != nil { return err } // Component health history _, err = db.Exec(` CREATE TABLE IF NOT EXISTS component_health ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, component TEXT NOT NULL, status TEXT NOT NULL, -- 'healthy', 'degraded', 'unhealthy' error_message TEXT, response_time_ms REAL, INDEX idx_component_status_time (component, status, timestamp) ) `) return err } // StatsDB wraps database operations for stats type StatsDB struct { db *sql.DB } // NewStatsDB creates a new stats database wrapper func NewStatsDB(db *sql.DB) *StatsDB { return &StatsDB{db: db} } // RecordTimeSeries records a time-series data point func (sdb *StatsDB) RecordTimeSeries(component, metric string, value float64, metadata string) error { _, err := sdb.db.Exec(` INSERT INTO stats_timeseries (component, metric, value, metadata) VALUES (?, ?, ?, ?) `, component, metric, value, metadata) return err } // RecordPerformance records a performance metric func (sdb *StatsDB) RecordPerformance(endpoint, method string, responseTime float64, statusCode int, bytesSent, bytesReceived int64, userAgent, ipAddress string) error { _, err := sdb.db.Exec(` INSERT INTO performance_metrics (endpoint, method, response_time_ms, status_code, bytes_sent, bytes_received, user_agent, ip_address) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `, endpoint, method, responseTime, statusCode, bytesSent, bytesReceived, userAgent, ipAddress) return err } // RecordBandwidth records bandwidth usage func (sdb *StatsDB) RecordBandwidth(torrentHash string, bytesServed, bytesFromPeers int64, peerCount int, source string) error { _, err := sdb.db.Exec(` INSERT INTO bandwidth_stats (torrent_hash, bytes_served, bytes_from_peers, peer_count, source) VALUES (?, ?, ?, ?, ?) `, torrentHash, bytesServed, bytesFromPeers, peerCount, source) return err } // RecordSystemMetrics records system performance metrics func (sdb *StatsDB) RecordSystemMetrics(cpuPercent float64, memoryBytes int64, goroutines, heapObjects, gcCycles int, diskUsage int64) error { _, err := sdb.db.Exec(` INSERT INTO system_metrics (cpu_percent, memory_bytes, goroutines, heap_objects, gc_cycles, disk_usage) VALUES (?, ?, ?, ?, ?, ?) `, cpuPercent, memoryBytes, goroutines, heapObjects, gcCycles, diskUsage) return err } // RecordComponentHealth records component health status func (sdb *StatsDB) RecordComponentHealth(component, status, errorMessage string, responseTime float64) error { _, err := sdb.db.Exec(` INSERT INTO component_health (component, status, error_message, response_time_ms) VALUES (?, ?, ?, ?) `, component, status, errorMessage, responseTime) return err } // GetAverageResponseTime gets average response time for last N minutes func (sdb *StatsDB) GetAverageResponseTime(minutes int) (float64, error) { var avg float64 err := sdb.db.QueryRow(` SELECT COALESCE(AVG(response_time_ms), 0) FROM performance_metrics WHERE timestamp > datetime('now', '-' || ? || ' minutes') `, minutes).Scan(&avg) return avg, err } // GetRequestsPerSecond gets requests per second for last N minutes func (sdb *StatsDB) GetRequestsPerSecond(minutes int) (float64, error) { var count int64 err := sdb.db.QueryRow(` SELECT COUNT(*) FROM performance_metrics WHERE timestamp > datetime('now', '-' || ? || ' minutes') `, minutes).Scan(&count) if err != nil { return 0, err } return float64(count) / float64(minutes*60), nil } // GetErrorRate gets error rate percentage for last N minutes func (sdb *StatsDB) GetErrorRate(minutes int) (float64, error) { var totalRequests, errorRequests int64 err := sdb.db.QueryRow(` SELECT COUNT(*) FROM performance_metrics WHERE timestamp > datetime('now', '-' || ? || ' minutes') `, minutes).Scan(&totalRequests) if err != nil { return 0, err } err = sdb.db.QueryRow(` SELECT COUNT(*) FROM performance_metrics WHERE timestamp > datetime('now', '-' || ? || ' minutes') AND status_code >= 400 `, minutes).Scan(&errorRequests) if err != nil { return 0, err } if totalRequests == 0 { return 0, nil } return float64(errorRequests) / float64(totalRequests) * 100, nil } // GetBandwidthStats gets bandwidth statistics for last N hours func (sdb *StatsDB) GetBandwidthStats(hours int) (map[string]interface{}, error) { rows, err := sdb.db.Query(` SELECT SUM(bytes_served) as total_served, SUM(bytes_from_peers) as total_from_peers, COUNT(DISTINCT torrent_hash) as active_torrents, AVG(peer_count) as avg_peer_count FROM bandwidth_stats WHERE timestamp > datetime('now', '-' || ? || ' hours') `, hours) if err != nil { return nil, err } defer rows.Close() var totalServed, totalFromPeers, activeTorrents, avgPeerCount int64 if rows.Next() { err = rows.Scan(&totalServed, &totalFromPeers, &activeTorrents, &avgPeerCount) if err != nil { return nil, err } } var p2pOffload float64 if totalServed > 0 { p2pOffload = float64(totalFromPeers) / float64(totalServed) * 100 } return map[string]interface{}{ "total_served": totalServed, "total_from_peers": totalFromPeers, "p2p_offload_percent": p2pOffload, "active_torrents": activeTorrents, "avg_peer_count": avgPeerCount, }, nil } // GetSystemHealthOverTime gets system health metrics over time func (sdb *StatsDB) GetSystemHealthOverTime(hours int) ([]map[string]interface{}, error) { rows, err := sdb.db.Query(` SELECT datetime(timestamp) as time, cpu_percent, memory_bytes, goroutines FROM system_metrics WHERE timestamp > datetime('now', '-' || ? || ' hours') ORDER BY timestamp DESC LIMIT 100 `, hours) if err != nil { return nil, err } defer rows.Close() var results []map[string]interface{} for rows.Next() { var timestamp string var cpu float64 var memory int64 var goroutines int err = rows.Scan(×tamp, &cpu, &memory, &goroutines) if err != nil { continue } results = append(results, map[string]interface{}{ "timestamp": timestamp, "cpu": cpu, "memory": memory, "goroutines": goroutines, }) } return results, nil } // CleanupOldStats removes old statistics data func (sdb *StatsDB) CleanupOldStats(daysToKeep int) error { tables := []string{ "stats_timeseries", "performance_metrics", "bandwidth_stats", "system_metrics", "component_health", } for _, table := range tables { _, err := sdb.db.Exec(` DELETE FROM `+table+` WHERE timestamp < datetime('now', '-' || ? || ' days') `, daysToKeep) if err != nil { log.Printf("Error cleaning up %s: %v", table, err) } } return nil } // GetTopTorrentsByBandwidth gets most popular torrents by bandwidth func (sdb *StatsDB) GetTopTorrentsByBandwidth(hours int, limit int) ([]map[string]interface{}, error) { rows, err := sdb.db.Query(` SELECT torrent_hash, SUM(bytes_served) as total_served, AVG(peer_count) as avg_peers, COUNT(*) as records FROM bandwidth_stats WHERE timestamp > datetime('now', '-' || ? || ' hours') GROUP BY torrent_hash ORDER BY total_served DESC LIMIT ? `, hours, limit) if err != nil { return nil, err } defer rows.Close() var results []map[string]interface{} for rows.Next() { var hash string var served int64 var avgPeers float64 var records int err = rows.Scan(&hash, &served, &avgPeers, &records) if err != nil { continue } results = append(results, map[string]interface{}{ "hash": hash, "bytes_served": served, "avg_peers": avgPeers, "activity": records, }) } return results, nil }