Files
nmon/internal/rollup/rate.go

68 lines
2.0 KiB
Go

package rollup
import "math"
// wrap32 is the modulus of a 32-bit octet counter.
const wrap32 = 1 << 32
// counterWindow is the result of measuring one counter over one window.
type counterWindow struct {
rateBps float64 // average bits per second over the span actually observed
inside int // samples inside the window; fewer than expected means a gap
}
// measureCounter computes the average rate of a counter over the window
// (startMs, endMs] from its raw samples, which must be sorted by time and may
// include samples before the window (the newest of them is the baseline, so a
// window's rate is the counter's slope across it rather than a guess).
//
// The counter is never averaged: the increase is summed between consecutive
// samples and divided by the time those samples span. A decrease is a 32-bit
// wrap when wide is false (the increase is d+2^32) and a reset otherwise (the
// counter restarted from zero, so the increase is the new value), which is
// what VictoriaMetrics' increase() assumes too. ok is false when fewer than
// two samples bound the window.
func measureCounter(pts []point, startMs, endMs int64, wide bool) (counterWindow, bool) {
var base *point
var in []point
for i := range pts {
switch {
case pts[i].ts <= startMs:
base = &pts[i]
case pts[i].ts <= endMs:
in = append(in, pts[i])
}
}
if len(in) == 0 {
return counterWindow{}, false
}
seq := in
if base != nil {
seq = append([]point{*base}, in...)
}
if len(seq) < 2 {
return counterWindow{inside: len(in)}, false
}
var inc float64
for i := 1; i < len(seq); i++ {
d := seq[i].v - seq[i-1].v
if d < 0 {
if wide {
d = seq[i].v
} else {
d += wrap32
}
}
inc += d
}
span := float64(seq[len(seq)-1].ts-seq[0].ts) / 1000
if span <= 0 {
return counterWindow{inside: len(in)}, false
}
rate := inc * 8 / span
if math.IsInf(rate, 0) || math.IsNaN(rate) {
return counterWindow{inside: len(in)}, false
}
return counterWindow{rateBps: rate, inside: len(in)}, true
}