mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-13 06:47:28 -08:00
rule: allow merging labels from group level
Support merging labels from groups to rule labels Signed-off-by: Seena Fallah <seenafallah@gmail.com>
This commit is contained in:
parent
d4f098ae80
commit
f253d36361
|
@ -58,6 +58,7 @@ import (
|
||||||
_ "github.com/prometheus/prometheus/plugins" // Register plugins.
|
_ "github.com/prometheus/prometheus/plugins" // Register plugins.
|
||||||
"github.com/prometheus/prometheus/promql/parser"
|
"github.com/prometheus/prometheus/promql/parser"
|
||||||
"github.com/prometheus/prometheus/promql/promqltest"
|
"github.com/prometheus/prometheus/promql/promqltest"
|
||||||
|
"github.com/prometheus/prometheus/rules"
|
||||||
"github.com/prometheus/prometheus/scrape"
|
"github.com/prometheus/prometheus/scrape"
|
||||||
"github.com/prometheus/prometheus/util/documentcli"
|
"github.com/prometheus/prometheus/util/documentcli"
|
||||||
)
|
)
|
||||||
|
@ -889,30 +890,30 @@ func compare(a, b compareRuleType) int {
|
||||||
|
|
||||||
func checkDuplicates(groups []rulefmt.RuleGroup) []compareRuleType {
|
func checkDuplicates(groups []rulefmt.RuleGroup) []compareRuleType {
|
||||||
var duplicates []compareRuleType
|
var duplicates []compareRuleType
|
||||||
var rules compareRuleTypes
|
var cRules compareRuleTypes
|
||||||
|
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
for _, rule := range group.Rules {
|
for _, rule := range group.Rules {
|
||||||
rules = append(rules, compareRuleType{
|
cRules = append(cRules, compareRuleType{
|
||||||
metric: ruleMetric(rule),
|
metric: ruleMetric(rule),
|
||||||
label: labels.FromMap(rule.Labels),
|
label: rules.FromMaps(group.Labels, rule.Labels),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rules) < 2 {
|
if len(cRules) < 2 {
|
||||||
return duplicates
|
return duplicates
|
||||||
}
|
}
|
||||||
sort.Sort(rules)
|
sort.Sort(cRules)
|
||||||
|
|
||||||
last := rules[0]
|
last := cRules[0]
|
||||||
for i := 1; i < len(rules); i++ {
|
for i := 1; i < len(cRules); i++ {
|
||||||
if compare(last, rules[i]) == 0 {
|
if compare(last, cRules[i]) == 0 {
|
||||||
// Don't add a duplicated rule multiple times.
|
// Don't add a duplicated rule multiple times.
|
||||||
if len(duplicates) == 0 || compare(last, duplicates[len(duplicates)-1]) != 0 {
|
if len(duplicates) == 0 || compare(last, duplicates[len(duplicates)-1]) != 0 {
|
||||||
duplicates = append(duplicates, rules[i])
|
duplicates = append(duplicates, cRules[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last = rules[i]
|
last = cRules[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
return duplicates
|
return duplicates
|
||||||
|
|
|
@ -21,6 +21,8 @@ An example rules file with an alert would be:
|
||||||
```yaml
|
```yaml
|
||||||
groups:
|
groups:
|
||||||
- name: example
|
- name: example
|
||||||
|
labels:
|
||||||
|
team: myteam
|
||||||
rules:
|
rules:
|
||||||
- alert: HighRequestLatency
|
- alert: HighRequestLatency
|
||||||
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5
|
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5
|
||||||
|
|
|
@ -89,6 +89,11 @@ name: <string>
|
||||||
# Offset the rule evaluation timestamp of this particular group by the specified duration into the past.
|
# Offset the rule evaluation timestamp of this particular group by the specified duration into the past.
|
||||||
[ query_offset: <duration> | default = global.rule_query_offset ]
|
[ query_offset: <duration> | default = global.rule_query_offset ]
|
||||||
|
|
||||||
|
# Labels to add or overwrite before storing the result for its rules.
|
||||||
|
# Labels defined in <rule> will override the key if it has a collision.
|
||||||
|
labels:
|
||||||
|
[ <labelname>: <labelvalue> ]
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
[ - <rule> ... ]
|
[ - <rule> ... ]
|
||||||
```
|
```
|
||||||
|
|
|
@ -111,6 +111,20 @@ func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for k, v := range g.Labels {
|
||||||
|
if !model.LabelName(k).IsValid() || k == model.MetricNameLabel {
|
||||||
|
errs = append(
|
||||||
|
errs, fmt.Errorf("invalid label name: %s", k),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !model.LabelValue(v).IsValid() {
|
||||||
|
errs = append(
|
||||||
|
errs, fmt.Errorf("invalid label value: %s", v),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
set[g.Name] = struct{}{}
|
set[g.Name] = struct{}{}
|
||||||
|
|
||||||
for i, r := range g.Rules {
|
for i, r := range g.Rules {
|
||||||
|
@ -136,11 +150,12 @@ func (g *RuleGroups) Validate(node ruleGroups) (errs []error) {
|
||||||
|
|
||||||
// RuleGroup is a list of sequentially evaluated recording and alerting rules.
|
// RuleGroup is a list of sequentially evaluated recording and alerting rules.
|
||||||
type RuleGroup struct {
|
type RuleGroup struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Interval model.Duration `yaml:"interval,omitempty"`
|
Interval model.Duration `yaml:"interval,omitempty"`
|
||||||
QueryOffset *model.Duration `yaml:"query_offset,omitempty"`
|
QueryOffset *model.Duration `yaml:"query_offset,omitempty"`
|
||||||
Limit int `yaml:"limit,omitempty"`
|
Limit int `yaml:"limit,omitempty"`
|
||||||
Rules []RuleNode `yaml:"rules"`
|
Rules []RuleNode `yaml:"rules"`
|
||||||
|
Labels map[string]string `yaml:"labels,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule describes an alerting or recording rule.
|
// Rule describes an alerting or recording rule.
|
||||||
|
|
|
@ -108,6 +108,23 @@ groups:
|
||||||
severity: "page"
|
severity: "page"
|
||||||
annotations:
|
annotations:
|
||||||
summary: "Instance {{ $labels.instance }} down"
|
summary: "Instance {{ $labels.instance }} down"
|
||||||
|
`,
|
||||||
|
shouldPass: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ruleString: `
|
||||||
|
groups:
|
||||||
|
- name: example
|
||||||
|
labels:
|
||||||
|
team: myteam
|
||||||
|
rules:
|
||||||
|
- alert: InstanceDown
|
||||||
|
expr: up == 0
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: "page"
|
||||||
|
annotations:
|
||||||
|
summary: "Instance {{ $labels.instance }} down"
|
||||||
`,
|
`,
|
||||||
shouldPass: true,
|
shouldPass: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -312,13 +312,15 @@ func (m *Manager) LoadGroups(
|
||||||
return nil, []error{fmt.Errorf("%s: %w", fn, err)}
|
return nil, []error{fmt.Errorf("%s: %w", fn, err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLabels := FromMaps(rg.Labels, r.Labels)
|
||||||
|
|
||||||
if r.Alert.Value != "" {
|
if r.Alert.Value != "" {
|
||||||
rules = append(rules, NewAlertingRule(
|
rules = append(rules, NewAlertingRule(
|
||||||
r.Alert.Value,
|
r.Alert.Value,
|
||||||
expr,
|
expr,
|
||||||
time.Duration(r.For),
|
time.Duration(r.For),
|
||||||
time.Duration(r.KeepFiringFor),
|
time.Duration(r.KeepFiringFor),
|
||||||
labels.FromMap(r.Labels),
|
mLabels,
|
||||||
labels.FromMap(r.Annotations),
|
labels.FromMap(r.Annotations),
|
||||||
externalLabels,
|
externalLabels,
|
||||||
externalURL,
|
externalURL,
|
||||||
|
@ -330,7 +332,7 @@ func (m *Manager) LoadGroups(
|
||||||
rules = append(rules, NewRecordingRule(
|
rules = append(rules, NewRecordingRule(
|
||||||
r.Record.Value,
|
r.Record.Value,
|
||||||
expr,
|
expr,
|
||||||
labels.FromMap(r.Labels),
|
mLabels,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,3 +503,16 @@ func (c sequentialRuleEvalController) Allow(_ context.Context, _ *Group, _ Rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c sequentialRuleEvalController) Done(_ context.Context) {}
|
func (c sequentialRuleEvalController) Done(_ context.Context) {}
|
||||||
|
|
||||||
|
// FromMaps returns new sorted Labels from the given maps, overriding each other in order.
|
||||||
|
func FromMaps(maps ...map[string]string) labels.Labels {
|
||||||
|
mLables := make(map[string]string)
|
||||||
|
|
||||||
|
for _, m := range maps {
|
||||||
|
for k, v := range m {
|
||||||
|
mLables[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels.FromMap(mLables)
|
||||||
|
}
|
||||||
|
|
|
@ -853,10 +853,11 @@ type ruleGroupsTest struct {
|
||||||
|
|
||||||
// ruleGroupTest forms a testing struct for running tests over rules.
|
// ruleGroupTest forms a testing struct for running tests over rules.
|
||||||
type ruleGroupTest struct {
|
type ruleGroupTest struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Interval model.Duration `yaml:"interval,omitempty"`
|
Interval model.Duration `yaml:"interval,omitempty"`
|
||||||
Limit int `yaml:"limit,omitempty"`
|
Limit int `yaml:"limit,omitempty"`
|
||||||
Rules []rulefmt.Rule `yaml:"rules"`
|
Rules []rulefmt.Rule `yaml:"rules"`
|
||||||
|
Labels map[string]string `yaml:"labels,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatRules(r *rulefmt.RuleGroups) ruleGroupsTest {
|
func formatRules(r *rulefmt.RuleGroups) ruleGroupsTest {
|
||||||
|
@ -879,6 +880,7 @@ func formatRules(r *rulefmt.RuleGroups) ruleGroupsTest {
|
||||||
Interval: g.Interval,
|
Interval: g.Interval,
|
||||||
Limit: g.Limit,
|
Limit: g.Limit,
|
||||||
Rules: rtmp,
|
Rules: rtmp,
|
||||||
|
Labels: g.Labels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return ruleGroupsTest{
|
return ruleGroupsTest{
|
||||||
|
@ -2154,3 +2156,18 @@ func optsFactory(storage storage.Storage, maxInflight, inflightQueries *atomic.I
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLabels_FromMaps(t *testing.T) {
|
||||||
|
mLabels := FromMaps(
|
||||||
|
map[string]string{"aaa": "101", "bbb": "222"},
|
||||||
|
map[string]string{"aaa": "111", "ccc": "333"},
|
||||||
|
)
|
||||||
|
|
||||||
|
expected := labels.New(
|
||||||
|
labels.Label{Name: "aaa", Value: "111"},
|
||||||
|
labels.Label{Name: "bbb", Value: "222"},
|
||||||
|
labels.Label{Name: "ccc", Value: "333"},
|
||||||
|
)
|
||||||
|
|
||||||
|
require.Equal(t, expected, mLabels, "unexpected labelset")
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ interface RuleGroup {
|
||||||
file: string;
|
file: string;
|
||||||
rules: Rule[];
|
rules: Rule[];
|
||||||
interval: number;
|
interval: number;
|
||||||
|
labels: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const kvSearchRule = new KVSearch<Rule>({
|
const kvSearchRule = new KVSearch<Rule>({
|
||||||
|
@ -93,6 +94,7 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
||||||
name: group.name,
|
name: group.name,
|
||||||
interval: group.interval,
|
interval: group.interval,
|
||||||
rules: ruleFilterList.map((value) => value.original),
|
rules: ruleFilterList.map((value) => value.original),
|
||||||
|
labels: group.labels,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +116,7 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
||||||
name: group.name,
|
name: group.name,
|
||||||
interval: group.interval,
|
interval: group.interval,
|
||||||
rules: group.rules.filter((value) => filter[value.state]),
|
rules: group.rules.filter((value) => filter[value.state]),
|
||||||
|
labels: group.labels,
|
||||||
};
|
};
|
||||||
if (newGroup.rules.length > 0) {
|
if (newGroup.rules.length > 0) {
|
||||||
result.push(newGroup);
|
result.push(newGroup);
|
||||||
|
|
|
@ -17,6 +17,7 @@ interface RuleGroup {
|
||||||
rules: Rule[];
|
rules: Rule[];
|
||||||
evaluationTime: string;
|
evaluationTime: string;
|
||||||
lastEvaluation: string;
|
lastEvaluation: string;
|
||||||
|
labels: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RulesMap {
|
export interface RulesMap {
|
||||||
|
@ -105,10 +106,10 @@ export const RulesContent: FC<RulesContentProps> = ({ response }) => {
|
||||||
<strong>keep_firing_for:</strong> {formatDuration(r.keepFiringFor * 1000)}
|
<strong>keep_firing_for:</strong> {formatDuration(r.keepFiringFor * 1000)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{r.labels && Object.keys(r.labels).length > 0 && (
|
{Object.keys(Object.assign({ ...g.labels }, { ...r.labels })).length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<strong>labels:</strong>
|
<strong>labels:</strong>
|
||||||
{Object.entries(r.labels).map(([key, value]) => (
|
{Object.entries(Object.assign({ ...g.labels }, { ...r.labels })).map(([key, value]) => (
|
||||||
<div className="ml-4" key={key}>
|
<div className="ml-4" key={key}>
|
||||||
{key}: {value}
|
{key}: {value}
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue