mirror of
https://github.com/prometheus/prometheus.git
synced 2025-08-06 22:27:17 +02:00
Merge pull request #5463 from prometheus/beorn7/templating
Follow-up on #5009
This commit is contained in:
commit
0be9388f8d
@ -443,7 +443,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
files = append(files, fs...)
|
files = append(files, fs...)
|
||||||
}
|
}
|
||||||
return ruleManager.Update(time.Duration(cfg.GlobalConfig.EvaluationInterval), files)
|
return ruleManager.Update(
|
||||||
|
time.Duration(cfg.GlobalConfig.EvaluationInterval),
|
||||||
|
files,
|
||||||
|
cfg.GlobalConfig.ExternalLabels,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,6 +751,7 @@ func reloadConfig(filename string, logger log.Logger, rls ...func(*config.Config
|
|||||||
if failed {
|
if failed {
|
||||||
return errors.Errorf("one or more errors occurred while applying the new configuration (--config.file=%q)", filename)
|
return errors.Errorf("one or more errors occurred while applying the new configuration (--config.file=%q)", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
promql.SetDefaultEvaluationInterval(time.Duration(conf.GlobalConfig.EvaluationInterval))
|
promql.SetDefaultEvaluationInterval(time.Duration(conf.GlobalConfig.EvaluationInterval))
|
||||||
level.Info(logger).Log("msg", "Completed loading of configuration file", "filename", filename)
|
level.Info(logger).Log("msg", "Completed loading of configuration file", "filename", filename)
|
||||||
return nil
|
return nil
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -42,12 +40,6 @@ func newPrometheusHTTPClient(serverURL string) (*prometheusHTTPClient, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *prometheusHTTPClient) do(req *http.Request) (*http.Response, []byte, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), c.requestTimeout)
|
|
||||||
defer cancel()
|
|
||||||
return c.httpClient.Do(ctx, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *prometheusHTTPClient) urlJoin(path string) string {
|
func (c *prometheusHTTPClient) urlJoin(path string) string {
|
||||||
return c.httpClient.URL(path, nil).String()
|
return c.httpClient.URL(path, nil).String()
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,8 @@ func (tg *testGroup) test(mint, maxt time.Time, evalInterval time.Duration, grou
|
|||||||
Logger: &dummyLogger{},
|
Logger: &dummyLogger{},
|
||||||
}
|
}
|
||||||
m := rules.NewManager(opts)
|
m := rules.NewManager(opts)
|
||||||
groupsMap, ers := m.LoadGroups(tg.Interval, ruleFiles...)
|
// TODO(beorn7): Provide a way to pass in external labels.
|
||||||
|
groupsMap, ers := m.LoadGroups(tg.Interval, nil, ruleFiles...)
|
||||||
if ers != nil {
|
if ers != nil {
|
||||||
return ers
|
return ers
|
||||||
}
|
}
|
||||||
|
@ -247,7 +247,7 @@ func (m *SDMock) HandleHypervisorListSuccessfully() {
|
|||||||
testHeader(m.t, r, "X-Auth-Token", tokenID)
|
testHeader(m.t, r, "X-Auth-Token", tokenID)
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
fmt.Fprintf(w, hypervisorListBody)
|
fmt.Fprint(w, hypervisorListBody)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,7 +544,7 @@ func (m *SDMock) HandleServerListSuccessfully() {
|
|||||||
testHeader(m.t, r, "X-Auth-Token", tokenID)
|
testHeader(m.t, r, "X-Auth-Token", tokenID)
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
fmt.Fprintf(w, serverListBody)
|
fmt.Fprint(w, serverListBody)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,6 +583,6 @@ func (m *SDMock) HandleFloatingIPListSuccessfully() {
|
|||||||
testHeader(m.t, r, "X-Auth-Token", tokenID)
|
testHeader(m.t, r, "X-Auth-Token", tokenID)
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
fmt.Fprintf(w, listOutput)
|
fmt.Fprint(w, listOutput)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,11 @@ The `annotations` clause specifies a set of informational labels that can be use
|
|||||||
|
|
||||||
#### Templating
|
#### Templating
|
||||||
|
|
||||||
Label and annotation values can be templated using [console templates](https://prometheus.io/docs/visualization/consoles).
|
Label and annotation values can be templated using [console
|
||||||
The `$labels` variable holds the label key/value pairs of an alert instance
|
templates](https://prometheus.io/docs/visualization/consoles). The `$labels`
|
||||||
and `$value` holds the evaluated value of an alert instance.
|
variable holds the label key/value pairs of an alert instance. The configured
|
||||||
|
external labels can be accessed via the `$externalLabels` variable. The
|
||||||
|
`$value` variable holds the evaluated value of an alert instance.
|
||||||
|
|
||||||
# To insert a firing element's label values:
|
# To insert a firing element's label values:
|
||||||
{{ $labels.<labelname> }}
|
{{ $labels.<labelname> }}
|
||||||
|
@ -89,8 +89,10 @@ parameterize templates, and have a few other differences.
|
|||||||
|
|
||||||
### Alert field templates
|
### Alert field templates
|
||||||
|
|
||||||
`.Value` and `.Labels` contain the alert value and labels. They are also exposed
|
`.Value`, `.Labels`, and `ExternalLabels` contain the alert value, the alert
|
||||||
as the `$value` and `$labels` variables for convenience.
|
labels, and the globally configured external labels, respectively. They are
|
||||||
|
also exposed as the `$value`, `$labels`, and `$externalLabels` variables for
|
||||||
|
convenience.
|
||||||
|
|
||||||
### Console templates
|
### Console templates
|
||||||
|
|
||||||
@ -104,7 +106,9 @@ auto-escaping. To bypass the auto-escaping use the `safe*` functions.,
|
|||||||
URL parameters are available as a map in `.Params`. To access multiple URL
|
URL parameters are available as a map in `.Params`. To access multiple URL
|
||||||
parameters by the same name, `.RawParams` is a map of the list values for each
|
parameters by the same name, `.RawParams` is a map of the list values for each
|
||||||
parameter. The URL path is available in `.Path`, excluding the `/consoles/`
|
parameter. The URL path is available in `.Path`, excluding the `/consoles/`
|
||||||
prefix.
|
prefix. The globally configured external labels are available as
|
||||||
|
`.ExternalLabels`. There are also convenience variables for all four:
|
||||||
|
`$rawParams`, `$params`, `$path`, and `$externalLabels`.
|
||||||
|
|
||||||
Consoles also have access to all the templates defined with `{{define
|
Consoles also have access to all the templates defined with `{{define
|
||||||
"templateName"}}...{{end}}` found in `*.lib` files in the directory pointed to
|
"templateName"}}...{{end}}` found in `*.lib` files in the directory pointed to
|
||||||
|
@ -16,6 +16,7 @@ package rulefmt
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -155,12 +156,16 @@ func testTemplateParsing(rl *Rule) (errs []error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trying to parse templates.
|
// Trying to parse templates.
|
||||||
tmplData := template.AlertTemplateData(make(map[string]string), 0)
|
tmplData := template.AlertTemplateData(map[string]string{}, map[string]string{}, 0)
|
||||||
defs := "{{$labels := .Labels}}{{$value := .Value}}"
|
defs := []string{
|
||||||
|
"{{$labels := .Labels}}",
|
||||||
|
"{{$externalLabels := .ExternalLabels}}",
|
||||||
|
"{{$value := .Value}}",
|
||||||
|
}
|
||||||
parseTest := func(text string) error {
|
parseTest := func(text string) error {
|
||||||
tmpl := template.NewTemplateExpander(
|
tmpl := template.NewTemplateExpander(
|
||||||
context.TODO(),
|
context.TODO(),
|
||||||
defs+text,
|
strings.Join(append(defs, text), ""),
|
||||||
"__alert_"+rl.Alert,
|
"__alert_"+rl.Alert,
|
||||||
tmplData,
|
tmplData,
|
||||||
model.Time(timestamp.FromTime(time.Now())),
|
model.Time(timestamp.FromTime(time.Now())),
|
||||||
|
@ -326,7 +326,6 @@ var errUnexpected = errors.New("unexpected error")
|
|||||||
// recover is the handler that turns panics into returns from the top level of Parse.
|
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||||
func (p *parser) recover(errp *error) {
|
func (p *parser) recover(errp *error) {
|
||||||
e := recover()
|
e := recover()
|
||||||
if e != nil {
|
|
||||||
if _, ok := e.(runtime.Error); ok {
|
if _, ok := e.(runtime.Error); ok {
|
||||||
// Print the stack trace but do not inhibit the running application.
|
// Print the stack trace but do not inhibit the running application.
|
||||||
buf := make([]byte, 64<<10)
|
buf := make([]byte, 64<<10)
|
||||||
@ -334,10 +333,9 @@ func (p *parser) recover(errp *error) {
|
|||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "parser panic: %v\n%s", e, buf)
|
fmt.Fprintf(os.Stderr, "parser panic: %v\n%s", e, buf)
|
||||||
*errp = errUnexpected
|
*errp = errUnexpected
|
||||||
} else {
|
} else if e != nil {
|
||||||
*errp = e.(error)
|
*errp = e.(error)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
p.lex.close()
|
p.lex.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -119,6 +120,8 @@ type AlertingRule struct {
|
|||||||
labels labels.Labels
|
labels labels.Labels
|
||||||
// Non-identifying key/value pairs.
|
// Non-identifying key/value pairs.
|
||||||
annotations labels.Labels
|
annotations labels.Labels
|
||||||
|
// External labels from the global config.
|
||||||
|
externalLabels map[string]string
|
||||||
// true if old state has been restored. We start persisting samples for ALERT_FOR_STATE
|
// true if old state has been restored. We start persisting samples for ALERT_FOR_STATE
|
||||||
// only after the restoration.
|
// only after the restoration.
|
||||||
restored bool
|
restored bool
|
||||||
@ -140,13 +143,23 @@ type AlertingRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewAlertingRule constructs a new AlertingRule.
|
// NewAlertingRule constructs a new AlertingRule.
|
||||||
func NewAlertingRule(name string, vec promql.Expr, hold time.Duration, lbls, anns labels.Labels, restored bool, logger log.Logger) *AlertingRule {
|
func NewAlertingRule(
|
||||||
|
name string, vec promql.Expr, hold time.Duration,
|
||||||
|
labels, annotations, externalLabels labels.Labels,
|
||||||
|
restored bool, logger log.Logger,
|
||||||
|
) *AlertingRule {
|
||||||
|
el := make(map[string]string, len(externalLabels))
|
||||||
|
for _, lbl := range externalLabels {
|
||||||
|
el[lbl.Name] = lbl.Value
|
||||||
|
}
|
||||||
|
|
||||||
return &AlertingRule{
|
return &AlertingRule{
|
||||||
name: name,
|
name: name,
|
||||||
vector: vec,
|
vector: vec,
|
||||||
holdDuration: hold,
|
holdDuration: hold,
|
||||||
labels: lbls,
|
labels: labels,
|
||||||
annotations: anns,
|
annotations: annotations,
|
||||||
|
externalLabels: el,
|
||||||
health: HealthUnknown,
|
health: HealthUnknown,
|
||||||
active: map[uint64]*Alert{},
|
active: map[uint64]*Alert{},
|
||||||
logger: logger,
|
logger: logger,
|
||||||
@ -305,15 +318,19 @@ func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc,
|
|||||||
l[lbl.Name] = lbl.Value
|
l[lbl.Name] = lbl.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
tmplData := template.AlertTemplateData(l, smpl.V)
|
tmplData := template.AlertTemplateData(l, r.externalLabels, smpl.V)
|
||||||
// Inject some convenience variables that are easier to remember for users
|
// Inject some convenience variables that are easier to remember for users
|
||||||
// who are not used to Go's templating system.
|
// who are not used to Go's templating system.
|
||||||
defs := "{{$labels := .Labels}}{{$value := .Value}}"
|
defs := []string{
|
||||||
|
"{{$labels := .Labels}}",
|
||||||
|
"{{$externalLabels := .ExternalLabels}}",
|
||||||
|
"{{$value := .Value}}",
|
||||||
|
}
|
||||||
|
|
||||||
expand := func(text string) string {
|
expand := func(text string) string {
|
||||||
tmpl := template.NewTemplateExpander(
|
tmpl := template.NewTemplateExpander(
|
||||||
ctx,
|
ctx,
|
||||||
defs+text,
|
strings.Join(append(defs, text), ""),
|
||||||
"__alert_"+r.Name(),
|
"__alert_"+r.Name(),
|
||||||
tmplData,
|
tmplData,
|
||||||
model.Time(timestamp.FromTime(ts)),
|
model.Time(timestamp.FromTime(ts)),
|
||||||
@ -449,7 +466,7 @@ func (r *AlertingRule) ForEachActiveAlert(f func(*Alert)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *AlertingRule) sendAlerts(ctx context.Context, ts time.Time, resendDelay time.Duration, interval time.Duration, notifyFunc NotifyFunc) {
|
func (r *AlertingRule) sendAlerts(ctx context.Context, ts time.Time, resendDelay time.Duration, interval time.Duration, notifyFunc NotifyFunc) {
|
||||||
alerts := make([]*Alert, 0)
|
alerts := []*Alert{}
|
||||||
r.ForEachActiveAlert(func(alert *Alert) {
|
r.ForEachActiveAlert(func(alert *Alert) {
|
||||||
if alert.needsSending(ts, resendDelay) {
|
if alert.needsSending(ts, resendDelay) {
|
||||||
alert.LastSentAt = ts
|
alert.LastSentAt = ts
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-kit/kit/log"
|
||||||
"github.com/prometheus/prometheus/pkg/labels"
|
"github.com/prometheus/prometheus/pkg/labels"
|
||||||
"github.com/prometheus/prometheus/pkg/timestamp"
|
"github.com/prometheus/prometheus/pkg/timestamp"
|
||||||
"github.com/prometheus/prometheus/promql"
|
"github.com/prometheus/prometheus/promql"
|
||||||
@ -26,7 +27,7 @@ import (
|
|||||||
func TestAlertingRuleHTMLSnippet(t *testing.T) {
|
func TestAlertingRuleHTMLSnippet(t *testing.T) {
|
||||||
expr, err := promql.ParseExpr(`foo{html="<b>BOLD<b>"}`)
|
expr, err := promql.ParseExpr(`foo{html="<b>BOLD<b>"}`)
|
||||||
testutil.Ok(t, err)
|
testutil.Ok(t, err)
|
||||||
rule := NewAlertingRule("testrule", expr, 0, labels.FromStrings("html", "<b>BOLD</b>"), labels.FromStrings("html", "<b>BOLD</b>"), false, nil)
|
rule := NewAlertingRule("testrule", expr, 0, labels.FromStrings("html", "<b>BOLD</b>"), labels.FromStrings("html", "<b>BOLD</b>"), nil, false, nil)
|
||||||
|
|
||||||
const want = `alert: <a href="/test/prefix/graph?g0.expr=ALERTS%7Balertname%3D%22testrule%22%7D&g0.tab=1">testrule</a>
|
const want = `alert: <a href="/test/prefix/graph?g0.expr=ALERTS%7Balertname%3D%22testrule%22%7D&g0.tab=1">testrule</a>
|
||||||
expr: <a href="/test/prefix/graph?g0.expr=foo%7Bhtml%3D%22%3Cb%3EBOLD%3Cb%3E%22%7D&g0.tab=1">foo{html="<b>BOLD<b>"}</a>
|
expr: <a href="/test/prefix/graph?g0.expr=foo%7Bhtml%3D%22%3Cb%3EBOLD%3Cb%3E%22%7D&g0.tab=1">foo{html="<b>BOLD<b>"}</a>
|
||||||
@ -62,7 +63,7 @@ func TestAlertingRuleLabelsUpdate(t *testing.T) {
|
|||||||
// If an alert is going back and forth between two label values it will never fire.
|
// If an alert is going back and forth between two label values it will never fire.
|
||||||
// Instead, you should write two alerts with constant labels.
|
// Instead, you should write two alerts with constant labels.
|
||||||
labels.FromStrings("severity", "{{ if lt $value 80.0 }}critical{{ else }}warning{{ end }}"),
|
labels.FromStrings("severity", "{{ if lt $value 80.0 }}critical{{ else }}warning{{ end }}"),
|
||||||
nil, true, nil,
|
nil, nil, true, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
results := []promql.Vector{
|
results := []promql.Vector{
|
||||||
@ -142,3 +143,96 @@ func TestAlertingRuleLabelsUpdate(t *testing.T) {
|
|||||||
testutil.Equals(t, result, filteredRes)
|
testutil.Equals(t, result, filteredRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAlertingRuleExternalLabelsInTemplate(t *testing.T) {
|
||||||
|
suite, err := promql.NewTest(t, `
|
||||||
|
load 1m
|
||||||
|
http_requests{job="app-server", instance="0"} 75 85 70 70
|
||||||
|
`)
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
defer suite.Close()
|
||||||
|
|
||||||
|
err = suite.Run()
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
|
||||||
|
expr, err := promql.ParseExpr(`http_requests < 100`)
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
|
||||||
|
ruleWithoutExternalLabels := NewAlertingRule(
|
||||||
|
"ExternalLabelDoesNotExist",
|
||||||
|
expr,
|
||||||
|
time.Minute,
|
||||||
|
labels.FromStrings("templated_label", "There are {{ len $externalLabels }} external Labels, of which foo is {{ $externalLabels.foo }}."),
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
true, log.NewNopLogger(),
|
||||||
|
)
|
||||||
|
ruleWithExternalLabels := NewAlertingRule(
|
||||||
|
"ExternalLabelExists",
|
||||||
|
expr,
|
||||||
|
time.Minute,
|
||||||
|
labels.FromStrings("templated_label", "There are {{ len $externalLabels }} external Labels, of which foo is {{ $externalLabels.foo }}."),
|
||||||
|
nil,
|
||||||
|
labels.FromStrings("foo", "bar", "dings", "bums"),
|
||||||
|
true, log.NewNopLogger(),
|
||||||
|
)
|
||||||
|
result := promql.Vector{
|
||||||
|
{
|
||||||
|
Metric: labels.FromStrings(
|
||||||
|
"__name__", "ALERTS",
|
||||||
|
"alertname", "ExternalLabelDoesNotExist",
|
||||||
|
"alertstate", "pending",
|
||||||
|
"instance", "0",
|
||||||
|
"job", "app-server",
|
||||||
|
"templated_label", "There are 0 external Labels, of which foo is .",
|
||||||
|
),
|
||||||
|
Point: promql.Point{V: 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Metric: labels.FromStrings(
|
||||||
|
"__name__", "ALERTS",
|
||||||
|
"alertname", "ExternalLabelExists",
|
||||||
|
"alertstate", "pending",
|
||||||
|
"instance", "0",
|
||||||
|
"job", "app-server",
|
||||||
|
"templated_label", "There are 2 external Labels, of which foo is bar.",
|
||||||
|
),
|
||||||
|
Point: promql.Point{V: 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
evalTime := time.Unix(0, 0)
|
||||||
|
result[0].Point.T = timestamp.FromTime(evalTime)
|
||||||
|
result[1].Point.T = timestamp.FromTime(evalTime)
|
||||||
|
|
||||||
|
var filteredRes promql.Vector // After removing 'ALERTS_FOR_STATE' samples.
|
||||||
|
res, err := ruleWithoutExternalLabels.Eval(
|
||||||
|
suite.Context(), evalTime, EngineQueryFunc(suite.QueryEngine(), suite.Storage()), nil,
|
||||||
|
)
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
for _, smpl := range res {
|
||||||
|
smplName := smpl.Metric.Get("__name__")
|
||||||
|
if smplName == "ALERTS" {
|
||||||
|
filteredRes = append(filteredRes, smpl)
|
||||||
|
} else {
|
||||||
|
// If not 'ALERTS', it has to be 'ALERTS_FOR_STATE'.
|
||||||
|
testutil.Equals(t, smplName, "ALERTS_FOR_STATE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = ruleWithExternalLabels.Eval(
|
||||||
|
suite.Context(), evalTime, EngineQueryFunc(suite.QueryEngine(), suite.Storage()), nil,
|
||||||
|
)
|
||||||
|
testutil.Ok(t, err)
|
||||||
|
for _, smpl := range res {
|
||||||
|
smplName := smpl.Metric.Get("__name__")
|
||||||
|
if smplName == "ALERTS" {
|
||||||
|
filteredRes = append(filteredRes, smpl)
|
||||||
|
} else {
|
||||||
|
// If not 'ALERTS', it has to be 'ALERTS_FOR_STATE'.
|
||||||
|
testutil.Equals(t, smplName, "ALERTS_FOR_STATE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testutil.Equals(t, result, filteredRes)
|
||||||
|
}
|
||||||
|
@ -751,11 +751,11 @@ func (m *Manager) Stop() {
|
|||||||
|
|
||||||
// Update the rule manager's state as the config requires. If
|
// Update the rule manager's state as the config requires. If
|
||||||
// loading the new rules failed the old rule set is restored.
|
// loading the new rules failed the old rule set is restored.
|
||||||
func (m *Manager) Update(interval time.Duration, files []string) error {
|
func (m *Manager) Update(interval time.Duration, files []string, externalLabels labels.Labels) error {
|
||||||
m.mtx.Lock()
|
m.mtx.Lock()
|
||||||
defer m.mtx.Unlock()
|
defer m.mtx.Unlock()
|
||||||
|
|
||||||
groups, errs := m.LoadGroups(interval, files...)
|
groups, errs := m.LoadGroups(interval, externalLabels, files...)
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
for _, e := range errs {
|
for _, e := range errs {
|
||||||
level.Error(m.logger).Log("msg", "loading groups failed", "err", e)
|
level.Error(m.logger).Log("msg", "loading groups failed", "err", e)
|
||||||
@ -803,7 +803,9 @@ func (m *Manager) Update(interval time.Duration, files []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadGroups reads groups from a list of files.
|
// LoadGroups reads groups from a list of files.
|
||||||
func (m *Manager) LoadGroups(interval time.Duration, filenames ...string) (map[string]*Group, []error) {
|
func (m *Manager) LoadGroups(
|
||||||
|
interval time.Duration, externalLabels labels.Labels, filenames ...string,
|
||||||
|
) (map[string]*Group, []error) {
|
||||||
groups := make(map[string]*Group)
|
groups := make(map[string]*Group)
|
||||||
|
|
||||||
shouldRestore := !m.restored
|
shouldRestore := !m.restored
|
||||||
@ -834,6 +836,7 @@ func (m *Manager) LoadGroups(interval time.Duration, filenames ...string) (map[s
|
|||||||
time.Duration(r.For),
|
time.Duration(r.For),
|
||||||
labels.FromMap(r.Labels),
|
labels.FromMap(r.Labels),
|
||||||
labels.FromMap(r.Annotations),
|
labels.FromMap(r.Annotations),
|
||||||
|
externalLabels,
|
||||||
m.restored,
|
m.restored,
|
||||||
log.With(m.logger, "alert", r.Alert),
|
log.With(m.logger, "alert", r.Alert),
|
||||||
))
|
))
|
||||||
|
@ -51,7 +51,7 @@ func TestAlertingRule(t *testing.T) {
|
|||||||
expr,
|
expr,
|
||||||
time.Minute,
|
time.Minute,
|
||||||
labels.FromStrings("severity", "{{\"c\"}}ritical"),
|
labels.FromStrings("severity", "{{\"c\"}}ritical"),
|
||||||
nil, true, nil,
|
nil, nil, true, nil,
|
||||||
)
|
)
|
||||||
result := promql.Vector{
|
result := promql.Vector{
|
||||||
{
|
{
|
||||||
@ -192,7 +192,7 @@ func TestForStateAddSamples(t *testing.T) {
|
|||||||
expr,
|
expr,
|
||||||
time.Minute,
|
time.Minute,
|
||||||
labels.FromStrings("severity", "{{\"c\"}}ritical"),
|
labels.FromStrings("severity", "{{\"c\"}}ritical"),
|
||||||
nil, true, nil,
|
nil, nil, true, nil,
|
||||||
)
|
)
|
||||||
result := promql.Vector{
|
result := promql.Vector{
|
||||||
{
|
{
|
||||||
@ -366,7 +366,7 @@ func TestForStateRestore(t *testing.T) {
|
|||||||
expr,
|
expr,
|
||||||
alertForDuration,
|
alertForDuration,
|
||||||
labels.FromStrings("severity", "critical"),
|
labels.FromStrings("severity", "critical"),
|
||||||
nil, true, nil,
|
nil, nil, true, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
group := NewGroup("default", "", time.Second, []Rule{rule}, true, opts)
|
group := NewGroup("default", "", time.Second, []Rule{rule}, true, opts)
|
||||||
@ -426,7 +426,7 @@ func TestForStateRestore(t *testing.T) {
|
|||||||
expr,
|
expr,
|
||||||
alertForDuration,
|
alertForDuration,
|
||||||
labels.FromStrings("severity", "critical"),
|
labels.FromStrings("severity", "critical"),
|
||||||
nil, false, nil,
|
nil, nil, false, nil,
|
||||||
)
|
)
|
||||||
newGroup := NewGroup("default", "", time.Second, []Rule{newRule}, true, opts)
|
newGroup := NewGroup("default", "", time.Second, []Rule{newRule}, true, opts)
|
||||||
|
|
||||||
@ -581,12 +581,12 @@ func readSeriesSet(ss storage.SeriesSet) (map[string][]promql.Point, error) {
|
|||||||
func TestCopyState(t *testing.T) {
|
func TestCopyState(t *testing.T) {
|
||||||
oldGroup := &Group{
|
oldGroup := &Group{
|
||||||
rules: []Rule{
|
rules: []Rule{
|
||||||
NewAlertingRule("alert", nil, 0, nil, nil, true, nil),
|
NewAlertingRule("alert", nil, 0, nil, nil, nil, true, nil),
|
||||||
NewRecordingRule("rule1", nil, nil),
|
NewRecordingRule("rule1", nil, nil),
|
||||||
NewRecordingRule("rule2", nil, nil),
|
NewRecordingRule("rule2", nil, nil),
|
||||||
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v1"}}),
|
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v1"}}),
|
||||||
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v2"}}),
|
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v2"}}),
|
||||||
NewAlertingRule("alert2", nil, 0, labels.Labels{{Name: "l2", Value: "v1"}}, nil, true, nil),
|
NewAlertingRule("alert2", nil, 0, labels.Labels{{Name: "l2", Value: "v1"}}, nil, nil, true, nil),
|
||||||
},
|
},
|
||||||
seriesInPreviousEval: []map[string]labels.Labels{
|
seriesInPreviousEval: []map[string]labels.Labels{
|
||||||
{"a": nil},
|
{"a": nil},
|
||||||
@ -604,10 +604,10 @@ func TestCopyState(t *testing.T) {
|
|||||||
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v0"}}),
|
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v0"}}),
|
||||||
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v1"}}),
|
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v1"}}),
|
||||||
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v2"}}),
|
NewRecordingRule("rule3", nil, labels.Labels{{Name: "l1", Value: "v2"}}),
|
||||||
NewAlertingRule("alert", nil, 0, nil, nil, true, nil),
|
NewAlertingRule("alert", nil, 0, nil, nil, nil, true, nil),
|
||||||
NewRecordingRule("rule1", nil, nil),
|
NewRecordingRule("rule1", nil, nil),
|
||||||
NewAlertingRule("alert2", nil, 0, labels.Labels{{Name: "l2", Value: "v0"}}, nil, true, nil),
|
NewAlertingRule("alert2", nil, 0, labels.Labels{{Name: "l2", Value: "v0"}}, nil, nil, true, nil),
|
||||||
NewAlertingRule("alert2", nil, 0, labels.Labels{{Name: "l2", Value: "v1"}}, nil, true, nil),
|
NewAlertingRule("alert2", nil, 0, labels.Labels{{Name: "l2", Value: "v1"}}, nil, nil, true, nil),
|
||||||
NewRecordingRule("rule4", nil, nil),
|
NewRecordingRule("rule4", nil, nil),
|
||||||
},
|
},
|
||||||
seriesInPreviousEval: make([]map[string]labels.Labels, 8),
|
seriesInPreviousEval: make([]map[string]labels.Labels, 8),
|
||||||
@ -654,7 +654,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
ruleManager.Run()
|
ruleManager.Run()
|
||||||
defer ruleManager.Stop()
|
defer ruleManager.Stop()
|
||||||
|
|
||||||
err := ruleManager.Update(10*time.Second, files)
|
err := ruleManager.Update(10*time.Second, files, nil)
|
||||||
testutil.Ok(t, err)
|
testutil.Ok(t, err)
|
||||||
testutil.Assert(t, len(ruleManager.groups) > 0, "expected non-empty rule groups")
|
testutil.Assert(t, len(ruleManager.groups) > 0, "expected non-empty rule groups")
|
||||||
for _, g := range ruleManager.groups {
|
for _, g := range ruleManager.groups {
|
||||||
@ -663,7 +663,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ruleManager.Update(10*time.Second, files)
|
err = ruleManager.Update(10*time.Second, files, nil)
|
||||||
testutil.Ok(t, err)
|
testutil.Ok(t, err)
|
||||||
for _, g := range ruleManager.groups {
|
for _, g := range ruleManager.groups {
|
||||||
for _, actual := range g.seriesInPreviousEval {
|
for _, actual := range g.seriesInPreviousEval {
|
||||||
@ -699,7 +699,7 @@ func TestNotify(t *testing.T) {
|
|||||||
|
|
||||||
expr, err := promql.ParseExpr("a > 1")
|
expr, err := promql.ParseExpr("a > 1")
|
||||||
testutil.Ok(t, err)
|
testutil.Ok(t, err)
|
||||||
rule := NewAlertingRule("aTooHigh", expr, 0, labels.Labels{}, labels.Labels{}, true, log.NewNopLogger())
|
rule := NewAlertingRule("aTooHigh", expr, 0, labels.Labels{}, labels.Labels{}, nil, true, log.NewNopLogger())
|
||||||
group := NewGroup("alert", "", time.Second, []Rule{rule}, true, opts)
|
group := NewGroup("alert", "", time.Second, []Rule{rule}, true, opts)
|
||||||
|
|
||||||
app, _ := storage.Appender()
|
app, _ := storage.Appender()
|
||||||
|
@ -28,9 +28,9 @@ import (
|
|||||||
text_template "text/template"
|
text_template "text/template"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/promql"
|
"github.com/prometheus/prometheus/promql"
|
||||||
"github.com/prometheus/prometheus/util/strutil"
|
"github.com/prometheus/prometheus/util/strutil"
|
||||||
)
|
)
|
||||||
@ -261,12 +261,14 @@ func NewTemplateExpander(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AlertTemplateData returns the interface to be used in expanding the template.
|
// AlertTemplateData returns the interface to be used in expanding the template.
|
||||||
func AlertTemplateData(labels map[string]string, value float64) interface{} {
|
func AlertTemplateData(labels map[string]string, externalLabels map[string]string, value float64) interface{} {
|
||||||
return struct {
|
return struct {
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
|
ExternalLabels map[string]string
|
||||||
Value float64
|
Value float64
|
||||||
}{
|
}{
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
|
ExternalLabels: externalLabels,
|
||||||
Value: value,
|
Value: value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,6 +142,7 @@ func (m rulesRetrieverMock) AlertingRules() []*rules.AlertingRule {
|
|||||||
time.Second,
|
time.Second,
|
||||||
labels.Labels{},
|
labels.Labels{},
|
||||||
labels.Labels{},
|
labels.Labels{},
|
||||||
|
labels.Labels{},
|
||||||
true,
|
true,
|
||||||
log.NewNopLogger(),
|
log.NewNopLogger(),
|
||||||
)
|
)
|
||||||
@ -151,6 +152,7 @@ func (m rulesRetrieverMock) AlertingRules() []*rules.AlertingRule {
|
|||||||
time.Second,
|
time.Second,
|
||||||
labels.Labels{},
|
labels.Labels{},
|
||||||
labels.Labels{},
|
labels.Labels{},
|
||||||
|
labels.Labels{},
|
||||||
true,
|
true,
|
||||||
log.NewNopLogger(),
|
log.NewNopLogger(),
|
||||||
)
|
)
|
||||||
|
@ -28,7 +28,6 @@ import (
|
|||||||
|
|
||||||
var scenarios = map[string]struct {
|
var scenarios = map[string]struct {
|
||||||
params string
|
params string
|
||||||
accept string
|
|
||||||
externalLabels labels.Labels
|
externalLabels labels.Labels
|
||||||
code int
|
code int
|
||||||
body string
|
body string
|
||||||
|
22
web/web.go
22
web/web.go
@ -541,19 +541,39 @@ func (h *Handler) consoles(w http.ResponseWriter, r *http.Request) {
|
|||||||
for k, v := range rawParams {
|
for k, v := range rawParams {
|
||||||
params[k] = v[0]
|
params[k] = v[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalLabels := map[string]string{}
|
||||||
|
h.mtx.RLock()
|
||||||
|
els := h.config.GlobalConfig.ExternalLabels
|
||||||
|
h.mtx.RUnlock()
|
||||||
|
for _, el := range els {
|
||||||
|
externalLabels[el.Name] = el.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject some convenience variables that are easier to remember for users
|
||||||
|
// who are not used to Go's templating system.
|
||||||
|
defs := []string{
|
||||||
|
"{{$rawParams := .RawParams }}",
|
||||||
|
"{{$params := .Params}}",
|
||||||
|
"{{$path := .Path}}",
|
||||||
|
"{{$externalLabels := .ExternalLabels}}",
|
||||||
|
}
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
RawParams url.Values
|
RawParams url.Values
|
||||||
Params map[string]string
|
Params map[string]string
|
||||||
Path string
|
Path string
|
||||||
|
ExternalLabels map[string]string
|
||||||
}{
|
}{
|
||||||
RawParams: rawParams,
|
RawParams: rawParams,
|
||||||
Params: params,
|
Params: params,
|
||||||
Path: strings.TrimLeft(name, "/"),
|
Path: strings.TrimLeft(name, "/"),
|
||||||
|
ExternalLabels: externalLabels,
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl := template.NewTemplateExpander(
|
tmpl := template.NewTemplateExpander(
|
||||||
h.context,
|
h.context,
|
||||||
string(text),
|
strings.Join(append(defs, string(text)), ""),
|
||||||
"__console_"+name,
|
"__console_"+name,
|
||||||
data,
|
data,
|
||||||
h.now(),
|
h.now(),
|
||||||
|
Loading…
Reference in New Issue
Block a user