vmalert: introduce additional HTTP URL params per-group configuration (#1892)

* vmalert: introduce additional HTTP URL params per-group configuration

The new group field `params` allows to configure custom HTTP URL params
per each group. These params will be applied to every request before
executing rule's expression. Hot config reload is also supported.

Field `extra_filter_labels` was deprecated in favour of `params` field.
vmalert will print deprecation log message if config file contains
the deprecated field.

`params` fields are supported by both Prometheus and Graphite datasource types.

Signed-off-by: hagen1778 <roman@victoriametrics.com>

* vmalert: provide more examples for `params` field

Signed-off-by: hagen1778 <roman@victoriametrics.com>

* vmalert: set higher priority for `params` setting

If there would be a conflict between URL params set in `datasource.url` flag
and params in group definition the latter will have higher priority.

Signed-off-by: hagen1778 <roman@victoriametrics.com>
This commit is contained in:
Roman Khavronenko 2021-12-02 14:45:08 +02:00 committed by Aliaksandr Valialkin
parent 8b67168609
commit 582c063698
No known key found for this signature in database
GPG key ID: A72BEC6CD3D0DED1
19 changed files with 256 additions and 97 deletions

View file

@ -99,12 +99,24 @@ name: <string>
# By default "prometheus" type is used. # By default "prometheus" type is used.
[ type: <string> ] [ type: <string> ]
# Optional list of label filters applied to every rule's # Warning: DEPRECATED
# request withing a group. Is compatible only with VM datasource. # Please use `params` instead:
# See more details at https://docs.victoriametrics.com#prometheus-querying-api-enhancements # params:
# extra_label: ["job=nodeexporter", "env=prod"]
extra_filter_labels: extra_filter_labels:
[ <labelname>: <labelvalue> ... ] [ <labelname>: <labelvalue> ... ]
# Optional list of HTTP URL parameters
# applied for all rules requests within a group
# For example:
# params:
# nocache: ["1"] # disable caching for vmselect
# denyPartialResponse: ["true"] # fail if one or more vmstorage nodes returned an error
# extra_label: ["env=dev"] # apply additional label filter "env=dev" for all requests
# see more details at https://docs.victoriametrics.com#prometheus-querying-api-enhancements
params:
[ <string>: [<string>, ...]]
# Optional list of labels added to every rule within a group. # Optional list of labels added to every rule within a group.
# It has priority over the external labels. # It has priority over the external labels.
# Labels are commonly used for adding environment # Labels are commonly used for adding environment
@ -472,6 +484,8 @@ a review to the dashboard.
## Configuration ## Configuration
### Flags
Pass `-help` to `vmalert` in order to see the full list of supported Pass `-help` to `vmalert` in order to see the full list of supported
command-line flags with their descriptions. command-line flags with their descriptions.
@ -693,12 +707,32 @@ The shortlist of configuration flags is the following:
Show VictoriaMetrics version Show VictoriaMetrics version
``` ```
### Hot config reload
`vmalert` supports "hot" config reload via the following methods: `vmalert` supports "hot" config reload via the following methods:
* send SIGHUP signal to `vmalert` process; * send SIGHUP signal to `vmalert` process;
* send GET request to `/-/reload` endpoint; * send GET request to `/-/reload` endpoint;
* configure `-rule.configCheckInterval` flag for periodic reload * configure `-rule.configCheckInterval` flag for periodic reload
on config change. on config change.
### URL params
To set additional URL params for `datasource.url`, `remoteWrite.url` or `remoteRead.url`
just add them in address: `-datasource.url=http://localhost:8428?nocache=1`.
To set additional URL params for specific [group of rules](#Groups) modify
the `params` group:
```yaml
groups:
- name: TestGroup
params:
denyPartialResponse: ["true"]
extra_label: ["env=dev"]
```
Please note, `params` are used only for executing rules expressions (requests to `datasource.url`).
If there would be a conflict between URL params set in `datasource.url` flag and params in group definition
the latter will have higher priority.
## Contributing ## Contributing
`vmalert` is mostly designed and built by VictoriaMetrics community. `vmalert` is mostly designed and built by VictoriaMetrics community.

View file

@ -71,7 +71,7 @@ func newAlertingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rule
q: qb.BuildWithParams(datasource.QuerierParams{ q: qb.BuildWithParams(datasource.QuerierParams{
DataSourceType: &group.Type, DataSourceType: &group.Type,
EvaluationInterval: group.Interval, EvaluationInterval: group.Interval,
ExtraLabels: group.ExtraFilterLabels, QueryParams: group.Params,
}), }),
alerts: make(map[uint64]*notifier.Alert), alerts: make(map[uint64]*notifier.Alert),
metrics: &alertingRuleMetrics{}, metrics: &alertingRuleMetrics{},

View file

@ -5,17 +5,18 @@ import (
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"io/ioutil" "io/ioutil"
"net/url"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource" "gopkg.in/yaml.v2"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate" "github.com/VictoriaMetrics/VictoriaMetrics/lib/envtemplate"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"gopkg.in/yaml.v2"
) )
// Group contains list of Rules grouped into // Group contains list of Rules grouped into
@ -30,6 +31,7 @@ type Group struct {
// ExtraFilterLabels is a list label filters applied to every rule // ExtraFilterLabels is a list label filters applied to every rule
// request withing a group. Is compatible only with VM datasources. // request withing a group. Is compatible only with VM datasources.
// See https://docs.victoriametrics.com#prometheus-querying-api-enhancements // See https://docs.victoriametrics.com#prometheus-querying-api-enhancements
// DEPRECATED: use Params field instead
ExtraFilterLabels map[string]string `yaml:"extra_filter_labels"` ExtraFilterLabels map[string]string `yaml:"extra_filter_labels"`
// Labels is a set of label value pairs, that will be added to every rule. // Labels is a set of label value pairs, that will be added to every rule.
// It has priority over the external labels. // It has priority over the external labels.
@ -37,11 +39,15 @@ type Group struct {
// Checksum stores the hash of yaml definition for this group. // Checksum stores the hash of yaml definition for this group.
// May be used to detect any changes like rules re-ordering etc. // May be used to detect any changes like rules re-ordering etc.
Checksum string Checksum string
// Optional HTTP URL parameters added to each rule request
Params url.Values `yaml:"params"`
// Catches all undefined fields and must be empty after parsing. // Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline"` XXX map[string]interface{} `yaml:",inline"`
} }
const extraLabelParam = "extra_label"
// UnmarshalYAML implements the yaml.Unmarshaler interface. // UnmarshalYAML implements the yaml.Unmarshaler interface.
func (g *Group) UnmarshalYAML(unmarshal func(interface{}) error) error { func (g *Group) UnmarshalYAML(unmarshal func(interface{}) error) error {
type group Group type group Group
@ -57,6 +63,16 @@ func (g *Group) UnmarshalYAML(unmarshal func(interface{}) error) error {
g.Type.Set(datasource.NewPrometheusType()) g.Type.Set(datasource.NewPrometheusType())
} }
// backward compatibility with deprecated `ExtraFilterLabels` param
if len(g.ExtraFilterLabels) > 0 {
if g.Params == nil {
g.Params = url.Values{}
}
for k, v := range g.ExtraFilterLabels {
g.Params.Add(extraLabelParam, fmt.Sprintf("%s=%s", k, v))
}
}
h := md5.New() h := md5.New()
h.Write(b) h.Write(b)
g.Checksum = fmt.Sprintf("%x", h.Sum(nil)) g.Checksum = fmt.Sprintf("%x", h.Sum(nil))
@ -178,6 +194,7 @@ func Parse(pathPatterns []string, validateAnnotations, validateExpressions bool)
fp = append(fp, matches...) fp = append(fp, matches...)
} }
errGroup := new(utils.ErrGroup) errGroup := new(utils.ErrGroup)
var isExtraFilterLabelsUsed bool
var groups []Group var groups []Group
for _, file := range fp { for _, file := range fp {
uniqueGroups := map[string]struct{}{} uniqueGroups := map[string]struct{}{}
@ -197,6 +214,9 @@ func Parse(pathPatterns []string, validateAnnotations, validateExpressions bool)
} }
uniqueGroups[g.Name] = struct{}{} uniqueGroups[g.Name] = struct{}{}
g.File = file g.File = file
if len(g.ExtraFilterLabels) > 0 {
isExtraFilterLabelsUsed = true
}
groups = append(groups, g) groups = append(groups, g)
} }
} }
@ -206,6 +226,9 @@ func Parse(pathPatterns []string, validateAnnotations, validateExpressions bool)
if len(groups) < 1 { if len(groups) < 1 {
logger.Warnf("no groups found in %s", strings.Join(pathPatterns, ";")) logger.Warnf("no groups found in %s", strings.Join(pathPatterns, ";"))
} }
if isExtraFilterLabelsUsed {
logger.Warnf("field `extra_filter_labels` is deprecated - use `params` instead")
}
return groups, nil return groups, nil
} }

