promql: adelta/aincrease/arate functions

This are the same functions as delta/increase/rate without
extrapolation of values at boundaries.

Signed-off-by: Jérôme LOYET <822436+fatpat@users.noreply.github.com>
This commit is contained in:
Jérôme LOYET 2024-01-20 10:37:06 +01:00
parent d23d196db5
commit 6038f723b8
9 changed files with 156 additions and 7 deletions

View file

@ -5,6 +5,7 @@
* [FEATURE] OTLP receiver: Add new option `otlp.promote_resource_attributes`, for any OTel resource attributes that should be promoted to metric labels. #14200
* [ENHANCEMENT] OTLP receiver: Warn when encountering exponential histograms with zero count and non-zero sum. #14706
* [BUGFIX] tsdb/wlog.Watcher.readSegmentForGC: Only count unknown record types against record_decode_failures_total metric. #14042
* [FEATURE] PromQL: Add `adelta`/`aincrease`/`arate` functions: the version without extrapolation of `delta`/`increase`/`rate` functions. #13436
## 2.54.1 / 2024-08-27

View file

@ -76,6 +76,43 @@ absent_over_time(sum(nonexistent{job="myjob"})[1h:])
In the first two examples, `absent_over_time()` tries to be smart about deriving
labels of the 1-element output vector from the input vector.
## `adelta()`
**This function has to be enabled via the [feature flag](../feature_flags/) `--enable-feature=promql-experimental-functions`.**
`adelta(v range-vector)` calculates the difference between the last and the first samples
in the range vector `v`, returning an instant vector with the given deltas and
equivalent labels. It does not extrapolate values at boundaries as `delta()` does.
`adelta` should only be used with gauges.
See `delta()` for more details.
## `aincrease()`
**This function has to be enabled via the [feature flag](../feature_flags/) `--enable-feature=promql-experimental-functions`.**
`aincrease(v range-vector)` calculates the increase in the time series in the range vector.
It does not extrapolate values at boundaries as `increase()` does.
`aincrease` should only be used with counters and native histograms where the
components behave like counters.
See `increase()` for more details.
## `arate()`
**This function has to be enabled via the [feature flag](../feature_flags/) `--enable-feature=promql-experimental-functions`.**
`arate(v range-vector)` calculates the per-second average rate of increase of the
time series in the range vector. It does not extrapolate at boundaries values
as `rate()` does.
`arate` should only be used with counters and native histograms where the
components behave like counters.
See `rate()` for more details.
## `ceil()`
`ceil(v instant-vector)` rounds the sample values of all elements in `v` up to
@ -679,7 +716,7 @@ over time and return an instant vector with per-series aggregation results:
If the [feature flag](../feature_flags.md#experimental-promql-functions)
`--enable-feature=promql-experimental-functions` is set, the following
additional functions are available:
* `adelta`, `aincrease` and `arate`: the acurate version of `delta`, `increase` and `rate` functions without extrapolation of data at boundaries.
* `mad_over_time(range-vector)`: the median absolute deviation of all points in the specified interval.
Note that all values in the specified interval have the same weight in the

View file

@ -66,9 +66,9 @@ func funcTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper)
// extrapolatedRate is a utility function for rate/increase/delta.
// It calculates the rate (allowing for counter resets if isCounter is true),
// extrapolates if the first/last sample is close to the boundary, and returns
// extrapolates if extrapolate is true and the first/last sample is close to the boundary, and returns
// the result as either per-second (if isRate is true) or overall.
func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper, isCounter, isRate bool) (Vector, annotations.Annotations) {
func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper, isCounter, isRate, extrapolate bool) (Vector, annotations.Annotations) {
ms := args[0].(*parser.MatrixSelector)
vs := ms.VectorSelector.(*parser.VectorSelector)
var (
@ -123,6 +123,13 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
return enh.Out, annos
}
if !extrapolate {
if isRate {
resultFloat /= ms.Range.Seconds()
}
return append(enh.Out, Sample{F: resultFloat, H: resultHistogram}), annos
}
// Duration between first/last samples and boundary of range.
durationToStart := float64(firstT-rangeStart) / 1000
durationToEnd := float64(rangeEnd-lastT) / 1000
@ -262,17 +269,17 @@ func histogramRate(points []HPoint, isCounter bool, metricName string, pos posra
// === delta(Matrix parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcDelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return extrapolatedRate(vals, args, enh, false, false)
return extrapolatedRate(vals, args, enh, false, false, true)
}
// === rate(node parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return extrapolatedRate(vals, args, enh, true, true)
return extrapolatedRate(vals, args, enh, true, true, true)
}
// === increase(node parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcIncrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return extrapolatedRate(vals, args, enh, true, false)
return extrapolatedRate(vals, args, enh, true, false, true)
}
// === irate(node parser.ValueTypeMatrix) (Vector, Annotations) ===
@ -285,6 +292,21 @@ func funcIdelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelpe
return instantValue(vals, enh.Out, false)
}
// === adelta(Matrix parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcAdelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return extrapolatedRate(vals, args, enh, false, false, false)
}
// === arate(node parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcArate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return extrapolatedRate(vals, args, enh, true, true, false)
}
// === aincrease(node parser.ValueTypeMatrix) (Vector, Annotations) ===
func funcAincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return extrapolatedRate(vals, args, enh, true, false, false)
}
func instantValue(vals []parser.Value, out Vector, isRate bool) (Vector, annotations.Annotations) {
samples := vals[0].(Matrix)[0]
// No sense in trying to compute a rate without at least two points. Drop
@ -1653,6 +1675,9 @@ var FunctionCalls = map[string]FunctionCall{
"absent_over_time": funcAbsentOverTime,
"acos": funcAcos,
"acosh": funcAcosh,
"adelta": funcAdelta,
"aincrease": funcAincrease,
"arate": funcArate,
"asin": funcAsin,
"asinh": funcAsinh,
"atan": funcAtan,

View file

@ -53,6 +53,24 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeVector},
ReturnType: ValueTypeVector,
},
"adelta": {
Name: "adelta",
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
Experimental: true,
},
"aincrease": {
Name: "aincrease",
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
Experimental: true,
},
"arate": {
Name: "arate",
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
Experimental: true,
},
"asin": {
Name: "asin",
ArgTypes: []ValueType{ValueTypeVector},

View file

@ -101,6 +101,29 @@ eval instant at 50m increase(http_requests[100m])
clear
# Tests for increase()/aincrease().
load 15s
http_requests{path="/foo"} 0x40 0+1x20 20x40
http_requests{path="/bar"} 0x40 0+1x10 0+1x10 10x40
eval instant at 21m5s increase(http_requests[15m])
{path="/foo"} 20.112994350282488
{path="/bar"} 20.112994350282488
eval instant at 21m5s increase(http_requests[100m])
{path="/foo"} 20.07936507936508
{path="/bar"} 20.07936507936508
eval instant at 21m5s aincrease(http_requests[15m])
{path="/foo"} 20
{path="/bar"} 20
eval instant at 21m5s aincrease(http_requests[100m])
{path="/foo"} 20
{path="/bar"} 20
clear
# Test for increase() with counter reset.
# When the counter is reset, it always starts at 0.
# So the sequence 3 2 (decreasing counter = reset) is interpreted the same as 3 0 1 2.

View file

@ -77,6 +77,24 @@ export const functionIdentifierTerms = [
info: 'Calculate the inverse hyperbolic cosine, in radians, for input series',
type: 'function',
},
{
label: 'adelta',
detail: 'function',
info: 'Calculate the non extrapolated difference between beginning and end of a range vector (for gauges)',
type: 'function',
},
{
label: 'aincrease',
detail: 'function',
info: 'Calculate the non extrapolated increase in value over a range of time (for counters)',
type: 'function',
},
{
label: 'arate',
detail: 'function',
info: 'Calculate the non extrapolated per-second increase over a range vector (for counters)',
type: 'function',
},
{
label: 'asin',
detail: 'function',

View file

@ -17,6 +17,9 @@ import {
AbsentOverTime,
Acos,
Acosh,
Adelta,
Aincrease,
Arate,
Asin,
Asinh,
Atan,
@ -138,6 +141,24 @@ const promqlFunctions: { [key: number]: PromQLFunction } = {
variadic: 0,
returnType: ValueType.vector,
},
[Adelta]: {
name: 'adelta',
argTypes: [ValueType.matrix],
variadic: 0,
returnType: ValueType.vector,
},
[Aincrease]: {
name: 'aincrease',
argTypes: [ValueType.matrix],
variadic: 0,
returnType: ValueType.vector,
},
[Arate]: {
name: 'arate',
argTypes: [ValueType.matrix],
variadic: 0,
returnType: ValueType.vector,
},
[Asin]: {
name: 'asin',
argTypes: [ValueType.vector],

View file

@ -20,7 +20,7 @@ export const promQLHighLight = styleTags({
NumberDurationLiteral: tags.number,
NumberDurationLiteralInDurationContext: tags.number,
Identifier: tags.variableName,
'Abs Absent AbsentOverTime Acos Acosh Asin Asinh Atan Atanh AvgOverTime Ceil Changes Clamp ClampMax ClampMin Cos Cosh CountOverTime DaysInMonth DayOfMonth DayOfWeek DayOfYear Deg Delta Deriv Exp Floor HistogramAvg HistogramCount HistogramFraction HistogramQuantile HistogramSum HoltWinters Hour Idelta Increase Irate LabelReplace LabelJoin LastOverTime Ln Log10 Log2 MaxOverTime MinOverTime Minute Month Pi PredictLinear PresentOverTime QuantileOverTime Rad Rate Resets Round Scalar Sgn Sin Sinh Sort SortDesc SortByLabel SortByLabelDesc Sqrt StddevOverTime StdvarOverTime SumOverTime Tan Tanh Time Timestamp Vector Year':
'Abs Absent AbsentOverTime Acos Acosh Adelta Aincrease Arate AAsin Asinh Atan Atanh AvgOverTime Ceil Changes Clamp ClampMax ClampMin Cos Cosh CountOverTime DaysInMonth DayOfMonth DayOfWeek DayOfYear Deg Delta Deriv Exp Floor HistogramAvg HistogramCount HistogramFraction HistogramQuantile HistogramSum HoltWinters Hour Idelta Increase Irate LabelReplace LabelJoin LastOverTime Ln Log10 Log2 MaxOverTime MinOverTime Minute Month Pi PredictLinear PresentOverTime QuantileOverTime Rad Rate Resets Round Scalar Sgn Sin Sinh Sort SortDesc SortByLabel SortByLabelDesc Sqrt StddevOverTime StdvarOverTime SumOverTime Tan Tanh Time Timestamp Vector Year':
tags.function(tags.variableName),
'Avg Bottomk Count Count_values Group LimitK LimitRatio Max Min Quantile Stddev Stdvar Sum Topk': tags.operatorKeyword,
'By Without Bool On Ignoring GroupLeft GroupRight Offset Start End': tags.modifier,

View file

@ -112,6 +112,9 @@ FunctionIdentifier {
Abs |
Acos |
Acosh |
Adelta |
Aincrease |
Arate |
Asin |
Asinh |
Atan |
@ -359,6 +362,9 @@ NumberDurationLiteralInDurationContext {
AbsentOverTime { condFn<"absent_over_time"> }
Acos { condFn<"acos"> }
Acosh { condFn<"acosh"> }
Adelta { condFn<"adelta"> }
Aincrease { condFn<"aincrease"> }
Arate { condFn<"arate"> }
Asin { condFn<"asin"> }
Asinh { condFn<"asinh">}
Atan { condFn<"atan"> }