package obs
import "time"
// series.go turns the rings into the shapes a card can draw.
//
// A number on the ops page answers "how many right now"; the shape
// beside it answers "is that unusual", which is the question the number
// provokes and cannot settle. Both come from the rings already kept for
// the panels below, so nothing here samples, stores or schedules.
// Bins is how many points a card's line carries. Small on purpose: the
// sparkline is a glance, and a point per pixel is a chart.
const Bins = 24
// SeriesWindow is how far back a card's line reaches. Three minutes is
// what an operator watching a deploy or a sync actually wants: long
// enough to show a spike rising and falling, short enough that the
// line is about NOW rather than about this morning.
const SeriesWindow = 3 * time.Minute
// ConcurrencyBins samples how many subprocesses were RUNNING at the
// start of each bin across the window ending at now.
//
// Running, not started: the number above the line counts what is in
// flight, and a line counting starts would answer a different question
// under the same heading — five running drawn over a bar of twelve.
// A call is in flight during [At, At+Duration).
func ConcurrencyBins(subs []Subprocess, now time.Time, window time.Duration, bins int) []int {
if bins <= 0 || window <= 0 {
return nil
}
out := make([]int, bins)
step := window / time.Duration(bins)
start := now.Add(-window)
for i := range out {
at := start.Add(time.Duration(i) * step)
for _, s := range subs {
if !s.At.After(at) && s.At.Add(s.Duration).After(at) {
out[i]++
}
}
}
return out
}
// CountBins counts events per bin across the window ending at now.
// Anything before the window is dropped rather than piled into the
// first bin, which would draw a spike that never happened.
func CountBins(ats []time.Time, now time.Time, window time.Duration, bins int) []int {
if bins <= 0 || window <= 0 {
return nil
}
out := make([]int, bins)
step := window / time.Duration(bins)
start := now.Add(-window)
for _, at := range ats {
if at.Before(start) || at.After(now) {
continue
}
i := int(at.Sub(start) / step)
if i >= bins {
i = bins - 1 // the instant now lands in the last bin, not past the end
}
if i >= 0 {
out[i]++
}
}
return out
}
// RenderTimes is when each render in the ring began.
func (s Snapshot) RenderTimes() []time.Time {
out := make([]time.Time, 0, len(s.Renders))
for _, r := range s.Renders {
out = append(out, r.At)
}
return out
}