Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add promql trigonometric functions #940

Merged
merged 8 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions pkg/integrations/prometheus/promql/metricsSearchHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,30 @@ func convertPqlToMetricsQuery(searchText string, startTime, endTime uint32, myid
mquery.Function = structs.Function{MathFunction: segutils.Deg}
case "rad":
mquery.Function = structs.Function{MathFunction: segutils.Rad}
case "acos":
mquery.Function = structs.Function{MathFunction: segutils.Acos}
case "acosh":
mquery.Function = structs.Function{MathFunction: segutils.Acosh}
case "asin":
mquery.Function = structs.Function{MathFunction: segutils.Asin}
case "asinh":
mquery.Function = structs.Function{MathFunction: segutils.Asinh}
case "atan":
mquery.Function = structs.Function{MathFunction: segutils.Atan}
case "atanh":
mquery.Function = structs.Function{MathFunction: segutils.Atanh}
case "cos":
mquery.Function = structs.Function{MathFunction: segutils.Cos}
case "cosh":
mquery.Function = structs.Function{MathFunction: segutils.Cosh}
case "sin":
mquery.Function = structs.Function{MathFunction: segutils.Sin}
case "sinh":
mquery.Function = structs.Function{MathFunction: segutils.Sinh}
case "tan":
mquery.Function = structs.Function{MathFunction: segutils.Tan}
case "tanh":
mquery.Function = structs.Function{MathFunction: segutils.Tanh}
case "clamp":
if len(expr.Args) != 3 {
return fmt.Errorf("parser.Inspect: Incorrect parameters: %v for the clamp function", expr.Args.String())
Expand Down
84 changes: 84 additions & 0 deletions pkg/integrations/prometheus/promql/metricsconsts.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,90 @@ const metricFunctions = `[
"eg": "rad(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "acos",
"name": "Arccosine (acos)",
"desc": "Calculates the arccosine of all elements in v.",
"eg": "acos(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "acosh",
"name": "Inverse hyperbolic cosine (acosh)",
"desc": "Calculates the inverse hyperbolic cosine of all elements in v.",
"eg": "acosh(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "asin",
"name": "Arcsine (asin)",
"desc": "Calculates the arcsine of all elements in v.",
"eg": "asin(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "asinh",
"name": "Inverse hyperbolic sine (asinh)",
"desc": "Calculates the inverse hyperbolic sine of all elements in v.",
"eg": "asinh(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "atan",
"name": "Arctangent (atan)",
"desc": "Calculates the arctangent of all elements in v.",
"eg": "atan(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "atanh",
"name": "Inverse hyperbolic tangent (atanh)",
"desc": "Calculates the inverse hyperbolic tangent of all elements in v.",
"eg": "atanh(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "cos",
"name": "Cosine (cos)",
"desc": "Calculates the cosine of all elements in v.",
"eg": "cos(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "cosh",
"name": "Hyperbolic cosine (cosh)",
"desc": "Calculates the hyperbolic cosine of all elements in v.",
"eg": "cosh(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "sin",
"name": "Sine (sin)",
"desc": "Calculates the sine of all elements in v.",
"eg": "sin(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "sinh",
"name": "Hyperbolic sine (sinh)",
"desc": "Calculates the hyperbolic sine of all elements in v.",
"eg": "sinh(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "tan",
"name": "Tangent (tan)",
"desc": "Calculates the tangent of all elements in v.",
"eg": "tan(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "tanh",
"name": "Hyperbolic tangent (tanh)",
"desc": "Calculates the hyperbolic tangent of all elements in v.",
"eg": "tanh(avg (system.disk.used))",
"isTimeRangeFunc": false
},
{
"fn": "clamp",
"name": "Clamp",
Expand Down
57 changes: 57 additions & 0 deletions pkg/segment/results/mresults/seriesresult.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,51 @@ func ApplyMathFunction(ts map[uint32]float64, function structs.Function) (map[ui
evaluate(ts, func(val float64) float64 {
return val * math.Pi / 180
})

case segutils.Acos:
err = evaluateWithErr(ts, func(val float64) (float64, error) {
if val < -1 || val > 1 {
return val, fmt.Errorf("evaluateWithErr: acos evaluate values in the range [-1,1]")
}
return math.Acos(val), nil
})
case segutils.Acosh:
err = evaluateWithErr(ts, func(val float64) (float64, error) {
if val < 1 {
return val, fmt.Errorf("evaluateWithErr: acosh evaluate values in the range [1,+Inf]")
}
return math.Acosh(val), nil
})
case segutils.Asin:
err = evaluateWithErr(ts, func(val float64) (float64, error) {
if val < -1 || val > 1 {
return val, fmt.Errorf("evaluateWithErr: asin evaluate values in the range [-1,1]")
}
return math.Asin(val), nil
})
case segutils.Asinh:
evaluate(ts, math.Asinh)
case segutils.Atan:
evaluate(ts, math.Atan)
case segutils.Atanh:
err = evaluateWithErr(ts, func(val float64) (float64, error) {
if val <= -1 || val >= 1 {
return val, fmt.Errorf("evaluateWithErr: atanh evaluate values in the range [-1,1]")
}
return math.Atanh(val), nil
})
case segutils.Cos:
evaluate(ts, math.Cos)
case segutils.Cosh:
evaluate(ts, math.Cosh)
case segutils.Sin:
evaluate(ts, math.Sin)
case segutils.Sinh:
evaluate(ts, math.Sinh)
case segutils.Tan:
evaluate(ts, math.Tan)
case segutils.Tanh:
evaluate(ts, math.Tanh)
case segutils.Clamp:
if len(function.ValueList) != 2 {
return ts, fmt.Errorf("ApplyMathFunction: clamp has incorrect parameters: %v", function.ValueList)
Expand Down Expand Up @@ -783,13 +828,25 @@ func reduceRunningEntries(entries []RunningEntry, fn utils.AggregateFunctions, f
}

type float64Func func(float64) float64
type float64FuncWithErr func(float64) (float64, error)

func evaluate(ts map[uint32]float64, mathFunc float64Func) {
for key, val := range ts {
ts[key] = mathFunc(val)
}
}

func evaluateWithErr(ts map[uint32]float64, mathFunc float64FuncWithErr) error {
for key, val := range ts {
resVal, err := mathFunc(val)
if err != nil {
return err
}
ts[key] = resVal
}
return nil
}

func applyFuncToNonNegativeValues(ts map[uint32]float64, mathFunc float64Func) error {
for key, val := range ts {
if val < 0 {
Expand Down
110 changes: 110 additions & 0 deletions pkg/segment/results/mresults/seriesresult_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/siglens/siglens/pkg/common/dtypeutils"
"github.com/siglens/siglens/pkg/segment/structs"
"github.com/siglens/siglens/pkg/segment/utils"
segutils "github.com/siglens/siglens/pkg/segment/utils"
"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -1391,6 +1392,115 @@ func Test_applyMathFunctionRad(t *testing.T) {
}
}

func Test_applyTrigonometricFunctionCos(t *testing.T) {
runTrigonometricFunctionTest(t, math.Cos, segutils.Cos, false)
}

func Test_applyTrigonometricFunctionCosh(t *testing.T) {
runTrigonometricFunctionTest(t, math.Cosh, segutils.Cosh, false)
}

func Test_applyTrigonometricFunctionSin(t *testing.T) {
runTrigonometricFunctionTest(t, math.Sin, segutils.Sin, false)
}

func Test_applyTrigonometricFunctionSinh(t *testing.T) {
runTrigonometricFunctionTest(t, math.Sinh, segutils.Sinh, false)
}

func Test_applyTrigonometricFunctionTan(t *testing.T) {
runTrigonometricFunctionTest(t, math.Tan, segutils.Tan, false)
}

func Test_applyTrigonometricFunctionTanh(t *testing.T) {
runTrigonometricFunctionTest(t, math.Tanh, segutils.Tanh, false)
}

func Test_applyTrigonometricFunctionAsinh(t *testing.T) {
runTrigonometricFunctionTest(t, math.Asinh, segutils.Asinh, false)
}

func Test_applyTrigonometricFunctionAtan(t *testing.T) {
runTrigonometricFunctionTest(t, math.Atan, segutils.Atan, false)
}

func Test_applyTrigonometricFunctionAcos(t *testing.T) {
runTrigonometricFunctionTest(t, math.Acos, segutils.Acos, true)
}

func Test_applyTrigonometricFunctionAsin(t *testing.T) {
runTrigonometricFunctionTest(t, math.Asin, segutils.Asin, true)
}

func Test_applyTrigonometricFunctionAtanh(t *testing.T) {
runTrigonometricFunctionTest(t, math.Atanh, segutils.Atanh, true)
}

func Test_applyTrigonometricFunctionAcosh(t *testing.T) {
runTrigonometricFunctionTest(t, math.Acosh, segutils.Acosh, true)
}

func runTrigonometricFunctionTest(t *testing.T, mathFunc float64Func, mathFunction utils.MathFunctions, testError bool) {
result := make(map[string]map[uint32]float64)
ts := make(map[uint32]float64)

// Define initial values based on whether we're testing an error case
if mathFunction == utils.Acosh {
ts[1] = 1.255
ts[2] = 6
ts[3] = 2.465
} else if testError {
ts[1] = 0.255
ts[2] = 0.6
ts[3] = -0.2465
} else {
ts[1] = -0.255
ts[2] = 0.6
ts[3] = 11.2465
}

result["metric"] = ts
ans := make(map[uint32]float64)
for key, val := range ts {
ans[key] = mathFunc(val)
}

metricsResults := &MetricsResult{
Results: result,
}

function := structs.Function{MathFunction: mathFunction}
err := metricsResults.ApplyFunctionsToResults(8, function)
assert.Nil(t, err)
for _, timeSeries := range metricsResults.Results {
for key, val := range timeSeries {

expectedVal, exists := ans[key]
if !exists {
t.Errorf("Should not have this key: %v", key)
}

if val != expectedVal {
t.Errorf("Expected value should be %v, but got %v", expectedVal, val)
}
}
}

if testError {
// Modify values to trigger error
ts[3] = -10.2465
err = metricsResults.ApplyFunctionsToResults(8, function)
assert.NotNil(t, err)
} else {
// Add specific test for acosh case where valid input should be > 1
if mathFunction == utils.Acosh {
ts[3] = 0.2465
err = metricsResults.ApplyFunctionsToResults(8, function)
assert.NotNil(t, err)
}
}
}

func Test_applyMathFunctionClamp(t *testing.T) {
result := make(map[string]map[uint32]float64)
ts := make(map[uint32]float64)
Expand Down
12 changes: 12 additions & 0 deletions pkg/segment/utils/segconsts.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,18 @@ const (
Clamp_Max
Clamp_Min
Timestamp
Acos
Acosh
Asin
Asinh
Atan
Atanh
Cos
Cosh
Sin
Sinh
Tan
Tanh
)

type RangeFunctions int
Expand Down
Loading