Change templateCaptureValue to support using template functions (#38766)

* Change templateCaptureValue to support using template functions

This commit changes templateCaptureValue to use float64 for the value
instead of *float64. This change means that annotations and labels can
use the float64 value with functions such as printf and avoid having to
check for nil. It also means that absent values are now printed as 0.

* Use math.NaN() instead of 0 for absent value
This commit is contained in:
George Robinson
2021-09-08 10:46:15 +01:00
committed by GitHub
parent 17f9bc138e
commit 5caf6cb369
2 changed files with 59 additions and 27 deletions

View File

@@ -3,6 +3,7 @@ package state
import (
"bytes"
"fmt"
"math"
"strconv"
"strings"
"sync"
@@ -110,16 +111,13 @@ func (c *cache) expandRuleLabelsAndAnnotations(alertRule *ngModels.AlertRule, la
// and labels template.
type templateCaptureValue struct {
Labels map[string]string
Value *float64
Value float64
}
// String implements the Stringer interface to print the value of each RefID
// in the template via {{ $values.A }} rather than {{ $values.A.Value }}.
func (v templateCaptureValue) String() string {
if v.Value != nil {
return strconv.FormatFloat(*v.Value, 'f', -1, 64)
}
return "null"
return strconv.FormatFloat(v.Value, 'f', -1, 64)
}
func expandTemplate(name, text string, labels map[string]string, alertInstance eval.Result) (result string, resultErr error) {
@@ -148,23 +146,31 @@ func expandTemplate(name, text string, labels map[string]string, alertInstance e
Value string
}{
Labels: labels,
Values: func() map[string]templateCaptureValue {
m := make(map[string]templateCaptureValue)
for k, v := range alertInstance.Values {
m[k] = templateCaptureValue{
Labels: v.Labels,
Value: v.Value,
}
}
return m
}(),
Value: alertInstance.EvaluationString,
Values: newTemplateCaptureValues(alertInstance.Values),
Value: alertInstance.EvaluationString,
}); err != nil {
return "", fmt.Errorf("error executing template %v: %s", name, err.Error())
}
return buffer.String(), nil
}
func newTemplateCaptureValues(values map[string]eval.NumberValueCapture) map[string]templateCaptureValue {
m := make(map[string]templateCaptureValue)
for k, v := range values {
var f float64
if v.Value != nil {
f = *v.Value
} else {
f = math.NaN()
}
m[k] = templateCaptureValue{
Labels: v.Labels,
Value: f,
}
}
return m
}
func (c *cache) set(entry *State) {
c.mtxStates.Lock()
defer c.mtxStates.Unlock()

View File

@@ -17,16 +17,16 @@ func TestTemplateCaptureValueStringer(t *testing.T) {
value templateCaptureValue
expected string
}{{
name: "nil value returns null",
value: templateCaptureValue{Value: nil},
expected: "null",
name: "0 is returned as integer value",
value: templateCaptureValue{Value: 0},
expected: "0",
}, {
name: "1.0 is returned as integer value",
value: templateCaptureValue{Value: ptr.Float64(1.0)},
value: templateCaptureValue{Value: 1.0},
expected: "1",
}, {
name: "1.1 is returned as decimal value",
value: templateCaptureValue{Value: ptr.Float64(1.1)},
value: templateCaptureValue{Value: 1.1},
expected: "1.1",
}}
@@ -46,12 +46,12 @@ func TestExpandTemplate(t *testing.T) {
expected string
expectedError error
}{{
name: "instance labels are expanded into $labels",
name: "labels are expanded into $labels",
text: "{{ $labels.instance }} is down",
labels: data.Labels{"instance": "foo"},
expected: "foo is down",
}, {
name: "missing instance label returns error",
name: "missing label in $labels returns error",
text: "{{ $labels.instance }} is down",
labels: data.Labels{},
expectedError: errors.New("error executing template __alert_test: template: __alert_test:1:86: executing \"__alert_test\" at <$labels.instance>: map has no entry for key \"instance\""),
@@ -63,11 +63,24 @@ func TestExpandTemplate(t *testing.T) {
"A": {
Var: "A",
Labels: data.Labels{"instance": "foo"},
Value: ptr.Float64(10),
Value: ptr.Float64(1),
},
},
},
expected: "foo has value 10",
expected: "foo has value 1",
}, {
name: "values can be passed to template functions such as printf",
text: "{{ $values.A.Labels.instance }} has value {{ $values.A.Value | printf \"%.1f\" }}",
alertInstance: eval.Result{
Values: map[string]eval.NumberValueCapture{
"A": {
Var: "A",
Labels: data.Labels{"instance": "foo"},
Value: ptr.Float64(1.1),
},
},
},
expected: "foo has value 1.1",
}, {
name: "missing label in $values returns error",
text: "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
@@ -76,13 +89,26 @@ func TestExpandTemplate(t *testing.T) {
"A": {
Var: "A",
Labels: data.Labels{},
Value: ptr.Float64(10),
Value: ptr.Float64(1),
},
},
},
expectedError: errors.New("error executing template __alert_test: template: __alert_test:1:86: executing \"__alert_test\" at <$values.A.Labels.instance>: map has no entry for key \"instance\""),
}, {
name: "value string is expanded into $value",
name: "missing value in $values is returned as NaN",
text: "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
alertInstance: eval.Result{
Values: map[string]eval.NumberValueCapture{
"A": {
Var: "A",
Labels: data.Labels{"instance": "foo"},
Value: nil,
},
},
},
expected: "foo has value NaN",
}, {
name: "assert value string is expanded into $value",
text: "{{ $value }}",
alertInstance: eval.Result{
EvaluationString: "[ var='A' labels={instance=foo} value=10 ]",