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 }