From b7bf11230a5ae185758c8d25138c83c564e52380 Mon Sep 17 00:00:00 2001 From: Julius Volz Date: Wed, 15 Oct 2014 19:13:52 +0200 Subject: [PATCH] Add absent() function. A common problem in Prometheus alerting is to detect when no timeseries exist for a given metric name and label combination. Unfortunately, Prometheus alert expressions need to be of vector type, and "count(nonexistent_metric)" results in an empty vector, yielding no output vector elements to base an alert on. The newly introduced absent() function solves this issue: ALERT FooAbsent IF absent(foo{job="myjob"}) [...] absent() has the following behavior: - if the vector passed to it has any elements, it returns an empty vector. - if the vector passed to it has no elements, it returns a 1-element vector with the value 1. In the second case, absent() tries to be smart about deriving labels of the 1-element output vector from the input vector: absent(nonexistent{job="myjob"}) => {job="myjob"} absent(nonexistent{job="myjob",instance=~".*"}) => {job="myjob"} absent(sum(nonexistent{job="myjob"})) => {} That is, if the passed vector is a literal vector selector, it takes all "=" label matchers as the basis for the output labels, but ignores all non-equals or regex matchers. Also, if the passed vector results from a non-selector expression, no labels can be derived. Change-Id: I948505a1488d50265ab5692a3286bd7c8c70cd78 --- rules/ast/functions.go | 29 +++++++++++++++++++++++++++++ rules/rules_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/rules/ast/functions.go b/rules/ast/functions.go index b838f09a5..9671be49b 100644 --- a/rules/ast/functions.go +++ b/rules/ast/functions.go @@ -454,6 +454,29 @@ func absImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { return vector } +// === absent(vector VectorNode) Vector === +func absentImpl(timestamp clientmodel.Timestamp, args []Node) interface{} { + n := args[0].(VectorNode) + if len(n.Eval(timestamp)) > 0 { + return Vector{} + } + m := clientmodel.Metric{} + if vs, ok := n.(*VectorSelector); ok { + for _, matcher := range vs.labelMatchers { + if matcher.Type == metric.Equal && matcher.Name != clientmodel.MetricNameLabel { + m[matcher.Name] = matcher.Value + } + } + } + return Vector{ + &clientmodel.Sample{ + Metric: m, + Value: 1, + Timestamp: timestamp, + }, + } +} + var functions = map[string]*Function{ "abs": { name: "abs", @@ -461,6 +484,12 @@ var functions = map[string]*Function{ returnType: VECTOR, callFn: absImpl, }, + "absent": { + name: "absent", + argTypes: []ExprType{VECTOR}, + returnType: VECTOR, + callFn: absentImpl, + }, "avg_over_time": { name: "avg_over_time", argTypes: []ExprType{MATRIX}, diff --git a/rules/rules_test.go b/rules/rules_test.go index eff731f9f..d93f6218f 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -596,6 +596,46 @@ func TestExpressions(t *testing.T) { fullRanges: 0, intervalRanges: 4, }, + { + expr: `absent(nonexistent)`, + output: []string{ + `{} => 1 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 0, + }, + { + expr: `absent(nonexistent{job="testjob", instance="testinstance", method=~".*"})`, + output: []string{ + `{instance="testinstance", job="testjob"} => 1 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 0, + }, + { + expr: `count_scalar(absent(http_requests))`, + output: []string{ + `scalar: 0 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 8, + }, + { + expr: `count_scalar(absent(sum(http_requests)))`, + output: []string{ + `scalar: 0 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 8, + }, + { + expr: `absent(sum(nonexistent{job="testjob", instance="testinstance"}))`, + output: []string{ + `{} => 1 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 0, + }, } storage, closer := newTestStorage(t)