PromQL: Rename greatest, least, to max_of, min_of.

Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com>
This commit is contained in:
Julien Pivotto 2026-05-19 11:41:16 +02:00
parent 7af21f195c
commit a74f0b2e7a
18 changed files with 165 additions and 170 deletions

View file

@ -77,7 +77,6 @@
"exp": true,
"first_over_time": false,
"floor": true,
"greatest": false,
"histogram_avg": true,
"histogram_count": true,
"histogram_fraction": true,
@ -94,12 +93,13 @@
"label_join": true,
"label_replace": true,
"last_over_time": true,
"least": false,
"ln": true,
"log10": true,
"log2": true,
"mad_over_time": false,
"max_of": false,
"max_over_time": true,
"min_of": false,
"min_over_time": true,
"minute": true,
"month": true,

View file

@ -223,10 +223,10 @@ For a **range query**, it resolves to the full range of the query (end time - st
For an **instant query**, it resolves to `0s`.
This is particularly useful in combination with `@end()` to look back over the entire query range, e.g., `max_over_time(metric[range()] @ end())`.
`least(<duration>, <duration>)` and `greatest(<duration>, <duration>)` select between two duration expressions.
`least` returns the smaller of the two, which is useful for capping a duration at a maximum value.
`greatest` returns the larger of the two, which is useful for enforcing a minimum value.
For example, `greatest(step(), 5s)` ensures the duration is never shorter than `5s`, while `least(range(), 1h)` caps the duration at `1h`.
`min_of(<duration>, <duration>)` and `max_of(<duration>, <duration>)` select between two duration expressions.
`min_of` returns the smaller of the two, which is useful for capping a duration at a maximum value.
`max_of` returns the larger of the two, which is useful for enforcing a minimum value.
For example, `max_of(step(), 5s)` ensures the duration is never shorter than `5s`, while `min_of(range(), 1h)` caps the duration at `1h`.
**Note**: Duration expressions are not supported in the @ timestamp operator.
@ -248,8 +248,8 @@ Examples of equivalent durations:
* `4h % 3h` is equivalent to `1h` or `3600s`
* `(2 ^ 3) * 1m` is equivalent to `8m` or `480s`
* `step() + 1` is equivalent to the query step width increased by 1s.
* `greatest(step(), 5s)` is equivalent to the larger of the query step width and `5s`.
* `least(2 * step() + 5s, 5m)` is equivalent to the smaller of twice the query step increased by `5s` and `5m`.
* `max_of(step(), 5s)` is equivalent to the larger of the query step width and `5s`.
* `min_of(2 * step() + 5s, 5m)` is equivalent to the smaller of twice the query step increased by `5s` and `5m`.
## OTLP Native Delta Support

View file

@ -693,22 +693,22 @@ This second example has the same effect than the first example, and illustrates
label_replace(up{job="api-server",service="a:c"}, "foo", "$name", "service", "(?P<name>.*):(?P<version>.*)")
```
## `greatest()`
## `max_of()`
**This function has to be enabled via the [feature
flag](../feature_flags.md#experimental-promql-functions)
`--enable-feature=promql-experimental-functions`.**
`greatest(a scalar, b scalar)` returns the larger of the two scalar values `a`
`max_of(a scalar, b scalar)` returns the larger of the two scalar values `a`
and `b`.
## `least()`
## `min_of()`
**This function has to be enabled via the [feature
flag](../feature_flags.md#experimental-promql-functions)
`--enable-feature=promql-experimental-functions`.**
`least(a scalar, b scalar)` returns the smaller of the two scalar values `a`
`min_of(a scalar, b scalar)` returns the smaller of the two scalar values `a`
and `b`.
## `ln()`

View file

@ -135,9 +135,9 @@ func (v *durationVisitor) evaluateDurationExpr(expr parser.Expr) (float64, error
return float64(v.step.Seconds()), nil
case parser.RANGE:
return float64(v.queryRange.Seconds()), nil
case parser.LEAST:
case parser.MIN_OF:
return math.Min(lhs, rhs), nil
case parser.GREATEST:
case parser.MAX_OF:
return math.Max(lhs, rhs), nil
case parser.ADD:
if n.LHS == nil {

View file

@ -228,7 +228,7 @@ func TestCalculateDuration(t *testing.T) {
expected: 150 * time.Second,
},
{
name: "greatest of step and range",
name: "max_of of step and range",
expr: &parser.DurationExpr{
LHS: &parser.DurationExpr{
Op: parser.STEP,
@ -236,7 +236,7 @@ func TestCalculateDuration(t *testing.T) {
RHS: &parser.DurationExpr{
Op: parser.RANGE,
},
Op: parser.GREATEST,
Op: parser.MAX_OF,
},
expected: 5 * time.Minute,
},

View file

@ -1432,13 +1432,13 @@ func funcSqrt(vectorVals []Vector, _ Matrix, _ parser.Expressions, enh *EvalNode
return simpleFloatFunc(vectorVals, enh, math.Sqrt), nil
}
// === greatest(a, b parser.ValueTypeScalar) Scalar ===
func funcGreatest(vectorVals []Vector, _ Matrix, _ parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
// === max_of(a, b parser.ValueTypeScalar) Scalar ===
func funcMaxOf(vectorVals []Vector, _ Matrix, _ parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return append(enh.Out, Sample{F: math.Max(vectorVals[0][0].F, vectorVals[1][0].F)}), nil
}
// === least(a, b parser.ValueTypeScalar) Scalar ===
func funcLeast(vectorVals []Vector, _ Matrix, _ parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
// === min_of(a, b parser.ValueTypeScalar) Scalar ===
func funcMinOf(vectorVals []Vector, _ Matrix, _ parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
return append(enh.Out, Sample{F: math.Min(vectorVals[0][0].F, vectorVals[1][0].F)}), nil
}
@ -2299,10 +2299,10 @@ var FunctionCalls = map[string]FunctionCall{
"increase": funcIncrease,
"info": nil,
"irate": funcIrate,
"greatest": funcGreatest,
"max_of": funcMaxOf,
"label_replace": nil, // evalLabelReplace not called via this map.
"label_join": nil, // evalLabelJoin not called via this map.
"least": funcLeast,
"min_of": funcMinOf,
"ln": funcLn,
"log10": funcLog10,
"log2": funcLog2,

View file

@ -263,8 +263,8 @@ var Functions = map[string]*Function{
Variadic: -1,
ReturnType: ValueTypeVector,
},
"greatest": {
Name: "greatest",
"max_of": {
Name: "max_of",
ArgTypes: []ValueType{ValueTypeScalar, ValueTypeScalar},
ReturnType: ValueTypeScalar,
Experimental: true,
@ -274,8 +274,8 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"least": {
Name: "least",
"min_of": {
Name: "min_of",
ArgTypes: []ValueType{ValueTypeScalar, ValueTypeScalar},
ReturnType: ValueTypeScalar,
Experimental: true,

View file

@ -159,8 +159,8 @@ START
END
STEP
RANGE
GREATEST
LEAST
MAX_OF
MIN_OF
%token preprocessorEnd
// Counter reset hints.
@ -185,7 +185,7 @@ START_METRIC_SELECTOR
// Type definitions for grammar rules.
%type <matchers> label_match_list
%type <matcher> label_matcher
%type <item> aggregate_op grouping_label match_op maybe_label metric_identifier unary_op at_modifier_preprocessors string_identifier counter_reset_hint greatest_least
%type <item> aggregate_op grouping_label match_op maybe_label metric_identifier unary_op at_modifier_preprocessors string_identifier counter_reset_hint max_of_min_of
%type <labels> label_set metric
%type <lblList> label_set_list
%type <label> label_set_item
@ -528,7 +528,7 @@ function_call : IDENTIFIER function_call_body
},
}
}
| greatest_least function_call_body
| max_of_min_of function_call_body
{
fn, exist := getFunction($1.Val, yylex.(*parser).functions)
if !exist{
@ -834,7 +834,7 @@ metric : metric_identifier label_set
;
metric_identifier: AVG | BOTTOMK | BY | COUNT | COUNT_VALUES | FILL | FILL_LEFT | FILL_RIGHT | GROUP | IDENTIFIER | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | QUANTILE | STDDEV | STDVAR | SUM | TOPK | WITHOUT | START | END | LIMITK | LIMIT_RATIO | STEP | RANGE | ANCHORED | SMOOTHED | GREATEST | LEAST;
metric_identifier: AVG | BOTTOMK | BY | COUNT | COUNT_VALUES | FILL | FILL_LEFT | FILL_RIGHT | GROUP | IDENTIFIER | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | QUANTILE | STDDEV | STDVAR | SUM | TOPK | WITHOUT | START | END | LIMITK | LIMIT_RATIO | STEP | RANGE | ANCHORED | SMOOTHED | MAX_OF | MIN_OF;
label_set : LEFT_BRACE label_set_list RIGHT_BRACE
{ $$ = labels.New($2...) }
@ -1092,7 +1092,7 @@ counter_reset_hint : UNKNOWN_COUNTER_RESET | COUNTER_RESET | NOT_COUNTER_RESET |
aggregate_op : AVG | BOTTOMK | COUNT | COUNT_VALUES | GROUP | MAX | MIN | QUANTILE | STDDEV | STDVAR | SUM | TOPK | LIMITK | LIMIT_RATIO;
// Inside of grouping options label names can be recognized as keywords by the lexer. This is a list of keywords that could also be a label name.
maybe_label : AVG | BOOL | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | GROUP_LEFT | GROUP_RIGHT | FILL | FILL_LEFT | FILL_RIGHT | IDENTIFIER | IGNORING | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | ON | QUANTILE | STDDEV | STDVAR | SUM | TOPK | START | END | ATAN2 | LIMITK | LIMIT_RATIO | STEP | RANGE | ANCHORED | SMOOTHED | GREATEST | LEAST;
maybe_label : AVG | BOOL | BOTTOMK | BY | COUNT | COUNT_VALUES | GROUP | GROUP_LEFT | GROUP_RIGHT | FILL | FILL_LEFT | FILL_RIGHT | IDENTIFIER | IGNORING | LAND | LOR | LUNLESS | MAX | METRIC_IDENTIFIER | MIN | OFFSET | ON | QUANTILE | STDDEV | STDVAR | SUM | TOPK | START | END | ATAN2 | LIMITK | LIMIT_RATIO | STEP | RANGE | ANCHORED | SMOOTHED | MAX_OF | MIN_OF;
unary_op : ADD | SUB;
@ -1267,7 +1267,7 @@ offset_duration_expr : number_duration_literal
yylex.(*parser).experimentalDurationExpr(de)
$$ = de
}
| greatest_least LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN
| max_of_min_of LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN
{
de := &DurationExpr{
Op: $1.Typ,
@ -1279,7 +1279,7 @@ offset_duration_expr : number_duration_literal
yylex.(*parser).experimentalDurationExpr(de)
$$ = de
}
| unary_op greatest_least LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN
| unary_op max_of_min_of LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN
{
de := &DurationExpr{
Op: $1.Typ,
@ -1313,7 +1313,7 @@ offset_duration_expr : number_duration_literal
| duration_expr
;
greatest_least: GREATEST | LEAST ;
max_of_min_of: MAX_OF | MIN_OF ;
duration_expr : number_duration_literal
{
@ -1417,7 +1417,7 @@ duration_expr : number_duration_literal
yylex.(*parser).experimentalDurationExpr(de)
$$ = de
}
| greatest_least LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN
| max_of_min_of LEFT_PAREN duration_expr COMMA duration_expr RIGHT_PAREN
{
de := &DurationExpr{
Op: $1.Typ,

View file

@ -130,8 +130,8 @@ const START = 57437
const END = 57438
const STEP = 57439
const RANGE = 57440
const GREATEST = 57441
const LEAST = 57442
const MAX_OF = 57441
const MIN_OF = 57442
const preprocessorEnd = 57443
const counterResetHintsStart = 57444
const UNKNOWN_COUNTER_RESET = 57445
@ -245,8 +245,8 @@ var yyToknames = [...]string{
"END",
"STEP",
"RANGE",
"GREATEST",
"LEAST",
"MAX_OF",
"MIN_OF",
"preprocessorEnd",
"counterResetHintsStart",
"UNKNOWN_COUNTER_RESET",

View file

@ -143,12 +143,12 @@ var key = map[string]ItemType{
"bool": BOOL,
// Preprocessors.
"start": START,
"end": END,
"step": STEP,
"range": RANGE,
"greatest": GREATEST,
"least": LEAST,
"start": START,
"end": END,
"step": STEP,
"range": RANGE,
"max_of": MAX_OF,
"min_of": MIN_OF,
}
var histogramDesc = map[string]ItemType{
@ -939,10 +939,10 @@ func lexNumber(l *Lexer) stateFn {
// durationKeywordTokens maps lowercase duration keyword names to their token types.
var durationKeywordTokens = map[string]ItemType{
"step": STEP,
"range": RANGE,
"greatest": GREATEST,
"least": LEAST,
"step": STEP,
"range": RANGE,
"max_of": MAX_OF,
"min_of": MIN_OF,
}
// durationKeywordStartChars is the set of lowercase runes that can start a duration keyword,

View file

@ -4610,7 +4610,7 @@ var testExpr = []struct {
},
},
{
input: `foo[greatest(step(),5s)]`,
input: `foo[max_of(step(),5s)]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
@ -4620,83 +4620,83 @@ var testExpr = []struct {
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: GREATEST,
Op: MAX_OF,
LHS: &DurationExpr{
Op: STEP,
StartPos: 13,
EndPos: 19,
StartPos: 11,
EndPos: 17,
},
RHS: &NumberLiteral{
Val: 5,
Duration: true,
PosRange: posrange.PositionRange{Start: 20, End: 22},
PosRange: posrange.PositionRange{Start: 18, End: 20},
},
StartPos: 4,
EndPos: 23,
EndPos: 21,
},
EndPos: 24,
EndPos: 22,
},
},
{
input: `foo offset greatest(step(),5s)`,
input: `foo offset max_of(step(),5s)`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 30},
PosRange: posrange.PositionRange{Start: 0, End: 28},
OriginalOffsetExpr: &DurationExpr{
Op: GREATEST,
Op: MAX_OF,
LHS: &DurationExpr{
Op: STEP,
StartPos: 20,
EndPos: 26,
StartPos: 18,
EndPos: 24,
},
RHS: &NumberLiteral{
Val: 5,
Duration: true,
PosRange: posrange.PositionRange{Start: 27, End: 29},
PosRange: posrange.PositionRange{Start: 25, End: 27},
},
StartPos: 11,
EndPos: 30,
EndPos: 28,
},
},
},
{
input: `foo offset -least(5s,step()+8s)`,
input: `foo offset -min_of(5s,step()+8s)`,
expected: &VectorSelector{
Name: "foo",
LabelMatchers: []*labels.Matcher{
MustLabelMatcher(labels.MatchEqual, model.MetricNameLabel, "foo"),
},
PosRange: posrange.PositionRange{Start: 0, End: 31},
PosRange: posrange.PositionRange{Start: 0, End: 32},
OriginalOffsetExpr: &DurationExpr{
Op: SUB,
RHS: &DurationExpr{
Op: LEAST,
Op: MIN_OF,
LHS: &NumberLiteral{
Val: 5,
Duration: true,
PosRange: posrange.PositionRange{Start: 18, End: 20},
PosRange: posrange.PositionRange{Start: 19, End: 21},
},
RHS: &DurationExpr{
Op: ADD,
LHS: &DurationExpr{
Op: STEP,
StartPos: 21,
EndPos: 27,
StartPos: 22,
EndPos: 28,
},
RHS: &NumberLiteral{
Val: 8,
Duration: true,
PosRange: posrange.PositionRange{Start: 28, End: 30},
PosRange: posrange.PositionRange{Start: 29, End: 31},
},
},
StartPos: 12,
EndPos: 30,
EndPos: 31,
},
StartPos: 11,
EndPos: 30,
EndPos: 31,
},
},
},
@ -4793,7 +4793,7 @@ var testExpr = []struct {
},
},
{
input: `foo[greatest(range(),5s)]`,
input: `foo[max_of(range(),5s)]`,
expected: &MatrixSelector{
VectorSelector: &VectorSelector{
Name: "foo",
@ -4803,21 +4803,21 @@ var testExpr = []struct {
PosRange: posrange.PositionRange{Start: 0, End: 3},
},
RangeExpr: &DurationExpr{
Op: GREATEST,
Op: MAX_OF,
LHS: &DurationExpr{
Op: RANGE,
StartPos: 13,
EndPos: 20,
StartPos: 11,
EndPos: 18,
},
RHS: &NumberLiteral{
Val: 5,
Duration: true,
PosRange: posrange.PositionRange{Start: 21, End: 23},
PosRange: posrange.PositionRange{Start: 19, End: 21},
},
StartPos: 4,
EndPos: 24,
EndPos: 22,
},
EndPos: 25,
EndPos: 23,
},
},
{

View file

@ -205,14 +205,14 @@ func (node *DurationExpr) writeTo(b *bytes.Buffer) {
b.WriteString("step()")
case node.Op == RANGE:
b.WriteString("range()")
case node.Op == LEAST:
b.WriteString("least(")
case node.Op == MIN_OF:
b.WriteString("min_of(")
b.WriteString(node.LHS.String())
b.WriteString(", ")
b.WriteString(node.RHS.String())
b.WriteByte(')')
case node.Op == GREATEST:
b.WriteString("greatest(")
case node.Op == MAX_OF:
b.WriteString("max_of(")
b.WriteString(node.LHS.String())
b.WriteString(", ")
b.WriteString(node.RHS.String())

View file

@ -270,22 +270,22 @@ func TestExprString(t *testing.T) {
out: "foo offset (5 * 2)",
},
{
in: "foo offset +least(10s, 20s)",
out: "foo offset least(10s, 20s)",
in: "foo offset +min_of(10s, 20s)",
out: "foo offset min_of(10s, 20s)",
},
{
in: "foo offset -least(10s, 20s)",
in: "foo offset -min_of(10s, 20s)",
},
{
in: "foo offset -least(10s, +greatest(step() ^ 2, 2))",
out: "foo offset -least(10s, greatest(step() ^ 2, 2))",
in: "foo offset -min_of(10s, +max_of(step() ^ 2, 2))",
out: "foo offset -min_of(10s, max_of(step() ^ 2, 2))",
},
{
in: "foo[200-least(-step()^+step(),1)]",
out: "foo[200 - least(-step() ^ step(), 1)]",
in: "foo[200-min_of(-step()^+step(),1)]",
out: "foo[200 - min_of(-step() ^ step(), 1)]",
},
{
in: "foo[200 - least(step() + 10s, -greatest(step() ^ 2, 3))]",
in: "foo[200 - min_of(step() + 10s, -max_of(step() ^ 2, 3))]",
},
{
in: "foo[range()]",
@ -300,7 +300,7 @@ func TestExprString(t *testing.T) {
in: "foo offset -range()",
},
{
in: "foo[greatest(range(), 5s)]",
in: "foo[max_of(range(), 5s)]",
},
{
in: `predict_linear(foo[1h], 3000)`,

View file

@ -167,13 +167,13 @@ eval range from 50s to 60s step 10s count_over_time(metric1_total[1+(STep()-5)*2
eval range from 50s to 60s step 5s count_over_time(metric1_total[step()+1])
{} 6 6 6
eval range from 50s to 60s step 5s count_over_time(metric1_total[least(step()+1,1h)])
eval range from 50s to 60s step 5s count_over_time(metric1_total[min_of(step()+1,1h)])
{} 6 6 6
eval range from 50s to 60s step 5s count_over_time(metric1_total[greatest(least(step()+1,1h),1ms)])
eval range from 50s to 60s step 5s count_over_time(metric1_total[max_of(min_of(step()+1,1h),1ms)])
{} 6 6 6
eval range from 50s to 60s step 5s count_over_time(metric1_total[((greatest(least((step()+1),((1h))),1ms)))])
eval range from 50s to 60s step 5s count_over_time(metric1_total[((max_of(min_of((step()+1),((1h))),1ms)))])
{} 6 6 6
eval range from 50s to 60s step 5s metric1_total offset STEP()
@ -200,31 +200,31 @@ eval range from 50s to 60s step 5s metric1_total offset (STEP()/10)
eval range from 50s to 60s step 5s metric1_total offset (step())
metric1_total{} 45 50 55
eval range from 50s to 60s step 5s metric1_total offset least(step(), 1s)
eval range from 50s to 60s step 5s metric1_total offset min_of(step(), 1s)
metric1_total{} 49 54 59
eval range from 50s to 60s step 5s metric1_total offset least(step(), 1s)+8000
eval range from 50s to 60s step 5s metric1_total offset min_of(step(), 1s)+8000
{} 8049 8054 8059
eval range from 50s to 60s step 5s metric1_total offset -least(step(), 1s)+8000
eval range from 50s to 60s step 5s metric1_total offset -min_of(step(), 1s)+8000
{} 8051 8056 8061
eval range from 50s to 60s step 5s metric1_total offset -(least(step(), 1s))+8000
eval range from 50s to 60s step 5s metric1_total offset -(min_of(step(), 1s))+8000
{} 8051 8056 8061
eval range from 50s to 60s step 5s metric1_total offset -least(step(), 1s)^0
eval range from 50s to 60s step 5s metric1_total offset -min_of(step(), 1s)^0
{} 1 1 1
eval range from 50s to 60s step 5s metric1_total offset +least(step(), 1s)^0
eval range from 50s to 60s step 5s metric1_total offset +min_of(step(), 1s)^0
{} 1 1 1
eval range from 50s to 60s step 5s metric1_total offset least(step(), 1s)^0
eval range from 50s to 60s step 5s metric1_total offset min_of(step(), 1s)^0
{} 1 1 1
eval range from 50s to 60s step 5s metric1_total offset greatest(3s,least(step(), 1s))+8000
eval range from 50s to 60s step 5s metric1_total offset max_of(3s,min_of(step(), 1s))+8000
{} 8047 8052 8057
eval range from 50s to 60s step 5s metric1_total offset -(least(step(), 2s)-5)+8000
eval range from 50s to 60s step 5s metric1_total offset -(min_of(step(), 2s)-5)+8000
{} 8047 8052 8057
# Test range() function - resolves to query range (end - start).
@ -238,7 +238,7 @@ eval range from 50s to 60s step 5s count_over_time(metric1_total[range()])
eval range from 50s to 60s step 5s metric1_total offset range()
metric1_total{} 40 45 50
eval range from 50s to 60s step 5s metric1_total offset least(range(), 8s)
eval range from 50s to 60s step 5s metric1_total offset min_of(range(), 8s)
metric1_total{} 42 47 52
clear

View file

@ -2188,54 +2188,54 @@ eval range from 0s to 9s step 3s metric_for_at @ start()
eval range from 1s to 9s step 2s metric_for_at @ end()
{__name__="metric_for_at"} 10 10 10 10 10
# Tests for least() and greatest() scalar functions.
# Tests for min_of() and max_of() scalar functions.
# least(a, b) returns math.Min(a, b).
# greatest(a, b) returns math.Max(a, b).
# min_of(a, b) returns math.Min(a, b).
# max_of(a, b) returns math.Max(a, b).
# Basic literal comparisons.
eval instant at 0s least(3, 5)
eval instant at 0s min_of(3, 5)
3
eval instant at 0s least(5, 3)
eval instant at 0s min_of(5, 3)
3
eval instant at 0s greatest(3, 5)
eval instant at 0s max_of(3, 5)
5
eval instant at 0s greatest(5, 3)
eval instant at 0s max_of(5, 3)
5
# Equal values.
eval instant at 0s least(4, 4)
eval instant at 0s min_of(4, 4)
4
eval instant at 0s greatest(4, 4)
eval instant at 0s max_of(4, 4)
4
# Negative values.
eval instant at 0s least(-2, -5)
eval instant at 0s min_of(-2, -5)
-5
eval instant at 0s greatest(-2, -5)
eval instant at 0s max_of(-2, -5)
-2
# Zero and positive.
eval instant at 0s least(0, 1)
eval instant at 0s min_of(0, 1)
0
eval instant at 0s greatest(0, 1)
eval instant at 0s max_of(0, 1)
1
# NaN propagation: any NaN input yields NaN.
eval instant at 0s least(NaN, 3)
eval instant at 0s min_of(NaN, 3)
NaN
eval instant at 0s least(3, NaN)
eval instant at 0s min_of(3, NaN)
NaN
eval instant at 0s greatest(NaN, 3)
eval instant at 0s max_of(NaN, 3)
NaN
eval instant at 0s greatest(3, NaN)
eval instant at 0s max_of(3, NaN)
NaN

View file

@ -75,11 +75,11 @@ func TestTranslateASTDurationExpressions(t *testing.T) {
},
{
name: "complex matrix selector range expression",
query: `foo[greatest(step(),5m+3m) ]`,
query: `foo[max_of(step(),5m+3m) ]`,
wantType: "matrixSelector",
wantFields: map[string]any{
"range": int64(0),
"rangeExpr": durationExpr("greatest",
"rangeExpr": durationExpr("max_of",
durationExpr("step", nil, nil, false),
durationExpr("+", durationNumber("300", true), durationNumber("180", true), false),
false,
@ -87,13 +87,13 @@ func TestTranslateASTDurationExpressions(t *testing.T) {
},
},
{
name: "nested least and greatest matrix selector range expression",
query: `foo[least(greatest(step(),5m+3m),10m-2m)]`,
name: "nested min_of and max_of matrix selector range expression",
query: `foo[min_of(max_of(step(),5m+3m),10m-2m)]`,
wantType: "matrixSelector",
wantFields: map[string]any{
"range": int64(0),
"rangeExpr": durationExpr("least",
durationExpr("greatest",
"rangeExpr": durationExpr("min_of",
durationExpr("max_of",
durationExpr("step", nil, nil, false),
durationExpr("+", durationNumber("300", true), durationNumber("180", true), false),
false,
@ -149,12 +149,12 @@ func TestTranslateASTDurationExpressions(t *testing.T) {
},
{
name: "vector selector offset expression",
query: `foo offset -least(5s,step()+8s)`,
query: `foo offset -min_of(5s,step()+8s)`,
wantType: "vectorSelector",
wantFields: map[string]any{
"offset": int64(0),
"offsetExpr": durationExpr("-", nil,
durationExpr("least",
durationExpr("min_of",
durationNumber("5", true),
durationExpr("+", durationExpr("step", nil, nil, false), durationNumber("8", true), false),
false,

View file

@ -1269,22 +1269,6 @@ const funcDocs: Record<string, React.ReactNode> = {
</ul>
</>
),
greatest: (
<>
<p>
<strong>
This function has to be enabled via the{" "}
<a href="../feature_flags.md#experimental-promql-functions">feature flag</a>
<code>--enable-feature=promql-experimental-functions</code>.
</strong>
</p>
<p>
<code>greatest(a scalar, b scalar)</code> returns the larger of the two scalar values <code>a</code>
and <code>b</code>.
</p>
</>
),
histogram_avg: (
<>
<p>
@ -2093,22 +2077,6 @@ const funcDocs: Record<string, React.ReactNode> = {
</p>
</>
),
least: (
<>
<p>
<strong>
This function has to be enabled via the{" "}
<a href="../feature_flags.md#experimental-promql-functions">feature flag</a>
<code>--enable-feature=promql-experimental-functions</code>.
</strong>
</p>
<p>
<code>least(a scalar, b scalar)</code> returns the smaller of the two scalar values <code>a</code>
and <code>b</code>.
</p>
</>
),
ln: (
<>
<p>
@ -2260,6 +2228,22 @@ const funcDocs: Record<string, React.ReactNode> = {
</p>
</>
),
max_of: (
<>
<p>
<strong>
This function has to be enabled via the{" "}
<a href="../feature_flags.md#experimental-promql-functions">feature flag</a>
<code>--enable-feature=promql-experimental-functions</code>.
</strong>
</p>
<p>
<code>max_of(a scalar, b scalar)</code> returns the larger of the two scalar values <code>a</code>
and <code>b</code>.
</p>
</>
),
max_over_time: (
<>
<p>
@ -2370,6 +2354,22 @@ const funcDocs: Record<string, React.ReactNode> = {
</p>
</>
),
min_of: (
<>
<p>
<strong>
This function has to be enabled via the{" "}
<a href="../feature_flags.md#experimental-promql-functions">feature flag</a>
<code>--enable-feature=promql-experimental-functions</code>.
</strong>
</p>
<p>
<code>min_of(a scalar, b scalar)</code> returns the smaller of the two scalar values <code>a</code>
and <code>b</code>.
</p>
</>
),
min_over_time: (
<>
<p>

View file

@ -56,12 +56,6 @@ export const functionSignatures: Record<string, Func> = {
exp: { name: "exp", argTypes: [valueType.vector], variadic: 0, returnType: valueType.vector },
first_over_time: { name: "first_over_time", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector },
floor: { name: "floor", argTypes: [valueType.vector], variadic: 0, returnType: valueType.vector },
greatest: {
name: "greatest",
argTypes: [valueType.scalar, valueType.scalar],
variadic: 0,
returnType: valueType.scalar,
},
histogram_avg: { name: "histogram_avg", argTypes: [valueType.vector], variadic: 0, returnType: valueType.vector },
histogram_count: { name: "histogram_count", argTypes: [valueType.vector], variadic: 0, returnType: valueType.vector },
histogram_fraction: {
@ -113,12 +107,13 @@ export const functionSignatures: Record<string, Func> = {
returnType: valueType.vector,
},
last_over_time: { name: "last_over_time", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector },
least: { name: "least", argTypes: [valueType.scalar, valueType.scalar], variadic: 0, returnType: valueType.scalar },
ln: { name: "ln", argTypes: [valueType.vector], variadic: 0, returnType: valueType.vector },
log10: { name: "log10", argTypes: [valueType.vector], variadic: 0, returnType: valueType.vector },
log2: { name: "log2", argTypes: [valueType.vector], variadic: 0, returnType: valueType.vector },
mad_over_time: { name: "mad_over_time", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector },
max_of: { name: "max_of", argTypes: [valueType.scalar, valueType.scalar], variadic: 0, returnType: valueType.scalar },
max_over_time: { name: "max_over_time", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector },
min_of: { name: "min_of", argTypes: [valueType.scalar, valueType.scalar], variadic: 0, returnType: valueType.scalar },
min_over_time: { name: "min_over_time", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector },
minute: { name: "minute", argTypes: [valueType.vector], variadic: 1, returnType: valueType.vector },
month: { name: "month", argTypes: [valueType.vector], variadic: 1, returnType: valueType.vector },