github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ts/server_test.go (about)

     1  // Copyright 2015 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_test
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"io"
    17  	"reflect"
    18  	"sort"
    19  	"strings"
    20  	"testing"
    21  	"unsafe"
    22  
    23  	"github.com/cockroachdb/cockroach/pkg/base"
    24  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver"
    25  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    26  	"github.com/cockroachdb/cockroach/pkg/rpc"
    27  	"github.com/cockroachdb/cockroach/pkg/server"
    28  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    29  	"github.com/cockroachdb/cockroach/pkg/ts"
    30  	"github.com/cockroachdb/cockroach/pkg/ts/tspb"
    31  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    32  	"github.com/cockroachdb/errors"
    33  	"github.com/gogo/protobuf/proto"
    34  	"github.com/kr/pretty"
    35  )
    36  
    37  func TestServerQuery(t *testing.T) {
    38  	defer leaktest.AfterTest(t)()
    39  	s, _, _ := serverutils.StartServer(t, base.TestServerArgs{
    40  		Knobs: base.TestingKnobs{
    41  			Store: &kvserver.StoreTestingKnobs{
    42  				DisableTimeSeriesMaintenanceQueue: true,
    43  			},
    44  		},
    45  	})
    46  	defer s.Stopper().Stop(context.Background())
    47  	tsrv := s.(*server.TestServer)
    48  
    49  	// Populate data directly.
    50  	tsdb := tsrv.TsDB()
    51  	if err := tsdb.StoreData(context.Background(), ts.Resolution10s, []tspb.TimeSeriesData{
    52  		{
    53  			Name:   "test.metric",
    54  			Source: "source1",
    55  			Datapoints: []tspb.TimeSeriesDatapoint{
    56  				{
    57  					TimestampNanos: 400 * 1e9,
    58  					Value:          100.0,
    59  				},
    60  				{
    61  					TimestampNanos: 500 * 1e9,
    62  					Value:          200.0,
    63  				},
    64  				{
    65  					TimestampNanos: 520 * 1e9,
    66  					Value:          300.0,
    67  				},
    68  			},
    69  		},
    70  		{
    71  			Name:   "test.metric",
    72  			Source: "source2",
    73  			Datapoints: []tspb.TimeSeriesDatapoint{
    74  				{
    75  					TimestampNanos: 400 * 1e9,
    76  					Value:          100.0,
    77  				},
    78  				{
    79  					TimestampNanos: 500 * 1e9,
    80  					Value:          200.0,
    81  				},
    82  				{
    83  					TimestampNanos: 510 * 1e9,
    84  					Value:          250.0,
    85  				},
    86  				{
    87  					TimestampNanos: 530 * 1e9,
    88  					Value:          350.0,
    89  				},
    90  			},
    91  		},
    92  		{
    93  			Name: "other.metric",
    94  			Datapoints: []tspb.TimeSeriesDatapoint{
    95  				{
    96  					TimestampNanos: 400 * 1e9,
    97  					Value:          100.0,
    98  				},
    99  				{
   100  					TimestampNanos: 500 * 1e9,
   101  					Value:          200.0,
   102  				},
   103  				{
   104  					TimestampNanos: 510 * 1e9,
   105  					Value:          250.0,
   106  				},
   107  			},
   108  		},
   109  	}); err != nil {
   110  		t.Fatal(err)
   111  	}
   112  
   113  	expectedResult := &tspb.TimeSeriesQueryResponse{
   114  		Results: []tspb.TimeSeriesQueryResponse_Result{
   115  			{
   116  				Query: tspb.Query{
   117  					Name:    "test.metric",
   118  					Sources: []string{"source1", "source2"},
   119  				},
   120  				Datapoints: []tspb.TimeSeriesDatapoint{
   121  					{
   122  						TimestampNanos: 500 * 1e9,
   123  						Value:          400.0,
   124  					},
   125  					{
   126  						TimestampNanos: 510 * 1e9,
   127  						Value:          500.0,
   128  					},
   129  					{
   130  						TimestampNanos: 520 * 1e9,
   131  						Value:          600.0,
   132  					},
   133  				},
   134  			},
   135  			{
   136  				Query: tspb.Query{
   137  					Name:    "other.metric",
   138  					Sources: []string{""},
   139  				},
   140  				Datapoints: []tspb.TimeSeriesDatapoint{
   141  					{
   142  						TimestampNanos: 500 * 1e9,
   143  						Value:          200.0,
   144  					},
   145  					{
   146  						TimestampNanos: 510 * 1e9,
   147  						Value:          250.0,
   148  					},
   149  				},
   150  			},
   151  			{
   152  				Query: tspb.Query{
   153  					Name:             "test.metric",
   154  					Sources:          []string{"source1", "source2"},
   155  					Downsampler:      tspb.TimeSeriesQueryAggregator_MAX.Enum(),
   156  					SourceAggregator: tspb.TimeSeriesQueryAggregator_MAX.Enum(),
   157  					Derivative:       tspb.TimeSeriesQueryDerivative_DERIVATIVE.Enum(),
   158  				},
   159  				Datapoints: []tspb.TimeSeriesDatapoint{
   160  					{
   161  						TimestampNanos: 500 * 1e9,
   162  						Value:          1.0,
   163  					},
   164  					{
   165  						TimestampNanos: 510 * 1e9,
   166  						Value:          5.0,
   167  					},
   168  					{
   169  						TimestampNanos: 520 * 1e9,
   170  						Value:          5.0,
   171  					},
   172  				},
   173  			},
   174  		},
   175  	}
   176  
   177  	conn, err := tsrv.RPCContext().GRPCDialNode(tsrv.Cfg.Addr, tsrv.NodeID(),
   178  		rpc.DefaultClass).Connect(context.Background())
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  	client := tspb.NewTimeSeriesClient(conn)
   183  	response, err := client.Query(context.Background(), &tspb.TimeSeriesQueryRequest{
   184  		StartNanos: 500 * 1e9,
   185  		EndNanos:   526 * 1e9,
   186  		Queries: []tspb.Query{
   187  			{
   188  				Name: "test.metric",
   189  			},
   190  			{
   191  				Name: "other.metric",
   192  			},
   193  			{
   194  				Name:             "test.metric",
   195  				Downsampler:      tspb.TimeSeriesQueryAggregator_MAX.Enum(),
   196  				SourceAggregator: tspb.TimeSeriesQueryAggregator_MAX.Enum(),
   197  				Derivative:       tspb.TimeSeriesQueryDerivative_DERIVATIVE.Enum(),
   198  			},
   199  		},
   200  	})
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  	for _, r := range response.Results {
   205  		sort.Strings(r.Sources)
   206  	}
   207  	if !proto.Equal(response, expectedResult) {
   208  		t.Fatalf("actual response \n%v\n did not match expected response \n%v",
   209  			response, expectedResult)
   210  	}
   211  
   212  	// Test a query with downsampling enabled. The query is a sum of maximums.
   213  	expectedResult = &tspb.TimeSeriesQueryResponse{
   214  		Results: []tspb.TimeSeriesQueryResponse_Result{
   215  			{
   216  				Query: tspb.Query{
   217  					Name:        "test.metric",
   218  					Sources:     []string{"source1", "source2"},
   219  					Downsampler: tspb.TimeSeriesQueryAggregator_MAX.Enum(),
   220  				},
   221  				Datapoints: []tspb.TimeSeriesDatapoint{
   222  					{
   223  						TimestampNanos: 0,
   224  						Value:          200.0,
   225  					},
   226  					{
   227  						TimestampNanos: 500 * 1e9,
   228  						Value:          650.0,
   229  					},
   230  				},
   231  			},
   232  		},
   233  	}
   234  	response, err = client.Query(context.Background(), &tspb.TimeSeriesQueryRequest{
   235  		StartNanos:  0,
   236  		EndNanos:    1000 * 1e9,
   237  		SampleNanos: 500 * 1e9,
   238  		Queries: []tspb.Query{
   239  			{
   240  				Name:        "test.metric",
   241  				Downsampler: tspb.TimeSeriesQueryAggregator_MAX.Enum(),
   242  			},
   243  		},
   244  	})
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  	for _, r := range response.Results {
   249  		sort.Strings(r.Sources)
   250  	}
   251  	if !proto.Equal(response, expectedResult) {
   252  		t.Fatalf("actual response \n%v\n did not match expected response \n%v",
   253  			response, expectedResult)
   254  	}
   255  }
   256  
   257  // TestServerQueryStarvation tests a very specific scenario, wherein a single
   258  // query request has more queries than the server's MaxWorkers count.
   259  func TestServerQueryStarvation(t *testing.T) {
   260  	defer leaktest.AfterTest(t)()
   261  	workerCount := 20
   262  	s, _, _ := serverutils.StartServer(t, base.TestServerArgs{
   263  		TimeSeriesQueryWorkerMax: workerCount,
   264  	})
   265  	defer s.Stopper().Stop(context.Background())
   266  	tsrv := s.(*server.TestServer)
   267  
   268  	seriesCount := workerCount * 2
   269  	if err := populateSeries(seriesCount, 10, 3, tsrv.TsDB()); err != nil {
   270  		t.Fatal(err)
   271  	}
   272  
   273  	conn, err := tsrv.RPCContext().GRPCDialNode(tsrv.Cfg.Addr, tsrv.NodeID(),
   274  		rpc.DefaultClass).Connect(context.Background())
   275  	if err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	client := tspb.NewTimeSeriesClient(conn)
   279  
   280  	queries := make([]tspb.Query, 0, seriesCount)
   281  	for i := 0; i < seriesCount; i++ {
   282  		queries = append(queries, tspb.Query{
   283  			Name: seriesName(i),
   284  		})
   285  	}
   286  
   287  	if _, err := client.Query(context.Background(), &tspb.TimeSeriesQueryRequest{
   288  		StartNanos: 0 * 1e9,
   289  		EndNanos:   500 * 1e9,
   290  		Queries:    queries,
   291  	}); err != nil {
   292  		t.Fatal(err)
   293  	}
   294  }
   295  
   296  // TestServerQueryMemoryManagement verifies that queries succeed under
   297  // constrained memory requirements.
   298  func TestServerQueryMemoryManagement(t *testing.T) {
   299  	defer leaktest.AfterTest(t)()
   300  
   301  	// Number of workers that will be available to process data.
   302  	workerCount := 20
   303  	// Number of series that will be queried.
   304  	seriesCount := workerCount * 2
   305  	// Number of data sources that will be generated.
   306  	sourceCount := 6
   307  	// Number of slabs (hours) of data we want to generate
   308  	slabCount := 5
   309  	// Generated datapoints every 100 seconds, so compute how many we want to
   310  	// generate data across the target number of hours.
   311  	valueCount := int(ts.Resolution10s.SlabDuration()/(100*1e9)) * slabCount
   312  
   313  	// MemoryBudget is a function of slab size and source count.
   314  	samplesPerSlab := ts.Resolution10s.SlabDuration() / ts.Resolution10s.SampleDuration()
   315  	sizeOfSlab := int64(unsafe.Sizeof(roachpb.InternalTimeSeriesData{})) + (int64(unsafe.Sizeof(roachpb.InternalTimeSeriesSample{})) * samplesPerSlab)
   316  	budget := 3 * sizeOfSlab * int64(sourceCount) * int64(workerCount)
   317  
   318  	s, _, _ := serverutils.StartServer(t, base.TestServerArgs{
   319  		TimeSeriesQueryWorkerMax:    workerCount,
   320  		TimeSeriesQueryMemoryBudget: budget,
   321  	})
   322  	defer s.Stopper().Stop(context.Background())
   323  	tsrv := s.(*server.TestServer)
   324  
   325  	if err := populateSeries(seriesCount, sourceCount, valueCount, tsrv.TsDB()); err != nil {
   326  		t.Fatal(err)
   327  	}
   328  
   329  	conn, err := tsrv.RPCContext().GRPCDialNode(tsrv.Cfg.Addr, tsrv.NodeID(),
   330  		rpc.DefaultClass).Connect(context.Background())
   331  	if err != nil {
   332  		t.Fatal(err)
   333  	}
   334  	client := tspb.NewTimeSeriesClient(conn)
   335  
   336  	queries := make([]tspb.Query, 0, seriesCount)
   337  	for i := 0; i < seriesCount; i++ {
   338  		queries = append(queries, tspb.Query{
   339  			Name: seriesName(i),
   340  		})
   341  	}
   342  
   343  	if _, err := client.Query(context.Background(), &tspb.TimeSeriesQueryRequest{
   344  		StartNanos: 0 * 1e9,
   345  		EndNanos:   5 * 3600 * 1e9,
   346  		Queries:    queries,
   347  	}); err != nil {
   348  		t.Fatal(err)
   349  	}
   350  }
   351  
   352  func TestServerDump(t *testing.T) {
   353  	defer leaktest.AfterTest(t)()
   354  
   355  	s, _, _ := serverutils.StartServer(t, base.TestServerArgs{
   356  		Knobs: base.TestingKnobs{
   357  			Store: &kvserver.StoreTestingKnobs{
   358  				DisableTimeSeriesMaintenanceQueue: true,
   359  			},
   360  		},
   361  	})
   362  	defer s.Stopper().Stop(context.Background())
   363  	tsrv := s.(*server.TestServer)
   364  
   365  	seriesCount := 10
   366  	sourceCount := 5
   367  	// Number of slabs (hours) of data we want to generate
   368  	slabCount := 5
   369  	// Generated datapoints every 100 seconds, so compute how many we want to
   370  	// generate data across the target number of hours.
   371  	valueCount := int(ts.Resolution10s.SlabDuration()/(100*1e9)) * slabCount
   372  
   373  	if err := populateSeries(seriesCount, sourceCount, valueCount, tsrv.TsDB()); err != nil {
   374  		t.Fatal(err)
   375  	}
   376  
   377  	conn, err := tsrv.RPCContext().GRPCDialNode(tsrv.Cfg.Addr, tsrv.NodeID(),
   378  		rpc.DefaultClass).Connect(context.Background())
   379  	if err != nil {
   380  		t.Fatal(err)
   381  	}
   382  	client := tspb.NewTimeSeriesClient(conn)
   383  
   384  	dumpClient, err := client.Dump(context.Background(), nil)
   385  	if err != nil {
   386  		t.Fatal(err)
   387  	}
   388  
   389  	// Read data from dump command.
   390  	resultMap := make(map[string]map[string]tspb.TimeSeriesData)
   391  	totalMsgCount := 0
   392  	for {
   393  		msg, err := dumpClient.Recv()
   394  		if err == io.EOF {
   395  			break
   396  		}
   397  		if err != nil {
   398  			t.Fatal(err)
   399  		}
   400  		// The dump will include all of the real time series metrics recorded by the
   401  		// server. Filter out everything other than the metrics we are creating
   402  		// for this test.
   403  		if !strings.HasPrefix(msg.Name, "metric.") {
   404  			continue
   405  		}
   406  		sourceMap, ok := resultMap[msg.Name]
   407  		if !ok {
   408  			sourceMap = make(map[string]tspb.TimeSeriesData)
   409  			resultMap[msg.Name] = sourceMap
   410  		}
   411  		if data, ok := sourceMap[msg.Source]; !ok {
   412  			sourceMap[msg.Source] = *msg
   413  		} else {
   414  			data.Datapoints = append(data.Datapoints, msg.Datapoints...)
   415  			sourceMap[msg.Source] = data
   416  		}
   417  		totalMsgCount++
   418  	}
   419  
   420  	// Generate expected data.
   421  	expectedMap := make(map[string]map[string]tspb.TimeSeriesData)
   422  	for series := 0; series < seriesCount; series++ {
   423  		sourceMap := make(map[string]tspb.TimeSeriesData)
   424  		expectedMap[seriesName(series)] = sourceMap
   425  		for source := 0; source < sourceCount; source++ {
   426  			sourceMap[sourceName(source)] = tspb.TimeSeriesData{
   427  				Name:       seriesName(series),
   428  				Source:     sourceName(source),
   429  				Datapoints: generateTimeSeriesDatapoints(valueCount),
   430  			}
   431  		}
   432  	}
   433  
   434  	if a, e := totalMsgCount, seriesCount*sourceCount*slabCount; a != e {
   435  		t.Fatalf("dump returned %d messages, expected %d", a, e)
   436  	}
   437  	if a, e := resultMap, expectedMap; !reflect.DeepEqual(a, e) {
   438  		for _, diff := range pretty.Diff(a, e) {
   439  			t.Error(diff)
   440  		}
   441  	}
   442  }
   443  
   444  func BenchmarkServerQuery(b *testing.B) {
   445  	s, _, _ := serverutils.StartServer(b, base.TestServerArgs{})
   446  	defer s.Stopper().Stop(context.Background())
   447  	tsrv := s.(*server.TestServer)
   448  
   449  	// Populate data for large number of time series.
   450  	seriesCount := 50
   451  	sourceCount := 10
   452  	if err := populateSeries(seriesCount, sourceCount, 3, tsrv.TsDB()); err != nil {
   453  		b.Fatal(err)
   454  	}
   455  
   456  	conn, err := tsrv.RPCContext().GRPCDialNode(tsrv.Cfg.Addr, tsrv.NodeID(),
   457  		rpc.DefaultClass).Connect(context.Background())
   458  	if err != nil {
   459  		b.Fatal(err)
   460  	}
   461  	client := tspb.NewTimeSeriesClient(conn)
   462  
   463  	queries := make([]tspb.Query, 0, seriesCount)
   464  	for i := 0; i < seriesCount; i++ {
   465  		queries = append(queries, tspb.Query{
   466  			Name: fmt.Sprintf("metric.%d", i),
   467  		})
   468  	}
   469  
   470  	b.ResetTimer()
   471  	for i := 0; i < b.N; i++ {
   472  		if _, err := client.Query(context.Background(), &tspb.TimeSeriesQueryRequest{
   473  			StartNanos: 0 * 1e9,
   474  			EndNanos:   500 * 1e9,
   475  			Queries:    queries,
   476  		}); err != nil {
   477  			b.Fatal(err)
   478  		}
   479  	}
   480  }
   481  
   482  func seriesName(seriesNum int) string {
   483  	return fmt.Sprintf("metric.%d", seriesNum)
   484  }
   485  
   486  func sourceName(sourceNum int) string {
   487  	return fmt.Sprintf("source.%d", sourceNum)
   488  }
   489  
   490  func generateTimeSeriesDatapoints(valueCount int) []tspb.TimeSeriesDatapoint {
   491  	result := make([]tspb.TimeSeriesDatapoint, 0, valueCount)
   492  	var i int64
   493  	for i = 0; i < int64(valueCount); i++ {
   494  		result = append(result, tspb.TimeSeriesDatapoint{
   495  			TimestampNanos: i * 100 * 1e9,
   496  			Value:          float64(i * 100),
   497  		})
   498  	}
   499  	return result
   500  }
   501  
   502  func populateSeries(seriesCount, sourceCount, valueCount int, tsdb *ts.DB) error {
   503  	for series := 0; series < seriesCount; series++ {
   504  		for source := 0; source < sourceCount; source++ {
   505  			if err := tsdb.StoreData(context.Background(), ts.Resolution10s, []tspb.TimeSeriesData{
   506  				{
   507  					Name:       seriesName(series),
   508  					Source:     sourceName(source),
   509  					Datapoints: generateTimeSeriesDatapoints(valueCount),
   510  				},
   511  			}); err != nil {
   512  				return errors.Errorf(
   513  					"error storing data for series %d, source %d: %s", series, source, err,
   514  				)
   515  			}
   516  		}
   517  	}
   518  	return nil
   519  }