Artifact d05e8aff5e4835070cbaa8a2fe94131eba90cd30c1397a2e73d18a5ebd3d1c6a:
- File
cmd/quarry/analytics_duck.go
— part of check-in
[e2b74749b4]
at
2026-08-25 22:19:15
on branch fleet-census
— Analytics: say whether history is being kept, and stop keeping it for ever
The live panels read in-memory rings, so a forge whose store failed to open looked exactly like a healthy one. Sink.Err() existed and nothing asked it. The page now distinguishes three states: off, broken, and recording to a named path.
Retention was 'append for ever', which is a default nobody was asked about. --analytics-keep sets the window, 14d by default, 0 keeps everything. Pruned at startup and daily. (user: dmestas, size: 2292) [annotate] [blame] [check-ins using] [more...]
//go:build !js && !wasip1 package main import ( "fmt" "log" "path/filepath" "time" "craftdesign.group/quarry/internal/obs" "craftdesign.group/quarry/internal/obsduck" ) // openAnalytics wires the durable observation store, returning a stop // function. Failure is NOT fatal: analytics is derived data about a // forge that works without it, and a forge that refuses to start // because its metrics store is unhappy has the priority backwards. // // Two files provide this — one behind !js, one behind js — so that the // wasm build the CGO-free rule exists for never reaches DuckDB. It is // not enough to leave the import out of the request path: go-duckdb's // own build constraints exclude wasm, so any reachable import fails the // build with "build constraints exclude all Go files". func openAnalytics(dir string, keep time.Duration) func() { if dir == "" { // Switched off is a choice, not a fault, and the panel has to // be able to tell the difference. obs.Default.SetHealth(obs.Health{}) return func() {} } path := filepath.Join(dir, "ops.duckdb") sink, err := obsduck.Open(path, 0) if err != nil { log.Printf("analytics: %v; the live panels still work", err) obs.Default.SetHealth(obs.Health{Configured: true, Path: path, Err: err.Error()}) return func() {} } obs.Default.SetSink(sink) obs.Default.SetHealth(obs.Health{Configured: true, Path: path, Kept: keptLabel(keep)}) stop := make(chan struct{}) go sink.FlushEvery(30*time.Second, stop) // Once at startup as well as on the timer: a forge that restarts // more often than daily would otherwise never reach a prune. if n, perr := sink.Prune(keep); perr != nil { log.Printf("analytics: %v", perr) } else if n > 0 { log.Printf("analytics: pruned %d observations older than %s", n, keptLabel(keep)) } go sink.PruneEvery(24*time.Hour, keep, stop) fmt.Println("quarry: analytics ->", path) return func() { close(stop) if err := sink.Close(); err != nil { log.Printf("analytics: close: %v", err) } } } // keptLabel names the retention window for the panel and the log. func keptLabel(keep time.Duration) string { if keep <= 0 { return "" } if d := keep / (24 * time.Hour); d > 0 && keep%(24*time.Hour) == 0 { return fmt.Sprintf("%dd", d) } return keep.String() }