2023-10-13 13:54:33 +02:00
|
|
|
package rule
|
2020-06-01 12:46:37 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
2023-12-06 19:39:35 +01:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
2020-06-01 12:46:37 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
|
|
|
)
|
|
|
|
|
2022-06-09 08:21:30 +02:00
|
|
|
func TestRecordingRule_Exec(t *testing.T) {
|
2024-07-12 21:57:56 +02:00
|
|
|
f := func(rule *RecordingRule, metrics []datasource.Metric, tssExpected []prompbmarshal.TimeSeries) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
fq := &datasource.FakeQuerier{}
|
|
|
|
fq.Add(metrics...)
|
|
|
|
rule.q = fq
|
|
|
|
rule.state = &ruleState{
|
|
|
|
entries: make([]StateEntry, 10),
|
|
|
|
}
|
|
|
|
tss, err := rule.exec(context.TODO(), time.Now(), 0)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected RecordingRule.exec error: %s", err)
|
|
|
|
}
|
|
|
|
if err := compareTimeSeries(t, tssExpected, tss); err != nil {
|
|
|
|
t.Fatalf("timeseries missmatch: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-01 12:46:37 +02:00
|
|
|
timestamp := time.Now()
|
2024-07-12 21:57:56 +02:00
|
|
|
|
|
|
|
f(&RecordingRule{
|
|
|
|
Name: "foo",
|
|
|
|
}, []datasource.Metric{
|
|
|
|
metricWithValueAndLabels(t, 10, "__name__", "bar"),
|
|
|
|
}, []prompbmarshal.TimeSeries{
|
|
|
|
newTimeSeries([]float64{10}, []int64{timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "foo",
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
f(&RecordingRule{
|
|
|
|
Name: "foobarbaz",
|
|
|
|
}, []datasource.Metric{
|
|
|
|
metricWithValueAndLabels(t, 1, "__name__", "foo", "job", "foo"),
|
|
|
|
metricWithValueAndLabels(t, 2, "__name__", "bar", "job", "bar"),
|
|
|
|
metricWithValueAndLabels(t, 3, "__name__", "baz", "job", "baz"),
|
|
|
|
}, []prompbmarshal.TimeSeries{
|
|
|
|
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "foobarbaz",
|
|
|
|
"job": "foo",
|
|
|
|
}),
|
|
|
|
newTimeSeries([]float64{2}, []int64{timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "foobarbaz",
|
|
|
|
"job": "bar",
|
|
|
|
}),
|
|
|
|
newTimeSeries([]float64{3}, []int64{timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "foobarbaz",
|
|
|
|
"job": "baz",
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
f(&RecordingRule{
|
|
|
|
Name: "job:foo",
|
|
|
|
Labels: map[string]string{
|
|
|
|
"source": "test",
|
2020-06-01 12:46:37 +02:00
|
|
|
},
|
2024-07-12 21:57:56 +02:00
|
|
|
}, []datasource.Metric{
|
|
|
|
metricWithValueAndLabels(t, 2, "__name__", "foo", "job", "foo"),
|
|
|
|
metricWithValueAndLabels(t, 1, "__name__", "bar", "job", "bar", "source", "origin"),
|
|
|
|
}, []prompbmarshal.TimeSeries{
|
|
|
|
newTimeSeries([]float64{2}, []int64{timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "job:foo",
|
|
|
|
"job": "foo",
|
|
|
|
"source": "test",
|
|
|
|
}),
|
|
|
|
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "job:foo",
|
|
|
|
"job": "bar",
|
|
|
|
"source": "test",
|
|
|
|
"exported_source": "origin",
|
|
|
|
}),
|
|
|
|
})
|
2020-06-01 12:46:37 +02:00
|
|
|
}
|
|
|
|
|
2022-06-09 08:21:30 +02:00
|
|
|
func TestRecordingRule_ExecRange(t *testing.T) {
|
2024-07-12 21:57:56 +02:00
|
|
|
f := func(rule *RecordingRule, metrics []datasource.Metric, tssExpected []prompbmarshal.TimeSeries) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
fq := &datasource.FakeQuerier{}
|
|
|
|
fq.Add(metrics...)
|
|
|
|
rule.q = fq
|
|
|
|
tss, err := rule.execRange(context.TODO(), time.Now(), time.Now())
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected RecordingRule.execRange error: %s", err)
|
|
|
|
}
|
|
|
|
if err := compareTimeSeries(t, tssExpected, tss); err != nil {
|
|
|
|
t.Fatalf("timeseries missmatch: %s", err)
|
|
|
|
}
|
2021-06-09 11:20:38 +02:00
|
|
|
}
|
|
|
|
|
2022-06-09 08:21:30 +02:00
|
|
|
timestamp := time.Now()
|
2024-07-12 21:57:56 +02:00
|
|
|
|
|
|
|
f(&RecordingRule{
|
|
|
|
Name: "foo",
|
|
|
|
}, []datasource.Metric{
|
|
|
|
metricWithValuesAndLabels(t, []float64{10, 20, 30}, "__name__", "bar"),
|
|
|
|
}, []prompbmarshal.TimeSeries{
|
|
|
|
newTimeSeries([]float64{10, 20, 30}, []int64{timestamp.UnixNano(), timestamp.UnixNano(), timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "foo",
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
f(&RecordingRule{
|
|
|
|
Name: "foobarbaz",
|
|
|
|
}, []datasource.Metric{
|
2022-06-09 08:21:30 +02:00
|
|
|
metricWithValuesAndLabels(t, []float64{1}, "__name__", "foo", "job", "foo"),
|
|
|
|
metricWithValuesAndLabels(t, []float64{2, 3}, "__name__", "bar", "job", "bar"),
|
|
|
|
metricWithValuesAndLabels(t, []float64{4, 5, 6}, "__name__", "baz", "job", "baz"),
|
2024-07-12 21:57:56 +02:00
|
|
|
}, []prompbmarshal.TimeSeries{
|
|
|
|
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "foobarbaz",
|
|
|
|
"job": "foo",
|
|
|
|
}),
|
|
|
|
newTimeSeries([]float64{2, 3}, []int64{timestamp.UnixNano(), timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "foobarbaz",
|
|
|
|
"job": "bar",
|
|
|
|
}),
|
|
|
|
newTimeSeries([]float64{4, 5, 6},
|
|
|
|
[]int64{timestamp.UnixNano(), timestamp.UnixNano(), timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "foobarbaz",
|
|
|
|
"job": "baz",
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
|
|
|
|
f(&RecordingRule{
|
|
|
|
Name: "job:foo",
|
2023-12-06 19:39:35 +01:00
|
|
|
Labels: map[string]string{
|
2024-07-12 21:57:56 +02:00
|
|
|
"source": "test",
|
2023-12-06 19:39:35 +01:00
|
|
|
},
|
2024-07-12 21:57:56 +02:00
|
|
|
}, []datasource.Metric{
|
|
|
|
metricWithValueAndLabels(t, 2, "__name__", "foo", "job", "foo"),
|
|
|
|
metricWithValueAndLabels(t, 1, "__name__", "bar", "job", "bar"),
|
|
|
|
}, []prompbmarshal.TimeSeries{
|
|
|
|
newTimeSeries([]float64{2}, []int64{timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "job:foo",
|
|
|
|
"job": "foo",
|
|
|
|
"source": "test",
|
|
|
|
}),
|
|
|
|
newTimeSeries([]float64{1}, []int64{timestamp.UnixNano()}, map[string]string{
|
|
|
|
"__name__": "job:foo",
|
|
|
|
"job": "bar",
|
|
|
|
"source": "test",
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRecordingRuleLimit_Failure(t *testing.T) {
|
|
|
|
f := func(limit int, errStrExpected string) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
testMetrics := []datasource.Metric{
|
|
|
|
metricWithValuesAndLabels(t, []float64{1}, "__name__", "foo", "job", "foo"),
|
|
|
|
metricWithValuesAndLabels(t, []float64{2, 3}, "__name__", "bar", "job", "bar"),
|
|
|
|
metricWithValuesAndLabels(t, []float64{4, 5, 6}, "__name__", "baz", "job", "baz"),
|
|
|
|
}
|
|
|
|
|
|
|
|
fq := &datasource.FakeQuerier{}
|
|
|
|
fq.Add(testMetrics...)
|
|
|
|
|
|
|
|
rule := &RecordingRule{Name: "job:foo",
|
|
|
|
state: &ruleState{entries: make([]StateEntry, 10)},
|
|
|
|
Labels: map[string]string{
|
|
|
|
"source": "test_limit",
|
|
|
|
},
|
|
|
|
metrics: &recordingRuleMetrics{
|
|
|
|
errors: utils.GetOrCreateCounter(`vmalert_recording_rules_errors_total{alertname="job:foo"}`),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
rule.q = fq
|
|
|
|
|
|
|
|
_, err := rule.exec(context.TODO(), time.Now(), limit)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("expecting non-nil error")
|
|
|
|
}
|
|
|
|
errStr := err.Error()
|
|
|
|
if !strings.Contains(errStr, errStrExpected) {
|
|
|
|
t.Fatalf("missing %q in the error %q", errStrExpected, errStr)
|
|
|
|
}
|
2023-12-06 19:39:35 +01:00
|
|
|
}
|
2024-07-12 21:57:56 +02:00
|
|
|
|
|
|
|
f(1, "exec exceeded limit of 1 with 3 series")
|
|
|
|
f(2, "exec exceeded limit of 2 with 3 series")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRecordingRuleLimit_Success(t *testing.T) {
|
|
|
|
f := func(limit int) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
testMetrics := []datasource.Metric{
|
|
|
|
metricWithValuesAndLabels(t, []float64{1}, "__name__", "foo", "job", "foo"),
|
|
|
|
metricWithValuesAndLabels(t, []float64{2, 3}, "__name__", "bar", "job", "bar"),
|
|
|
|
metricWithValuesAndLabels(t, []float64{4, 5, 6}, "__name__", "baz", "job", "baz"),
|
|
|
|
}
|
|
|
|
|
2023-10-13 13:54:33 +02:00
|
|
|
fq := &datasource.FakeQuerier{}
|
|
|
|
fq.Add(testMetrics...)
|
2024-07-12 21:57:56 +02:00
|
|
|
|
|
|
|
rule := &RecordingRule{Name: "job:foo",
|
|
|
|
state: &ruleState{entries: make([]StateEntry, 10)},
|
|
|
|
Labels: map[string]string{
|
|
|
|
"source": "test_limit",
|
|
|
|
},
|
|
|
|
metrics: &recordingRuleMetrics{
|
|
|
|
errors: utils.GetOrCreateCounter(`vmalert_recording_rules_errors_total{alertname="job:foo"}`),
|
|
|
|
},
|
|
|
|
}
|
2022-06-09 08:21:30 +02:00
|
|
|
rule.q = fq
|
2024-07-12 21:57:56 +02:00
|
|
|
|
|
|
|
_, err := rule.exec(context.TODO(), time.Now(), limit)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
2022-06-09 08:21:30 +02:00
|
|
|
}
|
|
|
|
}
|
2024-07-12 21:57:56 +02:00
|
|
|
|
|
|
|
f(0)
|
|
|
|
f(-1)
|
2022-06-09 08:21:30 +02:00
|
|
|
}
|
|
|
|
|
2024-07-12 21:57:56 +02:00
|
|
|
func TestRecordingRuleExec_Negative(t *testing.T) {
|
2022-09-14 14:04:24 +02:00
|
|
|
rr := &RecordingRule{
|
2023-10-13 13:54:33 +02:00
|
|
|
Name: "job:foo",
|
2022-09-14 14:04:24 +02:00
|
|
|
Labels: map[string]string{
|
|
|
|
"job": "test",
|
|
|
|
},
|
2023-10-13 13:54:33 +02:00
|
|
|
state: &ruleState{entries: make([]StateEntry, 10)},
|
2023-12-06 19:39:35 +01:00
|
|
|
metrics: &recordingRuleMetrics{
|
|
|
|
errors: utils.GetOrCreateCounter(`vmalert_recording_rules_errors_total{alertname="job:foo"}`),
|
|
|
|
},
|
2022-09-14 14:04:24 +02:00
|
|
|
}
|
2023-10-13 13:54:33 +02:00
|
|
|
fq := &datasource.FakeQuerier{}
|
2020-06-01 12:46:37 +02:00
|
|
|
expErr := "connection reset by peer"
|
2023-10-13 13:54:33 +02:00
|
|
|
fq.SetErr(errors.New(expErr))
|
2021-04-28 22:41:15 +02:00
|
|
|
rr.q = fq
|
2023-10-13 13:54:33 +02:00
|
|
|
_, err := rr.exec(context.TODO(), time.Now(), 0)
|
2020-06-01 12:46:37 +02:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("expected to get err; got nil")
|
|
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), expErr) {
|
|
|
|
t.Fatalf("expected to get err %q; got %q insterad", expErr, err)
|
|
|
|
}
|
|
|
|
|
2023-10-13 13:54:33 +02:00
|
|
|
fq.Reset()
|
2020-06-01 12:46:37 +02:00
|
|
|
|
|
|
|
// add metrics which differs only by `job` label
|
|
|
|
// which will be overridden by rule
|
2023-10-13 13:54:33 +02:00
|
|
|
fq.Add(metricWithValueAndLabels(t, 1, "__name__", "foo", "job", "foo"))
|
|
|
|
fq.Add(metricWithValueAndLabels(t, 2, "__name__", "foo", "job", "bar"))
|
2020-06-01 12:46:37 +02:00
|
|
|
|
2023-10-13 13:54:33 +02:00
|
|
|
_, err = rr.exec(context.TODO(), time.Now(), 0)
|
2023-12-22 16:07:47 +01:00
|
|
|
if err != nil {
|
2024-07-12 21:57:56 +02:00
|
|
|
t.Fatalf("cannot execute recroding rule: %s", err)
|
2020-06-01 12:46:37 +02:00
|
|
|
}
|
|
|
|
}
|