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.
|
||||
|
||||
| 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" >}}). |
|
||||
| $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 |
|
||||
| $value | The value string of the alert instance. For example, `[ var='A' labels={instance=foo} value=10 ]`. |
|
||||
| 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" >}}). |
|
||||
| $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 ]`. |
|
||||
|
@ -20,8 +20,10 @@ func extractEvalString(frame *data.Frame) (s string) {
|
||||
if evalMatches, ok := frame.Meta.Custom.([]classic.EvalMatch); ok {
|
||||
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 {
|
||||
sb.WriteString("[ ")
|
||||
sb.WriteString(fmt.Sprintf("var='%s%v' ", frame.RefID, i))
|
||||
sb.WriteString(fmt.Sprintf("metric='%s' ", m.Metric))
|
||||
sb.WriteString(fmt.Sprintf("labels={%s} ", m.Labels))
|
||||
|
||||
@ -66,10 +68,10 @@ func extractEvalString(frame *data.Frame) (s string) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractValues returns the RefID and value for all reduce and math expressions
|
||||
// in the frame. It does not return values for classic conditions as the values
|
||||
// in classic conditions do not have a RefID. It returns nil if there are
|
||||
// no results in the frame.
|
||||
// extractValues returns the RefID and value for all classic conditions, reduce, and math expressions in the frame.
|
||||
// For classic conditions the same refID can have multiple values due to multiple conditions, for them we use the index of
|
||||
// the condition in addition to the refID to distinguish between different values.
|
||||
// It returns nil if there are no results in the frame.
|
||||
func extractValues(frame *data.Frame) map[string]NumberValueCapture {
|
||||
if frame == nil {
|
||||
return nil
|
||||
@ -77,6 +79,24 @@ func extractValues(frame *data.Frame) map[string]NumberValueCapture {
|
||||
if frame.Meta == nil || frame.Meta.Custom == 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 {
|
||||
v := make(map[string]NumberValueCapture, len(caps))
|
||||
for _, c := range caps {
|
||||
|
@ -20,15 +20,15 @@ func TestExtractEvalString(t *testing.T) {
|
||||
inFrame: newMetaFrame([]classic.EvalMatch{
|
||||
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(32.3)},
|
||||
}, 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",
|
||||
inFrame: newMetaFrame([]classic.EvalMatch{
|
||||
{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(32.3)},
|
||||
{Metric: "Test", Labels: data.Labels{"host": "baz"}, Value: ptr.Float64(10)},
|
||||
}, ptr.Float64(1)),
|
||||
outString: `[ metric='Test' labels={host=foo} value=32.3 ], [ metric='Test' labels={host=baz} value=10 ]`,
|
||||
}, ptr.Float64(1), withRefID("A")),
|
||||
outString: `[ var='A0' metric='Test' labels={host=foo} value=32.3 ], [ var='A1' metric='Test' labels={host=baz} value=10 ]`,
|
||||
},
|
||||
{
|
||||
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": "baz"}, Value: ptr.Float64(10)},
|
||||
{Metric: "TestA", Labels: data.Labels{"host": "zip"}, Value: ptr.Float64(11)},
|
||||
}, ptr.Float64(1)),
|
||||
outString: `[ metric='Test' labels={host=foo} value=32.3 ], [ metric='Test' labels={host=baz} value=10 ], [ metric='TestA' labels={host=zip} value=11 ]`,
|
||||
}, ptr.Float64(1), withRefID("A")),
|
||||
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 {
|
||||
@ -57,11 +57,23 @@ func TestExtractValues(t *testing.T) {
|
||||
inFrame: newMetaFrame(nil, ptr.Float64(1)),
|
||||
values: nil,
|
||||
}, {
|
||||
desc: "Classic condition frame returns nil",
|
||||
desc: "Classic condition frame with one match",
|
||||
inFrame: newMetaFrame([]classic.EvalMatch{
|
||||
{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: ptr.Float64(1)},
|
||||
}, ptr.Float64(1)),
|
||||
values: nil,
|
||||
}, ptr.Float64(1), withRefID("A")),
|
||||
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",
|
||||
inFrame: newMetaFrame([]NumberValueCapture{
|
||||
@ -96,10 +108,24 @@ func TestExtractValues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newMetaFrame(custom interface{}, val *float64) *data.Frame {
|
||||
return data.NewFrame("",
|
||||
type frameCallback func(frame *data.Frame)
|
||||
|
||||
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})).
|
||||
SetMeta(&data.FrameMeta{
|
||||
Custom: custom,
|
||||
})
|
||||
|
||||
for _, cb := range callbacks {
|
||||
cb(f)
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ type Evaluation struct {
|
||||
EvaluationTime time.Time
|
||||
EvaluationState eval.State
|
||||
// Values contains the RefID and value of reduce and math expressions.
|
||||
// It does not contain values for classic conditions as the values
|
||||
// in classic conditions do not have a RefID.
|
||||
// Classic conditions can have different values for the same RefID as they can include multiple conditions.
|
||||
// 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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user