mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-22 08:10:44 +01:00
9616814728
Some checks are pending
build / Build (push) Waiting to run
CodeQL Go / Analyze (push) Waiting to run
main / lint (push) Waiting to run
main / test (test-full) (push) Blocked by required conditions
main / test (test-full-386) (push) Blocked by required conditions
main / test (test-pure) (push) Blocked by required conditions
address https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6706.
See
https://github.com/VictoriaMetrics/VictoriaMetrics/blob/vmalert-support-vlog-ds/docs/VictoriaLogs/vmalert.md.
Related fix
https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7254.
Note: in this pull request, vmalert doesn't support
[backfilling](https://github.com/VictoriaMetrics/VictoriaMetrics/blob/vmalert-support-vlog-ds/docs/VictoriaLogs/vmalert.md#rules-backfilling)
for rules with a customized time filter. It might be added in the
future, see [this
issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7289)
for details.
Feature can be tested with image
`victoriametrics/vmalert:heads-vmalert-support-vlog-ds-0-g420629c-scratch`.
---------
Signed-off-by: hagen1778 <roman@victoriametrics.com>
Co-authored-by: hagen1778 <roman@victoriametrics.com>
(cherry picked from commit 68bad22fd2
)
291 lines
8.4 KiB
Go
291 lines
8.4 KiB
Go
package rule
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/datasource"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
|
|
)
|
|
|
|
func TestRecordingRule_Exec(t *testing.T) {
|
|
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)
|
|
}
|
|
}
|
|
|
|
timestamp := time.Now()
|
|
|
|
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",
|
|
},
|
|
}, []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",
|
|
}),
|
|
})
|
|
}
|
|
|
|
func TestRecordingRule_ExecRange(t *testing.T) {
|
|
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)
|
|
}
|
|
}
|
|
|
|
timestamp := time.Now()
|
|
|
|
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{
|
|
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"),
|
|
}, []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",
|
|
Labels: map[string]string{
|
|
"source": "test",
|
|
},
|
|
}, []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)
|
|
}
|
|
}
|
|
|
|
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"),
|
|
}
|
|
|
|
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("unexpected error: %s", err)
|
|
}
|
|
}
|
|
|
|
f(0)
|
|
f(-1)
|
|
}
|
|
|
|
func TestRecordingRuleExec_Negative(t *testing.T) {
|
|
rr := &RecordingRule{
|
|
Name: "job:foo",
|
|
Labels: map[string]string{
|
|
"job": "test",
|
|
},
|
|
state: &ruleState{entries: make([]StateEntry, 10)},
|
|
metrics: &recordingRuleMetrics{
|
|
errors: utils.GetOrCreateCounter(`vmalert_recording_rules_errors_total{alertname="job:foo"}`),
|
|
},
|
|
}
|
|
fq := &datasource.FakeQuerier{}
|
|
expErr := "connection reset by peer"
|
|
fq.SetErr(errors.New(expErr))
|
|
rr.q = fq
|
|
_, err := rr.exec(context.TODO(), time.Now(), 0)
|
|
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)
|
|
}
|
|
|
|
fq.Reset()
|
|
|
|
// add metrics which differs only by `job` label
|
|
// which will be overridden by rule
|
|
fq.Add(metricWithValueAndLabels(t, 1, "__name__", "foo", "job", "foo"))
|
|
fq.Add(metricWithValueAndLabels(t, 2, "__name__", "foo", "job", "bar"))
|
|
|
|
_, err = rr.exec(context.TODO(), time.Now(), 0)
|
|
if err != nil {
|
|
t.Fatalf("cannot execute recroding rule: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestSetIntervalAsTimeFilter(t *testing.T) {
|
|
f := func(s, dType string, expected bool) {
|
|
t.Helper()
|
|
|
|
if setIntervalAsTimeFilter(dType, s) != expected {
|
|
t.Fatalf("unexpected result for hasTimeFilter(%q); want %v", s, expected)
|
|
}
|
|
}
|
|
|
|
f(`* | count()`, "prometheus", false)
|
|
|
|
f(`* | count()`, "vlogs", true)
|
|
f(`error OR _time:5m | count()`, "vlogs", true)
|
|
f(`(_time: 5m AND error) OR (_time: 5m AND warn) | count()`, "vlogs", true)
|
|
f(`* | error OR _time:5m | count()`, "vlogs", true)
|
|
|
|
f(`_time:5m | count()`, "vlogs", false)
|
|
f(`_time:2023-04-25T22:45:59Z | count()`, "vlogs", false)
|
|
f(`error AND _time:5m | count()`, "vlogs", false)
|
|
f(`* | error AND _time:5m | count()`, "vlogs", false)
|
|
}
|