2021-09-01 08:48:35 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"math"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Stats struct {
|
|
|
|
Samples int
|
|
|
|
Avg Float
|
|
|
|
Min Float
|
|
|
|
Max Float
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return `Stats` by value for less allocations/GC?
|
2021-09-07 09:24:50 +02:00
|
|
|
func (b *buffer) stats(from, to int64) (Stats, int64, int64, error) {
|
2021-09-01 08:48:35 +02:00
|
|
|
if from < b.start {
|
|
|
|
if b.prev != nil {
|
|
|
|
return b.prev.stats(from, to)
|
|
|
|
}
|
|
|
|
from = b.start
|
|
|
|
}
|
|
|
|
|
|
|
|
samples := 0
|
|
|
|
sum, min, max := 0.0, math.MaxFloat32, -math.MaxFloat32
|
|
|
|
|
|
|
|
var t int64
|
|
|
|
for t = from; t < to; t += b.frequency {
|
|
|
|
idx := int((t - b.start) / b.frequency)
|
|
|
|
if idx >= cap(b.data) {
|
|
|
|
b = b.next
|
|
|
|
if b == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
idx = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if t < b.start || idx >= len(b.data) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
xf := float64(b.data[idx])
|
|
|
|
if math.IsNaN(xf) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
samples += 1
|
|
|
|
sum += xf
|
|
|
|
min = math.Min(min, xf)
|
|
|
|
max = math.Max(max, xf)
|
|
|
|
}
|
|
|
|
|
2021-09-07 09:24:50 +02:00
|
|
|
return Stats{
|
2021-09-01 08:48:35 +02:00
|
|
|
Samples: samples,
|
|
|
|
Avg: Float(sum) / Float(samples),
|
|
|
|
Min: Float(min),
|
|
|
|
Max: Float(max),
|
|
|
|
}, from, t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function assmumes that `l.lock` is LOCKED!
|
|
|
|
// It basically works just like level.read but calculates min/max/avg for that data level.read would return.
|
|
|
|
// TODO: Make this DRY?
|
2021-09-08 10:29:36 +02:00
|
|
|
func (l *level) stats(offset int, from, to int64, aggreg AggregationStrategy) (Stats, int64, int64, error) {
|
|
|
|
if b := l.metrics[offset]; b != nil {
|
2021-09-01 08:48:35 +02:00
|
|
|
return b.stats(from, to)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(l.children) == 0 {
|
2021-09-07 09:24:50 +02:00
|
|
|
return Stats{}, 0, 0, ErrNoData
|
2021-09-01 08:48:35 +02:00
|
|
|
}
|
|
|
|
|
2021-09-07 09:24:50 +02:00
|
|
|
n := 0
|
2021-09-01 08:48:35 +02:00
|
|
|
samples := 0
|
2021-09-07 09:24:50 +02:00
|
|
|
avg, min, max := Float(0), Float(math.MaxFloat32), Float(-math.MaxFloat32)
|
2021-09-01 08:48:35 +02:00
|
|
|
for _, child := range l.children {
|
2021-09-07 09:24:50 +02:00
|
|
|
child.lock.RLock()
|
2021-09-08 10:29:36 +02:00
|
|
|
stats, cfrom, cto, err := child.stats(offset, from, to, aggreg)
|
2021-09-07 09:24:50 +02:00
|
|
|
child.lock.RUnlock()
|
|
|
|
|
|
|
|
if err == ErrNoData {
|
|
|
|
continue
|
|
|
|
}
|
2021-09-01 08:48:35 +02:00
|
|
|
|
|
|
|
if err != nil {
|
2021-09-07 09:24:50 +02:00
|
|
|
return Stats{}, 0, 0, err
|
2021-09-01 08:48:35 +02:00
|
|
|
}
|
|
|
|
|
2021-09-07 09:24:50 +02:00
|
|
|
if n == 0 {
|
|
|
|
from = cfrom
|
|
|
|
to = cto
|
|
|
|
} else if cfrom != from || cto != to {
|
|
|
|
return Stats{}, 0, 0, ErrDataDoesNotAlign
|
2021-09-01 08:48:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
samples += stats.Samples
|
2021-09-07 09:24:50 +02:00
|
|
|
avg += stats.Avg
|
2021-09-01 08:48:35 +02:00
|
|
|
min = Float(math.Min(float64(min), float64(stats.Min)))
|
|
|
|
max = Float(math.Max(float64(max), float64(stats.Max)))
|
2021-09-07 09:24:50 +02:00
|
|
|
n += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if n == 0 {
|
|
|
|
return Stats{}, 0, 0, ErrNoData
|
2021-09-01 08:48:35 +02:00
|
|
|
}
|
|
|
|
|
2021-09-08 10:29:36 +02:00
|
|
|
if aggreg == AvgAggregation {
|
2021-09-07 09:24:50 +02:00
|
|
|
avg /= Float(n)
|
2021-09-08 10:29:36 +02:00
|
|
|
} else if aggreg != SumAggregation {
|
|
|
|
return Stats{}, 0, 0, errors.New("invalid aggregation")
|
2021-09-01 08:48:35 +02:00
|
|
|
}
|
|
|
|
|
2021-09-07 09:24:50 +02:00
|
|
|
return Stats{
|
2021-09-01 08:48:35 +02:00
|
|
|
Samples: samples,
|
|
|
|
Avg: avg,
|
|
|
|
Min: min,
|
|
|
|
Max: max,
|
|
|
|
}, from, to, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MemoryStore) Stats(selector []string, metric string, from, to int64) (*Stats, int64, int64, error) {
|
2021-09-08 10:29:36 +02:00
|
|
|
l := m.root.findLevelOrCreate(selector, len(m.metrics))
|
2021-09-01 08:48:35 +02:00
|
|
|
l.lock.RLock()
|
|
|
|
defer l.lock.RUnlock()
|
|
|
|
|
|
|
|
if from > to {
|
|
|
|
return nil, 0, 0, errors.New("invalid time range")
|
|
|
|
}
|
|
|
|
|
|
|
|
minfo, ok := m.metrics[metric]
|
|
|
|
if !ok {
|
|
|
|
return nil, 0, 0, errors.New("unkown metric: " + metric)
|
|
|
|
}
|
|
|
|
|
2021-09-08 10:29:36 +02:00
|
|
|
stats, from, to, err := l.stats(minfo.offset, from, to, minfo.aggregation)
|
2021-09-07 09:24:50 +02:00
|
|
|
return &stats, from, to, err
|
2021-09-01 08:48:35 +02:00
|
|
|
}
|