Skip to content

Commit

Permalink
Merge pull request prometheus#13731 from suntala/suntala/native-histo…
Browse files Browse the repository at this point in the history
…gram-template

histograms: support expansion of native histogram values in templating
  • Loading branch information
beorn7 authored Apr 11, 2024
2 parents 612de02 + 9a7c6a5 commit 4ec5c25
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 10 deletions.
4 changes: 2 additions & 2 deletions docs/configuration/template_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The primary data structure for dealing with time series data is the sample, defi
```go
type sample struct {
Labels map[string]string
Value float64
Value interface{}
}
```

Expand All @@ -44,7 +44,7 @@ If functions are used in a pipeline, the pipeline value is passed as the last ar
| query | query string | []sample | Queries the database, does not support returning range vectors. |
| first | []sample | sample | Equivalent to `index a 0` |
| label | label, sample | string | Equivalent to `index sample.Labels label` |
| value | sample | float64 | Equivalent to `sample.Value` |
| value | sample | interface{} | Equivalent to `sample.Value` |
| sortByLabel | label, []samples | []sample | Sorts the samples by the given label. Is stable. |

`first`, `label` and `value` are intended to make query results easily usable in pipelines.
Expand Down
3 changes: 2 additions & 1 deletion model/rulefmt/rulefmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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}}",
Expand Down
2 changes: 1 addition & 1 deletion rules/alerting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
62 changes: 62 additions & 0 deletions rules/alerting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
21 changes: 15 additions & 6 deletions template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func init() {
// A version of vector that's easier to use from templates.
type sample struct {
Labels map[string]string
Value float64
Value interface{}
}
type queryResult []*sample

Expand Down Expand Up @@ -96,6 +96,9 @@ func query(ctx context.Context, q string, ts time.Time, queryFn QueryFunc) (quer
Value: v.F,
Labels: v.Metric.Map(),
}
if v.H != nil {
s.Value = v.H
}
result[n] = &s
}
return result, nil
Expand Down Expand Up @@ -160,7 +163,7 @@ func NewTemplateExpander(
"label": func(label string, s *sample) string {
return s.Labels[label]
},
"value": func(s *sample) float64 {
"value": func(s *sample) interface{} {
return s.Value
},
"strvalue": func(s *sample) string {
Expand Down Expand Up @@ -355,18 +358,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.
Expand Down
19 changes: 19 additions & 0 deletions template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/stretchr/testify/require"

"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql"
)
Expand All @@ -39,6 +40,12 @@ func TestTemplateExpansion(t *testing.T) {
text: "{{ 1 }}",
output: "1",
},
{
// Native histogram value.
text: "{{ . | value }}",
input: &sample{Value: &histogram.FloatHistogram{Count: 3, Sum: 10}},
output: (&histogram.FloatHistogram{Count: 3, Sum: 10}).String(),
},
{
// Non-ASCII space (not allowed in text/template, see https://github.com/golang/go/blob/master/src/text/template/parse/lex.go#L98)
text: "{{ }}",
Expand Down Expand Up @@ -84,6 +91,18 @@ func TestTemplateExpansion(t *testing.T) {
},
output: "11",
},
{
// Get value of a native histogram from query.
text: "{{ query \"metric{instance='a'}\" | first | value }}",
queryResult: promql.Vector{
{
Metric: labels.FromStrings(labels.MetricName, "metric", "instance", "a"),
T: 0,
H: &histogram.FloatHistogram{Count: 3, Sum: 10},
},
},
output: (&histogram.FloatHistogram{Count: 3, Sum: 10}).String(),
},
{
// Get label from query.
text: "{{ query \"metric{instance='a'}\" | first | label \"instance\" }}",
Expand Down

0 comments on commit 4ec5c25

Please sign in to comment.