From 34116882b49ed25ce218b55a860abfac71aa035e Mon Sep 17 00:00:00 2001 From: Roman Khavronenko Date: Wed, 18 May 2022 09:50:46 +0200 Subject: [PATCH] vmalert: support `scalar` type in response (#2610) See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2607 Signed-off-by: hagen1778 --- app/vmalert/Makefile | 4 +- .../config/testdata/rules/rules2-good.rules | 12 +++-- app/vmalert/datasource/vm_prom_api.go | 38 +++++++++---- app/vmalert/datasource/vm_test.go | 54 ++++++++++++------- docs/CHANGELOG.md | 1 + 5 files changed, 75 insertions(+), 34 deletions(-) diff --git a/app/vmalert/Makefile b/app/vmalert/Makefile index c96708456..c25722668 100644 --- a/app/vmalert/Makefile +++ b/app/vmalert/Makefile @@ -69,7 +69,7 @@ test-vmalert: go test -v -race -cover ./app/vmalert/remotewrite run-vmalert: vmalert - ./bin/vmalert -rule=app/vmalert/config/testdata/rules2-good.rules \ + ./bin/vmalert -rule=app/vmalert/config/testdata/rules/rules2-good.rules \ -datasource.url=http://localhost:8428 \ -notifier.url=http://localhost:9093 \ -notifier.url=http://127.0.0.1:9093 \ @@ -78,7 +78,7 @@ run-vmalert: vmalert -external.label=cluster=east-1 \ -external.label=replica=a \ -evaluationInterval=3s \ - -rule.configCheckInterval=10s + -configCheckInterval=10s run-vmalert-sd: vmalert ./bin/vmalert -rule=app/vmalert/config/testdata/rules2-good.rules \ diff --git a/app/vmalert/config/testdata/rules/rules2-good.rules b/app/vmalert/config/testdata/rules/rules2-good.rules index 1f431b32f..0387a41cd 100644 --- a/app/vmalert/config/testdata/rules/rules2-good.rules +++ b/app/vmalert/config/testdata/rules/rules2-good.rules @@ -2,8 +2,6 @@ groups: - name: TestGroup interval: 2s concurrency: 2 - extra_filter_labels: # deprecated param, use `params` instead - job: victoriametrics params: denyPartialResponse: ["true"] extra_label: ["env=dev"] @@ -49,4 +47,12 @@ groups: expr: |2 sum(code:requests:rate5m{code="200"}) / - sum(code:requests:rate5m) \ No newline at end of file + sum(code:requests:rate5m) + - record: code:requests:slo + labels: + recording: true + expr: 0.95 + - record: time:current + labels: + recording: true + expr: time() \ No newline at end of file diff --git a/app/vmalert/datasource/vm_prom_api.go b/app/vmalert/datasource/vm_prom_api.go index 6b72efca8..60975e439 100644 --- a/app/vmalert/datasource/vm_prom_api.go +++ b/app/vmalert/datasource/vm_prom_api.go @@ -31,13 +31,6 @@ type promInstant struct { } `json:"result"` } -type promRange struct { - Result []struct { - Labels map[string]string `json:"metric"` - TVs [][2]interface{} `json:"values"` - } `json:"result"` -} - func (r promInstant) metrics() ([]Metric, error) { var result []Metric for i, res := range r.Result { @@ -56,6 +49,13 @@ func (r promInstant) metrics() ([]Metric, error) { return result, nil } +type promRange struct { + Result []struct { + Labels map[string]string `json:"metric"` + TVs [][2]interface{} `json:"values"` + } `json:"result"` +} + func (r promRange) metrics() ([]Metric, error) { var result []Metric for i, res := range r.Result { @@ -80,9 +80,22 @@ func (r promRange) metrics() ([]Metric, error) { return result, nil } +type promScalar [2]interface{} + +func (r promScalar) metrics() ([]Metric, error) { + var m Metric + f, err := strconv.ParseFloat(r[1].(string), 64) + if err != nil { + return nil, fmt.Errorf("metric %v, unable to parse float64 from %s: %w", r, r[1], err) + } + m.Values = append(m.Values, f) + m.Timestamps = append(m.Timestamps, int64(r[0].(float64))) + return []Metric{m}, nil +} + const ( - statusSuccess, statusError = "success", "error" - rtVector, rtMatrix = "vector", "matrix" + statusSuccess, statusError = "success", "error" + rtVector, rtMatrix, rScalar = "vector", "matrix", "scalar" ) func parsePrometheusResponse(req *http.Request, resp *http.Response) ([]Metric, error) { @@ -109,7 +122,14 @@ func parsePrometheusResponse(req *http.Request, resp *http.Response) ([]Metric, return nil, err } return pr.metrics() + case rScalar: + var ps promScalar + if err := json.Unmarshal(r.Data.Result, &ps); err != nil { + return nil, err + } + return ps.metrics() default: + fmt.Println(string(r.Data.Result)) return nil, fmt.Errorf("unknown result type %q", r.Data.ResultType) } } diff --git a/app/vmalert/datasource/vm_test.go b/app/vmalert/datasource/vm_test.go index 09bf4fe04..dba689550 100644 --- a/app/vmalert/datasource/vm_test.go +++ b/app/vmalert/datasource/vm_test.go @@ -37,7 +37,7 @@ func TestVMInstantQuery(t *testing.T) { mux.HandleFunc("/render", func(w http.ResponseWriter, request *http.Request) { c++ switch c { - case 7: + case 8: w.Write([]byte(`[{"target":"constantLine(10)","tags":{"name":"constantLine(10)"},"datapoints":[[10,1611758343],[10,1611758373],[10,1611758403]]}]`)) } }) @@ -75,6 +75,8 @@ func TestVMInstantQuery(t *testing.T) { w.Write([]byte(`{"status":"success","data":{"resultType":"matrix"}}`)) case 6: w.Write([]byte(`{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"vm_rows"},"value":[1583786142,"13763"]},{"metric":{"__name__":"vm_requests"},"value":[1583786140,"2000"]}]}}`)) + case 7: + w.Write([]byte(`{"status":"success","data":{"resultType":"scalar","result":[1583786142, "1"]}}`)) } }) @@ -91,25 +93,20 @@ func TestVMInstantQuery(t *testing.T) { pq := s.BuildWithParams(QuerierParams{DataSourceType: &p, EvaluationInterval: 15 * time.Second}) ts := time.Now() - if _, err := pq.Query(ctx, query, ts); err == nil { - t.Fatalf("expected connection error got nil") + expErr := func(err string) { + if _, err := pq.Query(ctx, query, ts); err == nil { + t.Fatalf("expected %q got nil", err) + } } - if _, err := pq.Query(ctx, query, ts); err == nil { - t.Fatalf("expected invalid response status error got nil") - } - if _, err := pq.Query(ctx, query, ts); err == nil { - t.Fatalf("expected response body error got nil") - } - if _, err := pq.Query(ctx, query, ts); err == nil { - t.Fatalf("expected error status got nil") - } - if _, err := pq.Query(ctx, query, ts); err == nil { - t.Fatalf("expected unknown status got nil") - } - if _, err := pq.Query(ctx, query, ts); err == nil { - t.Fatalf("expected non-vector resultType error got nil") - } - m, err := pq.Query(ctx, query, ts) + + expErr("connection error") // 0 + expErr("invalid response status error") // 1 + expErr("response body error") // 2 + expErr("error status") // 3 + expErr("unknown status") // 4 + expErr("non-vector resultType error") // 5 + + m, err := pq.Query(ctx, query, ts) // 6 - vector if err != nil { t.Fatalf("unexpected %s", err) } @@ -132,10 +129,27 @@ func TestVMInstantQuery(t *testing.T) { t.Fatalf("unexpected metric %+v want %+v", m, expected) } + m, err = pq.Query(ctx, query, ts) // 7 - scalar + if err != nil { + t.Fatalf("unexpected %s", err) + } + if len(m) != 1 { + t.Fatalf("expected 1 metrics got %d in %+v", len(m), m) + } + expected = []Metric{ + { + Timestamps: []int64{1583786142}, + Values: []float64{1}, + }, + } + if !reflect.DeepEqual(m, expected) { + t.Fatalf("unexpected metric %+v want %+v", m, expected) + } + g := NewGraphiteType() gq := s.BuildWithParams(QuerierParams{DataSourceType: &g}) - m, err = gq.Query(ctx, queryRender, ts) + m, err = gq.Query(ctx, queryRender, ts) // 8 - graphite if err != nil { t.Fatalf("unexpected %s", err) } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 704087112..c611055ea 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -18,6 +18,7 @@ The following tip changes can be tested by building VictoriaMetrics components f * FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): support [reusable templates](https://prometheus.io/docs/prometheus/latest/configuration/template_examples/#defining-reusable-templates) for rules annotations. The path to the template files can be specified via `-rule.templates` flag. See more about this feature [here](https://docs.victoriametrics.com/vmalert.html#reusable-templates). Thanks to @AndrewChubatiuk for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2532). * FEATURE: [vmctl](https://docs.victoriametrics.com/vmctl.html): add `influx-prometheus-mode` command-line flag, which allows to restore the original time series written from Prometheus into InfluxDB during data migration from InfluxDB to VictoriaMetrics. See [this feature request](https://github.com/VictoriaMetrics/vmctl/issues/8). Thanks to @mback2k for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/2545). +* BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): support `scalar` result type in response. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2607. * BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): support strings in `humanize.*` template function. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2569. * BUGFIX: [vmalert](https://docs.victoriametrics.com/vmalert.html): proxy `/rules` requests to vmalert from Grafana's alerting UI. This removes errors in Grafana's UI for Grafana versions older than 8.5.*. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/2583. * BUGFIX: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): do not return values from [label_value()](https://docs.victoriametrics.com/MetricsQL.html#label_value) function if the original time series has no values at the selected timestamps.