diff --git a/app/vmselect/promql/rollup.go b/app/vmselect/promql/rollup.go index d8ff249d0f..c1d82237e5 100644 --- a/app/vmselect/promql/rollup.go +++ b/app/vmselect/promql/rollup.go @@ -46,6 +46,7 @@ var rollupFuncs = map[string]newRollupFunc{ "integrate": newRollupFuncOneArg(rollupIntegrate), "ideriv": newRollupFuncOneArg(rollupIderiv), "lifetime": newRollupFuncOneArg(rollupLifetime), + "scrape_interval": newRollupFuncOneArg(rollupScrapeInterval), "rollup": newRollupFuncOneArg(rollupFake), "rollup_rate": newRollupFuncOneArg(rollupFake), // + rollupFuncsRemoveCounterResets "rollup_deriv": newRollupFuncOneArg(rollupFake), @@ -62,6 +63,8 @@ var rollupFuncsMayAdjustWindow = map[string]bool{ "deriv_fast": true, "irate": true, "rate": true, + "lifetime": true, + "scrape_interval": true, } var rollupFuncsRemoveCounterResets = map[string]bool{ @@ -741,6 +744,21 @@ func rollupLifetime(rfa *rollupFuncArg) float64 { return float64(timestamps[len(timestamps)-1]-rfa.prevTimestamp) * 1e-3 } +func rollupScrapeInterval(rfa *rollupFuncArg) float64 { + // Calculate the average interval between data points. + timestamps := rfa.timestamps + if math.IsNaN(rfa.prevValue) { + if len(timestamps) < 2 { + return nan + } + return float64(timestamps[len(timestamps)-1]-timestamps[0]) * 1e-3 / float64(len(timestamps)-1) + } + if len(timestamps) == 0 { + return nan + } + return (float64(timestamps[len(timestamps)-1]-rfa.prevTimestamp) * 1e-3) / float64(len(timestamps)) +} + func rollupChanges(rfa *rollupFuncArg) float64 { // There is no need in handling NaNs here, since they must be cleaned up // before calling rollup funcs. diff --git a/app/vmselect/promql/rollup_test.go b/app/vmselect/promql/rollup_test.go index 2ce43cc242..f13dc617b6 100644 --- a/app/vmselect/promql/rollup_test.go +++ b/app/vmselect/promql/rollup_test.go @@ -612,6 +612,34 @@ func TestRollupFuncsNoWindow(t *testing.T) { timestampsExpected := []int64{0, 40, 80, 120, 160} testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected) }) + t.Run("scrape_interval", func(t *testing.T) { + rc := rollupConfig{ + Func: rollupScrapeInterval, + Start: 0, + End: 160, + Step: 40, + Window: 0, + } + rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step) + values := rc.Do(nil, testValues, testTimestamps) + valuesExpected := []float64{nan, 0.010333333333333333, 0.011, 0.013333333333333334, 0.01} + timestampsExpected := []int64{0, 40, 80, 120, 160} + testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected) + }) + t.Run("scrape_interval", func(t *testing.T) { + rc := rollupConfig{ + Func: rollupScrapeInterval, + Start: 0, + End: 160, + Step: 40, + Window: 80, + } + rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step) + values := rc.Do(nil, testValues, testTimestamps) + valuesExpected := []float64{nan, 0.010333333333333333, 0.010714285714285714, 0.012, 0.0125} + timestampsExpected := []int64{0, 40, 80, 120, 160} + testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected) + }) t.Run("changes", func(t *testing.T) { rc := rollupConfig{ Func: rollupChanges,