From 44f385fd519197f8b93075fc5ec795818f5fdb74 Mon Sep 17 00:00:00 2001 From: suntala Date: Tue, 12 Mar 2024 20:14:31 +0100 Subject: [PATCH] Support expansion of native histogram values in alert templates Co-authored-by: Aleks Fazlieva Signed-off-by: suntala --- model/rulefmt/rulefmt.go | 3 +- rules/alerting.go | 2 +- rules/alerting_test.go | 62 ++++++++++++++++++++++++++++++++++++++++ template/template.go | 14 ++++++--- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/model/rulefmt/rulefmt.go b/model/rulefmt/rulefmt.go index 03cbd8849..4ed1619d6 100644 --- a/model/rulefmt/rulefmt.go +++ b/model/rulefmt/rulefmt.go @@ -27,6 +27,7 @@ import ( "gopkg.in/yaml.v3" "github.com/prometheus/prometheus/model/timestamp" + "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/template" ) @@ -256,7 +257,7 @@ func testTemplateParsing(rl *RuleNode) (errs []error) { } // Trying to parse templates. - tmplData := template.AlertTemplateData(map[string]string{}, map[string]string{}, "", 0) + tmplData := template.AlertTemplateData(map[string]string{}, map[string]string{}, "", promql.Sample{}) defs := []string{ "{{$labels := .Labels}}", "{{$externalLabels := .ExternalLabels}}", diff --git a/rules/alerting.go b/rules/alerting.go index 7b0921a72..50c67fa2d 100644 --- a/rules/alerting.go +++ b/rules/alerting.go @@ -364,7 +364,7 @@ func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc, // Provide the alert information to the template. l := smpl.Metric.Map() - tmplData := template.AlertTemplateData(l, r.externalLabels, r.externalURL, smpl.F) + tmplData := template.AlertTemplateData(l, r.externalLabels, r.externalURL, smpl) // Inject some convenience variables that are easier to remember for users // who are not used to Go's templating system. defs := []string{ diff --git a/rules/alerting_test.go b/rules/alerting_test.go index a270731d9..ddfe345ef 100644 --- a/rules/alerting_test.go +++ b/rules/alerting_test.go @@ -23,6 +23,7 @@ import ( "github.com/prometheus/common/model" "github.com/stretchr/testify/require" + "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/relabel" "github.com/prometheus/prometheus/model/timestamp" @@ -85,6 +86,67 @@ func TestAlertingRuleState(t *testing.T) { } } +func TestAlertingRuleTemplateWithHistogram(t *testing.T) { + h := histogram.FloatHistogram{ + Schema: 0, + Count: 30, + Sum: 1111.1, + ZeroThreshold: 0.001, + ZeroCount: 2, + PositiveSpans: []histogram.Span{ + {Offset: 0, Length: 1}, + {Offset: 1, Length: 5}, + }, + PositiveBuckets: []float64{1, 1, 2, 1, 1, 1}, + NegativeSpans: []histogram.Span{ + {Offset: 1, Length: 4}, + {Offset: 4, Length: 3}, + }, + NegativeBuckets: []float64{-2, 2, 2, 7, 5, 5, 2}, + } + + q := func(ctx context.Context, qs string, t time.Time) (promql.Vector, error) { + return []promql.Sample{{H: &h}}, nil + } + + expr, err := parser.ParseExpr("foo") + require.NoError(t, err) + + rule := NewAlertingRule( + "HistogramAsValue", + expr, + time.Minute, + 0, + labels.FromStrings("histogram", "{{ $value }}"), + labels.EmptyLabels(), labels.EmptyLabels(), "", true, nil, + ) + + evalTime := time.Now() + res, err := rule.Eval(context.TODO(), evalTime, q, nil, 0) + require.NoError(t, err) + + require.Len(t, res, 2) + for _, smpl := range res { + smplName := smpl.Metric.Get("__name__") + if smplName == "ALERTS" { + result := promql.Sample{ + Metric: labels.FromStrings( + "__name__", "ALERTS", + "alertname", "HistogramAsValue", + "alertstate", "pending", + "histogram", h.String(), + ), + T: timestamp.FromTime(evalTime), + F: 1, + } + testutil.RequireEqual(t, result, smpl) + } else { + // If not 'ALERTS', it has to be 'ALERTS_FOR_STATE'. + require.Equal(t, "ALERTS_FOR_STATE", smplName) + } + } +} + func TestAlertingRuleLabelsUpdate(t *testing.T) { storage := promql.LoadedStorage(t, ` load 1m diff --git a/template/template.go b/template/template.go index 5d72a7e83..f04f5ea53 100644 --- a/template/template.go +++ b/template/template.go @@ -355,18 +355,24 @@ func NewTemplateExpander( } // AlertTemplateData returns the interface to be used in expanding the template. -func AlertTemplateData(labels, externalLabels map[string]string, externalURL string, value float64) interface{} { - return struct { +func AlertTemplateData(labels, externalLabels map[string]string, externalURL string, smpl promql.Sample) interface{} { + res := struct { Labels map[string]string ExternalLabels map[string]string ExternalURL string - Value float64 + Value interface{} }{ Labels: labels, ExternalLabels: externalLabels, ExternalURL: externalURL, - Value: value, + Value: smpl.F, } + + if smpl.H != nil { + res.Value = smpl.H + } + + return res } // Funcs adds the functions in fm to the Expander's function map.