diff --git a/pkg/datasource/response_handler.go b/pkg/datasource/response_handler.go index 466b055..b0d7c48 100644 --- a/pkg/datasource/response_handler.go +++ b/pkg/datasource/response_handler.go @@ -234,7 +234,7 @@ func getTrendPointValue(point zabbix.TrendPoint, valueType string) (float64, err return 0, fmt.Errorf("failed to get trend value, unknown value type: %s", valueType) } -var fixedUpdateIntervalPattern = regexp.MustCompile(`^(\d+)([shd]?)$`) +var fixedUpdateIntervalPattern = regexp.MustCompile(`^(\d+)([smhdw]?)$`) func parseItemUpdateInterval(delay string) *time.Duration { if valid := fixedUpdateIntervalPattern.MatchString(delay); !valid { diff --git a/pkg/datasource/zabbix.go b/pkg/datasource/zabbix.go index 6108415..23efc4d 100644 --- a/pkg/datasource/zabbix.go +++ b/pkg/datasource/zabbix.go @@ -100,39 +100,7 @@ func (ds *ZabbixDatasourceInstance) queryNumericDataForItems(ctx context.Context } series := convertHistoryToTimeSeries(history, items) - - // Align time series data if possible - useTrend := ds.isUseTrend(query.TimeRange) - if !query.Options.DisableDataAlignment && !ds.Settings.DisableDataAlignment && !useTrend { - for _, s := range series { - if s.Meta.Interval != nil { - s.TS = s.TS.Align(*s.Meta.Interval) - } - } - } - - series, err = applyFunctions(series, query.Functions) - if err != nil { - return nil, err - } - - for _, s := range series { - if int64(s.Len()) > query.MaxDataPoints && query.Interval > 0 { - downsampleFunc := consolidateBy - if downsampleFunc == "" { - downsampleFunc = "avg" - } - downsampled, err := applyGroupBy(s.TS, query.Interval.String(), downsampleFunc) - if err == nil { - s.TS = downsampled - } else { - ds.logger.Debug("Error downsampling series", "error", err) - } - } - } - - frames := convertTimeSeriesToDataFrames(series) - return frames, nil + return ds.applyDataProcessing(ctx, query, series) } func (ds *ZabbixDatasourceInstance) applyDataProcessing(ctx context.Context, query *QueryModel, series []*timeseries.TimeSeriesData) ([]*data.Frame, error) { @@ -140,10 +108,18 @@ func (ds *ZabbixDatasourceInstance) applyDataProcessing(ctx context.Context, que // Align time series data if possible useTrend := ds.isUseTrend(query.TimeRange) - if !query.Options.DisableDataAlignment && !ds.Settings.DisableDataAlignment && !useTrend { - for _, s := range series { - if s.Meta.Interval != nil { - s.TS = s.TS.Align(*s.Meta.Interval) + disableDataAlignment := query.Options.DisableDataAlignment || ds.Settings.DisableDataAlignment + if !disableDataAlignment { + if useTrend { + for _, s := range series { + // Trend data is already aligned (by 1 hour interval), but null values should be added + s.TS = s.TS.FillTrendWithNulls() + } + } else { + for _, s := range series { + if s.Meta.Interval != nil { + s.TS = s.TS.Align(*s.Meta.Interval) + } } } } diff --git a/pkg/timeseries/align.go b/pkg/timeseries/align.go index 41bf719..f2781c0 100644 --- a/pkg/timeseries/align.go +++ b/pkg/timeseries/align.go @@ -35,6 +35,36 @@ func (ts TimeSeries) Align(interval time.Duration) TimeSeries { 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 {