package db // MetricsSnapshot holds the scrape-time gauges derived from the database. type MetricsSnapshot struct { Observations int ObservationsLast24h int BySource map[string]int Entries int Projects int Sessions int PendingReflections int ThreadsByStatus map[string]int Peers int ByOriginNode map[string]int // node_id → observation count KnowledgeVector map[string]int64 // node_id → max hcl EarliestObservation string } // MetricsSnapshot computes database-derived gauges for Prometheus scraping. // All queries are cheap aggregations; nothing is stored or mutated. func (k *KnoxDB) MetricsSnapshot() (*MetricsSnapshot, error) { s := &MetricsSnapshot{ BySource: make(map[string]int), ThreadsByStatus: make(map[string]int), ByOriginNode: make(map[string]int), } if err := k.db.QueryRow("SELECT COUNT(*) FROM observations").Scan(&s.Observations); err != nil { return nil, err } if err := k.db.QueryRow("SELECT COUNT(*) FROM observations WHERE collected_at > datetime('now', '-1 day')").Scan(&s.ObservationsLast24h); err != nil { return nil, err } if err := k.db.QueryRow("SELECT COUNT(*) FROM entries").Scan(&s.Entries); err != nil { return nil, err } if err := k.db.QueryRow("SELECT COUNT(DISTINCT project) FROM entries").Scan(&s.Projects); err != nil { return nil, err } if err := k.db.QueryRow("SELECT COUNT(*) FROM sessions").Scan(&s.Sessions); err != nil { return nil, err } if err := k.db.QueryRow("SELECT COUNT(*) FROM sessions WHERE indexed=0").Scan(&s.PendingReflections); err != nil { return nil, err } if err := k.db.QueryRow("SELECT COUNT(*) FROM peers").Scan(&s.Peers); err != nil { return nil, err } if err := k.db.QueryRow("SELECT COALESCE(MIN(collected_at),'') FROM observations").Scan(&s.EarliestObservation); err != nil { return nil, err } rows, err := k.db.Query("SELECT COALESCE(source_id,'unknown'), COUNT(*) FROM observations GROUP BY source_id") if err != nil { return nil, err } defer rows.Close() for rows.Next() { var src string var n int if err := rows.Scan(&src, &n); err != nil { return nil, err } s.BySource[src] = n } if err := rows.Err(); err != nil { return nil, err } rows2, err := k.db.Query("SELECT COALESCE(status,'active'), COUNT(*) FROM threads GROUP BY status") if err != nil { return nil, err } defer rows2.Close() for rows2.Next() { var st string var n int if err := rows2.Scan(&st, &n); err != nil { return nil, err } s.ThreadsByStatus[st] = n } if err := rows2.Err(); err != nil { return nil, err } rows3, err := k.db.Query("SELECT node_id, COUNT(*) FROM observations WHERE node_id<>'' GROUP BY node_id") if err != nil { return nil, err } defer rows3.Close() for rows3.Next() { var nid string var n int if err := rows3.Scan(&nid, &n); err != nil { return nil, err } s.ByOriginNode[nid] = n } if err := rows3.Err(); err != nil { return nil, err } if s.KnowledgeVector, err = k.KnowledgeVector(); err != nil { return nil, err } return s, nil }