mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 05:34:05 -08:00
add absent_over_time (#6490)
* Implement absent_over_time Fixes #2882 Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
This commit is contained in:
parent
91d76c8023
commit
e0afec906f
|
@ -39,6 +39,29 @@ absent(sum(nonexistent{job="myjob"}))
|
|||
In the first two examples, `absent()` tries to be smart about deriving labels
|
||||
of the 1-element output vector from the input vector.
|
||||
|
||||
## `absent_over_time()`
|
||||
|
||||
`absent_over_time(v range-vector)` returns an empty vector if the range vector
|
||||
passed to it has any elements and a 1-element vector with the value 1 if the
|
||||
range vector passed to it has no elements.
|
||||
|
||||
This is useful for alerting on when no time series exist for a given metric name
|
||||
and label combination for a certain amount of time.
|
||||
|
||||
```
|
||||
absent_over_time(nonexistent{job="myjob"}[1h])
|
||||
# => {job="myjob"}
|
||||
|
||||
absent_over_time(nonexistent{job="myjob",instance=~".*"}[1h])
|
||||
# => {job="myjob"}
|
||||
|
||||
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.
|
||||
|
||||
## `ceil()`
|
||||
|
||||
`ceil(v instant-vector)` rounds the sample values of all elements in `v` up to
|
||||
|
|
|
@ -111,6 +111,9 @@ func BenchmarkRangeQuery(b *testing.B) {
|
|||
{
|
||||
expr: "rate(a_X[1d])",
|
||||
},
|
||||
{
|
||||
expr: "absent_over_time(a_X[1d])",
|
||||
},
|
||||
// Unary operators.
|
||||
{
|
||||
expr: "-a_X",
|
||||
|
|
|
@ -1074,11 +1074,51 @@ func (ev *evaluator) eval(expr Expr) Value {
|
|||
putPointSlice(ss.Points)
|
||||
}
|
||||
}
|
||||
|
||||
putPointSlice(points)
|
||||
|
||||
// The absent_over_time function returns 0 or 1 series. So far, the matrix
|
||||
// contains multiple series. The following code will create a new series
|
||||
// with values of 1 for the timestamps where no series has value.
|
||||
if e.Func.Name == "absent_over_time" {
|
||||
steps := int(1 + (ev.endTimestamp-ev.startTimestamp)/ev.interval)
|
||||
// Iterate once to look for a complete series.
|
||||
for _, s := range mat {
|
||||
if len(s.Points) == steps {
|
||||
return Matrix{}
|
||||
}
|
||||
}
|
||||
|
||||
found := map[int64]struct{}{}
|
||||
|
||||
for i, s := range mat {
|
||||
for _, p := range s.Points {
|
||||
found[p.T] = struct{}{}
|
||||
}
|
||||
if i > 0 && len(found) == steps {
|
||||
return Matrix{}
|
||||
}
|
||||
}
|
||||
|
||||
newp := make([]Point, 0, steps-len(found))
|
||||
for ts := ev.startTimestamp; ts <= ev.endTimestamp; ts += ev.interval {
|
||||
if _, ok := found[ts]; !ok {
|
||||
newp = append(newp, Point{T: ts, V: 1})
|
||||
}
|
||||
}
|
||||
|
||||
return Matrix{
|
||||
Series{
|
||||
Metric: createLabelsForAbsentFunction(e.Args[0]),
|
||||
Points: newp,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if mat.ContainsSameLabelset() {
|
||||
ev.errorf("vector cannot contain metrics with the same labelset")
|
||||
}
|
||||
|
||||
putPointSlice(points)
|
||||
return mat
|
||||
|
||||
case *ParenExpr:
|
||||
|
|
|
@ -480,33 +480,25 @@ func funcAbsent(vals []Value, args Expressions, enh *EvalNodeHelper) Vector {
|
|||
if len(vals[0].(Vector)) > 0 {
|
||||
return enh.out
|
||||
}
|
||||
m := labels.Labels{}
|
||||
empty := []string{}
|
||||
|
||||
if vs, ok := args[0].(*VectorSelector); ok {
|
||||
for _, ma := range vs.LabelMatchers {
|
||||
if ma.Name == labels.MetricName {
|
||||
continue
|
||||
}
|
||||
if ma.Type == labels.MatchEqual && !m.Has(ma.Name) {
|
||||
m = labels.NewBuilder(m).Set(ma.Name, ma.Value).Labels()
|
||||
} else {
|
||||
empty = append(empty, ma.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range empty {
|
||||
m = labels.NewBuilder(m).Set(v, "").Labels()
|
||||
}
|
||||
|
||||
return append(enh.out,
|
||||
Sample{
|
||||
Metric: labels.New(m...),
|
||||
Metric: createLabelsForAbsentFunction(args[0]),
|
||||
Point: Point{V: 1},
|
||||
})
|
||||
}
|
||||
|
||||
// === absent_over_time(Vector ValueTypeMatrix) Vector ===
|
||||
// As this function has a matrix as argument, it does not get all the Series.
|
||||
// This function will return 1 if the matrix has at least one element.
|
||||
// Due to engine optimization, this function is only called when this condition is true.
|
||||
// Then, the engine post-processes the results to get the expected output.
|
||||
func funcAbsentOverTime(vals []Value, args Expressions, enh *EvalNodeHelper) Vector {
|
||||
return append(enh.out,
|
||||
Sample{
|
||||
Point: Point{V: 1},
|
||||
})
|
||||
}
|
||||
|
||||
func simpleFunc(vals []Value, enh *EvalNodeHelper, f func(float64) float64) Vector {
|
||||
for _, el := range vals[0].(Vector) {
|
||||
enh.out = append(enh.out, Sample{
|
||||
|
@ -932,6 +924,12 @@ var Functions = map[string]*Function{
|
|||
ReturnType: ValueTypeVector,
|
||||
Call: funcAbsent,
|
||||
},
|
||||
"absent_over_time": {
|
||||
Name: "absent_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
Call: funcAbsentOverTime,
|
||||
},
|
||||
"avg_over_time": {
|
||||
Name: "avg_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
|
@ -1270,3 +1268,36 @@ func (s *vectorByReverseValueHeap) Pop() interface{} {
|
|||
*s = old[0 : n-1]
|
||||
return el
|
||||
}
|
||||
|
||||
// createLabelsForAbsentFunction returns the labels that are uniquely and exactly matched
|
||||
// in a given expression. It is used in the absent functions.
|
||||
func createLabelsForAbsentFunction(expr Expr) labels.Labels {
|
||||
m := labels.Labels{}
|
||||
|
||||
var lm []*labels.Matcher
|
||||
switch n := expr.(type) {
|
||||
case *VectorSelector:
|
||||
lm = n.LabelMatchers
|
||||
case *MatrixSelector:
|
||||
lm = n.LabelMatchers
|
||||
default:
|
||||
return m
|
||||
}
|
||||
|
||||
empty := []string{}
|
||||
for _, ma := range lm {
|
||||
if ma.Name == labels.MetricName {
|
||||
continue
|
||||
}
|
||||
if ma.Type == labels.MatchEqual && !m.Has(ma.Name) {
|
||||
m = labels.NewBuilder(m).Set(ma.Name, ma.Value).Labels()
|
||||
} else {
|
||||
empty = append(empty, ma.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range empty {
|
||||
m = labels.NewBuilder(m).Set(v, "").Labels()
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
|
66
promql/testdata/functions.test
vendored
66
promql/testdata/functions.test
vendored
|
@ -546,3 +546,69 @@ eval instant at 1m max_over_time(data[1m])
|
|||
{type="some_nan2"} 2
|
||||
{type="some_nan3"} 1
|
||||
{type="only_nan"} NaN
|
||||
|
||||
clear
|
||||
|
||||
# Testdata for absent_over_time()
|
||||
eval instant at 1m absent_over_time(http_requests[5m])
|
||||
{} 1
|
||||
|
||||
eval instant at 1m absent_over_time(http_requests{handler="/foo"}[5m])
|
||||
{handler="/foo"} 1
|
||||
|
||||
eval instant at 1m absent_over_time(http_requests{handler!="/foo"}[5m])
|
||||
{} 1
|
||||
|
||||
eval instant at 1m absent_over_time(http_requests{handler="/foo", handler="/bar", handler="/foobar"}[5m])
|
||||
{} 1
|
||||
|
||||
eval instant at 1m absent_over_time(rate(nonexistant[5m])[5m:])
|
||||
{} 1
|
||||
|
||||
eval instant at 1m absent_over_time(http_requests{handler="/foo", handler="/bar", instance="127.0.0.1"}[5m])
|
||||
{instance="127.0.0.1"} 1
|
||||
|
||||
load 1m
|
||||
http_requests{path="/foo",instance="127.0.0.1",job="httpd"} 1+1x10
|
||||
http_requests{path="/bar",instance="127.0.0.1",job="httpd"} 1+1x10
|
||||
httpd_handshake_failures_total{instance="127.0.0.1",job="node"} 1+1x15
|
||||
httpd_log_lines_total{instance="127.0.0.1",job="node"} 1
|
||||
ssl_certificate_expiry_seconds{job="ingress"} NaN NaN NaN NaN NaN
|
||||
|
||||
eval instant at 5m absent_over_time(http_requests[5m])
|
||||
|
||||
eval instant at 5m absent_over_time(rate(http_requests[5m])[5m:1m])
|
||||
|
||||
eval instant at 0m absent_over_time(httpd_log_lines_total[30s])
|
||||
|
||||
eval instant at 1m absent_over_time(httpd_log_lines_total[30s])
|
||||
{} 1
|
||||
|
||||
eval instant at 15m absent_over_time(http_requests[5m])
|
||||
|
||||
eval instant at 16m absent_over_time(http_requests[5m])
|
||||
{} 1
|
||||
|
||||
eval instant at 16m absent_over_time(http_requests[6m])
|
||||
|
||||
eval instant at 16m absent_over_time(httpd_handshake_failures_total[1m])
|
||||
|
||||
eval instant at 16m absent_over_time({instance="127.0.0.1"}[5m])
|
||||
|
||||
eval instant at 16m absent_over_time({instance="127.0.0.1"}[5m])
|
||||
|
||||
eval instant at 21m absent_over_time({instance="127.0.0.1"}[5m])
|
||||
{instance="127.0.0.1"} 1
|
||||
|
||||
eval instant at 21m absent_over_time({instance="127.0.0.1"}[20m])
|
||||
|
||||
eval instant at 21m absent_over_time({job="grok"}[20m])
|
||||
{job="grok"} 1
|
||||
|
||||
eval instant at 30m absent_over_time({instance="127.0.0.1"}[5m:5s])
|
||||
{} 1
|
||||
|
||||
eval instant at 5m absent_over_time({job="ingress"}[4m])
|
||||
|
||||
eval instant at 10m absent_over_time({job="ingress"}[4m])
|
||||
{job="ingress"} 1
|
||||
|
|
16
promql/testdata/legacy.test
vendored
16
promql/testdata/legacy.test
vendored
|
@ -260,7 +260,6 @@ eval instant at 50m {job=~".+-server", job!~"api-.+"}
|
|||
eval instant at 50m absent(nonexistent)
|
||||
{} 1
|
||||
|
||||
|
||||
eval instant at 50m absent(nonexistent{job="testjob", instance="testinstance", method=~".x"})
|
||||
{instance="testinstance", job="testjob"} 1
|
||||
|
||||
|
@ -280,6 +279,21 @@ eval instant at 50m absent(sum(http_requests))
|
|||
eval instant at 50m absent(sum(nonexistent{job="testjob", instance="testinstance"}))
|
||||
{} 1
|
||||
|
||||
eval instant at 50m absent(max(nonexistant))
|
||||
{} 1
|
||||
|
||||
eval instant at 50m absent(nonexistant > 1)
|
||||
{} 1
|
||||
|
||||
eval instant at 50m absent(a + b)
|
||||
{} 1
|
||||
|
||||
eval instant at 50m absent(a and b)
|
||||
{} 1
|
||||
|
||||
eval instant at 50m absent(rate(nonexistant[5m]))
|
||||
{} 1
|
||||
|
||||
eval instant at 50m http_requests{group="production",job="api-server"} offset 5m
|
||||
http_requests{group="production", instance="0", job="api-server"} 90
|
||||
http_requests{group="production", instance="1", job="api-server"} 180
|
||||
|
|
Loading…
Reference in a new issue