package llmfs import ( "fmt" "io" "sync" "time" "github.com/NERVsystems/llm9p/internal/llm" "github.com/NERVsystems/llm9p/internal/protocol" ) // Metrics tracks LLM performance statistics type Metrics struct { mu sync.RWMutex requestCount int64 totalInputToks int64 totalOutputToks int64 totalLatencyMs int64 lastLatencyMs int64 minLatencyMs int64 maxLatencyMs int64 lastRequestTime time.Time } // Global metrics instance var GlobalMetrics = &Metrics{ minLatencyMs: 999999, } // RecordRequest records a completed LLM request func (m *Metrics) RecordRequest(inputTokens, outputTokens int, latencyMs int64) { m.mu.Lock() defer m.mu.Unlock() m.requestCount++ m.totalInputToks += int64(inputTokens) m.totalOutputToks += int64(outputTokens) m.totalLatencyMs += latencyMs m.lastLatencyMs = latencyMs m.lastRequestTime = time.Now() if latencyMs < m.minLatencyMs { m.minLatencyMs = latencyMs } if latencyMs > m.maxLatencyMs { m.maxLatencyMs = latencyMs } } // Report returns a formatted metrics report func (m *Metrics) Report() string { m.mu.RLock() defer m.mu.RUnlock() if m.requestCount == 0 { return "requests: 0\n" } avgLatencyMs := m.totalLatencyMs / m.requestCount avgToksPerReq := (m.totalInputToks + m.totalOutputToks) / m.requestCount return fmt.Sprintf(`requests: %d input_tokens: %d output_tokens: %d total_tokens: %d avg_tokens_per_request: %d last_latency_ms: %d avg_latency_ms: %d min_latency_ms: %d max_latency_ms: %d last_request: %s `, m.requestCount, m.totalInputToks, m.totalOutputToks, m.totalInputToks+m.totalOutputToks, avgToksPerReq, m.lastLatencyMs, avgLatencyMs, m.minLatencyMs, m.maxLatencyMs, m.lastRequestTime.Format(time.RFC3339), ) } // MetricsFile exposes performance metrics via 9P type MetricsFile struct { *protocol.BaseFile client llm.Backend } // NewMetricsFile creates the metrics file and registers the metrics callback func NewMetricsFile(client llm.Backend) *MetricsFile { // Register our metrics callback with the llm package llm.SetMetricsCallback(func(inputTokens, outputTokens int, latencyMs int64) { GlobalMetrics.RecordRequest(inputTokens, outputTokens, latencyMs) }) return &MetricsFile{ BaseFile: protocol.NewBaseFile("metrics", 0444), client: client, } } func (f *MetricsFile) Read(p []byte, offset int64) (int, error) { content := GlobalMetrics.Report() if offset >= int64(len(content)) { return 0, io.EOF } n := copy(p, content[offset:]) return n, nil } func (f *MetricsFile) Write(p []byte, offset int64) (int, error) { return 0, protocol.ErrPermission } func (f *MetricsFile) Stat() protocol.Stat { s := f.BaseFile.Stat() s.Length = uint64(len(GlobalMetrics.Report())) return s }