github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ts/rollup_test.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package ts 12 13 import ( 14 "context" 15 "fmt" 16 "math" 17 "reflect" 18 "sort" 19 "testing" 20 "time" 21 22 "github.com/cockroachdb/cockroach/pkg/roachpb" 23 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 24 "github.com/cockroachdb/cockroach/pkg/ts/tspb" 25 "github.com/cockroachdb/cockroach/pkg/util/hlc" 26 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 27 "github.com/cockroachdb/cockroach/pkg/util/mon" 28 "github.com/kr/pretty" 29 ) 30 31 type itsdByTimestamp []roachpb.InternalTimeSeriesData 32 33 func (bt itsdByTimestamp) Len() int { 34 return len(bt) 35 } 36 37 func (bt itsdByTimestamp) Less(i int, j int) bool { 38 return bt[i].StartTimestampNanos < bt[j].StartTimestampNanos 39 } 40 41 func (bt itsdByTimestamp) Swap(i int, j int) { 42 bt[i], bt[j] = bt[j], bt[i] 43 } 44 45 func TestComputeRollupFromData(t *testing.T) { 46 defer leaktest.AfterTest(t)() 47 48 for _, tc := range []struct { 49 input tspb.TimeSeriesData 50 expected []roachpb.InternalTimeSeriesData 51 }{ 52 { 53 input: tsd("test.metric", "", 54 tsdp(10, 200), 55 tsdp(20, 300), 56 tsdp(40, 400), 57 tsdp(80, 400), 58 tsdp(97, 400), 59 tsdp(201, 41234), 60 tsdp(249, 423), 61 tsdp(424, 123), 62 tsdp(425, 342), 63 tsdp(426, 643), 64 tsdp(427, 835), 65 tsdp(1023, 999), 66 tsdp(1048, 888), 67 tsdp(1123, 999), 68 tsdp(1248, 888), 69 tsdp(1323, 999), 70 tsdp(1348, 888), 71 ), 72 expected: []roachpb.InternalTimeSeriesData{ 73 makeInternalColumnData(0, 50, []tspb.TimeSeriesDatapoint{ 74 tsdp(10, 200), 75 tsdp(20, 300), 76 tsdp(40, 400), 77 tsdp(80, 400), 78 tsdp(97, 400), 79 tsdp(201, 41234), 80 tsdp(249, 423), 81 tsdp(424, 123), 82 tsdp(425, 342), 83 tsdp(426, 643), 84 tsdp(427, 835), 85 }), 86 makeInternalColumnData(1000, 50, []tspb.TimeSeriesDatapoint{ 87 tsdp(1023, 999), 88 tsdp(1048, 888), 89 tsdp(1123, 999), 90 tsdp(1248, 888), 91 tsdp(1323, 999), 92 tsdp(1348, 888), 93 }), 94 }, 95 }, 96 { 97 input: tsd("test.metric", "", 98 tsdp(1023, 999), 99 tsdp(1048, 888), 100 tsdp(1123, 999), 101 tsdp(1248, 888), 102 ), 103 expected: []roachpb.InternalTimeSeriesData{ 104 makeInternalColumnData(1000, 50, []tspb.TimeSeriesDatapoint{ 105 tsdp(1023, 999), 106 tsdp(1048, 888), 107 tsdp(1123, 999), 108 tsdp(1248, 888), 109 }), 110 }, 111 }, 112 } { 113 t.Run("", func(t *testing.T) { 114 rollups := computeRollupsFromData(tc.input, 50) 115 internal, err := rollups.toInternal(1000, 50) 116 if err != nil { 117 t.Fatal(err) 118 } 119 if a, e := internal, tc.expected; !reflect.DeepEqual(a, e) { 120 for _, diff := range pretty.Diff(a, e) { 121 t.Error(diff) 122 } 123 } 124 125 // Compare expected to test model output; the notion of rollups is 126 // implemented on top of the testmodel, and though it is simple we need to 127 // exercise it here. 128 tm := newTestModelRunner(t) 129 tm.Start() 130 defer tm.Stop() 131 132 tm.storeInModel(resolution1ns, tc.input) 133 tm.rollup(math.MaxInt64, timeSeriesResolutionInfo{ 134 Name: "test.metric", 135 Resolution: resolution1ns, 136 }) 137 tm.prune(math.MaxInt64, timeSeriesResolutionInfo{ 138 Name: "test.metric", 139 Resolution: resolution1ns, 140 }) 141 142 var modelActual []roachpb.InternalTimeSeriesData 143 layout := tm.getModelDiskLayout() 144 for _, data := range layout { 145 var val roachpb.InternalTimeSeriesData 146 if err := data.GetProto(&val); err != nil { 147 t.Fatal(err) 148 } 149 modelActual = append(modelActual, val) 150 } 151 sort.Sort(itsdByTimestamp(modelActual)) 152 153 if a, e := modelActual, tc.expected; !reflect.DeepEqual(a, e) { 154 for _, diff := range pretty.Diff(a, e) { 155 t.Error(diff) 156 } 157 } 158 }) 159 } 160 } 161 162 func TestRollupBasic(t *testing.T) { 163 defer leaktest.AfterTest(t)() 164 tm := newTestModelRunner(t) 165 tm.Start() 166 defer tm.Stop() 167 168 series1a := tsd("test.metric", "a") 169 series1b := tsd("test.metric", "b") 170 series2 := tsd("test.othermetric", "a") 171 for i := 0; i < 500; i++ { 172 series1a.Datapoints = append(series1a.Datapoints, tsdp(time.Duration(i), float64(i))) 173 series1b.Datapoints = append(series1b.Datapoints, tsdp(time.Duration(i), float64(i))) 174 series2.Datapoints = append(series2.Datapoints, tsdp(time.Duration(i), float64(i))) 175 } 176 177 tm.storeTimeSeriesData(resolution1ns, []tspb.TimeSeriesData{series1a, series1b, series2}) 178 tm.assertKeyCount(150) 179 tm.assertModelCorrect() 180 181 now := 250 + resolution1nsDefaultRollupThreshold.Nanoseconds() 182 tm.rollup(now, timeSeriesResolutionInfo{ 183 Name: "test.metric", 184 Resolution: resolution1ns, 185 }) 186 tm.assertKeyCount(152) 187 tm.assertModelCorrect() 188 189 tm.prune(now, timeSeriesResolutionInfo{ 190 Name: "test.metric", 191 Resolution: resolution1ns, 192 }) 193 tm.assertKeyCount(102) 194 tm.assertModelCorrect() 195 196 // Specialty test - rollup only the real series, not the model, and ensure 197 // that the query remains the same. This ensures that the same result is 198 // returned from rolled-up data as is returned from data downsampled during 199 // a query. 200 memOpts := QueryMemoryOptions{ 201 // Large budget, but not maximum to avoid overflows. 202 BudgetBytes: math.MaxInt64, 203 EstimatedSources: 1, // Not needed for rollups 204 InterpolationLimitNanos: 0, 205 Columnar: tm.DB.WriteColumnar(), 206 } 207 if err := tm.DB.rollupTimeSeries( 208 context.Background(), 209 []timeSeriesResolutionInfo{ 210 { 211 Name: "test.othermetric", 212 Resolution: resolution1ns, 213 }, 214 }, 215 hlc.Timestamp{ 216 WallTime: 500 + resolution1nsDefaultRollupThreshold.Nanoseconds(), 217 Logical: 0, 218 }, 219 MakeQueryMemoryContext(tm.workerMemMonitor, tm.resultMemMonitor, memOpts), 220 ); err != nil { 221 t.Fatal(err) 222 } 223 224 if err := tm.DB.pruneTimeSeries( 225 context.Background(), 226 tm.DB.db, 227 []timeSeriesResolutionInfo{ 228 { 229 Name: "test.othermetric", 230 Resolution: resolution1ns, 231 }, 232 }, 233 hlc.Timestamp{ 234 WallTime: 500 + resolution1nsDefaultRollupThreshold.Nanoseconds(), 235 Logical: 0, 236 }, 237 ); err != nil { 238 t.Fatal(err) 239 } 240 241 { 242 query := tm.makeQuery("test.othermetric", resolution1ns, 0, 500) 243 query.SampleDurationNanos = 50 244 query.assertSuccess(10, 1) 245 } 246 } 247 248 func TestRollupMemoryConstraint(t *testing.T) { 249 defer leaktest.AfterTest(t)() 250 tm := newTestModelRunner(t) 251 tm.Start() 252 defer tm.Stop() 253 254 series1 := tsd("test.metric", "a") 255 series2 := tsd("test.othermetric", "a") 256 for i := 0; i < 500; i++ { 257 series1.Datapoints = append(series1.Datapoints, tsdp(time.Duration(i), float64(i))) 258 series2.Datapoints = append(series2.Datapoints, tsdp(time.Duration(i), float64(i))) 259 } 260 261 tm.storeTimeSeriesData(resolution1ns, []tspb.TimeSeriesData{series1, series2}) 262 tm.assertKeyCount(100) 263 tm.assertModelCorrect() 264 265 // Construct a memory monitor that will be used to measure the high-water 266 // mark of memory usage for the rollup process. 267 adjustedMon := mon.MakeMonitor( 268 "timeseries-test-worker-adjusted", 269 mon.MemoryResource, 270 nil, 271 nil, 272 1, 273 math.MaxInt64, 274 cluster.MakeTestingClusterSettings(), 275 ) 276 adjustedMon.Start(context.Background(), tm.workerMemMonitor, mon.BoundAccount{}) 277 defer adjustedMon.Stop(context.Background()) 278 279 // Roll up time series with the new monitor to measure high-water mark 280 // of 281 qmc := MakeQueryMemoryContext(&adjustedMon, &adjustedMon, QueryMemoryOptions{ 282 // Large budget, but not maximum to avoid overflows. 283 BudgetBytes: math.MaxInt64, 284 EstimatedSources: 1, // Not needed for rollups 285 Columnar: tm.DB.WriteColumnar(), 286 }) 287 tm.rollupWithMemoryContext(qmc, 500+resolution1nsDefaultRollupThreshold.Nanoseconds(), timeSeriesResolutionInfo{ 288 Name: "test.othermetric", 289 Resolution: resolution1ns, 290 }) 291 tm.prune(500+resolution1nsDefaultRollupThreshold.Nanoseconds(), timeSeriesResolutionInfo{ 292 Name: "test.othermetric", 293 Resolution: resolution1ns, 294 }) 295 296 tm.assertKeyCount(51) 297 tm.assertModelCorrect() 298 299 // Ensure that we used at least 50 slabs worth of memory at one time. 300 if a, e := adjustedMon.MaximumBytes(), 50*qmc.computeSizeOfSlab(resolution1ns); a < e { 301 t.Fatalf("memory usage for query was %d, wanted at least %d", a, e) 302 } 303 304 // Limit testing: set multiple constraints on memory and ensure that they 305 // are being respected through chunking. 306 for i, limit := range []int64{ 307 25 * qmc.computeSizeOfSlab(resolution1ns), 308 10 * qmc.computeSizeOfSlab(resolution1ns), 309 } { 310 // Generate a new series. 311 seriesName := fmt.Sprintf("metric.series%d", i) 312 seriesData := tsd(seriesName, "a") 313 for j := 0; j < 500; j++ { 314 seriesData.Datapoints = append(seriesData.Datapoints, tsdp(time.Duration(j), float64(j))) 315 } 316 tm.storeTimeSeriesData(resolution1ns, []tspb.TimeSeriesData{seriesData}) 317 tm.assertModelCorrect() 318 tm.assertKeyCount(51 + i /* rollups from previous iterations */ + 50) 319 320 // Restart monitor to clear query memory options. 321 adjustedMon.Stop(context.Background()) 322 adjustedMon.Start(context.Background(), tm.workerMemMonitor, mon.BoundAccount{}) 323 324 qmc := MakeQueryMemoryContext(&adjustedMon, &adjustedMon, QueryMemoryOptions{ 325 // Large budget, but not maximum to avoid overflows. 326 BudgetBytes: limit, 327 EstimatedSources: 1, // Not needed for rollups 328 Columnar: tm.DB.WriteColumnar(), 329 }) 330 tm.rollupWithMemoryContext(qmc, 500+resolution1nsDefaultRollupThreshold.Nanoseconds(), timeSeriesResolutionInfo{ 331 Name: seriesName, 332 Resolution: resolution1ns, 333 }) 334 tm.prune(500+resolution1nsDefaultRollupThreshold.Nanoseconds(), timeSeriesResolutionInfo{ 335 Name: seriesName, 336 Resolution: resolution1ns, 337 }) 338 339 tm.assertKeyCount(51 + i + 1) 340 tm.assertModelCorrect() 341 342 // Check budget was not exceeded. Computation of budget usage is not exact 343 // in the case of rollups, due to the fact that results are tracked with 344 // the same monitor but may vary in size based on the specific input 345 // rows. Because of this, allow up to 20% over limit. 346 if a, e := float64(adjustedMon.MaximumBytes()), float64(limit)*1.2; a > e { 347 t.Fatalf("memory usage for query was %f, wanted a limit of %f", a, e) 348 } 349 350 // Check that budget was used. 351 if a, e := float64(adjustedMon.MaximumBytes()), float64(limit)*0.95; a < e { 352 t.Fatalf("memory usage for query was %f, wanted at least %f", a, e) 353 } 354 } 355 }