github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/metric/metric.go (about)

     1  // Copyright 2015 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 metric
    12  
    13  import (
    14  	"encoding/json"
    15  	"fmt"
    16  	"math"
    17  	"sync/atomic"
    18  	"time"
    19  
    20  	"github.com/VividCortex/ewma"
    21  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    22  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    23  	"github.com/codahale/hdrhistogram"
    24  	"github.com/gogo/protobuf/proto"
    25  	prometheusgo "github.com/prometheus/client_model/go"
    26  	metrics "github.com/rcrowley/go-metrics"
    27  )
    28  
    29  const (
    30  	// MaxLatency is the maximum value tracked in latency histograms. Higher
    31  	// values will be recorded as this value instead.
    32  	MaxLatency = 10 * time.Second
    33  
    34  	// TestSampleInterval is passed to histograms during tests which don't
    35  	// want to concern themselves with supplying a "correct" interval.
    36  	TestSampleInterval = time.Duration(math.MaxInt64)
    37  
    38  	// The number of histograms to keep in rolling window.
    39  	histWrapNum = 2
    40  )
    41  
    42  // Iterable provides a method for synchronized access to interior objects.
    43  type Iterable interface {
    44  	// GetName returns the fully-qualified name of the metric.
    45  	GetName() string
    46  	// GetHelp returns the help text for the metric.
    47  	GetHelp() string
    48  	// GetMeasurement returns the label for the metric, which describes the entity
    49  	// it measures.
    50  	GetMeasurement() string
    51  	// GetUnit returns the unit that should be used to display the metric
    52  	// (e.g. in bytes).
    53  	GetUnit() Unit
    54  	// GetMetadata returns the metric's metadata, which can be used in charts.
    55  	GetMetadata() Metadata
    56  	// Inspect calls the given closure with each contained item.
    57  	Inspect(func(interface{}))
    58  }
    59  
    60  // PrometheusExportable is the standard interface for an individual metric
    61  // that can be exported to prometheus.
    62  type PrometheusExportable interface {
    63  	// GetName is a method on Metadata
    64  	GetName() string
    65  	// GetHelp is a method on Metadata
    66  	GetHelp() string
    67  	// GetType returns the prometheus type enum for this metric.
    68  	GetType() *prometheusgo.MetricType
    69  	// GetLabels is a method on Metadata
    70  	GetLabels() []*prometheusgo.LabelPair
    71  	// ToPrometheusMetric returns a filled-in prometheus metric of the right type
    72  	// for the given metric. It does not fill in labels.
    73  	// The implementation must return thread-safe data to the caller, i.e.
    74  	// usually a copy of internal state.
    75  	ToPrometheusMetric() *prometheusgo.Metric
    76  }
    77  
    78  // GetName returns the metric's name.
    79  func (m *Metadata) GetName() string {
    80  	return m.Name
    81  }
    82  
    83  // GetHelp returns the metric's help string.
    84  func (m *Metadata) GetHelp() string {
    85  	return m.Help
    86  }
    87  
    88  // GetMeasurement returns the entity measured by the metric.
    89  func (m *Metadata) GetMeasurement() string {
    90  	return m.Measurement
    91  }
    92  
    93  // GetUnit returns the metric's unit of measurement.
    94  func (m *Metadata) GetUnit() Unit {
    95  	return m.Unit
    96  }
    97  
    98  // GetLabels returns the metric's labels. For rationale behind the conversion
    99  // from metric.LabelPair to prometheusgo.LabelPair, see the LabelPair comment
   100  // in pkg/util/metric/metric.proto.
   101  func (m *Metadata) GetLabels() []*prometheusgo.LabelPair {
   102  	lps := make([]*prometheusgo.LabelPair, len(m.Labels))
   103  	// x satisfies the field XXX_unrecognized in prometheusgo.LabelPair.
   104  	var x []byte
   105  	for i, v := range m.Labels {
   106  		lps[i] = &prometheusgo.LabelPair{Name: v.Name, Value: v.Value, XXX_unrecognized: x}
   107  	}
   108  	return lps
   109  }
   110  
   111  // AddLabel adds a label/value pair for this metric.
   112  func (m *Metadata) AddLabel(name, value string) {
   113  	m.Labels = append(m.Labels,
   114  		&LabelPair{
   115  			Name:  proto.String(exportedLabel(name)),
   116  			Value: proto.String(value),
   117  		})
   118  }
   119  
   120  var _ Iterable = &Gauge{}
   121  var _ Iterable = &GaugeFloat64{}
   122  var _ Iterable = &Counter{}
   123  var _ Iterable = &Histogram{}
   124  var _ Iterable = &Rate{}
   125  
   126  var _ json.Marshaler = &Gauge{}
   127  var _ json.Marshaler = &GaugeFloat64{}
   128  var _ json.Marshaler = &Counter{}
   129  var _ json.Marshaler = &Registry{}
   130  var _ json.Marshaler = &Rate{}
   131  
   132  var _ PrometheusExportable = &Gauge{}
   133  var _ PrometheusExportable = &GaugeFloat64{}
   134  var _ PrometheusExportable = &Counter{}
   135  var _ PrometheusExportable = &Histogram{}
   136  var _ PrometheusExportable = &Rate{}
   137  
   138  type periodic interface {
   139  	nextTick() time.Time
   140  	tick()
   141  }
   142  
   143  var _ periodic = &Rate{}
   144  
   145  var now = timeutil.Now
   146  
   147  // TestingSetNow changes the clock used by the metric system. For use by
   148  // testing to precisely control the clock.
   149  func TestingSetNow(f func() time.Time) func() {
   150  	origNow := now
   151  	now = f
   152  	return func() {
   153  		now = origNow
   154  	}
   155  }
   156  
   157  func cloneHistogram(in *hdrhistogram.Histogram) *hdrhistogram.Histogram {
   158  	return hdrhistogram.Import(in.Export())
   159  }
   160  
   161  func maybeTick(m periodic) {
   162  	for m.nextTick().Before(now()) {
   163  		m.tick()
   164  	}
   165  }
   166  
   167  // A Histogram collects observed values by keeping bucketed counts. For
   168  // convenience, internally two sets of buckets are kept: A cumulative set (i.e.
   169  // data is never evicted) and a windowed set (which keeps only recently
   170  // collected samples).
   171  //
   172  // Top-level methods generally apply to the cumulative buckets; the windowed
   173  // variant is exposed through the Windowed method.
   174  type Histogram struct {
   175  	Metadata
   176  	maxVal int64
   177  	mu     struct {
   178  		syncutil.Mutex
   179  		cumulative *hdrhistogram.Histogram
   180  		sliding    *slidingHistogram
   181  	}
   182  }
   183  
   184  // NewHistogram initializes a given Histogram. The contained windowed histogram
   185  // rotates every 'duration'; both the windowed and the cumulative histogram
   186  // track nonnegative values up to 'maxVal' with 'sigFigs' decimal points of
   187  // precision.
   188  func NewHistogram(metadata Metadata, duration time.Duration, maxVal int64, sigFigs int) *Histogram {
   189  	dHist := newSlidingHistogram(duration, maxVal, sigFigs)
   190  	h := &Histogram{
   191  		Metadata: metadata,
   192  		maxVal:   maxVal,
   193  	}
   194  	h.mu.cumulative = hdrhistogram.New(0, maxVal, sigFigs)
   195  	h.mu.sliding = dHist
   196  	return h
   197  }
   198  
   199  // NewLatency is a convenience function which returns a histogram with
   200  // suitable defaults for latency tracking. Values are expressed in ns,
   201  // are truncated into the interval [0, MaxLatency] and are recorded
   202  // with one digit of precision (i.e. errors of <10ms at 100ms, <6s at 60s).
   203  //
   204  // The windowed portion of the Histogram retains values for approximately
   205  // histogramWindow.
   206  func NewLatency(metadata Metadata, histogramWindow time.Duration) *Histogram {
   207  	return NewHistogram(
   208  		metadata, histogramWindow, MaxLatency.Nanoseconds(), 1,
   209  	)
   210  }
   211  
   212  // Windowed returns a copy of the current windowed histogram data and its
   213  // rotation interval.
   214  func (h *Histogram) Windowed() (*hdrhistogram.Histogram, time.Duration) {
   215  	h.mu.Lock()
   216  	defer h.mu.Unlock()
   217  	return cloneHistogram(h.mu.sliding.Current()), h.mu.sliding.duration
   218  }
   219  
   220  // Snapshot returns a copy of the cumulative (i.e. all-time samples) histogram
   221  // data.
   222  func (h *Histogram) Snapshot() *hdrhistogram.Histogram {
   223  	h.mu.Lock()
   224  	defer h.mu.Unlock()
   225  	return cloneHistogram(h.mu.cumulative)
   226  }
   227  
   228  // RecordValue adds the given value to the histogram. Recording a value in
   229  // excess of the configured maximum value for that histogram results in
   230  // recording the maximum value instead.
   231  func (h *Histogram) RecordValue(v int64) {
   232  	h.mu.Lock()
   233  	defer h.mu.Unlock()
   234  
   235  	if h.mu.sliding.RecordValue(v) != nil {
   236  		_ = h.mu.sliding.RecordValue(h.maxVal)
   237  	}
   238  	if h.mu.cumulative.RecordValue(v) != nil {
   239  		_ = h.mu.cumulative.RecordValue(h.maxVal)
   240  	}
   241  }
   242  
   243  // TotalCount returns the (cumulative) number of samples.
   244  func (h *Histogram) TotalCount() int64 {
   245  	h.mu.Lock()
   246  	defer h.mu.Unlock()
   247  	return h.mu.cumulative.TotalCount()
   248  }
   249  
   250  // Min returns the minimum.
   251  func (h *Histogram) Min() int64 {
   252  	h.mu.Lock()
   253  	defer h.mu.Unlock()
   254  	return h.mu.cumulative.Min()
   255  }
   256  
   257  // Inspect calls the closure with the empty string and the receiver.
   258  func (h *Histogram) Inspect(f func(interface{})) {
   259  	h.mu.Lock()
   260  	maybeTick(h.mu.sliding)
   261  	h.mu.Unlock()
   262  	f(h)
   263  }
   264  
   265  // GetType returns the prometheus type enum for this metric.
   266  func (h *Histogram) GetType() *prometheusgo.MetricType {
   267  	return prometheusgo.MetricType_HISTOGRAM.Enum()
   268  }
   269  
   270  // ToPrometheusMetric returns a filled-in prometheus metric of the right type.
   271  func (h *Histogram) ToPrometheusMetric() *prometheusgo.Metric {
   272  	hist := &prometheusgo.Histogram{}
   273  
   274  	h.mu.Lock()
   275  	maybeTick(h.mu.sliding)
   276  	bars := h.mu.cumulative.Distribution()
   277  	hist.Bucket = make([]*prometheusgo.Bucket, 0, len(bars))
   278  
   279  	var cumCount uint64
   280  	var sum float64
   281  	for _, bar := range bars {
   282  		if bar.Count == 0 {
   283  			// No need to expose trivial buckets.
   284  			continue
   285  		}
   286  		upperBound := float64(bar.To)
   287  		sum += upperBound * float64(bar.Count)
   288  
   289  		cumCount += uint64(bar.Count)
   290  		curCumCount := cumCount // need a new alloc thanks to bad proto code
   291  
   292  		hist.Bucket = append(hist.Bucket, &prometheusgo.Bucket{
   293  			CumulativeCount: &curCumCount,
   294  			UpperBound:      &upperBound,
   295  		})
   296  	}
   297  	hist.SampleCount = &cumCount
   298  	hist.SampleSum = &sum // can do better here; we approximate in the loop
   299  	h.mu.Unlock()
   300  
   301  	return &prometheusgo.Metric{
   302  		Histogram: hist,
   303  	}
   304  }
   305  
   306  // GetMetadata returns the metric's metadata including the Prometheus
   307  // MetricType.
   308  func (h *Histogram) GetMetadata() Metadata {
   309  	baseMetadata := h.Metadata
   310  	baseMetadata.MetricType = prometheusgo.MetricType_HISTOGRAM
   311  	return baseMetadata
   312  }
   313  
   314  // A Counter holds a single mutable atomic value.
   315  type Counter struct {
   316  	Metadata
   317  	metrics.Counter
   318  }
   319  
   320  // NewCounter creates a counter.
   321  func NewCounter(metadata Metadata) *Counter {
   322  	return &Counter{metadata, metrics.NewCounter()}
   323  }
   324  
   325  // Dec overrides the metric.Counter method. This method should NOT be
   326  // used and serves only to prevent misuse of the metric type.
   327  func (c *Counter) Dec(int64) {
   328  	// From https://prometheus.io/docs/concepts/metric_types/#counter
   329  	// > Counters should not be used to expose current counts of items
   330  	// > whose number can also go down, e.g. the number of currently
   331  	// > running goroutines. Use gauges for this use case.
   332  	panic("Counter should not be decremented, use a Gauge instead")
   333  }
   334  
   335  // GetType returns the prometheus type enum for this metric.
   336  func (c *Counter) GetType() *prometheusgo.MetricType {
   337  	return prometheusgo.MetricType_COUNTER.Enum()
   338  }
   339  
   340  // Inspect calls the given closure with the empty string and itself.
   341  func (c *Counter) Inspect(f func(interface{})) { f(c) }
   342  
   343  // MarshalJSON marshals to JSON.
   344  func (c *Counter) MarshalJSON() ([]byte, error) {
   345  	return json.Marshal(c.Counter.Count())
   346  }
   347  
   348  // ToPrometheusMetric returns a filled-in prometheus metric of the right type.
   349  func (c *Counter) ToPrometheusMetric() *prometheusgo.Metric {
   350  	return &prometheusgo.Metric{
   351  		Counter: &prometheusgo.Counter{Value: proto.Float64(float64(c.Counter.Count()))},
   352  	}
   353  }
   354  
   355  // GetMetadata returns the metric's metadata including the Prometheus
   356  // MetricType.
   357  func (c *Counter) GetMetadata() Metadata {
   358  	baseMetadata := c.Metadata
   359  	baseMetadata.MetricType = prometheusgo.MetricType_COUNTER
   360  	return baseMetadata
   361  }
   362  
   363  // A Gauge atomically stores a single integer value.
   364  type Gauge struct {
   365  	Metadata
   366  	value *int64
   367  	fn    func() int64
   368  }
   369  
   370  // NewGauge creates a Gauge.
   371  func NewGauge(metadata Metadata) *Gauge {
   372  	return &Gauge{metadata, new(int64), nil}
   373  }
   374  
   375  // NewFunctionalGauge creates a Gauge metric whose value is determined when
   376  // asked for by calling the provided function.
   377  // Note that Update, Inc, and Dec should NOT be called on a Gauge returned
   378  // from NewFunctionalGauge.
   379  func NewFunctionalGauge(metadata Metadata, f func() int64) *Gauge {
   380  	return &Gauge{metadata, nil, f}
   381  }
   382  
   383  // Snapshot returns a read-only copy of the gauge.
   384  func (g *Gauge) Snapshot() metrics.Gauge {
   385  	return metrics.GaugeSnapshot(g.Value())
   386  }
   387  
   388  // Update updates the gauge's value.
   389  func (g *Gauge) Update(v int64) {
   390  	atomic.StoreInt64(g.value, v)
   391  }
   392  
   393  // Value returns the gauge's current value.
   394  func (g *Gauge) Value() int64 {
   395  	if g.fn != nil {
   396  		return g.fn()
   397  	}
   398  	return atomic.LoadInt64(g.value)
   399  }
   400  
   401  // Inc increments the gauge's value.
   402  func (g *Gauge) Inc(i int64) {
   403  	atomic.AddInt64(g.value, i)
   404  }
   405  
   406  // Dec decrements the gauge's value.
   407  func (g *Gauge) Dec(i int64) {
   408  	atomic.AddInt64(g.value, -i)
   409  }
   410  
   411  // GetType returns the prometheus type enum for this metric.
   412  func (g *Gauge) GetType() *prometheusgo.MetricType {
   413  	return prometheusgo.MetricType_GAUGE.Enum()
   414  }
   415  
   416  // Inspect calls the given closure with the empty string and itself.
   417  func (g *Gauge) Inspect(f func(interface{})) { f(g) }
   418  
   419  // MarshalJSON marshals to JSON.
   420  func (g *Gauge) MarshalJSON() ([]byte, error) {
   421  	return json.Marshal(g.Value())
   422  }
   423  
   424  // ToPrometheusMetric returns a filled-in prometheus metric of the right type.
   425  func (g *Gauge) ToPrometheusMetric() *prometheusgo.Metric {
   426  	return &prometheusgo.Metric{
   427  		Gauge: &prometheusgo.Gauge{Value: proto.Float64(float64(g.Value()))},
   428  	}
   429  }
   430  
   431  // GetMetadata returns the metric's metadata including the Prometheus
   432  // MetricType.
   433  func (g *Gauge) GetMetadata() Metadata {
   434  	baseMetadata := g.Metadata
   435  	baseMetadata.MetricType = prometheusgo.MetricType_GAUGE
   436  	return baseMetadata
   437  }
   438  
   439  // A GaugeFloat64 atomically stores a single float64 value.
   440  type GaugeFloat64 struct {
   441  	Metadata
   442  	metrics.GaugeFloat64
   443  }
   444  
   445  // NewGaugeFloat64 creates a GaugeFloat64.
   446  func NewGaugeFloat64(metadata Metadata) *GaugeFloat64 {
   447  	return &GaugeFloat64{metadata, metrics.NewGaugeFloat64()}
   448  }
   449  
   450  // GetType returns the prometheus type enum for this metric.
   451  func (g *GaugeFloat64) GetType() *prometheusgo.MetricType {
   452  	return prometheusgo.MetricType_GAUGE.Enum()
   453  }
   454  
   455  // Inspect calls the given closure with itself.
   456  func (g *GaugeFloat64) Inspect(f func(interface{})) { f(g) }
   457  
   458  // MarshalJSON marshals to JSON.
   459  func (g *GaugeFloat64) MarshalJSON() ([]byte, error) {
   460  	return json.Marshal(g.Value())
   461  }
   462  
   463  // ToPrometheusMetric returns a filled-in prometheus metric of the right type.
   464  func (g *GaugeFloat64) ToPrometheusMetric() *prometheusgo.Metric {
   465  	return &prometheusgo.Metric{
   466  		Gauge: &prometheusgo.Gauge{Value: proto.Float64(g.Value())},
   467  	}
   468  }
   469  
   470  // GetMetadata returns the metric's metadata including the Prometheus
   471  // MetricType.
   472  func (g *GaugeFloat64) GetMetadata() Metadata {
   473  	baseMetadata := g.Metadata
   474  	baseMetadata.MetricType = prometheusgo.MetricType_GAUGE
   475  	return baseMetadata
   476  }
   477  
   478  // A Rate is a exponential weighted moving average.
   479  type Rate struct {
   480  	Metadata
   481  	mu       syncutil.Mutex // protects fields below
   482  	curSum   float64
   483  	wrapped  ewma.MovingAverage
   484  	interval time.Duration
   485  	nextT    time.Time
   486  }
   487  
   488  // NewRate creates an EWMA rate on the given timescale. Timescales at
   489  // or below 2s are illegal and will cause a panic.
   490  func NewRate(metadata Metadata, timescale time.Duration) *Rate {
   491  	const tickInterval = time.Second
   492  	if timescale <= 2*time.Second {
   493  		panic(fmt.Sprintf("EWMA with per-second ticks makes no sense on timescale %s", timescale))
   494  	}
   495  	avgAge := float64(timescale) / float64(2*tickInterval)
   496  	return &Rate{
   497  		Metadata: metadata,
   498  		interval: tickInterval,
   499  		nextT:    now(),
   500  		wrapped:  ewma.NewMovingAverage(avgAge),
   501  	}
   502  }
   503  
   504  // GetType returns the prometheus type enum for this metric.
   505  func (e *Rate) GetType() *prometheusgo.MetricType {
   506  	return prometheusgo.MetricType_GAUGE.Enum()
   507  }
   508  
   509  // Inspect calls the given closure with itself.
   510  func (e *Rate) Inspect(f func(interface{})) { f(e) }
   511  
   512  // MarshalJSON marshals to JSON.
   513  func (e *Rate) MarshalJSON() ([]byte, error) {
   514  	return json.Marshal(e.Value())
   515  }
   516  
   517  // ToPrometheusMetric returns a filled-in prometheus metric of the right type.
   518  func (e *Rate) ToPrometheusMetric() *prometheusgo.Metric {
   519  	return &prometheusgo.Metric{
   520  		Gauge: &prometheusgo.Gauge{Value: proto.Float64(e.Value())},
   521  	}
   522  }
   523  
   524  // GetMetadata returns the metric's metadata including the Prometheus
   525  // MetricType.
   526  func (e *Rate) GetMetadata() Metadata {
   527  	baseMetadata := e.Metadata
   528  	baseMetadata.MetricType = prometheusgo.MetricType_GAUGE
   529  	return baseMetadata
   530  }
   531  
   532  // Value returns the current value of the Rate.
   533  func (e *Rate) Value() float64 {
   534  	e.mu.Lock()
   535  	defer e.mu.Unlock()
   536  	maybeTick(e)
   537  	return e.wrapped.Value()
   538  }
   539  
   540  func (e *Rate) nextTick() time.Time {
   541  	return e.nextT
   542  }
   543  
   544  func (e *Rate) tick() {
   545  	e.nextT = e.nextT.Add(e.interval)
   546  	e.wrapped.Add(e.curSum)
   547  	e.curSum = 0
   548  }
   549  
   550  // Add adds the given measurement to the Rate.
   551  func (e *Rate) Add(v float64) {
   552  	e.mu.Lock()
   553  	maybeTick(e)
   554  	e.curSum += v
   555  	e.mu.Unlock()
   556  }