diff --git a/frontend/src/constants/queryFunctionOptions.ts b/frontend/src/constants/queryFunctionOptions.ts index 4aa6332d67..4a7b3b0413 100644 --- a/frontend/src/constants/queryFunctionOptions.ts +++ b/frontend/src/constants/queryFunctionOptions.ts @@ -23,6 +23,10 @@ export const metricQueryFunctionOptions: SelectOption[] = [ value: QueryFunctionsTypes.ABSOLUTE, label: 'Absolute', }, + { + value: QueryFunctionsTypes.RUNNING_DIFF, + label: 'Running Diff', + }, { value: QueryFunctionsTypes.LOG_2, label: 'Log2', @@ -103,6 +107,9 @@ export const queryFunctionsTypesConfig: QueryFunctionConfigType = { absolute: { showInput: false, }, + runningDiff: { + showInput: false, + }, log2: { showInput: false, }, diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts index 7d566283e6..4a67619a61 100644 --- a/frontend/src/types/common/queryBuilder.ts +++ b/frontend/src/types/common/queryBuilder.ts @@ -158,6 +158,7 @@ export enum QueryFunctionsTypes { CLAMP_MIN = 'clampMin', CLAMP_MAX = 'clampMax', ABSOLUTE = 'absolute', + RUNNING_DIFF = 'runningDiff', LOG_2 = 'log2', LOG_10 = 'log10', CUMULATIVE_SUM = 'cumSum', diff --git a/pkg/query-service/app/queryBuilder/functions.go b/pkg/query-service/app/queryBuilder/functions.go index a933406d23..39f3cb6ecb 100644 --- a/pkg/query-service/app/queryBuilder/functions.go +++ b/pkg/query-service/app/queryBuilder/functions.go @@ -70,6 +70,22 @@ func funcAbsolute(result *v3.Result) *v3.Result { return result } +// funcRunningDiff returns the running difference of each point +func funcRunningDiff(result *v3.Result) *v3.Result { + for _, series := range result.Series { + // iterate over the point in reverse order + for idx := len(series.Points) - 1; idx >= 0; idx-- { + if idx > 0 { + series.Points[idx].Value = series.Points[idx].Value - series.Points[idx-1].Value + } + } + // remove the first point + // the timerange is already adjusted in the query range + series.Points = series.Points[1:] + } + return result +} + // funcLog2 returns the log2 of each point func funcLog2(result *v3.Result) *v3.Result { for _, series := range result.Series { @@ -256,6 +272,8 @@ func ApplyFunction(fn v3.Function, result *v3.Result) *v3.Result { } case v3.FunctionNameAbsolute: return funcAbsolute(result) + case v3.FunctionNameRunningDiff: + return funcRunningDiff(result) case v3.FunctionNameLog2: return funcLog2(result) case v3.FunctionNameLog10: diff --git a/pkg/query-service/app/queryBuilder/functions_test.go b/pkg/query-service/app/queryBuilder/functions_test.go index 08b407a789..d86ec10d9b 100644 --- a/pkg/query-service/app/queryBuilder/functions_test.go +++ b/pkg/query-service/app/queryBuilder/functions_test.go @@ -602,3 +602,70 @@ func TestFuncMedian5(t *testing.T) { } } } + +func TestFuncRunningDiff(t *testing.T) { + type args struct { + result *v3.Result + } + + tests := []struct { + name string + args args + want *v3.Result + }{ + { + name: "test funcRunningDiff", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 1}, {Timestamp: 2, Value: 2}, {Timestamp: 3, Value: 3}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 2, Value: 1}, {Timestamp: 3, Value: 1}}, + }, + }, + }, + }, + { + name: "test funcRunningDiff with start number as 8", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 8}, {Timestamp: 2, Value: 8}, {Timestamp: 3, Value: 8}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 2, Value: 0}, {Timestamp: 3, Value: 0}}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := funcRunningDiff(tt.args.result) + for j, series := range got.Series { + if len(series.Points) != len(tt.want.Series[j].Points) { + t.Errorf("funcRunningDiff() = len(series.Points) %v, len(tt.want.Series[j].Points) %v", len(series.Points), len(tt.want.Series[j].Points)) + } + for k, point := range series.Points { + if point.Value != tt.want.Series[j].Points[k].Value { + t.Errorf("funcRunningDiff() = %v, want %v", point.Value, tt.want.Series[j].Points[k].Value) + } + } + } + }) + } +} diff --git a/pkg/query-service/common/query_range.go b/pkg/query-service/common/query_range.go index c119f2d25e..c352c7d9f2 100644 --- a/pkg/query-service/common/query_range.go +++ b/pkg/query-service/common/query_range.go @@ -13,10 +13,20 @@ func AdjustedMetricTimeRange(start, end, step int64, mq v3.BuilderQuery) (int64, start = start - (start % (step * 1000)) // if the query is a rate query, we adjust the start time by one more step // so that we can calculate the rate for the first data point + hasRunningDiff := false + for _, fn := range mq.Functions { + if fn.Name == v3.FunctionNameRunningDiff { + hasRunningDiff = true + break + } + } if (mq.AggregateOperator.IsRateOperator() || mq.TimeAggregation.IsRateOperator()) && mq.Temporality != v3.Delta { start -= step * 1000 } + if hasRunningDiff { + start -= step * 1000 + } // align the end to the nearest minute adjustStep := int64(math.Min(float64(step), 60)) end = end - (end % (adjustStep * 1000)) diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index 9d388b32e2..a133cdaf23 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -627,21 +627,22 @@ func GetPercentileFromOperator(operator SpaceAggregation) float64 { type FunctionName string const ( - FunctionNameCutOffMin FunctionName = "cutOffMin" - FunctionNameCutOffMax FunctionName = "cutOffMax" - FunctionNameClampMin FunctionName = "clampMin" - FunctionNameClampMax FunctionName = "clampMax" - FunctionNameAbsolute FunctionName = "absolute" - FunctionNameLog2 FunctionName = "log2" - FunctionNameLog10 FunctionName = "log10" - FunctionNameCumSum FunctionName = "cumSum" - FunctionNameEWMA3 FunctionName = "ewma3" - FunctionNameEWMA5 FunctionName = "ewma5" - FunctionNameEWMA7 FunctionName = "ewma7" - FunctionNameMedian3 FunctionName = "median3" - FunctionNameMedian5 FunctionName = "median5" - FunctionNameMedian7 FunctionName = "median7" - FunctionNameTimeShift FunctionName = "timeShift" + FunctionNameCutOffMin FunctionName = "cutOffMin" + FunctionNameCutOffMax FunctionName = "cutOffMax" + FunctionNameClampMin FunctionName = "clampMin" + FunctionNameClampMax FunctionName = "clampMax" + FunctionNameAbsolute FunctionName = "absolute" + FunctionNameRunningDiff FunctionName = "runningDiff" + FunctionNameLog2 FunctionName = "log2" + FunctionNameLog10 FunctionName = "log10" + FunctionNameCumSum FunctionName = "cumSum" + FunctionNameEWMA3 FunctionName = "ewma3" + FunctionNameEWMA5 FunctionName = "ewma5" + FunctionNameEWMA7 FunctionName = "ewma7" + FunctionNameMedian3 FunctionName = "median3" + FunctionNameMedian5 FunctionName = "median5" + FunctionNameMedian7 FunctionName = "median7" + FunctionNameTimeShift FunctionName = "timeShift" ) func (f FunctionName) Validate() error { @@ -651,6 +652,7 @@ func (f FunctionName) Validate() error { FunctionNameClampMin, FunctionNameClampMax, FunctionNameAbsolute, + FunctionNameRunningDiff, FunctionNameLog2, FunctionNameLog10, FunctionNameCumSum,