From 5caf6cb36922b1bc1e2ebcedeabe5eb4e64d80b2 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Wed, 8 Sep 2021 10:46:15 +0100 Subject: [PATCH] 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 --- pkg/services/ngalert/state/cache.go | 38 +++++++++++-------- pkg/services/ngalert/state/cache_test.go | 48 ++++++++++++++++++------ 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/pkg/services/ngalert/state/cache.go b/pkg/services/ngalert/state/cache.go index 360ba0aa490..a1e9e437b66 100644 --- a/pkg/services/ngalert/state/cache.go +++ b/pkg/services/ngalert/state/cache.go @@ -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() diff --git a/pkg/services/ngalert/state/cache_test.go b/pkg/services/ngalert/state/cache_test.go index 946a2c6352b..9f553105ced 100644 --- a/pkg/services/ngalert/state/cache_test.go +++ b/pkg/services/ngalert/state/cache_test.go @@ -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 ]",