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  }