View file

@ -7,10 +7,11 @@ import (
"testing" "testing"
"time" "time"
"gopkg.in/yaml.v2"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/notifier"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
"gopkg.in/yaml.v2"
) )
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -472,6 +473,85 @@ concurrency: 16
rules: rules:
- alert: ExampleAlertWithFor - alert: ExampleAlertWithFor
expr: sum by(job) (up == 1) expr: sum by(job) (up == 1)
`)
})
t.Run("`params` change", func(t *testing.T) {
f(t, `
name: TestGroup
params:
nocache: ["1"]
rules:
- alert: foo
expr: sum by(job) (up == 1)
`, `
name: TestGroup
params:
nocache: ["0"]
rules:
- alert: foo
expr: sum by(job) (up == 1)
`) `)
}) })
} }
func TestGroupParams(t *testing.T) {
f := func(t *testing.T, data string, expParams url.Values) {
t.Helper()
var g Group
if err := yaml.Unmarshal([]byte(data), &g); err != nil {
t.Fatalf("failed to unmarshal: %s", err)
}
got, exp := g.Params.Encode(), expParams.Encode()
if got != exp {
t.Fatalf("expected to have %q; got %q", exp, got)
}
}
t.Run("no params", func(t *testing.T) {
f(t, `
name: TestGroup
rules:
- alert: ExampleAlertAlwaysFiring
expr: sum by(job) (up == 1)
`, url.Values{})
})
t.Run("params", func(t *testing.T) {
f(t, `
name: TestGroup
params:
nocache: ["1"]
denyPartialResponse: ["true"]
rules:
- alert: ExampleAlertAlwaysFiring
expr: sum by(job) (up == 1)
`, url.Values{"nocache": {"1"}, "denyPartialResponse": {"true"}})
})
t.Run("extra labels", func(t *testing.T) {
f(t, `
name: TestGroup
extra_filter_labels:
job: victoriametrics
env: prod
rules:
- alert: ExampleAlertAlwaysFiring
expr: sum by(job) (up == 1)
`, url.Values{extraLabelParam: {"job=victoriametrics", "env=prod"}})
})
t.Run("extra labels and params", func(t *testing.T) {
f(t, `
name: TestGroup
extra_filter_labels:
job: victoriametrics
params:
nocache: ["1"]
extra_label: ["env=prod"]
rules:
- alert: ExampleAlertAlwaysFiring
expr: sum by(job) (up == 1)
`, url.Values{"nocache": {"1"}, extraLabelParam: {"env=prod", "job=victoriametrics"}})
})
}

