package tests import ( "fmt" "testing" "time" at "github.com/VictoriaMetrics/VictoriaMetrics/apptest" "github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal" pb "github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" ) func TestSingleDeduplication_dedulicationIsOff(t *testing.T) { tc := at.NewTestCase(t) defer tc.Stop() sut := tc.MustStartVmsingle("vmsingle", []string{ "-storageDataPath=" + tc.Dir() + "/vmsingle", "-retentionPeriod=100y", "-dedup.minScrapeInterval=0", }) testDeduplication(tc, sut, false) } func TestSingleDeduplication_dedulicationIsOn(t *testing.T) { tc := at.NewTestCase(t) defer tc.Stop() sut := tc.MustStartVmsingle("vmsingle", []string{ "-storageDataPath=" + tc.Dir() + "/vmsingle", "-retentionPeriod=100y", "-dedup.minScrapeInterval=10s", }) testDeduplication(tc, sut, true) } func TestClusterDeduplication_deduplicationIsOff(t *testing.T) { tc := at.NewTestCase(t) defer tc.Stop() sut := tc.MustStartCluster(&at.ClusterOptions{ Vmstorage1Instance: "vmstorage1", Vmstorage1Flags: []string{ "-dedup.minScrapeInterval=0", }, Vmstorage2Instance: "vmstorage2", Vmstorage2Flags: []string{ "-dedup.minScrapeInterval=0", }, VminsertInstance: "vminsert", VmselectInstance: "vmselect", }) testDeduplication(tc, sut, false) } func TestClusterDeduplication_deduplicationIsOn(t *testing.T) { tc := at.NewTestCase(t) defer tc.Stop() sut := tc.MustStartCluster(&at.ClusterOptions{ Vmstorage1Instance: "vmstorage1", Vmstorage1Flags: []string{ "-dedup.minScrapeInterval=10s", }, Vmstorage2Instance: "vmstorage2", Vmstorage2Flags: []string{ "-dedup.minScrapeInterval=10s", }, VminsertInstance: "vminsert", VmselectInstance: "vmselect", }) testDeduplication(tc, sut, true) } // See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#deduplication func testDeduplication(tc *at.TestCase, sut at.PrometheusWriteQuerier, deduplicationIsOn bool) { t := tc.T() firstDayOfThisMonth := func() time.Time { t := time.Now().UTC() return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.UTC) } // Intentionally check that deduplication works for the current month, since // by reading the code it may seem that deduplication for the current month // is skipped. // // See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/6965 start := firstDayOfThisMonth() end := start.Add(1 * time.Hour) ts1 := start.Add(1 * time.Second).UnixMilli() ts3 := start.Add(3 * time.Second).UnixMilli() ts5 := start.Add(5 * time.Second).UnixMilli() ts10 := start.Add(10 * time.Second).UnixMilli() data := []pb.TimeSeries{ { Labels: []pb.Label{{Name: "__name__", Value: "metric1"}}, Samples: []pb.Sample{ {Timestamp: ts1, Value: 3}, {Timestamp: ts3, Value: 10}, {Timestamp: ts5, Value: 5}, }, }, { Labels: []pb.Label{{Name: "__name__", Value: "metric2"}}, Samples: []pb.Sample{ {Timestamp: ts1, Value: 3}, {Timestamp: ts3, Value: decimal.StaleNaN}, {Timestamp: ts5, Value: 5}, }, }, { Labels: []pb.Label{{Name: "__name__", Value: "metric3"}}, Samples: []pb.Sample{ {Timestamp: ts10, Value: 30}, {Timestamp: ts10, Value: 100}, {Timestamp: ts10, Value: 50}, }, }, { Labels: []pb.Label{{Name: "__name__", Value: "metric4"}}, Samples: []pb.Sample{ {Timestamp: ts10, Value: 30}, {Timestamp: ts10, Value: decimal.StaleNaN}, {Timestamp: ts10, Value: 50}, }, }, } sut.PrometheusAPIV1Write(t, data, at.QueryOpts{}) sut.ForceFlush(t) sut.ForceMerge(t) wantDuplicates := &at.PrometheusAPIV1QueryResponse{ Status: "success", Data: &at.QueryData{ ResultType: "matrix", Result: []*at.QueryResult{ {Metric: map[string]string{"__name__": "metric1"}, Samples: []*at.Sample{ {Timestamp: ts1, Value: 3}, {Timestamp: ts3, Value: 10}, {Timestamp: ts5, Value: 5}, }}, {Metric: map[string]string{"__name__": "metric2"}, Samples: []*at.Sample{ {Timestamp: ts1, Value: 3}, {Timestamp: ts3, Value: decimal.StaleNaN}, {Timestamp: ts5, Value: 5}, }}, {Metric: map[string]string{"__name__": "metric3"}, Samples: []*at.Sample{ {Timestamp: ts10, Value: 30}, {Timestamp: ts10, Value: 50}, {Timestamp: ts10, Value: 100}, }}, {Metric: map[string]string{"__name__": "metric4"}, Samples: []*at.Sample{ {Timestamp: ts10, Value: 30}, {Timestamp: ts10, Value: 50}, {Timestamp: ts10, Value: decimal.StaleNaN}, }}, }, }, } wantDeduped := &at.PrometheusAPIV1QueryResponse{ Status: "success", Data: &at.QueryData{ ResultType: "matrix", Result: []*at.QueryResult{ {Metric: map[string]string{"__name__": "metric1"}, Samples: []*at.Sample{ // VictoriaMetrics leaves a single raw sample with the // biggest timestamp for each time series per each // -dedup.minScrapeInterval discrete interval if // -dedup.minScrapeInterval is set to positive duration. {Timestamp: ts5, Value: 5}, }}, {Metric: map[string]string{"__name__": "metric2"}, Samples: []*at.Sample{ // Even if NaN is present among duplicates, VictoriaMetrics // still chooses the sample with the biggest timestamp. {Timestamp: ts5, Value: 5}, }}, {Metric: map[string]string{"__name__": "metric3"}, Samples: []*at.Sample{ // If multiple raw samples have the same timestamp on the // given -dedup.minScrapeInterval discrete interval, then // the sample with the biggest value is kept. {Timestamp: ts10, Value: 100}, }}, {Metric: map[string]string{"__name__": "metric4"}, Samples: []*at.Sample{ // If multiple raw samples have the same timestamp on the // given -dedup.minScrapeInterval discrete interval, then // stale markers are preferred over any other value. {Timestamp: ts10, Value: decimal.StaleNaN}, }}, }, }, } want := wantDuplicates if deduplicationIsOn { want = wantDeduped } tc.Assert(&at.AssertOptions{ Msg: "unexpected response", Got: func() any { got := sut.PrometheusAPIV1Export(t, `{__name__=~"metric.*"}`, at.QueryOpts{ ReduceMemUsage: "1", Start: fmt.Sprintf("%d", start.UnixMilli()), End: fmt.Sprintf("%d", end.UnixMilli()), }) // Delete cluster-specific labels from the metric name since they are // irrelevant for the test. for _, result := range got.Data.Result { delete(result.Metric, "vm_account_id") delete(result.Metric, "vm_project_id") } got.Sort() return got }, Want: want, CmpOpts: []cmp.Option{ cmpopts.EquateNaNs(), }, }) }