diff --git a/src/query/graphite/native/builtin_functions.go b/src/query/graphite/native/builtin_functions.go index 2d5073208f..61beb1ff90 100644 --- a/src/query/graphite/native/builtin_functions.go +++ b/src/query/graphite/native/builtin_functions.go @@ -1143,6 +1143,28 @@ func exclude(_ *common.Context, input singlePathSpec, pattern string) (ts.Series return r, nil } +// pow takes one metric or a wildcard seriesList followed by a constant, +// and raises the datapoint by the power of the constant provided at each point +// nolint: gocritic +func pow(ctx *common.Context, input singlePathSpec, factor float64) (ts.SeriesList, error) { + results := make([]*ts.Series, 0, len(input.Values)) + + for _, series := range input.Values { + numSteps := series.Len() + millisPerStep := series.MillisPerStep() + vals := ts.NewValues(ctx, millisPerStep, numSteps) + for i := 0; i < numSteps; i++ { + vals.SetValueAt(i, math.Pow(series.ValueAt(i), factor)) + } + newName := fmt.Sprintf("pow(%s, %f)", series.Name(), factor) + results = append(results, ts.NewSeries(ctx, newName, series.StartTime(), vals)) + } + + r := ts.SeriesList(input) + r.Values = results + return r, nil +} + // logarithm takes one metric or a wildcard seriesList, and draws the y-axis in // logarithmic format. func logarithm(ctx *common.Context, input singlePathSpec, base int) (ts.SeriesList, error) { @@ -2471,6 +2493,7 @@ func init() { MustRegisterFunction(perSecond).WithDefaultParams(map[uint8]interface{}{ 2: math.NaN(), // maxValue }) + MustRegisterFunction(pow) MustRegisterFunction(powSeries) MustRegisterFunction(rangeOfSeries) MustRegisterFunction(randomWalkFunction).WithDefaultParams(map[uint8]interface{}{ diff --git a/src/query/graphite/native/builtin_functions_test.go b/src/query/graphite/native/builtin_functions_test.go index 41dfcd3a36..5521e93645 100644 --- a/src/query/graphite/native/builtin_functions_test.go +++ b/src/query/graphite/native/builtin_functions_test.go @@ -3193,6 +3193,47 @@ func TestConsolidateBy(t *testing.T) { require.NotNil(t, err) } +func TestPow(t *testing.T) { + var ( + ctx = common.NewTestContext() + millisPerStep = 10000 + output = []float64{1.0, 4.0, 9.0, 16.0, 25.0} + output2 = []float64{0.0, 4.0, 16.0, 36.0, 64.0} + ) + + defer func() { _ = ctx.Close() }() + + series := ts.NewSeries( + ctx, + "foo", + ctx.StartTime, + common.NewTestSeriesValues(ctx, millisPerStep, []float64{1.0, 2.0, 3.0, 4.0, 5.0}), + ) + results, err := pow(ctx, singlePathSpec{ + Values: []*ts.Series{series}, + }, 2) + require.Nil(t, err) + expected := common.TestSeries{Name: `pow(foo, 2.000000)`, Data: output} + require.Nil(t, err) + common.CompareOutputsAndExpected(t, millisPerStep, ctx.StartTime, + []common.TestSeries{expected}, results.Values) + + series2 := ts.NewSeries( + ctx, + "foo", + ctx.StartTime, + common.NewTestSeriesValues(ctx, millisPerStep, []float64{0.0, 2.0, 4.0, 6.0, 8.0}), + ) + results2, err := pow(ctx, singlePathSpec{ + Values: []*ts.Series{series, series2}, + }, 2) + require.Nil(t, err) + expected2 := common.TestSeries{Name: `pow(foo, 2.000000)`, Data: output2} + require.Nil(t, err) + common.CompareOutputsAndExpected(t, millisPerStep, ctx.StartTime, + []common.TestSeries{expected, expected2}, results2.Values) +} + func TestCumulative(t *testing.T) { ctx := common.NewTestContext() defer ctx.Close() @@ -3528,6 +3569,7 @@ func TestFunctionsRegistered(t *testing.T) { "offset", "offsetToZero", "perSecond", + "pow", "powSeries", "randomWalk", "randomWalkFunction",