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
This commit is contained in:
Julius Volz 2014-10-15 19:13:52 +02:00 committed by Bjoern Rabenstein
parent 3d47f94149
commit b7bf11230a
2 changed files with 69 additions and 0 deletions

View File

@ -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},

View File

@ -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)