github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/encoding/m3tsz/roundtrip_test.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package m3tsz
    22  
    23  import (
    24  	"bytes"
    25  	"math"
    26  	"math/rand"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/dbnode/encoding/testgen"
    31  	"github.com/m3db/m3/src/dbnode/ts"
    32  	"github.com/m3db/m3/src/x/context"
    33  	xtime "github.com/m3db/m3/src/x/time"
    34  
    35  	"github.com/stretchr/testify/require"
    36  )
    37  
    38  func TestCountsRoundTrip(t *testing.T) {
    39  	numPoints := 1000
    40  	numIterations := 100
    41  	for i := 0; i < numIterations; i++ {
    42  		testRoundTrip(t, generateCounterDatapoints(numPoints))
    43  	}
    44  }
    45  
    46  func TestTimerRoundTrip(t *testing.T) {
    47  	numPoints := 1000
    48  	numIterations := 100
    49  	for i := 0; i < numIterations; i++ {
    50  		testRoundTrip(t, generateTimerDatapoints(numPoints))
    51  	}
    52  }
    53  
    54  func TestSmallGaugeRoundTrip(t *testing.T) {
    55  	numPoints := 1000
    56  	numIterations := 100
    57  	for i := 0; i < numIterations; i++ {
    58  		testRoundTrip(t, generateSmallFloatDatapoints(numPoints))
    59  	}
    60  }
    61  
    62  func TestPreciseGaugeRoundTrip(t *testing.T) {
    63  	numPoints := 1000
    64  	numIterations := 100
    65  	for i := 0; i < numIterations; i++ {
    66  		testRoundTrip(t, generatePreciseFloatDatapoints(numPoints))
    67  	}
    68  }
    69  
    70  func TestNegativeGaugeFloatsRoundTrip(t *testing.T) {
    71  	numPoints := 1000
    72  	numIterations := 100
    73  	for i := 0; i < numIterations; i++ {
    74  		testRoundTrip(t, generateNegativeFloatDatapoints(numPoints))
    75  	}
    76  }
    77  
    78  func TestMixedGaugeIntRoundTrip(t *testing.T) {
    79  	numPoints := 1000
    80  	numIterations := 100
    81  	for i := 0; i < numIterations; i++ {
    82  		testRoundTrip(t, generateMixSignIntDatapoints(numPoints))
    83  	}
    84  }
    85  
    86  func TestMixedRoundTrip(t *testing.T) {
    87  	timeUnit := time.Second
    88  	numPoints := 1000
    89  	numIterations := 100
    90  	for i := 0; i < numIterations; i++ {
    91  		testRoundTrip(t, generateMixedDatapoints(numPoints, timeUnit))
    92  	}
    93  }
    94  
    95  func TestPrecision(t *testing.T) {
    96  	var (
    97  		num       = 100
    98  		input     = make([]ts.Datapoint, 0, num)
    99  		timestamp = xtime.Now()
   100  	)
   101  
   102  	for i := 0; i < num; i++ {
   103  		input = append(input, ts.Datapoint{TimestampNanos: timestamp, Value: 187.80131100000006})
   104  		timestamp = timestamp.Add(time.Minute)
   105  	}
   106  
   107  	testRoundTrip(t, input)
   108  }
   109  
   110  func TestIntOverflow(t *testing.T) {
   111  	testRoundTrip(t, generateOverflowDatapoints())
   112  }
   113  
   114  func testRoundTrip(t *testing.T, input []ts.Datapoint) {
   115  	validateRoundTrip(t, input, true)
   116  	validateRoundTrip(t, input, false)
   117  }
   118  
   119  func validateRoundTrip(t *testing.T, input []ts.Datapoint, intOpt bool) {
   120  	ctx := context.NewBackground()
   121  	defer ctx.Close()
   122  
   123  	encoder := NewEncoder(testStartTime, nil, intOpt, nil)
   124  	timeUnits := make([]xtime.Unit, 0, len(input))
   125  	annotations := make([]ts.Annotation, 0, len(input))
   126  
   127  	for i, v := range input {
   128  		timeUnit := xtime.Second
   129  		if i == 0 {
   130  			timeUnit = xtime.Millisecond
   131  		} else if i == 10 {
   132  			timeUnit = xtime.Microsecond
   133  		}
   134  
   135  		var annotation, annotationCopy ts.Annotation
   136  		if i < 5 {
   137  			annotation = ts.Annotation("foo")
   138  		} else if i < 7 {
   139  			annotation = ts.Annotation("bar")
   140  		} else if i == 10 {
   141  			annotation = ts.Annotation("long annotation long annotation long annotation long annotation")
   142  		}
   143  
   144  		if annotation != nil {
   145  			annotationCopy = append(annotationCopy, annotation...)
   146  		}
   147  
   148  		annotations = append(annotations, annotationCopy)
   149  		timeUnits = append(timeUnits, timeUnit)
   150  
   151  		err := encoder.Encode(v, timeUnit, annotation)
   152  		require.NoError(t, err)
   153  
   154  		for j := range annotation {
   155  			// Invalidate the original annotation to make sure encoder is not holding a reference to it.
   156  			annotation[j] = '*'
   157  		}
   158  	}
   159  
   160  	decoder := NewDecoder(intOpt, nil)
   161  	stream, ok := encoder.Stream(ctx)
   162  	require.True(t, ok)
   163  
   164  	it := decoder.Decode(stream)
   165  	defer it.Close()
   166  
   167  	i := 0
   168  	for it.Next() {
   169  		v, u, a := it.Current()
   170  
   171  		expectedAnnotation := annotations[i]
   172  		if i > 0 && bytes.Equal(annotations[i-1], expectedAnnotation) {
   173  			// Repeated annotation values must be discarded.
   174  			expectedAnnotation = nil
   175  		}
   176  
   177  		require.Equal(t, input[i].TimestampNanos, v.TimestampNanos, "datapoint #%d", i)
   178  		require.Equal(t, input[i].Value, v.Value, "datapoint #%d", i)
   179  		require.Equal(t, timeUnits[i], u, "datapoint #%d", i)
   180  		require.Equal(t, expectedAnnotation, a, "datapoint #%d", i)
   181  
   182  		i++
   183  	}
   184  
   185  	require.NoError(t, it.Err())
   186  	require.Equal(t, len(input), i)
   187  	it.Reset(nil, nil)
   188  	it.Close()
   189  }
   190  
   191  func generateCounterDatapoints(numPoints int) []ts.Datapoint {
   192  	return generateDataPoints(numPoints, 12, 0)
   193  }
   194  
   195  func generateTimerDatapoints(numPoints int) []ts.Datapoint {
   196  	return generateDataPoints(numPoints, 7, 6)
   197  }
   198  
   199  func generateSmallFloatDatapoints(numPoints int) []ts.Datapoint {
   200  	return generateDataPoints(numPoints, 0, 1)
   201  }
   202  
   203  func generatePreciseFloatDatapoints(numPoints int) []ts.Datapoint {
   204  	return generateDataPoints(numPoints, 2, 16)
   205  }
   206  
   207  func generateNegativeFloatDatapoints(numPoints int) []ts.Datapoint {
   208  	dps := generateDataPoints(numPoints, 5, 3)
   209  	for i, dp := range dps {
   210  		dps[i].Value = -1 * dp.Value
   211  	}
   212  
   213  	return dps
   214  }
   215  
   216  func generateMixSignIntDatapoints(numPoints int) []ts.Datapoint {
   217  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   218  	dps := generateDataPoints(numPoints, 3, 0)
   219  	for i, dp := range dps {
   220  		if r.Float64() < 0.5 {
   221  			dps[i].Value = -1 * dp.Value
   222  		}
   223  	}
   224  
   225  	return dps
   226  }
   227  
   228  func generateDataPoints(numPoints int, numDig, numDec int) []ts.Datapoint {
   229  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   230  	var startTime int64 = 1427162462
   231  	currentTime := xtime.FromSeconds(startTime)
   232  	endTime := testStartTime.Add(2 * time.Hour)
   233  	currentValue := 1.0
   234  	res := []ts.Datapoint{{TimestampNanos: currentTime, Value: currentValue}}
   235  	for i := 1; i < numPoints; i++ {
   236  		currentTime = currentTime.Add(time.Second * time.Duration(rand.Intn(1200)))
   237  		currentValue = testgen.GenerateFloatVal(r, numDig, numDec)
   238  		if !currentTime.Before(endTime) {
   239  			break
   240  		}
   241  		res = append(res, ts.Datapoint{TimestampNanos: currentTime, Value: currentValue})
   242  	}
   243  	return res
   244  }
   245  
   246  func generateMixedDatapoints(numPoints int, timeUnit time.Duration) []ts.Datapoint {
   247  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   248  	var startTime int64 = 1427162462
   249  	currentTime := xtime.FromSeconds(startTime)
   250  	endTime := testStartTime.Add(2 * time.Hour)
   251  	currentValue := testgen.GenerateFloatVal(r, 3, 16)
   252  	res := []ts.Datapoint{{TimestampNanos: currentTime, Value: currentValue}}
   253  
   254  	for i := 1; i < numPoints; i++ {
   255  		currentTime = currentTime.Add(time.Second * time.Duration(r.Intn(7200)))
   256  		if r.Float64() < 0.1 {
   257  			currentValue = testgen.GenerateFloatVal(r, 5, 0)
   258  		} else if r.Float64() < 0.2 {
   259  			currentValue = testgen.GenerateFloatVal(r, 3, 16)
   260  		}
   261  
   262  		if !currentTime.Before(endTime) {
   263  			break
   264  		}
   265  		res = append(res, ts.Datapoint{TimestampNanos: currentTime, Value: currentValue})
   266  	}
   267  	return res
   268  }
   269  
   270  func generateOverflowDatapoints() []ts.Datapoint {
   271  	var startTime int64 = 1427162462
   272  	currentTime := xtime.FromSeconds(startTime)
   273  	largeInt := float64(math.MaxInt64 - 1)
   274  	largeNegInt := float64(math.MinInt64 + 1)
   275  
   276  	vals := []float64{largeInt, 10, largeNegInt, 10, largeNegInt, largeInt, -12, largeInt, 14.5, largeInt, largeNegInt, 12.34858499392, largeInt}
   277  	res := make([]ts.Datapoint, len(vals))
   278  
   279  	for i, val := range vals {
   280  		res[i] = ts.Datapoint{TimestampNanos: currentTime, Value: val}
   281  		currentTime = currentTime.Add(time.Second)
   282  	}
   283  
   284  	return res
   285  }