github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/rowexec/stats.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 rowexec
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"time"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/kv"
    19  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    20  	"github.com/cockroachdb/cockroach/pkg/sql/execinfra"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/execinfrapb"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/row"
    23  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    24  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    25  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    26  )
    27  
    28  // inputStatCollector wraps an execinfra.RowSource and collects stats from it.
    29  type inputStatCollector struct {
    30  	execinfra.RowSource
    31  	InputStats
    32  }
    33  
    34  var _ execinfra.RowSource = &inputStatCollector{}
    35  var _ execinfra.OpNode = &inputStatCollector{}
    36  
    37  // newInputStatCollector creates a new inputStatCollector that wraps the given
    38  // input.
    39  func newInputStatCollector(input execinfra.RowSource) *inputStatCollector {
    40  	return &inputStatCollector{RowSource: input}
    41  }
    42  
    43  // ChildCount is part of the OpNode interface.
    44  func (isc *inputStatCollector) ChildCount(verbose bool) int {
    45  	if _, ok := isc.RowSource.(execinfra.OpNode); ok {
    46  		return 1
    47  	}
    48  	return 0
    49  }
    50  
    51  // Child is part of the OpNode interface.
    52  func (isc *inputStatCollector) Child(nth int, verbose bool) execinfra.OpNode {
    53  	if nth == 0 {
    54  		return isc.RowSource.(execinfra.OpNode)
    55  	}
    56  	panic(fmt.Sprintf("invalid index %d", nth))
    57  }
    58  
    59  // Next implements the RowSource interface. It calls Next on the embedded
    60  // RowSource and collects stats.
    61  func (isc *inputStatCollector) Next() (sqlbase.EncDatumRow, *execinfrapb.ProducerMetadata) {
    62  	start := timeutil.Now()
    63  	row, meta := isc.RowSource.Next()
    64  	if row != nil {
    65  		isc.NumRows++
    66  	}
    67  	isc.StallTime += timeutil.Since(start)
    68  	return row, meta
    69  }
    70  
    71  const (
    72  	rowsReadTagSuffix  = "input.rows"
    73  	stallTimeTagSuffix = "stalltime"
    74  	// MaxMemoryTagSuffix is the tag suffix for the max memory used stat.
    75  	MaxMemoryTagSuffix = "mem.max"
    76  	// MaxDiskTagSuffix is the tag suffix for the max disk used stat.
    77  	MaxDiskTagSuffix = "disk.max"
    78  	// bytesReadTagSuffix is the tag suffix for the bytes read stat.
    79  	bytesReadTagSuffix = "bytes.read"
    80  )
    81  
    82  // Stats is a utility method that returns a map of the InputStats` stats to
    83  // output to a trace as tags. The given prefix is prefixed to the keys.
    84  func (is InputStats) Stats(prefix string) map[string]string {
    85  	return map[string]string{
    86  		prefix + rowsReadTagSuffix:  fmt.Sprintf("%d", is.NumRows),
    87  		prefix + stallTimeTagSuffix: is.RoundStallTime().String(),
    88  	}
    89  }
    90  
    91  const (
    92  	rowsReadQueryPlanSuffix  = "rows read"
    93  	stallTimeQueryPlanSuffix = "stall time"
    94  	// MaxMemoryQueryPlanSuffix is the tag suffix for the max memory used.
    95  	MaxMemoryQueryPlanSuffix = "max memory used"
    96  	// MaxDiskQueryPlanSuffix is the tag suffix for the max disk used.
    97  	MaxDiskQueryPlanSuffix = "max disk used"
    98  	// bytesReadQueryPlanSuffix is the tag suffix for the bytes read.
    99  	bytesReadQueryPlanSuffix = "bytes read"
   100  )
   101  
   102  // StatsForQueryPlan is a utility method that returns a list of the InputStats'
   103  // stats to output on a query plan. The given prefix is prefixed to each element
   104  // in the returned list.
   105  func (is InputStats) StatsForQueryPlan(prefix string) []string {
   106  	return []string{
   107  		fmt.Sprintf("%s%s: %d", prefix, rowsReadQueryPlanSuffix, is.NumRows),
   108  		fmt.Sprintf("%s%s: %v", prefix, stallTimeQueryPlanSuffix, is.RoundStallTime()),
   109  	}
   110  }
   111  
   112  // RoundStallTime returns the InputStats' StallTime rounded to the nearest
   113  // time.Millisecond.
   114  func (is InputStats) RoundStallTime() time.Duration {
   115  	return is.StallTime.Round(time.Microsecond)
   116  }
   117  
   118  // rowFetcherStatCollector is a wrapper on top of a row.Fetcher that collects stats.
   119  //
   120  // Only row.Fetcher methods that collect stats are overridden.
   121  type rowFetcherStatCollector struct {
   122  	*row.Fetcher
   123  	// stats contains the collected stats.
   124  	stats              InputStats
   125  	startScanStallTime time.Duration
   126  }
   127  
   128  var _ rowFetcher = &rowFetcherStatCollector{}
   129  
   130  // newRowFetcherStatCollector returns a new rowFetcherStatCollector.
   131  func newRowFetcherStatCollector(f *row.Fetcher) *rowFetcherStatCollector {
   132  	return &rowFetcherStatCollector{Fetcher: f}
   133  }
   134  
   135  // NextRow is part of the rowFetcher interface.
   136  func (c *rowFetcherStatCollector) NextRow(
   137  	ctx context.Context,
   138  ) (sqlbase.EncDatumRow, *sqlbase.TableDescriptor, *sqlbase.IndexDescriptor, error) {
   139  	start := timeutil.Now()
   140  	row, t, i, err := c.Fetcher.NextRow(ctx)
   141  	if row != nil {
   142  		c.stats.NumRows++
   143  	}
   144  	c.stats.StallTime += timeutil.Since(start)
   145  	return row, t, i, err
   146  }
   147  
   148  // StartScan is part of the rowFetcher interface.
   149  func (c *rowFetcherStatCollector) StartScan(
   150  	ctx context.Context,
   151  	txn *kv.Txn,
   152  	spans roachpb.Spans,
   153  	limitBatches bool,
   154  	limitHint int64,
   155  	traceKV bool,
   156  ) error {
   157  	start := timeutil.Now()
   158  	err := c.Fetcher.StartScan(ctx, txn, spans, limitBatches, limitHint, traceKV)
   159  	c.startScanStallTime += timeutil.Since(start)
   160  	return err
   161  }
   162  
   163  // StartInconsistentScan is part of the rowFetcher interface.
   164  func (c *rowFetcherStatCollector) StartInconsistentScan(
   165  	ctx context.Context,
   166  	db *kv.DB,
   167  	initialTimestamp hlc.Timestamp,
   168  	maxTimestampAge time.Duration,
   169  	spans roachpb.Spans,
   170  	limitBatches bool,
   171  	limitHint int64,
   172  	traceKV bool,
   173  ) error {
   174  	start := timeutil.Now()
   175  	err := c.Fetcher.StartInconsistentScan(
   176  		ctx, db, initialTimestamp, maxTimestampAge, spans, limitBatches, limitHint, traceKV,
   177  	)
   178  	c.startScanStallTime += timeutil.Since(start)
   179  	return err
   180  }
   181  
   182  // getInputStats is a utility function to check whether the given input is
   183  // collecting stats, returning true and the stats if so. If false is returned,
   184  // the input is not collecting stats.
   185  func getInputStats(flowCtx *execinfra.FlowCtx, input execinfra.RowSource) (InputStats, bool) {
   186  	isc, ok := input.(*inputStatCollector)
   187  	if !ok {
   188  		return InputStats{}, false
   189  	}
   190  	return getStatsInner(flowCtx, isc.InputStats), true
   191  }
   192  
   193  func getStatsInner(flowCtx *execinfra.FlowCtx, stats InputStats) InputStats {
   194  	if flowCtx.Cfg.TestingKnobs.DeterministicStats {
   195  		stats.StallTime = 0
   196  	}
   197  	return stats
   198  }
   199  
   200  // getFetcherInputStats is a utility function to check whether the given input
   201  // is collecting row fetcher stats, returning true and the stats if so. If
   202  // false is returned, the input is not collecting row fetcher stats.
   203  func getFetcherInputStats(flowCtx *execinfra.FlowCtx, f rowFetcher) (InputStats, bool) {
   204  	rfsc, ok := f.(*rowFetcherStatCollector)
   205  	if !ok {
   206  		return InputStats{}, false
   207  	}
   208  	// Add row fetcher start scan stall time to Next() stall time.
   209  	if !flowCtx.Cfg.TestingKnobs.DeterministicStats {
   210  		rfsc.stats.StallTime += rfsc.startScanStallTime
   211  	}
   212  	return getStatsInner(flowCtx, rfsc.stats), true
   213  }