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  }