diff --git a/app/victoria-metrics/main_test.go b/app/victoria-metrics/main_test.go index a463cfeff..b0fc39c3e 100644 --- a/app/victoria-metrics/main_test.go +++ b/app/victoria-metrics/main_test.go @@ -63,27 +63,28 @@ var ( ) type test struct { - Name string `json:"name"` - Data string `json:"data"` - Query string `json:"query"` - Result []Row `json:"result"` - Issue string `json:"issue"` + Name string `json:"name"` + Data string `json:"data"` + Query []string `json:"query"` + ResultMetrics []Metric `json:"result_metrics"` + ResultSeries Series `json:"result_series"` + Issue string `json:"issue"` } -type Row struct { +type Metric struct { Metric map[string]string `json:"metric"` Values []float64 `json:"values"` Timestamps []int64 `json:"timestamps"` } -func (r *Row) UnmarshalJSON(b []byte) error { - type withoutInterface Row - var to withoutInterface - if err := json.Unmarshal(populateTimeTpl(b), &to); err != nil { - return err - } - *r = Row(to) - return nil +type Series struct { + Status string `json:"status"` + Data []map[string]string `json:"data"` +} + +func (r *Metric) UnmarshalJSON(b []byte) error { + type plain Metric + return json.Unmarshal(populateTimeTpl(b), (*plain)(r)) } func populateTimeTpl(b []byte) []byte { @@ -241,7 +242,16 @@ func testRead(t *testing.T) { test := x t.Run(test.Name, func(t *testing.T) { t.Parallel() - rowContains(t, httpRead(t, testReadHTTPPath, test.Query), test.Result, test.Issue) + for _, q := range test.Query { + switch true { + case strings.HasPrefix(q, "/api/v1/export"): + checkMetricsResult(t, httpReadMetrics(t, testReadHTTPPath, q), test.ResultMetrics, q, test.Issue) + case strings.HasPrefix(q, "/api/v1/series"): + checkSeriesResult(t, httpReadSeries(t, testReadHTTPPath, q), test.ResultSeries, q, test.Issue) + default: + t.Fatalf("unsupported read query %s", q) + } + } }) } }) @@ -291,36 +301,50 @@ func tcpWrite(t *testing.T, address string, data string) { s.equalInt(n, len(data)) } -func httpRead(t *testing.T, address, query string) []Row { +func httpReadMetrics(t *testing.T, address, query string) []Metric { t.Helper() s := newSuite(t) resp, err := http.Get(address + query) s.noError(err) defer resp.Body.Close() s.equalInt(resp.StatusCode, 200) - var rows []Row + var rows []Metric for dec := json.NewDecoder(resp.Body); dec.More(); { - var row Row + var row Metric s.noError(dec.Decode(&row)) rows = append(rows, row) } return rows } -func rowContains(t *testing.T, rows, contains []Row, issue string) { +func httpReadSeries(t *testing.T, address, query string) Series { t.Helper() - for _, r := range rows { - contains = removeIfFound(r, contains) + s := newSuite(t) + resp, err := http.Get(address + query) + s.noError(err) + defer resp.Body.Close() + s.equalInt(resp.StatusCode, 200) + var Series Series + if err := json.NewDecoder(resp.Body).Decode(&Series); err != nil { + s.noError(err) } - if len(contains) > 0 { + return Series +} + +func checkMetricsResult(t *testing.T, got, want []Metric, query, issue string) { + t.Helper() + for _, r := range append([]Metric(nil), got...) { + want = removeIfFoundMetrics(r, want) + } + if len(want) > 0 { if issue != "" { issue = "Regression in " + issue } - t.Fatalf("result rows %+v not found in %+v.%s", contains, rows, issue) + t.Fatalf("query: %s. Result metrics %+v not found in %+v.%s", query, want, got, issue) } } -func removeIfFound(r Row, contains []Row) []Row { +func removeIfFoundMetrics(r Metric, contains []Metric) []Metric { for i, item := range contains { if reflect.DeepEqual(r.Metric, item.Metric) && reflect.DeepEqual(r.Values, item.Values) && reflect.DeepEqual(r.Timestamps, item.Timestamps) { @@ -331,6 +355,33 @@ func removeIfFound(r Row, contains []Row) []Row { return contains } +func checkSeriesResult(t *testing.T, got, want Series, query, issue string) { + t.Helper() + if got.Status != want.Status { + t.Fatalf("query %s. Result ResultSeries status mismatch %q - %q. %s", query, want.Status, got.Status, issue) + } + wantData := append([]map[string]string(nil), want.Data...) + for _, r := range got.Data { + wantData = removeIfFoundSeries(r, wantData) + } + if len(wantData) > 0 { + if issue != "" { + issue = "Regression in " + issue + } + t.Fatalf("query %s. Result series %+v not found in %+v.%s", query, wantData, got.Data, issue) + } +} + +func removeIfFoundSeries(r map[string]string, contains []map[string]string) []map[string]string { + for i, item := range contains { + if reflect.DeepEqual(r, item) { + contains[i] = contains[len(contains)-1] + return contains[:len(contains)-1] + } + } + return contains +} + type suite struct{ t *testing.T } func newSuite(t *testing.T) *suite { return &suite{t: t} } diff --git a/app/victoria-metrics/testdata/graphite/basic.json b/app/victoria-metrics/testdata/graphite/basic.json index fcebabff7..8aab53366 100644 --- a/app/victoria-metrics/testdata/graphite/basic.json +++ b/app/victoria-metrics/testdata/graphite/basic.json @@ -1,8 +1,8 @@ { "name": "basic_insertion", "data": "graphite.foo.bar.baz;tag1=value1;tag2=value2 123 {TIME}", - "query": "/api/v1/export?match={__name__!=\"\"}", - "result": [ + "query": ["/api/v1/export?match={__name__!=''}"], + "result_metrics": [ {"metric":{"__name__":"graphite.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_S}"]} ] } diff --git a/app/victoria-metrics/testdata/influxdb/basic.json b/app/victoria-metrics/testdata/influxdb/basic.json index c8fd4f02f..09d44b0b9 100644 --- a/app/victoria-metrics/testdata/influxdb/basic.json +++ b/app/victoria-metrics/testdata/influxdb/basic.json @@ -1,8 +1,8 @@ { "name": "basic_insertion", "data": "measurement,tag1=value1,tag2=value2 field1=1.23,field2=123 {TIME}", - "query": "/api/v1/export?match={__name__!=\"\"}", - "result": [ + "query": ["/api/v1/export?match={__name__!=''}"], + "result_metrics": [ {"metric":{"__name__":"measurement_field2","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_MS}"]}, {"metric":{"__name__":"measurement_field1","tag1":"value1","tag2":"value2"},"values":[1.23], "timestamps": ["{TIME_MS}"]} ] diff --git a/app/victoria-metrics/testdata/opentsdb/basic.json b/app/victoria-metrics/testdata/opentsdb/basic.json index 23c8f6668..010b40ffb 100644 --- a/app/victoria-metrics/testdata/opentsdb/basic.json +++ b/app/victoria-metrics/testdata/opentsdb/basic.json @@ -1,8 +1,8 @@ { "name": "basic_insertion", "data": "put openstdb.foo.bar.baz {TIME} 123 tag1=value1 tag2=value2", - "query": "/api/v1/export?match={__name__!=\"\"}", - "result": [ + "query": ["/api/v1/export?match={__name__!=''}"], + "result_metrics": [ {"metric":{"__name__":"openstdb.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_S}"]} ] } diff --git a/app/victoria-metrics/testdata/opentsdbhttp/basic.json b/app/victoria-metrics/testdata/opentsdbhttp/basic.json index f237a0f5a..e8eb44c8d 100644 --- a/app/victoria-metrics/testdata/opentsdbhttp/basic.json +++ b/app/victoria-metrics/testdata/opentsdbhttp/basic.json @@ -1,8 +1,8 @@ { "name": "basic_insertion", "data": "{\"metric\": \"opentsdbhttp.foo\", \"value\": 1001, \"timestamp\": {TIME}, \"tags\": {\"bar\":\"baz\", \"x\": \"y\"}}", - "query": "/api/v1/export?match={__name__!=\"\"}", - "result": [ + "query": ["/api/v1/export?match={__name__!=''}"], + "result_metrics": [ {"metric":{"__name__":"opentsdbhttp.foo","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_S}"]} ] } diff --git a/app/victoria-metrics/testdata/opentsdbhttp/multi_line.json b/app/victoria-metrics/testdata/opentsdbhttp/multi_line.json index 41ca1a3b4..f1f444f75 100644 --- a/app/victoria-metrics/testdata/opentsdbhttp/multi_line.json +++ b/app/victoria-metrics/testdata/opentsdbhttp/multi_line.json @@ -1,8 +1,8 @@ { "name": "multiline", "data": "[{\"metric\": \"opentsdbhttp.multiline1\", \"value\": 1001, \"timestamp\": \"{TIME}\", \"tags\": {\"bar\":\"baz\", \"x\": \"y\"}}, {\"metric\": \"opentsdbhttp.multiline2\", \"value\": 1002, \"timestamp\": {TIME}}]", - "query": "/api/v1/export?match={__name__!=\"\"}", - "result": [ + "query": ["/api/v1/export?match={__name__!=''}"], + "result_metrics": [ {"metric":{"__name__":"opentsdbhttp.multiline1","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_S}"]}, {"metric":{"__name__":"opentsdbhttp.multiline2"},"values":[1002], "timestamps": ["{TIME_S}"]} ] diff --git a/app/victoria-metrics/testdata/prometheus/basic.json b/app/victoria-metrics/testdata/prometheus/basic.json index 4fc3c2850..02cd92d3e 100644 --- a/app/victoria-metrics/testdata/prometheus/basic.json +++ b/app/victoria-metrics/testdata/prometheus/basic.json @@ -1,8 +1,8 @@ { "name": "basic_insertion", - "data": "[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.bar\"},{\"name\":\"baz\",\"value\":\"qux\"}],\"samples\":[{\"value\":100000,\"timestamp\":{TIME}}]}]", - "query": "/api/v1/export?match={__name__!=''}", - "result": [ + "data": "[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.bar\"},{\"name\":\"baz\",\"value\":\"qux\"}],\"samples\":[{\"value\":100000,\"timestamp\":\"{TIME}\"}]}]", + "query": ["/api/v1/export?match={__name__!=''}"], + "result_metrics": [ {"metric":{"__name__":"prometheus.bar","baz":"qux"},"values":[100000], "timestamps": ["{TIME_MS}"]} ] } diff --git a/app/victoria-metrics/testdata/prometheus/case-sensitive-regex.json b/app/victoria-metrics/testdata/prometheus/case-sensitive-regex.json index 08fe37092..f0241f8f4 100644 --- a/app/victoria-metrics/testdata/prometheus/case-sensitive-regex.json +++ b/app/victoria-metrics/testdata/prometheus/case-sensitive-regex.json @@ -2,8 +2,8 @@ "name": "case-sensitive-regex", "issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/161", "data": "[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.sensitiveRegex\"},{\"name\":\"label\",\"value\":\"sensitiveRegex\"}],\"samples\":[{\"value\":2,\"timestamp\":\"{TIME}\"}]},{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.sensitiveRegex\"},{\"name\":\"label\",\"value\":\"SensitiveRegex\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME}\"}]}]", - "query": "/api/v1/export?match={label=~'(?i)sensitiveregex'}", - "result": [ + "query": ["/api/v1/export?match={label=~'(?i)sensitiveregex'}"], + "result_metrics": [ {"metric":{"__name__":"prometheus.sensitiveRegex","label":"sensitiveRegex"},"values":[2], "timestamps": ["{TIME_MS}"]}, {"metric":{"__name__":"prometheus.sensitiveRegex","label":"SensitiveRegex"},"values":[1], "timestamps": ["{TIME_MS}"]} ] diff --git a/app/victoria-metrics/testdata/prometheus/duplicate-label.json b/app/victoria-metrics/testdata/prometheus/duplicate-label.json new file mode 100644 index 000000000..0da251329 --- /dev/null +++ b/app/victoria-metrics/testdata/prometheus/duplicate-label.json @@ -0,0 +1,9 @@ +{ + "name": "duplicate_label", + "issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/172", + "data": "[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.duplicate_label\"},{\"name\":\"duplicate\",\"value\":\"label\"},{\"name\":\"duplicate\",\"value\":\"label\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME}\"}]}]", + "query": ["/api/v1/export?match={__name__!=''}"], + "result_metrics": [ + {"metric":{"__name__":"prometheus.duplicate_label","duplicate":"label"},"values":[1], "timestamps": ["{TIME_MS}"]} + ] +} diff --git a/app/victoria-metrics/testdata/prometheus/match-series.json b/app/victoria-metrics/testdata/prometheus/match-series.json new file mode 100644 index 000000000..9d1547347 --- /dev/null +++ b/app/victoria-metrics/testdata/prometheus/match-series.json @@ -0,0 +1,15 @@ +{ + "name": "match_series", + "issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/155", + "data": "[{\"labels\":[{\"name\":\"__name__\",\"value\":\"MatchSeries\"},{\"name\":\"db\",\"value\":\"TenMinute\"},{\"name\":\"TurbineType\",\"value\":\"V112\"},{\"name\":\"Park\",\"value\":\"1\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME}\"}]},{\"labels\":[{\"name\":\"__name__\",\"value\":\"MatchSeries\"},{\"name\":\"db\",\"value\":\"TenMinute\"},{\"name\":\"TurbineType\",\"value\":\"V112\"},{\"name\":\"Park\",\"value\":\"2\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME}\"}]},{\"labels\":[{\"name\":\"__name__\",\"value\":\"MatchSeries\"},{\"name\":\"db\",\"value\":\"TenMinute\"},{\"name\":\"TurbineType\",\"value\":\"V112\"},{\"name\":\"Park\",\"value\":\"3\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME}\"}]},{\"labels\":[{\"name\":\"__name__\",\"value\":\"MatchSeries\"},{\"name\":\"db\",\"value\":\"TenMinute\"},{\"name\":\"TurbineType\",\"value\":\"V112\"},{\"name\":\"Park\",\"value\":\"4\"}],\"samples\":[{\"value\":1,\"timestamp\":\"{TIME}\"}]}]", + "query": ["/api/v1/series?match[]={__name__='MatchSeries'}", "/api/v1/series?match[]={__name__=~'MatchSeries.*'}"], + "result_series": { + "status": "success", + "data": [ + {"__name__":"MatchSeries","db":"TenMinute","Park":"1","TurbineType":"V112"}, + {"__name__":"MatchSeries","db":"TenMinute","Park":"2","TurbineType":"V112"}, + {"__name__":"MatchSeries","db":"TenMinute","Park":"3","TurbineType":"V112"}, + {"__name__":"MatchSeries","db":"TenMinute","Park":"4","TurbineType":"V112"} + ] + } +}