From 4984e71da641651bd601ec7cbf14bf6f975f4379 Mon Sep 17 00:00:00 2001 From: Hui Wang Date: Thu, 17 Oct 2024 17:00:34 +0800 Subject: [PATCH] vmalert-tool: add more syntax checks for `input_series` and `exp_samples` (#7263) address https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7224, allow using ``` exp_samples: - labels: '{}' ``` for prometheus compatibility. --------- Signed-off-by: hagen1778 Co-authored-by: hagen1778 --- app/vmalert-tool/unittest/input.go | 32 ++++++++++++++-------- app/vmalert-tool/unittest/input_test.go | 35 ++++++++++++++++++++++++- app/vmalert-tool/unittest/recording.go | 16 ++++++----- docs/changelog/CHANGELOG.md | 1 + 4 files changed, 65 insertions(+), 19 deletions(-) diff --git a/app/vmalert-tool/unittest/input.go b/app/vmalert-tool/unittest/input.go index 163bf47c3..dc70e73cc 100644 --- a/app/vmalert-tool/unittest/input.go +++ b/app/vmalert-tool/unittest/input.go @@ -43,18 +43,33 @@ func httpWrite(address string, r io.Reader) { // writeInputSeries send input series to vmstorage and flush them func writeInputSeries(input []series, interval *promutils.Duration, startStamp time.Time, dst string) error { r := testutil.WriteRequest{} + var err error + r.Timeseries, err = parseInputSeries(input, interval, startStamp) + if err != nil { + return err + } + + data := testutil.Compress(r) + // write input series to vm + httpWrite(dst, bytes.NewBuffer(data)) + vmstorage.Storage.DebugFlush() + return nil +} + +func parseInputSeries(input []series, interval *promutils.Duration, startStamp time.Time) ([]testutil.TimeSeries, error) { + var res []testutil.TimeSeries for _, data := range input { expr, err := metricsql.Parse(data.Series) if err != nil { - return fmt.Errorf("failed to parse series %s: %v", data.Series, err) + return res, fmt.Errorf("failed to parse series %s: %v", data.Series, err) } promvals, err := parseInputValue(data.Values, true) if err != nil { - return fmt.Errorf("failed to parse input series value %s: %v", data.Values, err) + return res, fmt.Errorf("failed to parse input series value %s: %v", data.Values, err) } metricExpr, ok := expr.(*metricsql.MetricExpr) - if !ok { - return fmt.Errorf("failed to parse series %s to metric expr: %v", data.Series, err) + if !ok || len(metricExpr.LabelFilterss) != 1 { + return res, fmt.Errorf("got invalid input series %s: %v", data.Series, err) } samples := make([]testutil.Sample, 0, len(promvals)) ts := startStamp @@ -71,14 +86,9 @@ func writeInputSeries(input []series, interval *promutils.Duration, startStamp t for _, filter := range metricExpr.LabelFilterss[0] { ls = append(ls, testutil.Label{Name: filter.Label, Value: filter.Value}) } - r.Timeseries = append(r.Timeseries, testutil.TimeSeries{Labels: ls, Samples: samples}) + res = append(res, testutil.TimeSeries{Labels: ls, Samples: samples}) } - - data := testutil.Compress(r) - // write input series to vm - httpWrite(dst, bytes.NewBuffer(data)) - vmstorage.Storage.DebugFlush() - return nil + return res, nil } // parseInputValue support input like "1", "1+1x1 _ -4 3+20x1", see more examples in test. diff --git a/app/vmalert-tool/unittest/input_test.go b/app/vmalert-tool/unittest/input_test.go index 6d6ccda3e..8b44d7c7f 100644 --- a/app/vmalert-tool/unittest/input_test.go +++ b/app/vmalert-tool/unittest/input_test.go @@ -2,8 +2,10 @@ package unittest import ( "testing" + "time" "github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal" + "github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils" ) func TestParseInputValue_Failure(t *testing.T) { @@ -43,7 +45,7 @@ func TestParseInputValue_Success(t *testing.T) { if decimal.IsStaleNaN(outputExpected[i].Value) && decimal.IsStaleNaN(output[i].Value) { continue } - t.Fatalf("unexpeccted Value field in the output\ngot\n%v\nwant\n%v", output, outputExpected) + t.Fatalf("unexpected Value field in the output\ngot\n%v\nwant\n%v", output, outputExpected) } } } @@ -64,3 +66,34 @@ func TestParseInputValue_Success(t *testing.T) { f("1+1x1 _ -4 stale 3+20x1", []sequenceValue{{Value: 1}, {Value: 2}, {Omitted: true}, {Value: -4}, {Value: decimal.StaleNaN}, {Value: 3}, {Value: 23}}) } + +func TestParseInputSeries_Success(t *testing.T) { + f := func(input []series) { + t.Helper() + var interval promutils.Duration + _, err := parseInputSeries(input, &interval, time.Now()) + if err != nil { + t.Fatalf("expect to see no error: %v", err) + } + } + + f([]series{{Series: "test", Values: "1"}}) + f([]series{{Series: "test{}", Values: "1"}}) + f([]series{{Series: "test{env=\"prod\",job=\"a\" }", Values: "1"}}) + f([]series{{Series: "{__name__=\"test\",env=\"prod\",job=\"a\" }", Values: "1"}}) +} + +func TestParseInputSeries_Fail(t *testing.T) { + f := func(input []series) { + t.Helper() + var interval promutils.Duration + _, err := parseInputSeries(input, &interval, time.Now()) + if err == nil { + t.Fatalf("expect to see error: %v", err) + } + } + + f([]series{{Series: "", Values: "1"}}) + f([]series{{Series: "{}", Values: "1"}}) + f([]series{{Series: "{env=\"prod\",job=\"a\" or env=\"dev\",job=\"b\"}", Values: "1"}}) +} diff --git a/app/vmalert-tool/unittest/recording.go b/app/vmalert-tool/unittest/recording.go index a47d104d6..d54d8c1e5 100644 --- a/app/vmalert-tool/unittest/recording.go +++ b/app/vmalert-tool/unittest/recording.go @@ -57,16 +57,18 @@ Outer: continue Outer } metricsqlMetricExpr, ok := metricsqlExpr.(*metricsql.MetricExpr) - if !ok { + if !ok || len(metricsqlMetricExpr.LabelFilterss) > 1 { checkErrs = append(checkErrs, fmt.Errorf("\n expr: %q, time: %s, err: %v", mt.Expr, - mt.EvalTime.Duration().String(), fmt.Errorf("got unsupported metricsql type"))) + mt.EvalTime.Duration().String(), fmt.Errorf("got invalid exp_samples: %q", s.Labels))) continue Outer } - for _, l := range metricsqlMetricExpr.LabelFilterss[0] { - expLb = append(expLb, datasource.Label{ - Name: l.Label, - Value: l.Value, - }) + if len(metricsqlMetricExpr.LabelFilterss) > 0 { + for _, l := range metricsqlMetricExpr.LabelFilterss[0] { + expLb = append(expLb, datasource.Label{ + Name: l.Label, + Value: l.Value, + }) + } } } sort.Slice(expLb, func(i, j int) bool { diff --git a/docs/changelog/CHANGELOG.md b/docs/changelog/CHANGELOG.md index 800f093da..b9a1e5b8c 100644 --- a/docs/changelog/CHANGELOG.md +++ b/docs/changelog/CHANGELOG.md @@ -30,6 +30,7 @@ See also [LTS releases](https://docs.victoriametrics.com/lts-releases/). * BUGFIX: [vmui](https://docs.victoriametrics.com/#vmui): fix error messages rendering from overflowing the screen with long messages. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/7207). * BUGFIX: [vmctl](https://docs.victoriametrics.com/vmctl/): properly add metrics tags for `opentsdb` migration source. Previously it could have empty values. See [this PR](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/7161). * BUGFIX: [vmalert-tool](https://docs.victoriametrics.com/vmalert-tool/): reduce the initial health check interval for datasource. This reduces the time spent on evaluating rules by vmalert-tool. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6970). +* BUGFIX: [vmalert-tool](https://docs.victoriametrics.com/vmalert-tool/): allow specifying empty labels list `labels: '{}'` in the same way as promtool does. This improves compatibility between these tools. ## [v1.104.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.104.0)