mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-09 23:59:40 +00:00
1ae8035470
- [x] Add golangci.yml configuration with fieldalignment linter - [x] Implement observation graph structure with edge detection - [x] Add LEANN-inspired hybrid vector storage with hub threshold - [x] Implement graph-aware search with selective recomputation - [x] Add auto-tuner for dynamic hub threshold adjustment - [x] Add graph and vector metrics tracking and reporting - [x] Extend configuration for graph parameters - [x] Add graph rebuild background service with periodic updates - [x] Add HTTP endpoints for graph stats and vector metrics - [x] Update UI with advanced metrics sidebar panel - [x] Implement AST-aware code chunking for Go, Python, TypeScript
273 lines
7.2 KiB
Go
273 lines
7.2 KiB
Go
package hybrid
|
||
|
||
import (
|
||
"fmt"
|
||
"sync"
|
||
"sync/atomic"
|
||
"time"
|
||
)
|
||
|
||
// Metrics tracks performance and usage statistics for hybrid vector storage
|
||
type Metrics struct {
|
||
startTime time.Time
|
||
recentLatencies []time.Duration
|
||
latenciesMu sync.Mutex
|
||
totalQueries atomic.Int64
|
||
hubOnlyQueries atomic.Int64
|
||
hybridQueries atomic.Int64
|
||
onDemandQueries atomic.Int64
|
||
graphQueries atomic.Int64
|
||
totalLatency atomic.Int64 // Sum in microseconds
|
||
hubLatency atomic.Int64
|
||
recomputeLatency atomic.Int64
|
||
totalDocuments atomic.Int64
|
||
hubDocuments atomic.Int64
|
||
storedEmbeddings atomic.Int64
|
||
recomputedCount atomic.Int64
|
||
cacheHits atomic.Int64
|
||
cacheMisses atomic.Int64
|
||
graphTraversals atomic.Int64
|
||
avgTraversalDepth atomic.Int64
|
||
}
|
||
|
||
// NewMetrics creates a new metrics tracker
|
||
func NewMetrics() *Metrics {
|
||
return &Metrics{
|
||
recentLatencies: make([]time.Duration, 0, 1000),
|
||
startTime: time.Now(),
|
||
}
|
||
}
|
||
|
||
// RecordQuery records a query execution
|
||
func (m *Metrics) RecordQuery(queryType string, latency time.Duration, recomputed int) {
|
||
m.totalQueries.Add(1)
|
||
m.totalLatency.Add(latency.Microseconds())
|
||
|
||
switch queryType {
|
||
case "hub_only":
|
||
m.hubOnlyQueries.Add(1)
|
||
case "hybrid":
|
||
m.hybridQueries.Add(1)
|
||
case "on_demand":
|
||
m.onDemandQueries.Add(1)
|
||
case "graph":
|
||
m.graphQueries.Add(1)
|
||
}
|
||
|
||
if recomputed > 0 {
|
||
m.recomputedCount.Add(int64(recomputed))
|
||
}
|
||
|
||
// Track recent latencies
|
||
m.latenciesMu.Lock()
|
||
m.recentLatencies = append(m.recentLatencies, latency)
|
||
if len(m.recentLatencies) > 1000 {
|
||
m.recentLatencies = m.recentLatencies[len(m.recentLatencies)-1000:]
|
||
}
|
||
m.latenciesMu.Unlock()
|
||
}
|
||
|
||
// RecordHubLatency records time spent in hub search
|
||
func (m *Metrics) RecordHubLatency(latency time.Duration) {
|
||
m.hubLatency.Add(latency.Microseconds())
|
||
}
|
||
|
||
// RecordRecomputeLatency records time spent recomputing embeddings
|
||
func (m *Metrics) RecordRecomputeLatency(latency time.Duration) {
|
||
m.recomputeLatency.Add(latency.Microseconds())
|
||
}
|
||
|
||
// RecordCacheHit records a content cache hit
|
||
func (m *Metrics) RecordCacheHit() {
|
||
m.cacheHits.Add(1)
|
||
}
|
||
|
||
// RecordCacheMiss records a content cache miss
|
||
func (m *Metrics) RecordCacheMiss() {
|
||
m.cacheMisses.Add(1)
|
||
}
|
||
|
||
// RecordGraphTraversal records a graph traversal operation
|
||
func (m *Metrics) RecordGraphTraversal(depth int) {
|
||
m.graphTraversals.Add(1)
|
||
m.avgTraversalDepth.Add(int64(depth))
|
||
}
|
||
|
||
// UpdateStorageStats updates current storage statistics
|
||
func (m *Metrics) UpdateStorageStats(total, hubs, stored int) {
|
||
m.totalDocuments.Store(int64(total))
|
||
m.hubDocuments.Store(int64(hubs))
|
||
m.storedEmbeddings.Store(int64(stored))
|
||
}
|
||
|
||
// GetSnapshot returns current metrics snapshot
|
||
func (m *Metrics) GetSnapshot() MetricsSnapshot {
|
||
m.latenciesMu.Lock()
|
||
defer m.latenciesMu.Unlock()
|
||
|
||
totalQueries := m.totalQueries.Load()
|
||
|
||
snapshot := MetricsSnapshot{
|
||
// Query counts
|
||
TotalQueries: totalQueries,
|
||
HubOnlyQueries: m.hubOnlyQueries.Load(),
|
||
HybridQueries: m.hybridQueries.Load(),
|
||
OnDemandQueries: m.onDemandQueries.Load(),
|
||
GraphQueries: m.graphQueries.Load(),
|
||
|
||
// Storage
|
||
TotalDocuments: int(m.totalDocuments.Load()),
|
||
HubDocuments: int(m.hubDocuments.Load()),
|
||
StoredEmbeddings: int(m.storedEmbeddings.Load()),
|
||
RecomputedTotal: m.recomputedCount.Load(),
|
||
|
||
// Cache
|
||
CacheHits: m.cacheHits.Load(),
|
||
CacheMisses: m.cacheMisses.Load(),
|
||
|
||
// Graph
|
||
GraphTraversals: m.graphTraversals.Load(),
|
||
|
||
// Runtime
|
||
Uptime: time.Since(m.startTime),
|
||
}
|
||
|
||
// Calculate latencies
|
||
if totalQueries > 0 {
|
||
snapshot.AvgLatency = time.Duration(m.totalLatency.Load()/totalQueries) * time.Microsecond
|
||
snapshot.AvgHubLatency = time.Duration(m.hubLatency.Load()/totalQueries) * time.Microsecond
|
||
}
|
||
|
||
if m.recomputedCount.Load() > 0 {
|
||
snapshot.AvgRecomputeLatency = time.Duration(m.recomputeLatency.Load()/m.recomputedCount.Load()) * time.Microsecond
|
||
}
|
||
|
||
// Calculate percentiles
|
||
if len(m.recentLatencies) > 0 {
|
||
sorted := make([]time.Duration, len(m.recentLatencies))
|
||
copy(sorted, m.recentLatencies)
|
||
sortDurations(sorted)
|
||
|
||
snapshot.P50Latency = percentile(sorted, 0.50)
|
||
snapshot.P95Latency = percentile(sorted, 0.95)
|
||
snapshot.P99Latency = percentile(sorted, 0.99)
|
||
}
|
||
|
||
// Calculate cache hit rate
|
||
totalCacheOps := snapshot.CacheHits + snapshot.CacheMisses
|
||
if totalCacheOps > 0 {
|
||
snapshot.CacheHitRate = float64(snapshot.CacheHits) / float64(totalCacheOps)
|
||
}
|
||
|
||
// Calculate storage savings
|
||
if snapshot.TotalDocuments > 0 {
|
||
embeddingSize := 384 * 4 // 384 dims × 4 bytes
|
||
fullStorage := snapshot.TotalDocuments * embeddingSize
|
||
actualStorage := snapshot.StoredEmbeddings * embeddingSize
|
||
|
||
if fullStorage > 0 {
|
||
snapshot.StorageSavingsPercent = (1.0 - float64(actualStorage)/float64(fullStorage)) * 100
|
||
}
|
||
}
|
||
|
||
// Calculate avg traversal depth
|
||
if snapshot.GraphTraversals > 0 {
|
||
snapshot.AvgTraversalDepth = float64(m.avgTraversalDepth.Load()) / float64(snapshot.GraphTraversals)
|
||
}
|
||
|
||
return snapshot
|
||
}
|
||
|
||
// MetricsSnapshot represents a point-in-time metrics snapshot
|
||
type MetricsSnapshot struct {
|
||
// Query metrics
|
||
TotalQueries int64
|
||
HubOnlyQueries int64
|
||
HybridQueries int64
|
||
OnDemandQueries int64
|
||
GraphQueries int64
|
||
|
||
// Latency metrics
|
||
AvgLatency time.Duration
|
||
P50Latency time.Duration
|
||
P95Latency time.Duration
|
||
P99Latency time.Duration
|
||
AvgHubLatency time.Duration
|
||
AvgRecomputeLatency time.Duration
|
||
|
||
// Storage metrics
|
||
TotalDocuments int
|
||
HubDocuments int
|
||
StoredEmbeddings int
|
||
StorageSavingsPercent float64
|
||
RecomputedTotal int64
|
||
|
||
// Cache metrics
|
||
CacheHits int64
|
||
CacheMisses int64
|
||
CacheHitRate float64
|
||
|
||
// Graph metrics
|
||
GraphTraversals int64
|
||
AvgTraversalDepth float64
|
||
|
||
// Runtime
|
||
Uptime time.Duration
|
||
}
|
||
|
||
// sortDurations sorts a slice of durations in ascending order
|
||
func sortDurations(durations []time.Duration) {
|
||
n := len(durations)
|
||
for i := 0; i < n-1; i++ {
|
||
for j := 0; j < n-i-1; j++ {
|
||
if durations[j] > durations[j+1] {
|
||
durations[j], durations[j+1] = durations[j+1], durations[j]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// percentile calculates the Nth percentile from a sorted slice
|
||
func percentile(sorted []time.Duration, p float64) time.Duration {
|
||
if len(sorted) == 0 {
|
||
return 0
|
||
}
|
||
|
||
idx := int(float64(len(sorted)) * p)
|
||
if idx >= len(sorted) {
|
||
idx = len(sorted) - 1
|
||
}
|
||
|
||
return sorted[idx]
|
||
}
|
||
|
||
// String returns a human-readable representation of metrics
|
||
func (s MetricsSnapshot) String() string {
|
||
return fmt.Sprintf(`Hybrid Vector Storage Metrics:
|
||
Queries:
|
||
Total: %d (Hub: %d, Hybrid: %d, OnDemand: %d, Graph: %d)
|
||
Avg Latency: %v (p50: %v, p95: %v, p99: %v)
|
||
Hub Latency: %v, Recompute Latency: %v
|
||
Storage:
|
||
Documents: %d (Hubs: %d, %.1f%%)
|
||
Stored Embeddings: %d
|
||
Savings: %.1f%%
|
||
Total Recomputed: %d
|
||
Cache:
|
||
Hits: %d, Misses: %d (Hit Rate: %.1f%%)
|
||
Graph:
|
||
Traversals: %d (Avg Depth: %.2f)
|
||
Runtime: %v`,
|
||
s.TotalQueries, s.HubOnlyQueries, s.HybridQueries, s.OnDemandQueries, s.GraphQueries,
|
||
s.AvgLatency, s.P50Latency, s.P95Latency, s.P99Latency,
|
||
s.AvgHubLatency, s.AvgRecomputeLatency,
|
||
s.TotalDocuments, s.HubDocuments, float64(s.HubDocuments)/float64(s.TotalDocuments)*100,
|
||
s.StoredEmbeddings,
|
||
s.StorageSavingsPercent,
|
||
s.RecomputedTotal,
|
||
s.CacheHits, s.CacheMisses, s.CacheHitRate*100,
|
||
s.GraphTraversals, s.AvgTraversalDepth,
|
||
s.Uptime,
|
||
)
|
||
}
|