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 }