promql: Add sgn, clamp and last_over_time functions (#8457)

* Add sgn, clamp and last_over_time functions

Signed-off-by: schou <pschou@users.noreply.github.com>
This commit is contained in:
pschou 2021-02-20 10:34:52 -05:00 committed by GitHub
parent d35cf369f2
commit aff3c702ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 6 deletions

View file

@ -73,6 +73,15 @@ For each input time series, `changes(v range-vector)` returns the number of
times its value has changed within the provided time range as an instant
vector.
## `clamp()`
`clamp(v instant-vector, min scalar, max scalar)`
clamps the sample values of all elements in `v` to have a lower limit of `min` and an upper limit of `max`.
Special cases:
- Return an empty vector if `min > max`
- Return `NaN` if `min` or `max` is `NaN`
## `clamp_max()`
`clamp_max(v instant-vector, max scalar)` clamps the sample values of all
@ -370,6 +379,10 @@ Given a single-element input vector, `scalar(v instant-vector)` returns the
sample value of that single element as a scalar. If the input vector does not
have exactly one element, `scalar` will return `NaN`.
## `sgn()`
`sgn(v instant-vector)` returns a vector with all sample values converted to their sign, defined as this: 1 if v is positive, -1 if v is negative and 0 if v is equal to zero.
## `sort()`
`sort(v instant-vector)` returns vector elements sorted by their sample values,
@ -418,6 +431,7 @@ over time and return an instant vector with per-series aggregation results:
* `quantile_over_time(scalar, range-vector)`: the φ-quantile (0 ≤ φ ≤ 1) of the values in the specified interval.
* `stddev_over_time(range-vector)`: the population standard deviation of the values in the specified interval.
* `stdvar_over_time(range-vector)`: the population standard variance of the values in the specified interval.
* `last_over_time(range-vector)`: the most recent point value in specified interval.
Note that all values in the specified interval have the same weight in the
aggregation even if the values are not equally spaced throughout the interval.

View file

@ -1216,11 +1216,16 @@ func (ev *evaluator) eval(expr parser.Expr) (parser.Value, storage.Warnings) {
ev.currentSamples -= len(points)
points = points[:0]
it.Reset(s.Iterator())
metric := selVS.Series[i].Labels()
// The last_over_time function acts like offset; thus, it
// should keep the metric name. For all the other range
// vector functions, the only change needed is to drop the
// metric name in the output.
if e.Func.Name != "last_over_time" {
metric = dropMetricName(metric)
}
ss := Series{
// For all range vector functions, the only change to the
// output labels is dropping the metric name so just do
// it once here.
Metric: dropMetricName(selVS.Series[i].Labels()),
Metric: metric,
Points: getPointSlice(numSteps),
}
inMatrix[0].Metric = selVS.Series[i].Labels()

View file

@ -278,6 +278,23 @@ func funcSortDesc(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel
return Vector(byValueSorter)
}
// === clamp(Vector parser.ValueTypeVector, min, max Scalar) Vector ===
func funcClamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
vec := vals[0].(Vector)
min := vals[1].(Vector)[0].Point.V
max := vals[2].(Vector)[0].Point.V
if max < min {
return enh.Out
}
for _, el := range vec {
enh.Out = append(enh.Out, Sample{
Metric: enh.DropMetricName(el.Metric),
Point: Point{V: math.Max(min, math.Min(max, el.V))},
})
}
return enh.Out
}
// === clamp_max(Vector parser.ValueTypeVector, max Scalar) Vector ===
func funcClampMax(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
vec := vals[0].(Vector)
@ -383,7 +400,16 @@ func funcCountOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNo
})
}
// === floor(Vector parser.ValueTypeVector) Vector ===
// === last_over_time(Matrix parser.ValueTypeMatrix) Vector ===
func funcLastOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
el := vals[0].(Matrix)[0]
return append(enh.Out, Sample{
Metric: el.Metric,
Point: Point{V: el.Points[len(el.Points)-1].V},
})
}
// === max_over_time(Matrix parser.ValueTypeMatrix) Vector ===
func funcMaxOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
@ -537,6 +563,18 @@ func funcLog10(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper
return simpleFunc(vals, enh, math.Log10)
}
// === sgn(Vector parser.ValueTypeVector) Vector ===
func funcSgn(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return simpleFunc(vals, enh, func(v float64) float64 {
if v < 0 {
return -1
} else if v > 0 {
return 1
}
return v
})
}
// === timestamp(Vector parser.ValueTypeVector) Vector ===
func funcTimestamp(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
vec := vals[0].(Vector)
@ -893,6 +931,7 @@ var FunctionCalls = map[string]FunctionCall{
"avg_over_time": funcAvgOverTime,
"ceil": funcCeil,
"changes": funcChanges,
"clamp": funcClamp,
"clamp_max": funcClampMax,
"clamp_min": funcClampMin,
"count_over_time": funcCountOverTime,
@ -914,6 +953,7 @@ var FunctionCalls = map[string]FunctionCall{
"ln": funcLn,
"log10": funcLog10,
"log2": funcLog2,
"last_over_time": funcLastOverTime,
"max_over_time": funcMaxOverTime,
"min_over_time": funcMinOverTime,
"minute": funcMinute,
@ -924,6 +964,7 @@ var FunctionCalls = map[string]FunctionCall{
"resets": funcResets,
"round": funcRound,
"scalar": funcScalar,
"sgn": funcSgn,
"sort": funcSort,
"sort_desc": funcSortDesc,
"sqrt": funcSqrt,

View file

@ -54,6 +54,11 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"clamp": {
Name: "clamp",
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar, ValueTypeScalar},
ReturnType: ValueTypeVector,
},
"clamp_max": {
Name: "clamp_max",
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar},
@ -149,6 +154,11 @@ var Functions = map[string]*Function{
Variadic: -1,
ReturnType: ValueTypeVector,
},
"last_over_time": {
Name: "last_over_time",
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"ln": {
Name: "ln",
ArgTypes: []ValueType{ValueTypeVector},
@ -217,6 +227,11 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeScalar,
},
"sgn": {
Name: "sgn",
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"sort": {
Name: "sort",
ArgTypes: []ValueType{ValueTypeVector},

View file

@ -372,7 +372,7 @@ eval instant at 60m vector(time())
{} 3600
# Tests for clamp_max and clamp_min().
# Tests for clamp_max, clamp_min(), and clamp().
load 5m
test_clamp{src="clamp-a"} -50
test_clamp{src="clamp-b"} 0
@ -388,6 +388,11 @@ eval instant at 0m clamp_min(test_clamp, -25)
{src="clamp-b"} 0
{src="clamp-c"} 100
eval instant at 0m clamp(test_clamp, -25, 75)
{src="clamp-a"} -25
{src="clamp-b"} 0
{src="clamp-c"} 75
eval instant at 0m clamp_max(clamp_min(test_clamp, -20), 70)
{src="clamp-a"} -20
{src="clamp-b"} 0
@ -398,6 +403,36 @@ eval instant at 0m clamp_max((clamp_min(test_clamp, (-20))), (70))
{src="clamp-b"} 0
{src="clamp-c"} 70
eval instant at 0m clamp(test_clamp, 0, NaN)
{src="clamp-a"} NaN
{src="clamp-b"} NaN
{src="clamp-c"} NaN
eval instant at 0m clamp(test_clamp, NaN, 0)
{src="clamp-a"} NaN
{src="clamp-b"} NaN
{src="clamp-c"} NaN
eval instant at 0m clamp(test_clamp, 5, -5)
# Test cases for sgn.
clear
load 5m
test_sgn{src="sgn-a"} -Inf
test_sgn{src="sgn-b"} Inf
test_sgn{src="sgn-c"} NaN
test_sgn{src="sgn-d"} -50
test_sgn{src="sgn-e"} 0
test_sgn{src="sgn-f"} 100
eval instant at 0m sgn(test_sgn)
{src="sgn-a"} -1
{src="sgn-b"} 1
{src="sgn-c"} NaN
{src="sgn-d"} -1
{src="sgn-e"} 0
{src="sgn-f"} 1
# Tests for sort/sort_desc.
clear
@ -745,6 +780,13 @@ eval instant at 1m max_over_time(data[1m])
{type="some_nan3"} 1
{type="only_nan"} NaN
eval instant at 1m last_over_time(data[1m])
data{type="numbers"} 3
data{type="some_nan"} NaN
data{type="some_nan2"} 1
data{type="some_nan3"} 1
data{type="only_nan"} NaN
clear
# Test for absent()