Files
grafana-zabbix/pkg/timeseries/align.go

151 lines
3.4 KiB
Go

package timeseries
import (
"math"
"sort"
"time"
)
// Aligns point's time stamps according to provided interval.
func (ts TimeSeries) Align(interval time.Duration) TimeSeries {
if interval <= 0 || ts.Len() < 2 {
return ts
}
alignedTs := NewTimeSeries()
var frameTs = ts[0].GetTimeFrame(interval)
var pointFrameTs time.Time
var point TimePoint
for i := 0; i < ts.Len(); i++ {
point = ts[i]
pointFrameTs = point.GetTimeFrame(interval)
if pointFrameTs.After(frameTs) {
for frameTs.Before(pointFrameTs) {
alignedTs = append(alignedTs, TimePoint{Time: frameTs, Value: nil})
frameTs = frameTs.Add(interval)
}
}
alignedTs = append(alignedTs, TimePoint{Time: pointFrameTs, Value: point.Value})
frameTs = frameTs.Add(interval)
}
return alignedTs
}
// Fill missing points in trend by null values
func (ts TimeSeries) FillTrendWithNulls() TimeSeries {
if ts.Len() < 2 {
return ts
}
interval := time.Hour
alignedTs := NewTimeSeries()
var frameTs = ts[0].GetTimeFrame(interval)
var pointFrameTs time.Time
var point TimePoint
for i := 0; i < ts.Len(); i++ {
point = ts[i]
pointFrameTs = point.GetTimeFrame(interval)
if pointFrameTs.After(frameTs) {
for frameTs.Before(pointFrameTs) {
alignedTs = append(alignedTs, TimePoint{Time: frameTs, Value: nil})
frameTs = frameTs.Add(interval)
}
}
alignedTs = append(alignedTs, point)
frameTs = frameTs.Add(interval)
}
return alignedTs
}
// Detects interval between data points in milliseconds based on median delta between points.
func (ts TimeSeries) DetectInterval() time.Duration {
if ts.Len() < 2 {
return 0
}
deltas := make([]int, 0)
for i := 1; i < ts.Len(); i++ {
delta := ts[i].Time.Sub(ts[i-1].Time)
deltas = append(deltas, int(delta.Milliseconds()))
}
sort.Ints(deltas)
midIndex := int(math.Floor(float64(len(deltas)) * 0.5))
return time.Duration(deltas[midIndex]) * time.Millisecond
}
// AlignSeriesIntervals aligns series to the same time interval
func AlignSeriesIntervals(series []*TimeSeriesData) []*TimeSeriesData {
if len(series) == 0 {
return series
}
// Skip if interval not defined
for _, s := range series {
if s.Meta.Interval == nil {
return series
}
}
minInterval := *series[0].Meta.Interval
for _, s := range series {
if *s.Meta.Interval < minInterval {
minInterval = *s.Meta.Interval
}
}
// 0 interval means series is not aligned, so it's tricky to align multiple series
if minInterval == 0 {
return series
}
for _, s := range series {
if s.Len() < 2 || *s.Meta.Interval == minInterval {
continue
}
s.TS = s.TS.Interpolate(minInterval)
}
return series
}
func (ts TimeSeries) Interpolate(interval time.Duration) TimeSeries {
if interval <= 0 || ts.Len() < 2 {
return ts
}
alignedTs := NewTimeSeries()
var frameTs = ts[0].Time
var pointFrameTs time.Time
var point TimePoint
var nextPoint TimePoint
for i := 0; i < ts.Len()-1; i++ {
point = ts[i]
nextPoint = ts[i+1]
pointFrameTs = point.Time
if point.Value != nil && nextPoint.Value != nil {
frameTs = pointFrameTs.Add(interval)
for frameTs.Before(nextPoint.Time) {
pointValue := linearInterpolation(frameTs, point, nextPoint)
alignedTs = append(alignedTs, TimePoint{Time: frameTs, Value: &pointValue})
frameTs = frameTs.Add(interval)
}
}
alignedTs = append(alignedTs, TimePoint{Time: pointFrameTs, Value: point.Value})
frameTs = frameTs.Add(interval)
}
return alignedTs
}