mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-02-09 15:27:11 +00:00
Add regression test for duplicated labels and series
Part of https://github.com/VictoriaMetrics/VictoriaMetrics/issues/187 cover: - https://github.com/VictoriaMetrics/VictoriaMetrics/issues/155 - https://github.com/VictoriaMetrics/VictoriaMetrics/issues/172
This commit is contained in:
parent
fa81f82714
commit
1e6ae9eff4
10 changed files with 114 additions and 39 deletions
|
@ -63,27 +63,28 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type test struct {
|
type test struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Data string `json:"data"`
|
Data string `json:"data"`
|
||||||
Query string `json:"query"`
|
Query []string `json:"query"`
|
||||||
Result []Row `json:"result"`
|
ResultMetrics []Metric `json:"result_metrics"`
|
||||||
Issue string `json:"issue"`
|
ResultSeries Series `json:"result_series"`
|
||||||
|
Issue string `json:"issue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Row struct {
|
type Metric struct {
|
||||||
Metric map[string]string `json:"metric"`
|
Metric map[string]string `json:"metric"`
|
||||||
Values []float64 `json:"values"`
|
Values []float64 `json:"values"`
|
||||||
Timestamps []int64 `json:"timestamps"`
|
Timestamps []int64 `json:"timestamps"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Row) UnmarshalJSON(b []byte) error {
|
type Series struct {
|
||||||
type withoutInterface Row
|
Status string `json:"status"`
|
||||||
var to withoutInterface
|
Data []map[string]string `json:"data"`
|
||||||
if err := json.Unmarshal(populateTimeTpl(b), &to); err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
func (r *Metric) UnmarshalJSON(b []byte) error {
|
||||||
*r = Row(to)
|
type plain Metric
|
||||||
return nil
|
return json.Unmarshal(populateTimeTpl(b), (*plain)(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
func populateTimeTpl(b []byte) []byte {
|
func populateTimeTpl(b []byte) []byte {
|
||||||
|
@ -241,7 +242,16 @@ func testRead(t *testing.T) {
|
||||||
test := x
|
test := x
|
||||||
t.Run(test.Name, func(t *testing.T) {
|
t.Run(test.Name, func(t *testing.T) {
|
||||||
t.Parallel()
|
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))
|
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()
|
t.Helper()
|
||||||
s := newSuite(t)
|
s := newSuite(t)
|
||||||
resp, err := http.Get(address + query)
|
resp, err := http.Get(address + query)
|
||||||
s.noError(err)
|
s.noError(err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
s.equalInt(resp.StatusCode, 200)
|
s.equalInt(resp.StatusCode, 200)
|
||||||
var rows []Row
|
var rows []Metric
|
||||||
for dec := json.NewDecoder(resp.Body); dec.More(); {
|
for dec := json.NewDecoder(resp.Body); dec.More(); {
|
||||||
var row Row
|
var row Metric
|
||||||
s.noError(dec.Decode(&row))
|
s.noError(dec.Decode(&row))
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
func rowContains(t *testing.T, rows, contains []Row, issue string) {
|
func httpReadSeries(t *testing.T, address, query string) Series {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, r := range rows {
|
s := newSuite(t)
|
||||||
contains = removeIfFound(r, contains)
|
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 != "" {
|
if issue != "" {
|
||||||
issue = "Regression in " + 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 {
|
for i, item := range contains {
|
||||||
if reflect.DeepEqual(r.Metric, item.Metric) && reflect.DeepEqual(r.Values, item.Values) &&
|
if reflect.DeepEqual(r.Metric, item.Metric) && reflect.DeepEqual(r.Values, item.Values) &&
|
||||||
reflect.DeepEqual(r.Timestamps, item.Timestamps) {
|
reflect.DeepEqual(r.Timestamps, item.Timestamps) {
|
||||||
|
@ -331,6 +355,33 @@ func removeIfFound(r Row, contains []Row) []Row {
|
||||||
return contains
|
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 }
|
type suite struct{ t *testing.T }
|
||||||
|
|
||||||
func newSuite(t *testing.T) *suite { return &suite{t: t} }
|
func newSuite(t *testing.T) *suite { return &suite{t: t} }
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "basic_insertion",
|
"name": "basic_insertion",
|
||||||
"data": "graphite.foo.bar.baz;tag1=value1;tag2=value2 123 {TIME}",
|
"data": "graphite.foo.bar.baz;tag1=value1;tag2=value2 123 {TIME}",
|
||||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||||
"result": [
|
"result_metrics": [
|
||||||
{"metric":{"__name__":"graphite.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_S}"]}
|
{"metric":{"__name__":"graphite.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_S}"]}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "basic_insertion",
|
"name": "basic_insertion",
|
||||||
"data": "measurement,tag1=value1,tag2=value2 field1=1.23,field2=123 {TIME}",
|
"data": "measurement,tag1=value1,tag2=value2 field1=1.23,field2=123 {TIME}",
|
||||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||||
"result": [
|
"result_metrics": [
|
||||||
{"metric":{"__name__":"measurement_field2","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_MS}"]},
|
{"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}"]}
|
{"metric":{"__name__":"measurement_field1","tag1":"value1","tag2":"value2"},"values":[1.23], "timestamps": ["{TIME_MS}"]}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "basic_insertion",
|
"name": "basic_insertion",
|
||||||
"data": "put openstdb.foo.bar.baz {TIME} 123 tag1=value1 tag2=value2",
|
"data": "put openstdb.foo.bar.baz {TIME} 123 tag1=value1 tag2=value2",
|
||||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||||
"result": [
|
"result_metrics": [
|
||||||
{"metric":{"__name__":"openstdb.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_S}"]}
|
{"metric":{"__name__":"openstdb.foo.bar.baz","tag1":"value1","tag2":"value2"},"values":[123], "timestamps": ["{TIME_S}"]}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "basic_insertion",
|
"name": "basic_insertion",
|
||||||
"data": "{\"metric\": \"opentsdbhttp.foo\", \"value\": 1001, \"timestamp\": {TIME}, \"tags\": {\"bar\":\"baz\", \"x\": \"y\"}}",
|
"data": "{\"metric\": \"opentsdbhttp.foo\", \"value\": 1001, \"timestamp\": {TIME}, \"tags\": {\"bar\":\"baz\", \"x\": \"y\"}}",
|
||||||
"query": "/api/v1/export?match={__name__!=\"\"}",
|
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||||
"result": [
|
"result_metrics": [
|
||||||
{"metric":{"__name__":"opentsdbhttp.foo","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_S}"]}
|
{"metric":{"__name__":"opentsdbhttp.foo","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_S}"]}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "multiline",
|
"name": "multiline",
|
||||||
"data": "[{\"metric\": \"opentsdbhttp.multiline1\", \"value\": 1001, \"timestamp\": \"{TIME}\", \"tags\": {\"bar\":\"baz\", \"x\": \"y\"}}, {\"metric\": \"opentsdbhttp.multiline2\", \"value\": 1002, \"timestamp\": {TIME}}]",
|
"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__!=\"\"}",
|
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||||
"result": [
|
"result_metrics": [
|
||||||
{"metric":{"__name__":"opentsdbhttp.multiline1","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_S}"]},
|
{"metric":{"__name__":"opentsdbhttp.multiline1","bar":"baz","x":"y"},"values":[1001], "timestamps": ["{TIME_S}"]},
|
||||||
{"metric":{"__name__":"opentsdbhttp.multiline2"},"values":[1002], "timestamps": ["{TIME_S}"]}
|
{"metric":{"__name__":"opentsdbhttp.multiline2"},"values":[1002], "timestamps": ["{TIME_S}"]}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "basic_insertion",
|
"name": "basic_insertion",
|
||||||
"data": "[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.bar\"},{\"name\":\"baz\",\"value\":\"qux\"}],\"samples\":[{\"value\":100000,\"timestamp\":{TIME}}]}]",
|
"data": "[{\"labels\":[{\"name\":\"__name__\",\"value\":\"prometheus.bar\"},{\"name\":\"baz\",\"value\":\"qux\"}],\"samples\":[{\"value\":100000,\"timestamp\":\"{TIME}\"}]}]",
|
||||||
"query": "/api/v1/export?match={__name__!=''}",
|
"query": ["/api/v1/export?match={__name__!=''}"],
|
||||||
"result": [
|
"result_metrics": [
|
||||||
{"metric":{"__name__":"prometheus.bar","baz":"qux"},"values":[100000], "timestamps": ["{TIME_MS}"]}
|
{"metric":{"__name__":"prometheus.bar","baz":"qux"},"values":[100000], "timestamps": ["{TIME_MS}"]}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
"name": "case-sensitive-regex",
|
"name": "case-sensitive-regex",
|
||||||
"issue": "https://github.com/VictoriaMetrics/VictoriaMetrics/issues/161",
|
"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}\"}]}]",
|
"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'}",
|
"query": ["/api/v1/export?match={label=~'(?i)sensitiveregex'}"],
|
||||||
"result": [
|
"result_metrics": [
|
||||||
{"metric":{"__name__":"prometheus.sensitiveRegex","label":"sensitiveRegex"},"values":[2], "timestamps": ["{TIME_MS}"]},
|
{"metric":{"__name__":"prometheus.sensitiveRegex","label":"sensitiveRegex"},"values":[2], "timestamps": ["{TIME_MS}"]},
|
||||||
{"metric":{"__name__":"prometheus.sensitiveRegex","label":"SensitiveRegex"},"values":[1], "timestamps": ["{TIME_MS}"]}
|
{"metric":{"__name__":"prometheus.sensitiveRegex","label":"SensitiveRegex"},"values":[1], "timestamps": ["{TIME_MS}"]}
|
||||||
]
|
]
|
||||||
|
|
9
app/victoria-metrics/testdata/prometheus/duplicate-label.json
vendored
Normal file
9
app/victoria-metrics/testdata/prometheus/duplicate-label.json
vendored
Normal file
|
@ -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}"]}
|
||||||
|
]
|
||||||
|
}
|
15
app/victoria-metrics/testdata/prometheus/match-series.json
vendored
Normal file
15
app/victoria-metrics/testdata/prometheus/match-series.json
vendored
Normal file
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue