mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Classic conditions can now display multiple values (#46971)
* Alerting: Extract classic condition values by RefID * uncapitalise function * update documentation * Update pkg/services/ngalert/eval/extract_md.go Co-authored-by: George Robinson <george.robinson@grafana.com> * Update pkg/services/ngalert/state/state.go Co-authored-by: George Robinson <george.robinson@grafana.com> * Update pkg/services/ngalert/state/state.go Co-authored-by: George Robinson <george.robinson@grafana.com> * Update pkg/services/ngalert/eval/extract_md.go Co-authored-by: George Robinson <george.robinson@grafana.com> * Update docs/sources/alerting/unified-alerting/alerting-rules/alert-annotation-label.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update pkg/services/ngalert/eval/extract_md.go Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Run prettier Co-authored-by: George Robinson <george.robinson@grafana.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
This commit is contained in:
parent
feaa4a5c64
commit
84e5f336fe
@ -31,8 +31,8 @@ Labels are key-value pairs that contain information about, and are used to uniqu
|
|||||||
|
|
||||||
The following template variables are available when expanding annotations and labels.
|
The following template variables are available when expanding annotations and labels.
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| $labels | The labels from the query or condition. For example, `{{ $labels.instance }}` and `{{ $labels.job }}`. This is unavailable when the rule uses a [classic condition]({{< relref "./create-grafana-managed-rule/#single-and-multi-dimensional-rule" >}}). |
|
| $labels | The labels from the query or condition. For example, `{{ $labels.instance }}` and `{{ $labels.job }}`. This is unavailable when the rule uses a [classic condition]({{< relref "./create-grafana-managed-rule/#single-and-multi-dimensional-rule" >}}). |
|
||||||
| $values | The values of all reduce and math expressions that were evaluated for this alert rule. For example, `{{ $values.A }}`, `{{ $values.A.Labels }}` and `{{ $values.A.Value }}` where `A` is the `refID` of the expression. This is unavailable when the rule uses a classic condition |
|
| $values | The values of all reduce and math expressions that were evaluated for this alert rule. For example, `{{ $values.A }}`, `{{ $values.A.Labels }}` and `{{ $values.A.Value }}` where `A` is the `refID` of the expression. If the rule uses classic conditions, then a combination of the `refID` and position of the condition is used. For example, `{{ $values.A0.Value }}` or `{{ $values.A1.Value }}` |
|
||||||
| $value | The value string of the alert instance. For example, `[ var='A' labels={instance=foo} value=10 ]`. |
|
| $value | The value string of the alert instance. For example, `[ var='A' labels={instance=foo} value=10 ]`. |
|
||||||
|
@ -20,8 +20,10 @@ func extractEvalString(frame *data.Frame) (s string) {
|
|||||||
if evalMatches, ok := frame.Meta.Custom.([]classic.EvalMatch); ok {
|
if evalMatches, ok := frame.Meta.Custom.([]classic.EvalMatch); ok {
|
||||||
sb := strings.Builder{}
|
sb := strings.Builder{}
|
||||||
|
|
||||||
|
// TODO: Should we simplify when we only have one match and use the name notation of $labels.A?
|
||||||
for i, m := range evalMatches {
|
for i, m := range evalMatches {
|
||||||
sb.WriteString("[ ")
|
sb.WriteString("[ ")
|
||||||
|
sb.WriteString(fmt.Sprintf("var='%s%v' ", frame.RefID, i))
|
||||||
sb.WriteString(fmt.Sprintf("metric='%s' ", m.Metric))
|
sb.WriteString(fmt.Sprintf("metric='%s' ", m.Metric))
|
||||||
sb.WriteString(fmt.Sprintf("labels={%s} ", m.Labels))
|
sb.WriteString(fmt.Sprintf("labels={%s} ", m.Labels))
|
||||||
|
|
||||||
@ -66,10 +68,10 @@ func extractEvalString(frame *data.Frame) (s string) {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractValues returns the RefID and value for all reduce and math expressions
|
// extractValues returns the RefID and value for all classic conditions, reduce, and math expressions in the frame.
|
||||||
// in the frame. It does not return values for classic conditions as the values
|
// For classic conditions the same refID can have multiple values due to multiple conditions, for them we use the index of
|
||||||
// in classic conditions do not have a RefID. It returns nil if there are
|
// the condition in addition to the refID to distinguish between different values.
|
||||||
// no results in the frame.
|
// It returns nil if there are no results in the frame.
|
||||||
func extractValues(frame *data.Frame) map[string]NumberValueCapture {
|
func extractValues(frame *data.Frame) map[string]NumberValueCapture {
|
||||||
if frame == nil {
|
if frame == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -77,6 +79,24 @@ func extractValues(frame *data.Frame) map[string]NumberValueCapture {
|
|||||||
if frame.Meta == nil || frame.Meta.Custom == nil {
|
if frame.Meta == nil || frame.Meta.Custom == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches, ok := frame.Meta.Custom.([]classic.EvalMatch); ok {
|
||||||
|
// Classic evaluations only have a single match but it can contain multiple conditions.
|
||||||
|
// Conditions have a strict ordering which we can rely on to distinguish between values.
|
||||||
|
v := make(map[string]NumberValueCapture, len(matches))
|
||||||
|
for i, match := range matches {
|
||||||
|
// In classic conditions we use refID and the condition position as a way to distinguish between values.
|
||||||
|
// We can guarantee determinism as conditions are ordered and this order is preserved when marshaling.
|
||||||
|
refID := fmt.Sprintf("%s%d", frame.RefID, i)
|
||||||
|
v[refID] = NumberValueCapture{
|
||||||
|
Var: frame.RefID,
|
||||||
|
Labels: match.Labels,
|
||||||
|
Value: match.Value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
if caps, ok := frame.Meta.Custom.([]NumberValueCapture); ok {
|
if caps, ok := frame.Meta.Custom.([]NumberValueCapture); ok {
|
||||||
v := make(map[string]NumberValueCapture, len(caps))
|
v := make(map[string]NumberValueCapture, len(caps))
|
||||||
for _, c := range caps {
|
for _, c := range caps {
|
||||||
|
@ -20,15 +20,15 @@ func TestExtractEvalString(t *testing.T) {
|
|||||||
inFrame: newMetaFrame([]classic.EvalMatch{
|
inFrame: newMetaFrame([]classic.EvalMatch{
|
||||||
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(32.3)},
|
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(32.3)},
|
||||||
}, ptr.Float64(1)),
|
}, ptr.Float64(1)),
|
||||||
outString: `[ metric='Test' labels={host=foo} value=32.3 ]`,
|
outString: `[ var='0' metric='Test' labels={host=foo} value=32.3 ]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "2 EvalMatches",
|
desc: "2 EvalMatches",
|
||||||
inFrame: newMetaFrame([]classic.EvalMatch{
|
inFrame: newMetaFrame([]classic.EvalMatch{
|
||||||
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(32.3)},
|
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(32.3)},
|
||||||
{Metric: "Test", Labels: data.Labels{"host": "baz"}, Value: ptr.Float64(10)},
|
{Metric: "Test", Labels: data.Labels{"host": "baz"}, Value: ptr.Float64(10)},
|
||||||
}, ptr.Float64(1)),
|
}, ptr.Float64(1), withRefID("A")),
|
||||||
outString: `[ metric='Test' labels={host=foo} value=32.3 ], [ metric='Test' labels={host=baz} value=10 ]`,
|
outString: `[ var='A0' metric='Test' labels={host=foo} value=32.3 ], [ var='A1' metric='Test' labels={host=baz} value=10 ]`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "3 EvalMatches",
|
desc: "3 EvalMatches",
|
||||||
@ -36,8 +36,8 @@ func TestExtractEvalString(t *testing.T) {
|
|||||||
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(32.3)},
|
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(32.3)},
|
||||||
{Metric: "Test", Labels: data.Labels{"host": "baz"}, Value: ptr.Float64(10)},
|
{Metric: "Test", Labels: data.Labels{"host": "baz"}, Value: ptr.Float64(10)},
|
||||||
{Metric: "TestA", Labels: data.Labels{"host": "zip"}, Value: ptr.Float64(11)},
|
{Metric: "TestA", Labels: data.Labels{"host": "zip"}, Value: ptr.Float64(11)},
|
||||||
}, ptr.Float64(1)),
|
}, ptr.Float64(1), withRefID("A")),
|
||||||
outString: `[ metric='Test' labels={host=foo} value=32.3 ], [ metric='Test' labels={host=baz} value=10 ], [ metric='TestA' labels={host=zip} value=11 ]`,
|
outString: `[ var='A0' metric='Test' labels={host=foo} value=32.3 ], [ var='A1' metric='Test' labels={host=baz} value=10 ], [ var='A2' metric='TestA' labels={host=zip} value=11 ]`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@ -57,11 +57,23 @@ func TestExtractValues(t *testing.T) {
|
|||||||
inFrame: newMetaFrame(nil, ptr.Float64(1)),
|
inFrame: newMetaFrame(nil, ptr.Float64(1)),
|
||||||
values: nil,
|
values: nil,
|
||||||
}, {
|
}, {
|
||||||
desc: "Classic condition frame returns nil",
|
desc: "Classic condition frame with one match",
|
||||||
inFrame: newMetaFrame([]classic.EvalMatch{
|
inFrame: newMetaFrame([]classic.EvalMatch{
|
||||||
{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(1)},
|
{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(1)},
|
||||||
}, ptr.Float64(1)),
|
}, ptr.Float64(1), withRefID("A")),
|
||||||
values: nil,
|
values: map[string]NumberValueCapture{
|
||||||
|
"A0": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(1)},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
desc: "Classic condition frame with multiple matches",
|
||||||
|
inFrame: newMetaFrame([]classic.EvalMatch{
|
||||||
|
{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(1)},
|
||||||
|
{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(3)},
|
||||||
|
}, ptr.Float64(1), withRefID("A")),
|
||||||
|
values: map[string]NumberValueCapture{
|
||||||
|
"A0": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(1)},
|
||||||
|
"A1": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(3)},
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
desc: "Nil value",
|
desc: "Nil value",
|
||||||
inFrame: newMetaFrame([]NumberValueCapture{
|
inFrame: newMetaFrame([]NumberValueCapture{
|
||||||
@ -96,10 +108,24 @@ func TestExtractValues(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMetaFrame(custom interface{}, val *float64) *data.Frame {
|
type frameCallback func(frame *data.Frame)
|
||||||
return data.NewFrame("",
|
|
||||||
|
func withRefID(refID string) frameCallback {
|
||||||
|
return func(frame *data.Frame) {
|
||||||
|
frame.RefID = refID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMetaFrame(custom interface{}, val *float64, callbacks ...frameCallback) *data.Frame {
|
||||||
|
f := data.NewFrame("",
|
||||||
data.NewField("", nil, []*float64{val})).
|
data.NewField("", nil, []*float64{val})).
|
||||||
SetMeta(&data.FrameMeta{
|
SetMeta(&data.FrameMeta{
|
||||||
Custom: custom,
|
Custom: custom,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for _, cb := range callbacks {
|
||||||
|
cb(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,8 @@ type Evaluation struct {
|
|||||||
EvaluationTime time.Time
|
EvaluationTime time.Time
|
||||||
EvaluationState eval.State
|
EvaluationState eval.State
|
||||||
// Values contains the RefID and value of reduce and math expressions.
|
// Values contains the RefID and value of reduce and math expressions.
|
||||||
// It does not contain values for classic conditions as the values
|
// Classic conditions can have different values for the same RefID as they can include multiple conditions.
|
||||||
// in classic conditions do not have a RefID.
|
// For these, we use the index of the condition in addition RefID as the key e.g. "A0, A1, A2, etc.".
|
||||||
Values map[string]*float64
|
Values map[string]*float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user