View file

@ -1,5 +1,8 @@
groups: groups:
- name: groupGorSingleAlert - name: groupGorSingleAlert
params:
nocache: ["1"]
denyPartialResponse: ["true"]
rules: rules:
- alert: VMRows - alert: VMRows
for: 10s for: 10s

View file

@ -2,8 +2,11 @@ groups:
- name: TestGroup - name: TestGroup
interval: 2s interval: 2s
concurrency: 2 concurrency: 2
extra_filter_labels: extra_filter_labels: # deprecated param, use `params` instead
job: victoriametrics job: victoriametrics
params:
denyPartialResponse: ["true"]
extra_label: ["env=dev"]
rules: rules:
- alert: Conns - alert: Conns
expr: sum(vm_tcplistener_conns) by(instance) > 1 expr: sum(vm_tcplistener_conns) by(instance) > 1

View file

@ -2,6 +2,7 @@ package datasource
import ( import (
"context" "context"
"net/url"
"time" "time"
) )
@ -20,8 +21,7 @@ type QuerierBuilder interface {
type QuerierParams struct { type QuerierParams struct {
DataSourceType *Type DataSourceType *Type
EvaluationInterval time.Duration EvaluationInterval time.Duration
// see https://docs.victoriametrics.com/#prometheus-querying-api-enhancements QueryParams url.Values
ExtraLabels map[string]string
} }
// Metric is the basic entity which should be return by datasource // Metric is the basic entity which should be return by datasource

View file

@ -4,6 +4,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strings" "strings"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
@ -40,9 +41,9 @@ type Param struct {
} }
// Init creates a Querier from provided flag values. // Init creates a Querier from provided flag values.
// Provided extraParams will be added as GET params to // Provided extraParams will be added as GET params for
// each request. // each request.
func Init(extraParams []Param) (QuerierBuilder, error) { func Init(extraParams url.Values) (QuerierBuilder, error) {
if *addr == "" { if *addr == "" {
return nil, fmt.Errorf("datasource.url is empty") return nil, fmt.Errorf("datasource.url is empty")
} }
@ -56,11 +57,11 @@ func Init(extraParams []Param) (QuerierBuilder, error) {
tr.MaxIdleConns = tr.MaxIdleConnsPerHost tr.MaxIdleConns = tr.MaxIdleConnsPerHost
} }
if extraParams == nil {
extraParams = url.Values{}
}
if *roundDigits > 0 { if *roundDigits > 0 {
extraParams = append(extraParams, Param{ extraParams.Set("round_digits", fmt.Sprintf("%d", *roundDigits))
Key: "round_digits",
Value: fmt.Sprintf("%d", *roundDigits),
})
} }
authCfg, err := utils.AuthConfig(*basicAuthUsername, *basicAuthPassword, *basicAuthPasswordFile, *bearerToken, *bearerTokenFile) authCfg, err := utils.AuthConfig(*basicAuthUsername, *basicAuthPassword, *basicAuthPasswordFile, *bearerToken, *bearerTokenFile)

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url"
"strings" "strings"
"time" "time"
@ -22,8 +23,7 @@ type VMStorage struct {
dataSourceType Type dataSourceType Type
evaluationInterval time.Duration evaluationInterval time.Duration
extraLabels []string extraParams url.Values
extraParams []Param
disablePathAppend bool disablePathAppend bool
} }
@ -47,9 +47,7 @@ func (s *VMStorage) ApplyParams(params QuerierParams) *VMStorage {
s.dataSourceType = *params.DataSourceType s.dataSourceType = *params.DataSourceType
} }
s.evaluationInterval = params.EvaluationInterval s.evaluationInterval = params.EvaluationInterval
for k, v := range params.ExtraLabels { s.extraParams = params.QueryParams
s.extraLabels = append(s.extraLabels, fmt.Sprintf("%s=%s", k, v))
}
return s return s
} }

