stillbox/internal/trending/slidingwindow/slidingwindow.go

117 lines
2.2 KiB
Go
Raw Normal View History

2024-10-22 23:44:40 -04:00
package slidingwindow
import "time"
// Clock specifies the needed time related functions used by the time series.
// To use a custom clock implement the interface and pass it to the time series constructor.
// The default clock uses time.Now()
type Clock interface {
Now() time.Time
}
type slidingWindow struct {
buffer []float64
length int
end time.Time
start time.Time
oldest int
newest int
step time.Duration
duration time.Duration
clock Clock
}
var defaultStep = time.Hour * 24
var defaultDuration = time.Hour * 24 * 7
type options struct {
clock Clock
step time.Duration
duration time.Duration
}
type option func(*options)
func WithStep(step time.Duration) option {
return func(o *options) {
o.step = step
}
}
func WithDuration(duration time.Duration) option {
return func(o *options) {
o.duration = duration
}
}
func WithClock(clock Clock) option {
return func(o *options) {
o.clock = clock
}
}
func NewSlidingWindow(os ...option) *slidingWindow {
opts := options{}
for _, o := range os {
o(&opts)
}
if opts.clock == nil {
2024-10-30 09:49:45 -04:00
panic("clock not set")
2024-10-22 23:44:40 -04:00
}
if opts.step.Nanoseconds() == 0 {
opts.step = defaultStep
}
if opts.duration.Nanoseconds() == 0 {
opts.duration = defaultDuration
}
return newSlidingWindow(opts.step, opts.duration, opts.clock)
}
func newSlidingWindow(step time.Duration, duration time.Duration, clock Clock) *slidingWindow {
length := int(duration / step)
now := clock.Now()
return &slidingWindow{
buffer: make([]float64, length),
length: length,
end: now.Truncate(step).Add(-duration),
start: now,
step: step,
duration: duration,
oldest: 1,
clock: clock,
}
}
func (sw *slidingWindow) Insert(score float64) {
sw.advance()
if score > sw.buffer[sw.newest] {
sw.buffer[sw.newest] = score
}
}
func (sw *slidingWindow) Max() float64 {
sw.advance()
max := 0.0
for i := range sw.buffer {
if sw.buffer[i] > max {
max = sw.buffer[i]
}
}
return max
}
func (sw *slidingWindow) advance() {
newEnd := sw.clock.Now().Truncate(sw.step).Add(-sw.duration)
for newEnd.After(sw.end) {
sw.end = sw.end.Add(sw.step)
sw.buffer[sw.oldest] = 0.0
sw.newest = sw.oldest
sw.oldest = (sw.oldest + 1) % sw.length
}
}