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 }