github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/sstable/category_stats.go (about) 1 // Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package sstable 6 7 import ( 8 "cmp" 9 "slices" 10 "sync" 11 12 "github.com/cockroachdb/errors" 13 "github.com/cockroachdb/redact" 14 ) 15 16 // Category is a user-understandable string, where stats are aggregated for 17 // each category. The cardinality of this should be low, say < 20. The prefix 18 // "pebble-" is reserved for internal Pebble categories. 19 // 20 // Examples of categories that can be useful in the CockroachDB context are: 21 // sql-user, sql-stats, raft, rangefeed, mvcc-gc, range-snapshot. 22 type Category string 23 24 // QoSLevel describes whether the read is latency-sensitive or not. Each 25 // category must map to a single QoSLevel. While category strings are opaque 26 // to Pebble, the QoSLevel may be internally utilized in Pebble to better 27 // optimize future reads. 28 type QoSLevel int 29 30 const ( 31 // LatencySensitiveQoSLevel is the default when QoSLevel is not specified, 32 // and represents reads that are latency-sensitive. 33 LatencySensitiveQoSLevel QoSLevel = iota 34 // NonLatencySensitiveQoSLevel represents reads that are not 35 // latency-sensitive. 36 NonLatencySensitiveQoSLevel 37 ) 38 39 // SafeFormat implements the redact.SafeFormatter interface. 40 func (q QoSLevel) SafeFormat(p redact.SafePrinter, verb rune) { 41 switch q { 42 case LatencySensitiveQoSLevel: 43 p.Printf("latency") 44 case NonLatencySensitiveQoSLevel: 45 p.Printf("non-latency") 46 default: 47 p.Printf("<unknown-qos>") 48 } 49 } 50 51 // StringToQoSForTesting returns the QoSLevel for the string, or panics if the 52 // string is not known. 53 func StringToQoSForTesting(s string) QoSLevel { 54 switch s { 55 case "latency": 56 return LatencySensitiveQoSLevel 57 case "non-latency": 58 return NonLatencySensitiveQoSLevel 59 } 60 panic(errors.AssertionFailedf("unknown QoS %s", s)) 61 } 62 63 // CategoryAndQoS specifies both the Category and the QoSLevel. 64 type CategoryAndQoS struct { 65 Category 66 QoSLevel 67 } 68 69 // CategoryStats provides stats about a category of reads. 70 type CategoryStats struct { 71 // BlockBytes is the bytes in the loaded blocks. If the block was 72 // compressed, this is the compressed bytes. Currently, only the index 73 // blocks, data blocks containing points, and filter blocks are included. 74 // Additionally, value blocks read after the corresponding iterator is 75 // closed are not included. 76 BlockBytes uint64 77 // BlockBytesInCache is the subset of BlockBytes that were in the block 78 // cache. 79 BlockBytesInCache uint64 80 } 81 82 func (s *CategoryStats) aggregate(a CategoryStats) { 83 s.BlockBytes += a.BlockBytes 84 s.BlockBytesInCache += a.BlockBytesInCache 85 } 86 87 // CategoryStatsAggregate is the aggregate for the given category. 88 type CategoryStatsAggregate struct { 89 Category 90 QoSLevel 91 CategoryStats 92 } 93 94 type categoryStatsWithMu struct { 95 mu sync.Mutex 96 // Protected by mu. 97 stats CategoryStatsAggregate 98 } 99 100 // CategoryStatsCollector collects and aggregates the stats per category. 101 type CategoryStatsCollector struct { 102 // mu protects additions to statsMap. 103 mu sync.Mutex 104 // Category => categoryStatsWithMu. 105 statsMap sync.Map 106 } 107 108 func (c *CategoryStatsCollector) reportStats( 109 category Category, qosLevel QoSLevel, stats CategoryStats, 110 ) { 111 v, ok := c.statsMap.Load(category) 112 if !ok { 113 c.mu.Lock() 114 v, _ = c.statsMap.LoadOrStore(category, &categoryStatsWithMu{ 115 stats: CategoryStatsAggregate{Category: category, QoSLevel: qosLevel}, 116 }) 117 c.mu.Unlock() 118 } 119 aggStats := v.(*categoryStatsWithMu) 120 aggStats.mu.Lock() 121 aggStats.stats.CategoryStats.aggregate(stats) 122 aggStats.mu.Unlock() 123 } 124 125 // GetStats returns the aggregated stats. 126 func (c *CategoryStatsCollector) GetStats() []CategoryStatsAggregate { 127 var stats []CategoryStatsAggregate 128 c.statsMap.Range(func(_, v any) bool { 129 aggStats := v.(*categoryStatsWithMu) 130 aggStats.mu.Lock() 131 s := aggStats.stats 132 aggStats.mu.Unlock() 133 if len(s.Category) == 0 { 134 s.Category = "_unknown" 135 } 136 stats = append(stats, s) 137 return true 138 }) 139 slices.SortFunc(stats, func(a, b CategoryStatsAggregate) int { 140 return cmp.Compare(a.Category, b.Category) 141 }) 142 return stats 143 } 144 145 // iterStatsAccumulator is a helper for a sstable iterator to accumulate 146 // stats, which are reported to the CategoryStatsCollector when the 147 // accumulator is closed. 148 type iterStatsAccumulator struct { 149 Category 150 QoSLevel 151 stats CategoryStats 152 collector *CategoryStatsCollector 153 } 154 155 func (accum *iterStatsAccumulator) init( 156 categoryAndQoS CategoryAndQoS, collector *CategoryStatsCollector, 157 ) { 158 accum.Category = categoryAndQoS.Category 159 accum.QoSLevel = categoryAndQoS.QoSLevel 160 accum.collector = collector 161 } 162 163 func (accum *iterStatsAccumulator) reportStats(blockBytes, blockBytesInCache uint64) { 164 accum.stats.BlockBytes += blockBytes 165 accum.stats.BlockBytesInCache += blockBytesInCache 166 } 167 168 func (accum *iterStatsAccumulator) close() { 169 if accum.collector != nil { 170 accum.collector.reportStats(accum.Category, accum.QoSLevel, accum.stats) 171 } 172 }