// Copyright 2017 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tsdb import ( "testing" "github.com/prometheus/prometheus/tsdb/tsdbutil" prom_testutil "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/tsdb/chunks" ) const ( float = "float" intHistogram = "integer histogram" floatHistogram = "float histogram" gaugeIntHistogram = "gauge int histogram" gaugeFloatHistogram = "gauge float histogram" ) type testValue struct { Ts int64 V int64 CounterResetHeader histogram.CounterResetHint } type sampleTypeScenario struct { sampleType string appendFunc func(appender storage.Appender, lbls labels.Labels, ts, value int64) (storage.SeriesRef, sample, error) sampleFunc func(ts, value int64) sample } var sampleTypeScenarios = map[string]sampleTypeScenario{ float: { sampleType: sampleMetricTypeFloat, appendFunc: func(appender storage.Appender, lbls labels.Labels, ts, value int64) (storage.SeriesRef, sample, error) { s := sample{t: ts, f: float64(value)} ref, err := appender.Append(0, lbls, ts, s.f) return ref, s, err }, sampleFunc: func(ts, value int64) sample { return sample{t: ts, f: float64(value)} }, }, intHistogram: { sampleType: sampleMetricTypeHistogram, appendFunc: func(appender storage.Appender, lbls labels.Labels, ts, value int64) (storage.SeriesRef, sample, error) { s := sample{t: ts, h: tsdbutil.GenerateTestHistogram(int(value))} ref, err := appender.AppendHistogram(0, lbls, ts, s.h, nil) return ref, s, err }, sampleFunc: func(ts, value int64) sample { return sample{t: ts, h: tsdbutil.GenerateTestHistogram(int(value))} }, }, floatHistogram: { sampleType: sampleMetricTypeHistogram, appendFunc: func(appender storage.Appender, lbls labels.Labels, ts, value int64) (storage.SeriesRef, sample, error) { s := sample{t: ts, fh: tsdbutil.GenerateTestFloatHistogram(int(value))} ref, err := appender.AppendHistogram(0, lbls, ts, nil, s.fh) return ref, s, err }, sampleFunc: func(ts, value int64) sample { return sample{t: ts, fh: tsdbutil.GenerateTestFloatHistogram(int(value))} }, }, gaugeIntHistogram: { sampleType: sampleMetricTypeHistogram, appendFunc: func(appender storage.Appender, lbls labels.Labels, ts, value int64) (storage.SeriesRef, sample, error) { s := sample{t: ts, h: tsdbutil.GenerateTestGaugeHistogram(int(value))} ref, err := appender.AppendHistogram(0, lbls, ts, s.h, nil) return ref, s, err }, sampleFunc: func(ts, value int64) sample { return sample{t: ts, h: tsdbutil.GenerateTestGaugeHistogram(int(value))} }, }, gaugeFloatHistogram: { sampleType: sampleMetricTypeHistogram, appendFunc: func(appender storage.Appender, lbls labels.Labels, ts, value int64) (storage.SeriesRef, sample, error) { s := sample{t: ts, fh: tsdbutil.GenerateTestGaugeFloatHistogram(int(value))} ref, err := appender.AppendHistogram(0, lbls, ts, nil, s.fh) return ref, s, err }, sampleFunc: func(ts, value int64) sample { return sample{t: ts, fh: tsdbutil.GenerateTestGaugeFloatHistogram(int(value))} }, }, } // requireEqualSeries checks that the actual series are equal to the expected ones. It ignores the counter reset hints for histograms. func requireEqualSeries(t *testing.T, expected, actual map[string][]chunks.Sample, ignoreCounterResets bool) { for name, expectedItem := range expected { actualItem, ok := actual[name] require.True(t, ok, "Expected series %s not found", name) if ignoreCounterResets { requireEqualSamples(t, name, expectedItem, actualItem, requireEqualSamplesIgnoreCounterResets) } else { requireEqualSamples(t, name, expectedItem, actualItem) } } for name := range actual { _, ok := expected[name] require.True(t, ok, "Unexpected series %s", name) } } func requireEqualOOOSamples(t *testing.T, expectedSamples int, db *DB) { require.Equal(t, float64(expectedSamples), prom_testutil.ToFloat64(db.head.metrics.outOfOrderSamplesAppended.WithLabelValues(sampleMetricTypeFloat))+ prom_testutil.ToFloat64(db.head.metrics.outOfOrderSamplesAppended.WithLabelValues(sampleMetricTypeHistogram)), "number of ooo appended samples mismatch") } type requireEqualSamplesOption int const ( requireEqualSamplesNoOption requireEqualSamplesOption = iota requireEqualSamplesIgnoreCounterResets requireEqualSamplesInUseBucketCompare ) func requireEqualSamples(t *testing.T, name string, expected, actual []chunks.Sample, options ...requireEqualSamplesOption) { var ( ignoreCounterResets bool inUseBucketCompare bool ) for _, option := range options { switch option { case requireEqualSamplesIgnoreCounterResets: ignoreCounterResets = true case requireEqualSamplesInUseBucketCompare: inUseBucketCompare = true } } require.Equal(t, len(expected), len(actual), "Length not equal to expected for %s", name) for i, s := range expected { expectedSample := s actualSample := actual[i] require.Equal(t, expectedSample.T(), actualSample.T(), "Different timestamps for %s[%d]", name, i) require.Equal(t, expectedSample.Type().String(), actualSample.Type().String(), "Different types for %s[%d] at ts %d", name, i, expectedSample.T()) switch { case s.H() != nil: { expectedHist := expectedSample.H() actualHist := actualSample.H() if ignoreCounterResets && expectedHist.CounterResetHint != histogram.GaugeType { expectedHist.CounterResetHint = histogram.UnknownCounterReset actualHist.CounterResetHint = histogram.UnknownCounterReset } else { require.Equal(t, expectedHist.CounterResetHint, actualHist.CounterResetHint, "Sample header doesn't match for %s[%d] at ts %d, expected: %s, actual: %s", name, i, expectedSample.T(), counterResetAsString(expectedHist.CounterResetHint), counterResetAsString(actualHist.CounterResetHint)) } if inUseBucketCompare { expectedSample.H().Compact(0) actualSample.H().Compact(0) } require.Equal(t, expectedHist, actualHist, "Sample doesn't match for %s[%d] at ts %d", name, i, expectedSample.T()) } case s.FH() != nil: { expectedHist := expectedSample.FH() actualHist := actualSample.FH() if ignoreCounterResets { expectedHist.CounterResetHint = histogram.UnknownCounterReset actualHist.CounterResetHint = histogram.UnknownCounterReset } else { require.Equal(t, expectedHist.CounterResetHint, actualHist.CounterResetHint, "Sample header doesn't match for %s[%d] at ts %d, expected: %s, actual: %s", name, i, expectedSample.T(), counterResetAsString(expectedHist.CounterResetHint), counterResetAsString(actualHist.CounterResetHint)) } if inUseBucketCompare { expectedSample.FH().Compact(0) actualSample.FH().Compact(0) } require.Equal(t, expectedHist, actualHist, "Sample doesn't match for %s[%d] at ts %d", name, i, expectedSample.T()) } default: expectedFloat := expectedSample.F() actualFloat := actualSample.F() require.Equal(t, expectedFloat, actualFloat, "Sample doesn't match for %s[%d] at ts %d", name, i, expectedSample.T()) } } } func counterResetAsString(h histogram.CounterResetHint) string { switch h { case histogram.UnknownCounterReset: return "UnknownCounterReset" case histogram.CounterReset: return "CounterReset" case histogram.NotCounterReset: return "NotCounterReset" case histogram.GaugeType: return "GaugeType" } panic("Unexpected counter reset type") }