github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/colexec/stats_test.go (about)

     1  // Copyright 2019 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 colexec
    12  
    13  import (
    14  	"context"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/col/coldata"
    19  	"github.com/cockroachdb/cockroach/pkg/sql/colexecbase"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/types"
    23  	"github.com/cockroachdb/cockroach/pkg/testutils/colcontainerutils"
    24  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    25  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  // TestNumBatches is a unit test for NumBatches field of VectorizedStats.
    30  func TestNumBatches(t *testing.T) {
    31  	defer leaktest.AfterTest(t)()
    32  	nBatches := 10
    33  	noop := NewNoop(makeFiniteChunksSourceWithBatchSize(nBatches, coldata.BatchSize()))
    34  	vsc := NewVectorizedStatsCollector(
    35  		noop, 0 /* id */, execinfrapb.ProcessorIDTagKey, true, /* isStall */
    36  		timeutil.NewStopWatch(), nil /* memMonitors */, nil, /* diskMonitors */
    37  	)
    38  	vsc.Init()
    39  	for {
    40  		b := vsc.Next(context.Background())
    41  		if b.Length() == 0 {
    42  			break
    43  		}
    44  	}
    45  	require.Equal(t, nBatches, int(vsc.NumBatches))
    46  }
    47  
    48  // TestNumTuples is a unit test for NumTuples field of VectorizedStats.
    49  func TestNumTuples(t *testing.T) {
    50  	defer leaktest.AfterTest(t)()
    51  	nBatches := 10
    52  	for _, batchSize := range []int{1, 16, 1024} {
    53  		noop := NewNoop(makeFiniteChunksSourceWithBatchSize(nBatches, batchSize))
    54  		vsc := NewVectorizedStatsCollector(
    55  			noop, 0 /* id */, execinfrapb.ProcessorIDTagKey, true, /* isStall */
    56  			timeutil.NewStopWatch(), nil /* memMonitors */, nil, /* diskMonitors */
    57  		)
    58  		vsc.Init()
    59  		for {
    60  			b := vsc.Next(context.Background())
    61  			if b.Length() == 0 {
    62  				break
    63  			}
    64  		}
    65  		require.Equal(t, nBatches*batchSize, int(vsc.NumTuples))
    66  	}
    67  }
    68  
    69  // TestVectorizedStatsCollector is an integration test for the
    70  // VectorizedStatsCollector. It creates two inputs and feeds them into the
    71  // merge joiner and makes sure that all the stats measured on the latter are as
    72  // expected.
    73  func TestVectorizedStatsCollector(t *testing.T) {
    74  	defer leaktest.AfterTest(t)()
    75  	queueCfg, cleanup := colcontainerutils.NewTestingDiskQueueCfg(t, true /* inMem */)
    76  	defer cleanup()
    77  	for nBatches := 1; nBatches < 5; nBatches++ {
    78  		timeSource := timeutil.NewTestTimeSource()
    79  		mjInputWatch := timeutil.NewTestStopWatch(timeSource.Now)
    80  
    81  		leftSource := &timeAdvancingOperator{
    82  			OneInputNode: NewOneInputNode(makeFiniteChunksSourceWithBatchSize(nBatches, coldata.BatchSize())),
    83  			timeSource:   timeSource,
    84  		}
    85  		leftInput := NewVectorizedStatsCollector(
    86  			leftSource, 0 /* id */, execinfrapb.ProcessorIDTagKey, true, /* isStall */
    87  			timeutil.NewTestStopWatch(timeSource.Now), nil /* memMonitors */, nil, /* diskMonitors */
    88  		)
    89  		leftInput.SetOutputWatch(mjInputWatch)
    90  
    91  		rightSource := &timeAdvancingOperator{
    92  			OneInputNode: NewOneInputNode(makeFiniteChunksSourceWithBatchSize(nBatches, coldata.BatchSize())),
    93  			timeSource:   timeSource,
    94  		}
    95  		rightInput := NewVectorizedStatsCollector(
    96  			rightSource, 1 /* id */, execinfrapb.ProcessorIDTagKey, true, /* isStall */
    97  			timeutil.NewTestStopWatch(timeSource.Now), nil /* memMonitors */, nil, /* diskMonitors */
    98  		)
    99  		rightInput.SetOutputWatch(mjInputWatch)
   100  
   101  		mergeJoiner, err := newMergeJoinOp(
   102  			testAllocator, defaultMemoryLimit, queueCfg,
   103  			colexecbase.NewTestingSemaphore(4), sqlbase.InnerJoin, leftInput, rightInput,
   104  			[]*types.T{types.Int}, []*types.T{types.Int},
   105  			[]execinfrapb.Ordering_Column{{ColIdx: 0}},
   106  			[]execinfrapb.Ordering_Column{{ColIdx: 0}},
   107  			testDiskAcc,
   108  		)
   109  		if err != nil {
   110  			t.Fatal(err)
   111  		}
   112  		timeAdvancingMergeJoiner := &timeAdvancingOperator{
   113  			OneInputNode: NewOneInputNode(mergeJoiner),
   114  			timeSource:   timeSource,
   115  		}
   116  		mjStatsCollector := NewVectorizedStatsCollector(
   117  			timeAdvancingMergeJoiner, 2 /* id */, execinfrapb.ProcessorIDTagKey, false, /* isStall */
   118  			mjInputWatch, nil /* memMonitors */, nil, /* diskMonitors */
   119  		)
   120  
   121  		// The inputs are identical, so the merge joiner should output nBatches
   122  		// batches with each having coldata.BatchSize() tuples.
   123  		mjStatsCollector.Init()
   124  		batchCount := 0
   125  		for {
   126  			b := mjStatsCollector.Next(context.Background())
   127  			if b.Length() == 0 {
   128  				break
   129  			}
   130  			require.Equal(t, coldata.BatchSize(), b.Length())
   131  			batchCount++
   132  		}
   133  		mjStatsCollector.finalizeStats()
   134  
   135  		require.Equal(t, nBatches, batchCount)
   136  		require.Equal(t, nBatches, int(mjStatsCollector.NumBatches))
   137  		require.Equal(t, nBatches*coldata.BatchSize(), int(mjStatsCollector.NumTuples))
   138  		// Two inputs are advancing the time source for a total of 2 * nBatches
   139  		// advances, but these do not count towards merge joiner execution time.
   140  		// Merge joiner advances the time on its every non-empty batch totaling
   141  		// nBatches advances that should be accounted for in stats.
   142  		require.Equal(t, time.Duration(nBatches), mjStatsCollector.Time)
   143  	}
   144  }
   145  
   146  func makeFiniteChunksSourceWithBatchSize(nBatches int, batchSize int) colexecbase.Operator {
   147  	typs := []*types.T{types.Int}
   148  	batch := testAllocator.NewMemBatchWithSize(typs, batchSize)
   149  	vec := batch.ColVec(0).Int64()
   150  	for i := 0; i < batchSize; i++ {
   151  		vec[i] = int64(i)
   152  	}
   153  	batch.SetLength(batchSize)
   154  	return newFiniteChunksSource(batch, typs, nBatches, 1 /* matchLen */)
   155  }
   156  
   157  // timeAdvancingOperator is an Operator that advances the time source upon
   158  // receiving a non-empty batch from its input. It is used for testing only.
   159  type timeAdvancingOperator struct {
   160  	OneInputNode
   161  
   162  	timeSource *timeutil.TestTimeSource
   163  }
   164  
   165  var _ colexecbase.Operator = &timeAdvancingOperator{}
   166  
   167  func (o *timeAdvancingOperator) Init() {
   168  	o.input.Init()
   169  }
   170  
   171  func (o *timeAdvancingOperator) Next(ctx context.Context) coldata.Batch {
   172  	b := o.input.Next(ctx)
   173  	if b.Length() > 0 {
   174  		o.timeSource.Advance()
   175  	}
   176  	return b
   177  }