mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-12 22:37:27 -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/promql/parser"
|
||||
"github.com/prometheus/prometheus/promql/promqltest"
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/scrape"
|
||||
"github.com/prometheus/prometheus/util/documentcli"
|
||||
)
|
||||
|
@ -889,30 +890,30 @@ func compare(a, b compareRuleType) int {
|
|||
|
||||
func checkDuplicates(groups []rulefmt.RuleGroup) []compareRuleType {
|
||||
var duplicates []compareRuleType
|
||||
var rules compareRuleTypes
|
||||
var cRules compareRuleTypes
|
||||
|
||||
for _, group := range groups {
|
||||
for _, rule := range group.Rules {
|
||||
rules = append(rules, compareRuleType{
|
||||
cRules = append(cRules, compareRuleType{
|
||||
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
|
||||
}
|
||||
sort.Sort(rules)
|
||||
sort.Sort(cRules)
|
||||
|
||||
last := rules[0]
|
||||
for i := 1; i < len(rules); i++ {
|
||||
if compare(last, rules[i]) == 0 {
|
||||
last := cRules[0]
|
||||
for i := 1; i < len(cRules); i++ {
|
||||
if compare(last, cRules[i]) == 0 {
|
||||
// Don't add a duplicated rule multiple times.
|
||||
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
|
||||
|
|
|
@ -21,6 +21,8 @@ An example rules file with an alert would be:
|
|||
```yaml
|
||||
groups:
|
||||
- name: example
|
||||
labels:
|
||||
team: myteam
|
||||
rules:
|
||||
- alert: HighRequestLatency
|
||||
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.
|
||||
[ 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:
|
||||
[ - <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{}{}
|
||||
|
||||
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.
|
||||
type RuleGroup struct {
|
||||
Name string `yaml:"name"`
|
||||
Interval model.Duration `yaml:"interval,omitempty"`
|
||||
QueryOffset *model.Duration `yaml:"query_offset,omitempty"`
|
||||
Limit int `yaml:"limit,omitempty"`
|
||||
Rules []RuleNode `yaml:"rules"`
|
||||
Name string `yaml:"name"`
|
||||
Interval model.Duration `yaml:"interval,omitempty"`
|
||||
QueryOffset *model.Duration `yaml:"query_offset,omitempty"`
|
||||
Limit int `yaml:"limit,omitempty"`
|
||||
Rules []RuleNode `yaml:"rules"`
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// Rule describes an alerting or recording rule.
|
||||
|
|
|
@ -108,6 +108,23 @@ groups:
|
|||
severity: "page"
|
||||
annotations:
|
||||
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,
|
||||
},
|
||||
|
|
|
@ -312,13 +312,15 @@ func (m *Manager) LoadGroups(
|
|||
return nil, []error{fmt.Errorf("%s: %w", fn, err)}
|
||||
}
|
||||
|
||||
mLabels := FromMaps(rg.Labels, r.Labels)
|
||||
|
||||
if r.Alert.Value != "" {
|
||||
rules = append(rules, NewAlertingRule(
|
||||
r.Alert.Value,
|
||||
expr,
|
||||
time.Duration(r.For),
|
||||
time.Duration(r.KeepFiringFor),
|
||||
labels.FromMap(r.Labels),
|
||||
mLabels,
|
||||
labels.FromMap(r.Annotations),
|
||||
externalLabels,
|
||||
externalURL,
|
||||
|
@ -330,7 +332,7 @@ func (m *Manager) LoadGroups(
|
|||
rules = append(rules, NewRecordingRule(
|
||||
r.Record.Value,
|
||||
expr,
|
||||
labels.FromMap(r.Labels),
|
||||
mLabels,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -501,3 +503,16 @@ func (c sequentialRuleEvalController) Allow(_ context.Context, _ *Group, _ Rule)
|
|||
}
|
||||
|
||||
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.
|
||||
type ruleGroupTest struct {
|
||||
Name string `yaml:"name"`
|
||||
Interval model.Duration `yaml:"interval,omitempty"`
|
||||
Limit int `yaml:"limit,omitempty"`
|
||||
Rules []rulefmt.Rule `yaml:"rules"`
|
||||
Name string `yaml:"name"`
|
||||
Interval model.Duration `yaml:"interval,omitempty"`
|
||||
Limit int `yaml:"limit,omitempty"`
|
||||
Rules []rulefmt.Rule `yaml:"rules"`
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
}
|
||||
|
||||
func formatRules(r *rulefmt.RuleGroups) ruleGroupsTest {
|
||||
|
@ -879,6 +880,7 @@ func formatRules(r *rulefmt.RuleGroups) ruleGroupsTest {
|
|||
Interval: g.Interval,
|
||||
Limit: g.Limit,
|
||||
Rules: rtmp,
|
||||
Labels: g.Labels,
|
||||
})
|
||||
}
|
||||
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;
|
||||
rules: Rule[];
|
||||
interval: number;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
const kvSearchRule = new KVSearch<Rule>({
|
||||
|
@ -93,6 +94,7 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
|||
name: group.name,
|
||||
interval: group.interval,
|
||||
rules: ruleFilterList.map((value) => value.original),
|
||||
labels: group.labels,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +116,7 @@ const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
|
|||
name: group.name,
|
||||
interval: group.interval,
|
||||
rules: group.rules.filter((value) => filter[value.state]),
|
||||
labels: group.labels,
|
||||
};
|
||||
if (newGroup.rules.length > 0) {
|
||||
result.push(newGroup);
|
||||
|
|
|
@ -17,6 +17,7 @@ interface RuleGroup {
|
|||
rules: Rule[];
|
||||
evaluationTime: string;
|
||||
lastEvaluation: string;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface RulesMap {
|
||||
|
@ -105,10 +106,10 @@ export const RulesContent: FC<RulesContentProps> = ({ response }) => {
|
|||
<strong>keep_firing_for:</strong> {formatDuration(r.keepFiringFor * 1000)}
|
||||
</div>
|
||||
)}
|
||||
{r.labels && Object.keys(r.labels).length > 0 && (
|
||||
{Object.keys(Object.assign({ ...g.labels }, { ...r.labels })).length > 0 && (
|
||||
<div>
|
||||
<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}>
|
||||
{key}: {value}
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue