Artifact 1d5d761b2283189c5c4c4ffaa8b8e3c5edb14e8c50d8738fc0e0ddbcd7179f79:
- File
internal/obs/stats.go
— part of check-in
[5229b05522]
at
2026-08-25 19:02:08
on branch trunk
— Add internal/obs, which trunk imports but did not carry
Check-in 2cacce9b01aa added obs.Default.Spawned, RecordSubprocess, RecordRender and Snapshot calls across zitcli, web and api, but the package itself was never added — so trunk did not compile at all.
The files are the ones from the authoring session's tree, unmodified, tests included. Nothing here is my design; this only makes the import resolve so the tree builds again. (user: dmestas, size: 2644) [annotate] [blame] [check-ins using]
package obs import ( "cmp" "slices" "time" ) // ring is a fixed-size window of the most recent values. Bounded on // purpose: this package is the snapshot, and an unbounded buffer in a // long-lived server is a leak wearing a feature's clothes. type ring[T any] struct { buf []T next int full bool } func newRing[T any](n int) ring[T] { return ring[T]{buf: make([]T, n)} } func (r *ring[T]) push(v T) { r.buf[r.next] = v r.next++ if r.next == len(r.buf) { r.next, r.full = 0, true } } // all returns the window newest first. func (r *ring[T]) all() []T { n := r.next if r.full { n = len(r.buf) } out := make([]T, 0, n) for i := 0; i < n; i++ { j := r.next - 1 - i if j < 0 { j += len(r.buf) } out = append(out, r.buf[j]) } return out } // pathStat accumulates one route's durations. Kept as a sample window // rather than a running mean: a mean hides the tail, and the tail is // what a reader is looking for on this page. type pathStat struct { n int samples ring[time.Duration] max time.Duration } func (p *pathStat) add(d time.Duration) { if p.samples.buf == nil { p.samples = newRing[time.Duration](128) } p.n++ p.samples.push(d) if d > p.max { p.max = d } } // PathStat is one route's rolled-up timing, for the admin surface. type PathStat struct { Path string N int P50 time.Duration P95 time.Duration Max time.Duration } // Snapshot is everything the ops page shows, taken under one lock so // the panels agree with each other. type Snapshot struct { Paths []PathStat Renders []Render Subprocess []Subprocess LiveSubproc int PeakSubproc int } // Snapshot reads the rings. Slowest paths first by p95, because the // question this page answers is "what is slow", not "what is busy". func (r *Recorder) Snapshot() Snapshot { if r == nil { return Snapshot{} } r.mu.Lock() defer r.mu.Unlock() s := Snapshot{ Renders: r.rend.all(), Subprocess: r.subs.all(), LiveSubproc: r.live, PeakSubproc: r.liveMax, } for path, st := range r.paths { xs := st.samples.all() slices.Sort(xs) s.Paths = append(s.Paths, PathStat{ Path: path, N: st.n, Max: st.max, P50: pct(xs, 0.50), P95: pct(xs, 0.95), }) } slices.SortFunc(s.Paths, func(a, b PathStat) int { if c := cmp.Compare(b.P95, a.P95); c != 0 { return c } return cmp.Compare(a.Path, b.Path) }) return s } // pct is the nearest-rank percentile of a SORTED slice. func pct(xs []time.Duration, q float64) time.Duration { if len(xs) == 0 { return 0 } i := int(float64(len(xs))*q+0.5) - 1 if i < 0 { i = 0 } if i >= len(xs) { i = len(xs) - 1 } return xs[i] }