From d626c5c2a9ca3e8cff4803ba1fc34ef864845a02 Mon Sep 17 00:00:00 2001 From: Nikolay Date: Fri, 21 May 2021 13:55:43 +0300 Subject: [PATCH] changes vmalert query function (#1307) * changes vmalert query function for prometheus rules compatibility its better to use labels as map. it simplifies template evaluation and allow to ignore can't evaluate field error because map will return default value. fixes https://github.com/VictoriaMetrics/operator/issues/243 --- .../config/testdata/rules-query-good.rules | 15 ++++++ app/vmalert/notifier/template_func.go | 47 +++++++++++++++---- 2 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 app/vmalert/config/testdata/rules-query-good.rules diff --git a/app/vmalert/config/testdata/rules-query-good.rules b/app/vmalert/config/testdata/rules-query-good.rules new file mode 100644 index 000000000..e0038895b --- /dev/null +++ b/app/vmalert/config/testdata/rules-query-good.rules @@ -0,0 +1,15 @@ +groups: + - name: alertmanager.rules + rules: + - alert: AlertmanagerConfigInconsistent + annotations: + message: | + The configuration of the instances of the Alertmanager cluster `{{ $labels.namespace }}/{{ $labels.service }}` are out of sync. + {{ range printf "alertmanager_config_hash{namespace=\"%s\",service=\"%s\"}" $labels.namespace $labels.service | query }} + Configuration hash for pod {{ .Labels.pod }} is "{{ printf "%.f" .Value }}" + {{ end }} + expr: | + count by(namespace,service) (count_values by(namespace,service) ("config_hash", alertmanager_config_hash{job="alertmanager-main",namespace="openshift-monitoring"})) != 1 + for: 5m + labels: + severity: critical \ No newline at end of file diff --git a/app/vmalert/notifier/template_func.go b/app/vmalert/notifier/template_func.go index f21c41a5c..3bcc967ba 100644 --- a/app/vmalert/notifier/template_func.go +++ b/app/vmalert/notifier/template_func.go @@ -28,6 +28,31 @@ import ( "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource" ) +// metric is private copy of datasource.Metric, +// it is used for templating annotations, +// Labels as map simplifies templates evaluation. +type metric struct { + Labels map[string]string + Timestamp int64 + Value float64 +} + +// datasourceMetricsToTemplateMetrics converts Metrics from datasource package to private copy for templating. +func datasourceMetricsToTemplateMetrics(ms []datasource.Metric) []metric { + mss := make([]metric, 0, len(ms)) + for _, m := range ms { + labelsMap := make(map[string]string, len(m.Labels)) + for _, labelValue := range m.Labels { + labelsMap[labelValue.Name] = labelValue.Value + } + mss = append(mss, metric{ + Labels: labelsMap, + Timestamp: m.Timestamp, + Value: m.Value}) + } + return mss +} + // QueryFn is used to wrap a call to datasource into simple-to-use function // for templating functions. type QueryFn func(query string) ([]datasource.Metric, error) @@ -211,34 +236,34 @@ func InitTemplateFunc(externalURL *url.URL) { // For example, {{ query "foo" | first | value }} will // execute "/api/v1/query?query=foo" request and will return // the first value in response. - "query": func(q string) ([]datasource.Metric, error) { + "query": func(q string) ([]metric, error) { // query function supposed to be substituted at funcsWithQuery(). // it is present here only for validation purposes, when there is no // provided datasource. // // return non-empty slice to pass validation with chained functions in template // see issue #989 for details - return []datasource.Metric{{}}, nil + return []metric{{}}, nil }, // first returns the first by order element from the given metrics list. // usually used alongside with `query` template function. - "first": func(metrics []datasource.Metric) (datasource.Metric, error) { + "first": func(metrics []metric) (metric, error) { if len(metrics) > 0 { return metrics[0], nil } - return datasource.Metric{}, errors.New("first() called on vector with no elements") + return metric{}, errors.New("first() called on vector with no elements") }, // label returns the value of the given label name for the given metric. // usually used alongside with `query` template function. - "label": func(label string, m datasource.Metric) string { - return m.Label(label) + "label": func(label string, m metric) string { + return m.Labels[label] }, // value returns the value of the given metric. // usually used alongside with `query` template function. - "value": func(m datasource.Metric) float64 { + "value": func(m metric) float64 { return m.Value }, @@ -266,8 +291,12 @@ func funcsWithQuery(query QueryFn) textTpl.FuncMap { for k, fn := range tmplFunc { fm[k] = fn } - fm["query"] = func(q string) ([]datasource.Metric, error) { - return query(q) + fm["query"] = func(q string) ([]metric, error) { + result, err := query(q) + if err != nil { + return nil, err + } + return datasourceMetricsToTemplateMetrics(result), nil } return fm }