github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/server/telemetry/features.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 telemetry
    12  
    13  import (
    14  	"fmt"
    15  	"sync/atomic"
    16  
    17  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    18  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    19  	"github.com/cockroachdb/cockroach/pkg/util/metric"
    20  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    21  	"github.com/cockroachdb/errors"
    22  )
    23  
    24  // Bucket10 buckets a number by order of magnitude base 10, eg 637 -> 100.
    25  // This can be used in telemetry to get ballpark ideas of how users use a given
    26  // feature, such as file sizes, qps, etc, without being as revealing as the
    27  // raw numbers.
    28  // The numbers 0-10 are reported unchanged.
    29  func Bucket10(num int64) int64 {
    30  	if num <= 0 {
    31  		return 0
    32  	}
    33  	if num < 10 {
    34  		return num
    35  	}
    36  	res := int64(10)
    37  	for ; res < 1000000000000000000 && res*10 < num; res *= 10 {
    38  	}
    39  	return res
    40  }
    41  
    42  // CountBucketed counts the feature identified by prefix and the value, using
    43  // the bucketed value to pick a feature bucket to increment, e.g. a prefix of
    44  // "foo.bar" and value of 632 would be counted as "foo.bar.100".
    45  func CountBucketed(prefix string, value int64) {
    46  	Count(fmt.Sprintf("%s.%d", prefix, Bucket10(value)))
    47  }
    48  
    49  // Count retrieves and increments the usage counter for the passed feature.
    50  // High-volume callers may want to instead use `GetCounter` and hold on to the
    51  // returned Counter between calls to Inc, to avoid contention in the registry.
    52  func Count(feature string) {
    53  	Inc(GetCounter(feature))
    54  }
    55  
    56  // Counter represents the usage counter for a given 'feature'.
    57  type Counter *int32
    58  
    59  // Inc increments the counter.
    60  func Inc(c Counter) {
    61  	atomic.AddInt32(c, 1)
    62  }
    63  
    64  // Read reads the current value of the counter.
    65  func Read(c Counter) int32 {
    66  	return atomic.LoadInt32(c)
    67  }
    68  
    69  // GetCounterOnce returns a counter from the global registry,
    70  // and asserts it didn't exist previously.
    71  func GetCounterOnce(feature string) Counter {
    72  	counters.RLock()
    73  	_, ok := counters.m[feature]
    74  	counters.RUnlock()
    75  	if ok {
    76  		panic("counter already exists: " + feature)
    77  	}
    78  	return GetCounter(feature)
    79  }
    80  
    81  // GetCounter returns a counter from the global registry.
    82  func GetCounter(feature string) Counter {
    83  	counters.RLock()
    84  	i, ok := counters.m[feature]
    85  	counters.RUnlock()
    86  	if ok {
    87  		return i
    88  	}
    89  
    90  	counters.Lock()
    91  	defer counters.Unlock()
    92  	i, ok = counters.m[feature]
    93  	if !ok {
    94  		i = new(int32)
    95  		counters.m[feature] = i
    96  	}
    97  	return i
    98  }
    99  
   100  // CounterWithMetric combines a telemetry and a metrics counter.
   101  type CounterWithMetric struct {
   102  	telemetry Counter
   103  	metric    *metric.Counter
   104  }
   105  
   106  // Necessary for metric metadata registration.
   107  var _ metric.Iterable = CounterWithMetric{}
   108  
   109  // NewCounterWithMetric creates a CounterWithMetric.
   110  func NewCounterWithMetric(metadata metric.Metadata) CounterWithMetric {
   111  	return CounterWithMetric{
   112  		telemetry: GetCounter(metadata.Name),
   113  		metric:    metric.NewCounter(metadata),
   114  	}
   115  }
   116  
   117  // Inc increments both counters.
   118  func (c CounterWithMetric) Inc() {
   119  	Inc(c.telemetry)
   120  	c.metric.Inc(1)
   121  }
   122  
   123  // Forward the metric.Iterable interface to the metric counter. We
   124  // don't just embed the counter because our Inc() interface is a bit
   125  // different.
   126  
   127  // GetName implements metric.Iterable
   128  func (c CounterWithMetric) GetName() string {
   129  	return c.metric.GetName()
   130  }
   131  
   132  // GetHelp implements metric.Iterable
   133  func (c CounterWithMetric) GetHelp() string {
   134  	return c.metric.GetHelp()
   135  }
   136  
   137  // GetMeasurement implements metric.Iterable
   138  func (c CounterWithMetric) GetMeasurement() string {
   139  	return c.metric.GetMeasurement()
   140  }
   141  
   142  // GetUnit implements metric.Iterable
   143  func (c CounterWithMetric) GetUnit() metric.Unit {
   144  	return c.metric.GetUnit()
   145  }
   146  
   147  // GetMetadata implements metric.Iterable
   148  func (c CounterWithMetric) GetMetadata() metric.Metadata {
   149  	return c.metric.GetMetadata()
   150  }
   151  
   152  // Inspect implements metric.Iterable
   153  func (c CounterWithMetric) Inspect(f func(interface{})) {
   154  	c.metric.Inspect(f)
   155  }
   156  
   157  func init() {
   158  	counters.m = make(map[string]Counter, approxFeatureCount)
   159  }
   160  
   161  var approxFeatureCount = 1500
   162  
   163  // counters stores the registry of feature-usage counts.
   164  // TODO(dt): consider a lock-free map.
   165  var counters struct {
   166  	syncutil.RWMutex
   167  	m map[string]Counter
   168  }
   169  
   170  // QuantizeCounts controls if counts are quantized when fetched.
   171  type QuantizeCounts bool
   172  
   173  // ResetCounters controls if counts are reset when fetched.
   174  type ResetCounters bool
   175  
   176  const (
   177  	// Quantized returns counts quantized to order of magnitude.
   178  	Quantized QuantizeCounts = true
   179  	// Raw returns the raw, unquantized counter values.
   180  	Raw QuantizeCounts = false
   181  	// ResetCounts resets the counter to zero after fetching its value.
   182  	ResetCounts ResetCounters = true
   183  	// ReadOnly leaves the counter value unchanged when reading it.
   184  	ReadOnly ResetCounters = false
   185  )
   186  
   187  // GetRawFeatureCounts returns current raw, un-quanitzed feature counter values.
   188  func GetRawFeatureCounts() map[string]int32 {
   189  	return GetFeatureCounts(Raw, ReadOnly)
   190  }
   191  
   192  // GetFeatureCounts returns the current feature usage counts.
   193  //
   194  // It optionally quantizes quantizes the returned counts to just order of
   195  // magnitude using the `Bucket10` helper, and optionally resets the counters to
   196  // zero i.e. if flushing accumulated counts during a report.
   197  func GetFeatureCounts(quantize QuantizeCounts, reset ResetCounters) map[string]int32 {
   198  	counters.RLock()
   199  	m := make(map[string]int32, len(counters.m))
   200  	for k, cnt := range counters.m {
   201  		var val int32
   202  		if reset {
   203  			val = atomic.SwapInt32(cnt, 0)
   204  		} else {
   205  			val = atomic.LoadInt32(cnt)
   206  		}
   207  		if val != 0 {
   208  			m[k] = val
   209  		}
   210  	}
   211  	counters.RUnlock()
   212  	if quantize {
   213  		for k := range m {
   214  			m[k] = int32(Bucket10(int64(m[k])))
   215  		}
   216  	}
   217  	return m
   218  }
   219  
   220  // RecordError takes an error and increments the corresponding count
   221  // for its error code, and, if it is an unimplemented or internal
   222  // error, the count for that feature or the internal error's shortened
   223  // stack trace.
   224  func RecordError(err error) {
   225  	if err == nil {
   226  		return
   227  	}
   228  
   229  	code := pgerror.GetPGCode(err)
   230  	Count("errorcodes." + code)
   231  
   232  	tkeys := errors.GetTelemetryKeys(err)
   233  	if len(tkeys) > 0 {
   234  		var prefix string
   235  		switch code {
   236  		case pgcode.FeatureNotSupported:
   237  			prefix = "unimplemented."
   238  		case pgcode.Internal:
   239  			prefix = "internalerror."
   240  		default:
   241  			prefix = "othererror." + code + "."
   242  		}
   243  
   244  		for _, tk := range tkeys {
   245  			Count(prefix + tk)
   246  		}
   247  	}
   248  }