LEM/pkg/lem/metrics.go
Snider 56eda1a081 refactor: migrate all 25 commands from passthrough to cobra framework
Replace passthrough() + stdlib flag.FlagSet anti-pattern with proper
cobra integration. Every Run* function now takes a typed *Opts struct
and returns error. Flags registered via cli.StringFlag/IntFlag/etc.
Commands participate in Core lifecycle with full cobra flag parsing.

- 6 command groups: gen, score, data, export, infra, mon
- 25 commands converted, 0 passthrough() calls remain
- Delete passthrough() helper from lem.go
- Update export_test.go to use ExportOpts struct

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-23 03:32:53 +00:00

121 lines
3.1 KiB
Go

package lem
import (
"fmt"
"os"
"time"
)
const targetTotal = 15000
// MetricsOpts holds configuration for the metrics command.
type MetricsOpts struct {
DB string // DuckDB database path (defaults to LEM_DB env)
Influx string // InfluxDB URL
InfluxDB string // InfluxDB database name
}
// RunMetrics reads golden set stats from DuckDB and pushes them to InfluxDB as
// golden_set_stats, golden_set_domain, and golden_set_voice measurements.
func RunMetrics(cfg MetricsOpts) error {
if cfg.DB == "" {
cfg.DB = os.Getenv("LEM_DB")
}
if cfg.DB == "" {
return fmt.Errorf("--db or LEM_DB required (path to DuckDB file)")
}
db, err := OpenDB(cfg.DB)
if err != nil {
return fmt.Errorf("open db: %w", err)
}
defer db.Close()
// Query overall stats.
var total, domains, voices int
var avgGenTime, avgChars float64
err = db.conn.QueryRow(`
SELECT count(*), count(DISTINCT domain), count(DISTINCT voice),
coalesce(avg(gen_time), 0), coalesce(avg(char_count), 0)
FROM golden_set
`).Scan(&total, &domains, &voices, &avgGenTime, &avgChars)
if err != nil {
return fmt.Errorf("query golden_set stats: %w", err)
}
if total == 0 {
fmt.Println("No golden set data in DuckDB.")
return nil
}
nowNs := time.Now().UTC().UnixNano()
pct := float64(total) / float64(targetTotal) * 100.0
var lines []string
// Overall stats measurement.
lines = append(lines, fmt.Sprintf(
"golden_set_stats total_examples=%di,domains=%di,voices=%di,avg_gen_time=%.2f,avg_response_chars=%.0f,completion_pct=%.1f %d",
total, domains, voices, avgGenTime, avgChars, pct, nowNs,
))
// Per-domain stats.
domainRows, err := db.conn.Query(`
SELECT domain, count(*) AS n, avg(gen_time) AS avg_t
FROM golden_set GROUP BY domain
`)
if err != nil {
return fmt.Errorf("query domains: %w", err)
}
domainCount := 0
for domainRows.Next() {
var domain string
var n int
var avgT float64
if err := domainRows.Scan(&domain, &n, &avgT); err != nil {
return fmt.Errorf("scan domain row: %w", err)
}
lines = append(lines, fmt.Sprintf(
"golden_set_domain,domain=%s count=%di,avg_gen_time=%.2f %d",
escapeLp(domain), n, avgT, nowNs,
))
domainCount++
}
domainRows.Close()
// Per-voice stats.
voiceRows, err := db.conn.Query(`
SELECT voice, count(*) AS n, avg(char_count) AS avg_c, avg(gen_time) AS avg_t
FROM golden_set GROUP BY voice
`)
if err != nil {
return fmt.Errorf("query voices: %w", err)
}
voiceCount := 0
for voiceRows.Next() {
var voice string
var n int
var avgC, avgT float64
if err := voiceRows.Scan(&voice, &n, &avgC, &avgT); err != nil {
return fmt.Errorf("scan voice row: %w", err)
}
lines = append(lines, fmt.Sprintf(
"golden_set_voice,voice=%s count=%di,avg_chars=%.0f,avg_gen_time=%.2f %d",
escapeLp(voice), n, avgC, avgT, nowNs,
))
voiceCount++
}
voiceRows.Close()
// Write to InfluxDB.
influx := NewInfluxClient(cfg.Influx, cfg.InfluxDB)
if err := influx.WriteLp(lines); err != nil {
return fmt.Errorf("write metrics: %w", err)
}
fmt.Printf("Wrote metrics to InfluxDB: %d examples, %d domains, %d voices (%d points)\n",
total, domainCount, voiceCount, len(lines))
return nil
}