View file

@ -54,6 +54,14 @@ func (s *VMStorage) setGraphiteReqParams(r *http.Request, query string, timestam
} }
r.URL.Path += graphitePath r.URL.Path += graphitePath
q := r.URL.Query() q := r.URL.Query()
for k, vs := range s.extraParams {
if q.Has(k) { // extraParams are prior to params in URL
q.Del(k)
}
for _, v := range vs {
q.Add(k, v)
}
}
q.Set("format", "json") q.Set("format", "json")
q.Set("target", query) q.Set("target", query)
from := "-5min" from := "-5min"

View file

@ -150,6 +150,14 @@ func (s *VMStorage) setPrometheusRangeReqParams(r *http.Request, query string, s
func (s *VMStorage) setPrometheusReqParams(r *http.Request, query string) { func (s *VMStorage) setPrometheusReqParams(r *http.Request, query string) {
q := r.URL.Query() q := r.URL.Query()
for k, vs := range s.extraParams {
if q.Has(k) { // extraParams are prior to params in URL
q.Del(k)
}
for _, v := range vs {
q.Add(k, v)
}
}
q.Set("query", query) q.Set("query", query)
if s.evaluationInterval > 0 { if s.evaluationInterval > 0 {
// set step as evaluationInterval by default // set step as evaluationInterval by default
@ -159,11 +167,5 @@ func (s *VMStorage) setPrometheusReqParams(r *http.Request, query string) {
// override step with user-specified value // override step with user-specified value
q.Set("step", s.queryStep.String()) q.Set("step", s.queryStep.String())
} }
for _, l := range s.extraLabels {
q.Add("extra_label", l)
}
for _, p := range s.extraParams {
q.Add(p.Key, p.Value)
}
r.URL.RawQuery = q.Encode() r.URL.RawQuery = q.Encode()
} }

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
@ -440,10 +441,10 @@ func TestRequestParams(t *testing.T) {
}, },
}, },
{ {
"round digits", "prometheus extra params",
false, false,
&VMStorage{ &VMStorage{
extraParams: []Param{{"round_digits", "10"}}, extraParams: url.Values{"round_digits": {"10"}},
}, },
func(t *testing.T, r *http.Request) { func(t *testing.T, r *http.Request) {
exp := fmt.Sprintf("query=%s&round_digits=10&time=%d", query, timestamp.Unix()) exp := fmt.Sprintf("query=%s&round_digits=10&time=%d", query, timestamp.Unix())
@ -451,45 +452,32 @@ func TestRequestParams(t *testing.T) {
}, },
}, },
{ {
"extra labels", "prometheus extra params range",
false,
&VMStorage{
extraLabels: []string{
"env=prod",
"query=es=cape",
},
},
func(t *testing.T, r *http.Request) {
exp := fmt.Sprintf("extra_label=env%%3Dprod&extra_label=query%%3Des%%3Dcape&query=%s&time=%d", query, timestamp.Unix())
checkEqualString(t, exp, r.URL.RawQuery)
},
},
{
"extra labels range",
true, true,
&VMStorage{ &VMStorage{
extraLabels: []string{ extraParams: url.Values{
"env=prod", "nocache": {"1"},
"query=es=cape", "max_lookback": {"1h"},
}, },
}, },
func(t *testing.T, r *http.Request) { func(t *testing.T, r *http.Request) {
exp := fmt.Sprintf("end=%d&extra_label=env%%3Dprod&extra_label=query%%3Des%%3Dcape&query=%s&start=%d", exp := fmt.Sprintf("end=%d&max_lookback=1h&nocache=1&query=%s&start=%d",
timestamp.Unix(), query, timestamp.Unix()) timestamp.Unix(), query, timestamp.Unix())
checkEqualString(t, exp, r.URL.RawQuery) checkEqualString(t, exp, r.URL.RawQuery)
}, },
}, },
{ {
"extra params", "graphite extra params",
false, false,
&VMStorage{ &VMStorage{
extraParams: []Param{ dataSourceType: NewGraphiteType(),
{Key: "nocache", Value: "1"}, extraParams: url.Values{
{Key: "max_lookback", Value: "1h"}, "nocache": {"1"},
"max_lookback": {"1h"},
}, },
}, },
func(t *testing.T, r *http.Request) { func(t *testing.T, r *http.Request) {
exp := fmt.Sprintf("max_lookback=1h&nocache=1&query=%s&time=%d", query, timestamp.Unix()) exp := fmt.Sprintf("format=json&from=-5min&max_lookback=1h&nocache=1&target=%s&until=now", query)
checkEqualString(t, exp, r.URL.RawQuery) checkEqualString(t, exp, r.URL.RawQuery)
}, },
}, },
@ -519,7 +507,7 @@ func TestRequestParams(t *testing.T) {
func checkEqualString(t *testing.T, exp, got string) { func checkEqualString(t *testing.T, exp, got string) {
t.Helper() t.Helper()
if got != exp { if got != exp {
t.Errorf("expected to get %q; got %q", exp, got) t.Errorf("expected to get: \n%q; \ngot: \n%q", exp, got)
} }
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"net/url"
"sync" "sync"
"time" "time"
@ -27,8 +28,8 @@ type Group struct {
Concurrency int Concurrency int
Checksum string Checksum string
ExtraFilterLabels map[string]string Labels map[string]string
Labels map[string]string Params url.Values
doneCh chan struct{} doneCh chan struct{}
finishedCh chan struct{} finishedCh chan struct{}
@ -71,14 +72,14 @@ func mergeLabels(groupName, ruleName string, set1, set2 map[string]string) map[s
func newGroup(cfg config.Group, qb datasource.QuerierBuilder, defaultInterval time.Duration, labels map[string]string) *Group { func newGroup(cfg config.Group, qb datasource.QuerierBuilder, defaultInterval time.Duration, labels map[string]string) *Group {
g := &Group{ g := &Group{
Type: cfg.Type, Type: cfg.Type,
Name: cfg.Name, Name: cfg.Name,
File: cfg.File, File: cfg.File,
Interval: cfg.Interval.Duration(), Interval: cfg.Interval.Duration(),
Concurrency: cfg.Concurrency, Concurrency: cfg.Concurrency,
Checksum: cfg.Checksum, Checksum: cfg.Checksum,
ExtraFilterLabels: cfg.ExtraFilterLabels, Params: cfg.Params,
Labels: cfg.Labels, Labels: cfg.Labels,
doneCh: make(chan struct{}), doneCh: make(chan struct{}),
finishedCh: make(chan struct{}), finishedCh: make(chan struct{}),
@ -198,7 +199,7 @@ func (g *Group) updateWith(newGroup *Group) error {
// group.Start function // group.Start function
g.Type = newGroup.Type g.Type = newGroup.Type
g.Concurrency = newGroup.Concurrency g.Concurrency = newGroup.Concurrency
g.ExtraFilterLabels = newGroup.ExtraFilterLabels g.Params = newGroup.Params
g.Labels = newGroup.Labels g.Labels = newGroup.Labels
g.Checksum = newGroup.Checksum g.Checksum = newGroup.Checksum
g.Rules = newRules g.Rules = newRules

View file

@ -104,8 +104,7 @@ func main() {
} }
// prevent queries from caching and boundaries aligning // prevent queries from caching and boundaries aligning
// when querying VictoriaMetrics datasource. // when querying VictoriaMetrics datasource.
noCache := datasource.Param{Key: "nocache", Value: "1"} q, err := datasource.Init(url.Values{"nocache": {"1"}})
q, err := datasource.Init([]datasource.Param{noCache})
if err != nil { if err != nil {
logger.Fatalf("failed to init datasource: %s", err) logger.Fatalf("failed to init datasource: %s", err)
} }

View file

@ -3,6 +3,8 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"sort"
"sync" "sync"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config" "github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/config"
@ -161,13 +163,13 @@ func (g *Group) toAPI() APIGroup {
// encode as string to avoid rounding // encode as string to avoid rounding
ID: fmt.Sprintf("%d", g.ID()), ID: fmt.Sprintf("%d", g.ID()),
Name: g.Name, Name: g.Name,
Type: g.Type.String(), Type: g.Type.String(),
File: g.File, File: g.File,
Interval: g.Interval.String(), Interval: g.Interval.String(),
Concurrency: g.Concurrency, Concurrency: g.Concurrency,
ExtraFilterLabels: g.ExtraFilterLabels, Params: urlValuesToStrings(g.Params),
Labels: g.Labels, Labels: g.Labels,
} }
for _, r := range g.Rules { for _, r := range g.Rules {
switch v := r.(type) { switch v := r.(type) {
@ -179,3 +181,24 @@ func (g *Group) toAPI() APIGroup {
} }
return ag return ag
} }
func urlValuesToStrings(values url.Values) []string {
if len(values) < 1 {
return nil
}
keys := make([]string, 0, len(values))
for k := range values {
keys = append(keys, k)
}
sort.Strings(keys)
var res []string
for _, k := range keys {
params := values[k]
for _, v := range params {
res = append(res, fmt.Sprintf("%s=%s", k, v))
}
}
return res
}

View file

@ -70,7 +70,7 @@ func newRecordingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rul
q: qb.BuildWithParams(datasource.QuerierParams{ q: qb.BuildWithParams(datasource.QuerierParams{
DataSourceType: &group.Type, DataSourceType: &group.Type,
EvaluationInterval: group.Interval, EvaluationInterval: group.Interval,
ExtraLabels: group.ExtraFilterLabels, QueryParams: group.Params,
}), }),
} }

View file

@ -54,10 +54,10 @@
{% if rNotOk[g.Name] > 0 %}<span class="badge bg-danger" title="Number of rules with status Error">{%d rNotOk[g.Name] %}</span> {% endif %} {% if rNotOk[g.Name] > 0 %}<span class="badge bg-danger" title="Number of rules with status Error">{%d rNotOk[g.Name] %}</span> {% endif %}
<span class="badge bg-success" title="Number of rules withs status Ok">{%d rOk[g.Name] %}</span> <span class="badge bg-success" title="Number of rules withs status Ok">{%d rOk[g.Name] %}</span>
<p class="fs-6 fw-lighter">{%s g.File %}</p> <p class="fs-6 fw-lighter">{%s g.File %}</p>
{% if len(g.ExtraFilterLabels) > 0 %} {% if len(g.Params) > 0 %}
<div class="fs-6 fw-lighter">Extra filter labels <div class="fs-6 fw-lighter">Extra params
{% for k, v := range g.ExtraFilterLabels %} {% for _, param := range g.Params %}
<span class="float-left badge bg-primary">{%s k %}={%s v %}</span> <span class="float-left badge bg-primary">{%s param %}</span>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}

View file

@ -211,22 +211,18 @@ func StreamListGroups(qw422016 *qt422016.Writer, groups []APIGroup) {
qw422016.N().S(`</p> qw422016.N().S(`</p>
`) `)
//line app/vmalert/web.qtpl:57 //line app/vmalert/web.qtpl:57
if len(g.ExtraFilterLabels) > 0 { if len(g.Params) > 0 {
//line app/vmalert/web.qtpl:57 //line app/vmalert/web.qtpl:57
qw422016.N().S(` qw422016.N().S(`
<div class="fs-6 fw-lighter">Extra filter labels <div class="fs-6 fw-lighter">Extra params
`) `)
//line app/vmalert/web.qtpl:59 //line app/vmalert/web.qtpl:59
for k, v := range g.ExtraFilterLabels { for _, param := range g.Params {
//line app/vmalert/web.qtpl:59 //line app/vmalert/web.qtpl:59
qw422016.N().S(` qw422016.N().S(`
<span class="float-left badge bg-primary">`) <span class="float-left badge bg-primary">`)
//line app/vmalert/web.qtpl:60 //line app/vmalert/web.qtpl:60
qw422016.E().S(k) qw422016.E().S(param)
//line app/vmalert/web.qtpl:60
qw422016.N().S(`=`)
//line app/vmalert/web.qtpl:60
qw422016.E().S(v)
//line app/vmalert/web.qtpl:60 //line app/vmalert/web.qtpl:60
qw422016.N().S(`</span> qw422016.N().S(`</span>
`) `)

View file

@ -23,16 +23,16 @@ type APIAlert struct {
// APIGroup represents Group for WEB view // APIGroup represents Group for WEB view
type APIGroup struct { type APIGroup struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
ID string `json:"id"` ID string `json:"id"`
File string `json:"file"` File string `json:"file"`
Interval string `json:"interval"` Interval string `json:"interval"`
Concurrency int `json:"concurrency"` Concurrency int `json:"concurrency"`
ExtraFilterLabels map[string]string `json:"extra_filter_labels"` Params []string `json:"params"`
Labels map[string]string `json:"labels,omitempty"` Labels map[string]string `json:"labels,omitempty"`
AlertingRules []APIAlertingRule `json:"alerting_rules"` AlertingRules []APIAlertingRule `json:"alerting_rules"`
RecordingRules []APIRecordingRule `json:"recording_rules"` RecordingRules []APIRecordingRule `json:"recording_rules"`
} }
// APIAlertingRule represents AlertingRule for WEB view // APIAlertingRule represents AlertingRule for WEB view