diff --git a/app/vmselect/promql/rollup.go b/app/vmselect/promql/rollup.go index f2272097f..d8ff249d0 100644 --- a/app/vmselect/promql/rollup.go +++ b/app/vmselect/promql/rollup.go @@ -45,6 +45,7 @@ var rollupFuncs = map[string]newRollupFunc{ "distinct_over_time": newRollupFuncOneArg(rollupDistinct), "integrate": newRollupFuncOneArg(rollupIntegrate), "ideriv": newRollupFuncOneArg(rollupIderiv), + "lifetime": newRollupFuncOneArg(rollupLifetime), "rollup": newRollupFuncOneArg(rollupFake), "rollup_rate": newRollupFuncOneArg(rollupFake), // + rollupFuncsRemoveCounterResets "rollup_deriv": newRollupFuncOneArg(rollupFake), @@ -725,6 +726,21 @@ func rollupIderiv(rfa *rollupFuncArg) float64 { return dv / (float64(dt) * 1e-3) } +func rollupLifetime(rfa *rollupFuncArg) float64 { + // Calculate the duration between the first and the last 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 + } + if len(timestamps) == 0 { + return nan + } + return float64(timestamps[len(timestamps)-1]-rfa.prevTimestamp) * 1e-3 +} + 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 00fbbff37..2ce43cc24 100644 --- a/app/vmselect/promql/rollup_test.go +++ b/app/vmselect/promql/rollup_test.go @@ -584,6 +584,34 @@ func TestRollupFuncsNoWindow(t *testing.T) { timestampsExpected := []int64{10, 50, 90, 130} testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected) }) + t.Run("lifetime", func(t *testing.T) { + rc := rollupConfig{ + Func: rollupLifetime, + 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.031, 0.044, 0.04, 0.01} + timestampsExpected := []int64{0, 40, 80, 120, 160} + testRowsEqual(t, values, rc.Timestamps, valuesExpected, timestampsExpected) + }) + t.Run("lifetime", func(t *testing.T) { + rc := rollupConfig{ + Func: rollupLifetime, + Start: 0, + End: 160, + Step: 40, + Window: 200, + } + rc.Timestamps = getTimestamps(rc.Start, rc.End, rc.Step) + values := rc.Do(nil, testValues, testTimestamps) + valuesExpected := []float64{nan, 0.031, 0.075, 0.115, 0.125} + 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,