From 26c555ef9e5e39650e59c48bfebe16b16730dac9 Mon Sep 17 00:00:00 2001 From: JeremyWhite Date: Wed, 15 Aug 2018 13:10:18 -0500 Subject: [PATCH] Issue #530 - Updated Vendored github.com/rcrowley/go-metrics package to version - revision:e2704e165165ec55d062f5919b4b29494e9fa790 --- .../github.com/rcrowley/go-metrics/README.md | 65 +++++++-- vendor/github.com/rcrowley/go-metrics/ewma.go | 46 ++++-- .../github.com/rcrowley/go-metrics/gauge.go | 36 +++++ .../rcrowley/go-metrics/gauge_float64.go | 52 +++++-- vendor/github.com/rcrowley/go-metrics/json.go | 62 +------- vendor/github.com/rcrowley/go-metrics/log.go | 36 +++-- .../github.com/rcrowley/go-metrics/meter.go | 114 +++++++++------ .../rcrowley/go-metrics/registry.go | 137 +++++++++++++++++- .../github.com/rcrowley/go-metrics/runtime.go | 64 ++++---- .../go-metrics/runtime_gccpufraction.go | 9 ++ .../go-metrics/runtime_no_gccpufraction.go | 9 ++ .../github.com/rcrowley/go-metrics/sample.go | 9 +- .../github.com/rcrowley/go-metrics/timer.go | 18 +++ .../rcrowley/go-metrics/validate.sh | 2 +- vendor/vendor.json | 2 +- 15 files changed, 477 insertions(+), 184 deletions(-) create mode 100644 vendor/github.com/rcrowley/go-metrics/runtime_gccpufraction.go create mode 100644 vendor/github.com/rcrowley/go-metrics/runtime_no_gccpufraction.go diff --git a/vendor/github.com/rcrowley/go-metrics/README.md b/vendor/github.com/rcrowley/go-metrics/README.md index e36b9dcbb..b7356b5fc 100644 --- a/vendor/github.com/rcrowley/go-metrics/README.md +++ b/vendor/github.com/rcrowley/go-metrics/README.md @@ -21,6 +21,9 @@ g := metrics.NewGauge() metrics.Register("bar", g) g.Update(47) +r := NewRegistry() +g := metrics.NewRegisteredFunctionalGauge("cache-evictions", r, func() int64 { return cache.getEvictionsCount() }) + s := metrics.NewExpDecaySample(1028, 0.015) // or metrics.NewUniformSample(1028) h := metrics.NewHistogram(s) metrics.Register("baz", h) @@ -36,10 +39,29 @@ t.Time(func() {}) t.Update(47) ``` +Register() is not threadsafe. For threadsafe metric registration use +GetOrRegister: + +```go +t := metrics.GetOrRegisterTimer("account.create.latency", nil) +t.Time(func() {}) +t.Update(47) +``` + +**NOTE:** Be sure to unregister short-lived meters and timers otherwise they will +leak memory: + +```go +// Will call Stop() on the Meter to allow for garbage collection +metrics.Unregister("quux") +// Or similarly for a Timer that embeds a Meter +metrics.Unregister("bang") +``` + Periodically log every metric in human-readable form to standard error: ```go -go metrics.Log(metrics.DefaultRegistry, 60e9, log.New(os.Stderr, "metrics: ", log.Lmicroseconds)) +go metrics.Log(metrics.DefaultRegistry, 5 * time.Second, log.New(os.Stderr, "metrics: ", log.Lmicroseconds)) ``` Periodically log every metric in slightly-more-parseable form to syslog: @@ -67,14 +89,15 @@ issues [#121](https://github.com/rcrowley/go-metrics/issues/121) and [#124](https://github.com/rcrowley/go-metrics/issues/124) for progress and details. ```go -import "github.com/rcrowley/go-metrics/influxdb" - -go influxdb.Influxdb(metrics.DefaultRegistry, 10e9, &influxdb.Config{ - Host: "127.0.0.1:8086", - Database: "metrics", - Username: "test", - Password: "test", -}) +import "github.com/vrischmann/go-metrics-influxdb" + +go influxdb.InfluxDB(metrics.DefaultRegistry, + 10e9, + "127.0.0.1:8086", + "database-name", + "username", + "password" +) ``` Periodically upload every metric to Librato using the [Librato client](https://github.com/mihasya/go-metrics-librato): @@ -103,6 +126,19 @@ import "github.com/rcrowley/go-metrics/stathat" go stathat.Stathat(metrics.DefaultRegistry, 10e9, "example@example.com") ``` +Maintain all metrics along with expvars at `/debug/metrics`: + +This uses the same mechanism as [the official expvar](http://golang.org/pkg/expvar/) +but exposed under `/debug/metrics`, which shows a json representation of all your usual expvars +as well as all your go-metrics. + + +```go +import "github.com/rcrowley/go-metrics/exp" + +exp.Exp(metrics.DefaultRegistry) +``` + Installation ------------ @@ -121,5 +157,12 @@ Publishing Metrics Clients are available for the following destinations: -* Librato - [https://github.com/mihasya/go-metrics-librato](https://github.com/mihasya/go-metrics-librato) -* Graphite - [https://github.com/cyberdelia/go-metrics-graphite](https://github.com/cyberdelia/go-metrics-graphite) +* Librato - https://github.com/mihasya/go-metrics-librato +* Graphite - https://github.com/cyberdelia/go-metrics-graphite +* InfluxDB - https://github.com/vrischmann/go-metrics-influxdb +* Ganglia - https://github.com/appscode/metlia +* Prometheus - https://github.com/deathowl/go-metrics-prometheus +* DataDog - https://github.com/syntaqx/go-metrics-datadog +* SignalFX - https://github.com/pascallouisperez/go-metrics-signalfx +* Honeycomb - https://github.com/getspine/go-metrics-honeycomb +* Wavefront - https://github.com/wavefrontHQ/go-metrics-wavefront diff --git a/vendor/github.com/rcrowley/go-metrics/ewma.go b/vendor/github.com/rcrowley/go-metrics/ewma.go index 694a1d033..a8183dd7e 100644 --- a/vendor/github.com/rcrowley/go-metrics/ewma.go +++ b/vendor/github.com/rcrowley/go-metrics/ewma.go @@ -79,16 +79,15 @@ func (NilEWMA) Update(n int64) {} type StandardEWMA struct { uncounted int64 // /!\ this should be the first member to ensure 64-bit alignment alpha float64 - rate float64 - init bool + rate uint64 + init uint32 mutex sync.Mutex } // Rate returns the moving average rate of events per second. func (a *StandardEWMA) Rate() float64 { - a.mutex.Lock() - defer a.mutex.Unlock() - return a.rate * float64(1e9) + currentRate := math.Float64frombits(atomic.LoadUint64(&a.rate)) * float64(1e9) + return currentRate } // Snapshot returns a read-only copy of the EWMA. @@ -99,17 +98,38 @@ func (a *StandardEWMA) Snapshot() EWMA { // Tick ticks the clock to update the moving average. It assumes it is called // every five seconds. func (a *StandardEWMA) Tick() { + // Optimization to avoid mutex locking in the hot-path. + if atomic.LoadUint32(&a.init) == 1 { + a.updateRate(a.fetchInstantRate()) + } else { + // Slow-path: this is only needed on the first Tick() and preserves transactional updating + // of init and rate in the else block. The first conditional is needed below because + // a different thread could have set a.init = 1 between the time of the first atomic load and when + // the lock was acquired. + a.mutex.Lock() + if atomic.LoadUint32(&a.init) == 1 { + // The fetchInstantRate() uses atomic loading, which is unecessary in this critical section + // but again, this section is only invoked on the first successful Tick() operation. + a.updateRate(a.fetchInstantRate()) + } else { + atomic.StoreUint32(&a.init, 1) + atomic.StoreUint64(&a.rate, math.Float64bits(a.fetchInstantRate())) + } + a.mutex.Unlock() + } +} + +func (a *StandardEWMA) fetchInstantRate() float64 { count := atomic.LoadInt64(&a.uncounted) atomic.AddInt64(&a.uncounted, -count) instantRate := float64(count) / float64(5e9) - a.mutex.Lock() - defer a.mutex.Unlock() - if a.init { - a.rate += a.alpha * (instantRate - a.rate) - } else { - a.init = true - a.rate = instantRate - } + return instantRate +} + +func (a *StandardEWMA) updateRate(instantRate float64) { + currentRate := math.Float64frombits(atomic.LoadUint64(&a.rate)) + currentRate += a.alpha * (instantRate - currentRate) + atomic.StoreUint64(&a.rate, math.Float64bits(currentRate)) } // Update adds n uncounted events. diff --git a/vendor/github.com/rcrowley/go-metrics/gauge.go b/vendor/github.com/rcrowley/go-metrics/gauge.go index 807638a31..cb57a9388 100644 --- a/vendor/github.com/rcrowley/go-metrics/gauge.go +++ b/vendor/github.com/rcrowley/go-metrics/gauge.go @@ -36,6 +36,24 @@ func NewRegisteredGauge(name string, r Registry) Gauge { return c } +// NewFunctionalGauge constructs a new FunctionalGauge. +func NewFunctionalGauge(f func() int64) Gauge { + if UseNilMetrics { + return NilGauge{} + } + return &FunctionalGauge{value: f} +} + +// NewRegisteredFunctionalGauge constructs and registers a new StandardGauge. +func NewRegisteredFunctionalGauge(name string, r Registry, f func() int64) Gauge { + c := NewFunctionalGauge(f) + if nil == r { + r = DefaultRegistry + } + r.Register(name, c) + return c +} + // GaugeSnapshot is a read-only copy of another Gauge. type GaugeSnapshot int64 @@ -82,3 +100,21 @@ func (g *StandardGauge) Update(v int64) { func (g *StandardGauge) Value() int64 { return atomic.LoadInt64(&g.value) } + +// FunctionalGauge returns value from given function +type FunctionalGauge struct { + value func() int64 +} + +// Value returns the gauge's current value. +func (g FunctionalGauge) Value() int64 { + return g.value() +} + +// Snapshot returns the snapshot. +func (g FunctionalGauge) Snapshot() Gauge { return GaugeSnapshot(g.Value()) } + +// Update panics. +func (FunctionalGauge) Update(int64) { + panic("Update called on a FunctionalGauge") +} diff --git a/vendor/github.com/rcrowley/go-metrics/gauge_float64.go b/vendor/github.com/rcrowley/go-metrics/gauge_float64.go index 47c3566c2..3962e6db0 100644 --- a/vendor/github.com/rcrowley/go-metrics/gauge_float64.go +++ b/vendor/github.com/rcrowley/go-metrics/gauge_float64.go @@ -1,6 +1,9 @@ package metrics -import "sync" +import ( + "math" + "sync/atomic" +) // GaugeFloat64s hold a float64 value that can be set arbitrarily. type GaugeFloat64 interface { @@ -38,6 +41,24 @@ func NewRegisteredGaugeFloat64(name string, r Registry) GaugeFloat64 { return c } +// NewFunctionalGauge constructs a new FunctionalGauge. +func NewFunctionalGaugeFloat64(f func() float64) GaugeFloat64 { + if UseNilMetrics { + return NilGaugeFloat64{} + } + return &FunctionalGaugeFloat64{value: f} +} + +// NewRegisteredFunctionalGauge constructs and registers a new StandardGauge. +func NewRegisteredFunctionalGaugeFloat64(name string, r Registry, f func() float64) GaugeFloat64 { + c := NewFunctionalGaugeFloat64(f) + if nil == r { + r = DefaultRegistry + } + r.Register(name, c) + return c +} + // GaugeFloat64Snapshot is a read-only copy of another GaugeFloat64. type GaugeFloat64Snapshot float64 @@ -67,8 +88,7 @@ func (NilGaugeFloat64) Value() float64 { return 0.0 } // StandardGaugeFloat64 is the standard implementation of a GaugeFloat64 and uses // sync.Mutex to manage a single float64 value. type StandardGaugeFloat64 struct { - mutex sync.Mutex - value float64 + value uint64 } // Snapshot returns a read-only copy of the gauge. @@ -78,14 +98,28 @@ func (g *StandardGaugeFloat64) Snapshot() GaugeFloat64 { // Update updates the gauge's value. func (g *StandardGaugeFloat64) Update(v float64) { - g.mutex.Lock() - defer g.mutex.Unlock() - g.value = v + atomic.StoreUint64(&g.value, math.Float64bits(v)) } // Value returns the gauge's current value. func (g *StandardGaugeFloat64) Value() float64 { - g.mutex.Lock() - defer g.mutex.Unlock() - return g.value + return math.Float64frombits(atomic.LoadUint64(&g.value)) +} + +// FunctionalGaugeFloat64 returns value from given function +type FunctionalGaugeFloat64 struct { + value func() float64 +} + +// Value returns the gauge's current value. +func (g FunctionalGaugeFloat64) Value() float64 { + return g.value() +} + +// Snapshot returns the snapshot. +func (g FunctionalGaugeFloat64) Snapshot() GaugeFloat64 { return GaugeFloat64Snapshot(g.Value()) } + +// Update panics. +func (FunctionalGaugeFloat64) Update(float64) { + panic("Update called on a FunctionalGaugeFloat64") } diff --git a/vendor/github.com/rcrowley/go-metrics/json.go b/vendor/github.com/rcrowley/go-metrics/json.go index 2676aeea5..174b9477e 100644 --- a/vendor/github.com/rcrowley/go-metrics/json.go +++ b/vendor/github.com/rcrowley/go-metrics/json.go @@ -9,63 +9,7 @@ import ( // MarshalJSON returns a byte slice containing a JSON representation of all // the metrics in the Registry. func (r *StandardRegistry) MarshalJSON() ([]byte, error) { - data := make(map[string]map[string]interface{}) - r.Each(func(name string, i interface{}) { - values := make(map[string]interface{}) - switch metric := i.(type) { - case Counter: - values["count"] = metric.Count() - case Gauge: - values["value"] = metric.Value() - case GaugeFloat64: - values["value"] = metric.Value() - case Healthcheck: - values["error"] = nil - metric.Check() - if err := metric.Error(); nil != err { - values["error"] = metric.Error().Error() - } - case Histogram: - h := metric.Snapshot() - ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) - values["count"] = h.Count() - values["min"] = h.Min() - values["max"] = h.Max() - values["mean"] = h.Mean() - values["stddev"] = h.StdDev() - values["median"] = ps[0] - values["75%"] = ps[1] - values["95%"] = ps[2] - values["99%"] = ps[3] - values["99.9%"] = ps[4] - case Meter: - m := metric.Snapshot() - values["count"] = m.Count() - values["1m.rate"] = m.Rate1() - values["5m.rate"] = m.Rate5() - values["15m.rate"] = m.Rate15() - values["mean.rate"] = m.RateMean() - case Timer: - t := metric.Snapshot() - ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) - values["count"] = t.Count() - values["min"] = t.Min() - values["max"] = t.Max() - values["mean"] = t.Mean() - values["stddev"] = t.StdDev() - values["median"] = ps[0] - values["75%"] = ps[1] - values["95%"] = ps[2] - values["99%"] = ps[3] - values["99.9%"] = ps[4] - values["1m.rate"] = t.Rate1() - values["5m.rate"] = t.Rate5() - values["15m.rate"] = t.Rate15() - values["mean.rate"] = t.RateMean() - } - data[name] = values - }) - return json.Marshal(data) + return json.Marshal(r.GetAll()) } // WriteJSON writes metrics from the given registry periodically to the @@ -81,3 +25,7 @@ func WriteJSON(r Registry, d time.Duration, w io.Writer) { func WriteJSONOnce(r Registry, w io.Writer) { json.NewEncoder(w).Encode(r) } + +func (p *PrefixedRegistry) MarshalJSON() ([]byte, error) { + return json.Marshal(p.GetAll()) +} diff --git a/vendor/github.com/rcrowley/go-metrics/log.go b/vendor/github.com/rcrowley/go-metrics/log.go index 278a8a441..f8074c045 100644 --- a/vendor/github.com/rcrowley/go-metrics/log.go +++ b/vendor/github.com/rcrowley/go-metrics/log.go @@ -1,14 +1,24 @@ package metrics import ( - "log" "time" ) +type Logger interface { + Printf(format string, v ...interface{}) +} + +func Log(r Registry, freq time.Duration, l Logger) { + LogScaled(r, freq, time.Nanosecond, l) +} + // Output each metric in the given registry periodically using the given -// logger. -func Log(r Registry, d time.Duration, l *log.Logger) { - for _ = range time.Tick(d) { +// logger. Print timings in `scale` units (eg time.Millisecond) rather than nanos. +func LogScaled(r Registry, freq time.Duration, scale time.Duration, l Logger) { + du := float64(scale) + duSuffix := scale.String()[1:] + + for _ = range time.Tick(freq) { r.Each(func(name string, i interface{}) { switch metric := i.(type) { case Counter: @@ -51,15 +61,15 @@ func Log(r Registry, d time.Duration, l *log.Logger) { ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) l.Printf("timer %s\n", name) l.Printf(" count: %9d\n", t.Count()) - l.Printf(" min: %9d\n", t.Min()) - l.Printf(" max: %9d\n", t.Max()) - l.Printf(" mean: %12.2f\n", t.Mean()) - l.Printf(" stddev: %12.2f\n", t.StdDev()) - l.Printf(" median: %12.2f\n", ps[0]) - l.Printf(" 75%%: %12.2f\n", ps[1]) - l.Printf(" 95%%: %12.2f\n", ps[2]) - l.Printf(" 99%%: %12.2f\n", ps[3]) - l.Printf(" 99.9%%: %12.2f\n", ps[4]) + l.Printf(" min: %12.2f%s\n", float64(t.Min())/du, duSuffix) + l.Printf(" max: %12.2f%s\n", float64(t.Max())/du, duSuffix) + l.Printf(" mean: %12.2f%s\n", t.Mean()/du, duSuffix) + l.Printf(" stddev: %12.2f%s\n", t.StdDev()/du, duSuffix) + l.Printf(" median: %12.2f%s\n", ps[0]/du, duSuffix) + l.Printf(" 75%%: %12.2f%s\n", ps[1]/du, duSuffix) + l.Printf(" 95%%: %12.2f%s\n", ps[2]/du, duSuffix) + l.Printf(" 99%%: %12.2f%s\n", ps[3]/du, duSuffix) + l.Printf(" 99.9%%: %12.2f%s\n", ps[4]/du, duSuffix) l.Printf(" 1-min rate: %12.2f\n", t.Rate1()) l.Printf(" 5-min rate: %12.2f\n", t.Rate5()) l.Printf(" 15-min rate: %12.2f\n", t.Rate15()) diff --git a/vendor/github.com/rcrowley/go-metrics/meter.go b/vendor/github.com/rcrowley/go-metrics/meter.go index 0389ab0b8..7807406a3 100644 --- a/vendor/github.com/rcrowley/go-metrics/meter.go +++ b/vendor/github.com/rcrowley/go-metrics/meter.go @@ -1,7 +1,9 @@ package metrics import ( + "math" "sync" + "sync/atomic" "time" ) @@ -15,10 +17,13 @@ type Meter interface { Rate15() float64 RateMean() float64 Snapshot() Meter + Stop() } // GetOrRegisterMeter returns an existing Meter or constructs and registers a // new StandardMeter. +// Be sure to unregister the meter from the registry once it is of no use to +// allow for garbage collection. func GetOrRegisterMeter(name string, r Registry) Meter { if nil == r { r = DefaultRegistry @@ -27,6 +32,7 @@ func GetOrRegisterMeter(name string, r Registry) Meter { } // NewMeter constructs a new StandardMeter and launches a goroutine. +// Be sure to call Stop() once the meter is of no use to allow for garbage collection. func NewMeter() Meter { if UseNilMetrics { return NilMeter{} @@ -34,7 +40,7 @@ func NewMeter() Meter { m := newStandardMeter() arbiter.Lock() defer arbiter.Unlock() - arbiter.meters = append(arbiter.meters, m) + arbiter.meters[m] = struct{}{} if !arbiter.started { arbiter.started = true go arbiter.tick() @@ -44,6 +50,8 @@ func NewMeter() Meter { // NewMeter constructs and registers a new StandardMeter and launches a // goroutine. +// Be sure to unregister the meter from the registry once it is of no use to +// allow for garbage collection. func NewRegisteredMeter(name string, r Registry) Meter { c := NewMeter() if nil == r { @@ -56,7 +64,7 @@ func NewRegisteredMeter(name string, r Registry) Meter { // MeterSnapshot is a read-only copy of another Meter. type MeterSnapshot struct { count int64 - rate1, rate5, rate15, rateMean float64 + rate1, rate5, rate15, rateMean uint64 } // Count returns the count of events at the time the snapshot was taken. @@ -69,23 +77,26 @@ func (*MeterSnapshot) Mark(n int64) { // Rate1 returns the one-minute moving average rate of events per second at the // time the snapshot was taken. -func (m *MeterSnapshot) Rate1() float64 { return m.rate1 } +func (m *MeterSnapshot) Rate1() float64 { return math.Float64frombits(m.rate1) } // Rate5 returns the five-minute moving average rate of events per second at // the time the snapshot was taken. -func (m *MeterSnapshot) Rate5() float64 { return m.rate5 } +func (m *MeterSnapshot) Rate5() float64 { return math.Float64frombits(m.rate5) } // Rate15 returns the fifteen-minute moving average rate of events per second // at the time the snapshot was taken. -func (m *MeterSnapshot) Rate15() float64 { return m.rate15 } +func (m *MeterSnapshot) Rate15() float64 { return math.Float64frombits(m.rate15) } // RateMean returns the meter's mean rate of events per second at the time the // snapshot was taken. -func (m *MeterSnapshot) RateMean() float64 { return m.rateMean } +func (m *MeterSnapshot) RateMean() float64 { return math.Float64frombits(m.rateMean) } // Snapshot returns the snapshot. func (m *MeterSnapshot) Snapshot() Meter { return m } +// Stop is a no-op. +func (m *MeterSnapshot) Stop() {} + // NilMeter is a no-op Meter. type NilMeter struct{} @@ -110,12 +121,17 @@ func (NilMeter) RateMean() float64 { return 0.0 } // Snapshot is a no-op. func (NilMeter) Snapshot() Meter { return NilMeter{} } +// Stop is a no-op. +func (NilMeter) Stop() {} + // StandardMeter is the standard implementation of a Meter. type StandardMeter struct { - lock sync.RWMutex + // Only used on stop. + lock sync.Mutex snapshot *MeterSnapshot a1, a5, a15 EWMA startTime time.Time + stopped uint32 } func newStandardMeter() *StandardMeter { @@ -128,19 +144,32 @@ func newStandardMeter() *StandardMeter { } } +// Stop stops the meter, Mark() will be a no-op if you use it after being stopped. +func (m *StandardMeter) Stop() { + m.lock.Lock() + stopped := m.stopped + m.stopped = 1 + m.lock.Unlock() + if stopped != 1 { + arbiter.Lock() + delete(arbiter.meters, m) + arbiter.Unlock() + } +} + // Count returns the number of events recorded. func (m *StandardMeter) Count() int64 { - m.lock.RLock() - count := m.snapshot.count - m.lock.RUnlock() - return count + return atomic.LoadInt64(&m.snapshot.count) } // Mark records the occurance of n events. func (m *StandardMeter) Mark(n int64) { - m.lock.Lock() - defer m.lock.Unlock() - m.snapshot.count += n + if atomic.LoadUint32(&m.stopped) == 1 { + return + } + + atomic.AddInt64(&m.snapshot.count, n) + m.a1.Update(n) m.a5.Update(n) m.a15.Update(n) @@ -149,70 +178,65 @@ func (m *StandardMeter) Mark(n int64) { // Rate1 returns the one-minute moving average rate of events per second. func (m *StandardMeter) Rate1() float64 { - m.lock.RLock() - rate1 := m.snapshot.rate1 - m.lock.RUnlock() - return rate1 + return math.Float64frombits(atomic.LoadUint64(&m.snapshot.rate1)) } // Rate5 returns the five-minute moving average rate of events per second. func (m *StandardMeter) Rate5() float64 { - m.lock.RLock() - rate5 := m.snapshot.rate5 - m.lock.RUnlock() - return rate5 + return math.Float64frombits(atomic.LoadUint64(&m.snapshot.rate5)) } // Rate15 returns the fifteen-minute moving average rate of events per second. func (m *StandardMeter) Rate15() float64 { - m.lock.RLock() - rate15 := m.snapshot.rate15 - m.lock.RUnlock() - return rate15 + return math.Float64frombits(atomic.LoadUint64(&m.snapshot.rate15)) } // RateMean returns the meter's mean rate of events per second. func (m *StandardMeter) RateMean() float64 { - m.lock.RLock() - rateMean := m.snapshot.rateMean - m.lock.RUnlock() - return rateMean + return math.Float64frombits(atomic.LoadUint64(&m.snapshot.rateMean)) } // Snapshot returns a read-only copy of the meter. func (m *StandardMeter) Snapshot() Meter { - m.lock.RLock() - snapshot := *m.snapshot - m.lock.RUnlock() - return &snapshot + copiedSnapshot := MeterSnapshot{ + count: atomic.LoadInt64(&m.snapshot.count), + rate1: atomic.LoadUint64(&m.snapshot.rate1), + rate5: atomic.LoadUint64(&m.snapshot.rate5), + rate15: atomic.LoadUint64(&m.snapshot.rate15), + rateMean: atomic.LoadUint64(&m.snapshot.rateMean), + } + return &copiedSnapshot } func (m *StandardMeter) updateSnapshot() { - // should run with write lock held on m.lock - snapshot := m.snapshot - snapshot.rate1 = m.a1.Rate() - snapshot.rate5 = m.a5.Rate() - snapshot.rate15 = m.a15.Rate() - snapshot.rateMean = float64(snapshot.count) / time.Since(m.startTime).Seconds() + rate1 := math.Float64bits(m.a1.Rate()) + rate5 := math.Float64bits(m.a5.Rate()) + rate15 := math.Float64bits(m.a15.Rate()) + rateMean := math.Float64bits(float64(m.Count()) / time.Since(m.startTime).Seconds()) + + atomic.StoreUint64(&m.snapshot.rate1, rate1) + atomic.StoreUint64(&m.snapshot.rate5, rate5) + atomic.StoreUint64(&m.snapshot.rate15, rate15) + atomic.StoreUint64(&m.snapshot.rateMean, rateMean) } func (m *StandardMeter) tick() { - m.lock.Lock() - defer m.lock.Unlock() m.a1.Tick() m.a5.Tick() m.a15.Tick() m.updateSnapshot() } +// meterArbiter ticks meters every 5s from a single goroutine. +// meters are references in a set for future stopping. type meterArbiter struct { sync.RWMutex started bool - meters []*StandardMeter + meters map[*StandardMeter]struct{} ticker *time.Ticker } -var arbiter = meterArbiter{ticker: time.NewTicker(5e9)} +var arbiter = meterArbiter{ticker: time.NewTicker(5e9), meters: make(map[*StandardMeter]struct{})} // Ticks meters on the scheduled interval func (ma *meterArbiter) tick() { @@ -227,7 +251,7 @@ func (ma *meterArbiter) tick() { func (ma *meterArbiter) tickMeters() { ma.RLock() defer ma.RUnlock() - for _, meter := range ma.meters { + for meter := range ma.meters { meter.tick() } } diff --git a/vendor/github.com/rcrowley/go-metrics/registry.go b/vendor/github.com/rcrowley/go-metrics/registry.go index a3b26eba5..b3bab64e1 100644 --- a/vendor/github.com/rcrowley/go-metrics/registry.go +++ b/vendor/github.com/rcrowley/go-metrics/registry.go @@ -3,6 +3,7 @@ package metrics import ( "fmt" "reflect" + "strings" "sync" ) @@ -28,6 +29,9 @@ type Registry interface { // Get the metric by the given name or nil if none is registered. Get(string) interface{} + // GetAll metrics in the Registry. + GetAll() map[string]map[string]interface{} + // Gets an existing metric or registers the given one. // The interface can be the metric to register if not found in registry, // or a function returning the metric for lazy instantiation. @@ -50,7 +54,7 @@ type Registry interface { // of names to metrics. type StandardRegistry struct { metrics map[string]interface{} - mutex sync.Mutex + mutex sync.RWMutex } // Create a new registry. @@ -67,8 +71,8 @@ func (r *StandardRegistry) Each(f func(string, interface{})) { // Get the metric by the given name or nil if none is registered. func (r *StandardRegistry) Get(name string) interface{} { - r.mutex.Lock() - defer r.mutex.Unlock() + r.mutex.RLock() + defer r.mutex.RUnlock() return r.metrics[name] } @@ -77,6 +81,15 @@ func (r *StandardRegistry) Get(name string) interface{} { // The interface can be the metric to register if not found in registry, // or a function returning the metric for lazy instantiation. func (r *StandardRegistry) GetOrRegister(name string, i interface{}) interface{} { + // access the read lock first which should be re-entrant + r.mutex.RLock() + metric, ok := r.metrics[name] + r.mutex.RUnlock() + if ok { + return metric + } + + // only take the write lock if we'll be modifying the metrics map r.mutex.Lock() defer r.mutex.Unlock() if metric, ok := r.metrics[name]; ok { @@ -99,8 +112,8 @@ func (r *StandardRegistry) Register(name string, i interface{}) error { // Run all registered healthchecks. func (r *StandardRegistry) RunHealthchecks() { - r.mutex.Lock() - defer r.mutex.Unlock() + r.mutex.RLock() + defer r.mutex.RUnlock() for _, i := range r.metrics { if h, ok := i.(Healthcheck); ok { h.Check() @@ -108,10 +121,72 @@ func (r *StandardRegistry) RunHealthchecks() { } } +// GetAll metrics in the Registry +func (r *StandardRegistry) GetAll() map[string]map[string]interface{} { + data := make(map[string]map[string]interface{}) + r.Each(func(name string, i interface{}) { + values := make(map[string]interface{}) + switch metric := i.(type) { + case Counter: + values["count"] = metric.Count() + case Gauge: + values["value"] = metric.Value() + case GaugeFloat64: + values["value"] = metric.Value() + case Healthcheck: + values["error"] = nil + metric.Check() + if err := metric.Error(); nil != err { + values["error"] = metric.Error().Error() + } + case Histogram: + h := metric.Snapshot() + ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + values["count"] = h.Count() + values["min"] = h.Min() + values["max"] = h.Max() + values["mean"] = h.Mean() + values["stddev"] = h.StdDev() + values["median"] = ps[0] + values["75%"] = ps[1] + values["95%"] = ps[2] + values["99%"] = ps[3] + values["99.9%"] = ps[4] + case Meter: + m := metric.Snapshot() + values["count"] = m.Count() + values["1m.rate"] = m.Rate1() + values["5m.rate"] = m.Rate5() + values["15m.rate"] = m.Rate15() + values["mean.rate"] = m.RateMean() + case Timer: + t := metric.Snapshot() + ps := t.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) + values["count"] = t.Count() + values["min"] = t.Min() + values["max"] = t.Max() + values["mean"] = t.Mean() + values["stddev"] = t.StdDev() + values["median"] = ps[0] + values["75%"] = ps[1] + values["95%"] = ps[2] + values["99%"] = ps[3] + values["99.9%"] = ps[4] + values["1m.rate"] = t.Rate1() + values["5m.rate"] = t.Rate5() + values["15m.rate"] = t.Rate15() + values["mean.rate"] = t.RateMean() + } + data[name] = values + }) + return data +} + // Unregister the metric with the given name. func (r *StandardRegistry) Unregister(name string) { r.mutex.Lock() defer r.mutex.Unlock() + r.stop(name) delete(r.metrics, name) } @@ -120,6 +195,7 @@ func (r *StandardRegistry) UnregisterAll() { r.mutex.Lock() defer r.mutex.Unlock() for name, _ := range r.metrics { + r.stop(name) delete(r.metrics, name) } } @@ -145,6 +221,19 @@ func (r *StandardRegistry) registered() map[string]interface{} { return metrics } +func (r *StandardRegistry) stop(name string) { + if i, ok := r.metrics[name]; ok { + if s, ok := i.(Stoppable); ok { + s.Stop() + } + } +} + +// Stoppable defines the metrics which has to be stopped. +type Stoppable interface { + Stop() +} + type PrefixedRegistry struct { underlying Registry prefix string @@ -157,14 +246,43 @@ func NewPrefixedRegistry(prefix string) Registry { } } +func NewPrefixedChildRegistry(parent Registry, prefix string) Registry { + return &PrefixedRegistry{ + underlying: parent, + prefix: prefix, + } +} + // Call the given function for each registered metric. func (r *PrefixedRegistry) Each(fn func(string, interface{})) { - r.underlying.Each(fn) + wrappedFn := func(prefix string) func(string, interface{}) { + return func(name string, iface interface{}) { + if strings.HasPrefix(name, prefix) { + fn(name, iface) + } else { + return + } + } + } + + baseRegistry, prefix := findPrefix(r, "") + baseRegistry.Each(wrappedFn(prefix)) +} + +func findPrefix(registry Registry, prefix string) (Registry, string) { + switch r := registry.(type) { + case *PrefixedRegistry: + return findPrefix(r.underlying, r.prefix+prefix) + case *StandardRegistry: + return r, prefix + } + return nil, "" } // Get the metric by the given name or nil if none is registered. func (r *PrefixedRegistry) Get(name string) interface{} { - return r.underlying.Get(name) + realName := r.prefix + name + return r.underlying.Get(realName) } // Gets an existing metric or registers the given one. @@ -186,6 +304,11 @@ func (r *PrefixedRegistry) RunHealthchecks() { r.underlying.RunHealthchecks() } +// GetAll metrics in the Registry +func (r *PrefixedRegistry) GetAll() map[string]map[string]interface{} { + return r.underlying.GetAll() +} + // Unregister the metric with the given name. The name will be prefixed. func (r *PrefixedRegistry) Unregister(name string) { realName := r.prefix + name diff --git a/vendor/github.com/rcrowley/go-metrics/runtime.go b/vendor/github.com/rcrowley/go-metrics/runtime.go index 82574bf25..11c6b785a 100644 --- a/vendor/github.com/rcrowley/go-metrics/runtime.go +++ b/vendor/github.com/rcrowley/go-metrics/runtime.go @@ -2,6 +2,7 @@ package metrics import ( "runtime" + "runtime/pprof" "time" ) @@ -9,35 +10,37 @@ var ( memStats runtime.MemStats runtimeMetrics struct { MemStats struct { - Alloc Gauge - BuckHashSys Gauge - DebugGC Gauge - EnableGC Gauge - Frees Gauge - HeapAlloc Gauge - HeapIdle Gauge - HeapInuse Gauge - HeapObjects Gauge - HeapReleased Gauge - HeapSys Gauge - LastGC Gauge - Lookups Gauge - Mallocs Gauge - MCacheInuse Gauge - MCacheSys Gauge - MSpanInuse Gauge - MSpanSys Gauge - NextGC Gauge - NumGC Gauge - PauseNs Histogram - PauseTotalNs Gauge - StackInuse Gauge - StackSys Gauge - Sys Gauge - TotalAlloc Gauge + Alloc Gauge + BuckHashSys Gauge + DebugGC Gauge + EnableGC Gauge + Frees Gauge + HeapAlloc Gauge + HeapIdle Gauge + HeapInuse Gauge + HeapObjects Gauge + HeapReleased Gauge + HeapSys Gauge + LastGC Gauge + Lookups Gauge + Mallocs Gauge + MCacheInuse Gauge + MCacheSys Gauge + MSpanInuse Gauge + MSpanSys Gauge + NextGC Gauge + NumGC Gauge + GCCPUFraction GaugeFloat64 + PauseNs Histogram + PauseTotalNs Gauge + StackInuse Gauge + StackSys Gauge + Sys Gauge + TotalAlloc Gauge } NumCgoCall Gauge NumGoroutine Gauge + NumThread Gauge ReadMemStats Timer } frees uint64 @@ -45,6 +48,8 @@ var ( mallocs uint64 numGC uint32 numCgoCalls int64 + + threadCreateProfile = pprof.Lookup("threadcreate") ) // Capture new values for the Go runtime statistics exported in @@ -97,6 +102,7 @@ func CaptureRuntimeMemStatsOnce(r Registry) { runtimeMetrics.MemStats.MSpanSys.Update(int64(memStats.MSpanSys)) runtimeMetrics.MemStats.NextGC.Update(int64(memStats.NextGC)) runtimeMetrics.MemStats.NumGC.Update(int64(memStats.NumGC - numGC)) + runtimeMetrics.MemStats.GCCPUFraction.Update(gcCPUFraction(&memStats)) // i := numGC % uint32(len(memStats.PauseNs)) @@ -132,6 +138,8 @@ func CaptureRuntimeMemStatsOnce(r Registry) { numCgoCalls = currentNumCgoCalls runtimeMetrics.NumGoroutine.Update(int64(runtime.NumGoroutine())) + + runtimeMetrics.NumThread.Update(int64(threadCreateProfile.Count())) } // Register runtimeMetrics for the Go runtime statistics exported in runtime and @@ -158,6 +166,7 @@ func RegisterRuntimeMemStats(r Registry) { runtimeMetrics.MemStats.MSpanSys = NewGauge() runtimeMetrics.MemStats.NextGC = NewGauge() runtimeMetrics.MemStats.NumGC = NewGauge() + runtimeMetrics.MemStats.GCCPUFraction = NewGaugeFloat64() runtimeMetrics.MemStats.PauseNs = NewHistogram(NewExpDecaySample(1028, 0.015)) runtimeMetrics.MemStats.PauseTotalNs = NewGauge() runtimeMetrics.MemStats.StackInuse = NewGauge() @@ -166,6 +175,7 @@ func RegisterRuntimeMemStats(r Registry) { runtimeMetrics.MemStats.TotalAlloc = NewGauge() runtimeMetrics.NumCgoCall = NewGauge() runtimeMetrics.NumGoroutine = NewGauge() + runtimeMetrics.NumThread = NewGauge() runtimeMetrics.ReadMemStats = NewTimer() r.Register("runtime.MemStats.Alloc", runtimeMetrics.MemStats.Alloc) @@ -188,6 +198,7 @@ func RegisterRuntimeMemStats(r Registry) { r.Register("runtime.MemStats.MSpanSys", runtimeMetrics.MemStats.MSpanSys) r.Register("runtime.MemStats.NextGC", runtimeMetrics.MemStats.NextGC) r.Register("runtime.MemStats.NumGC", runtimeMetrics.MemStats.NumGC) + r.Register("runtime.MemStats.GCCPUFraction", runtimeMetrics.MemStats.GCCPUFraction) r.Register("runtime.MemStats.PauseNs", runtimeMetrics.MemStats.PauseNs) r.Register("runtime.MemStats.PauseTotalNs", runtimeMetrics.MemStats.PauseTotalNs) r.Register("runtime.MemStats.StackInuse", runtimeMetrics.MemStats.StackInuse) @@ -196,5 +207,6 @@ func RegisterRuntimeMemStats(r Registry) { r.Register("runtime.MemStats.TotalAlloc", runtimeMetrics.MemStats.TotalAlloc) r.Register("runtime.NumCgoCall", runtimeMetrics.NumCgoCall) r.Register("runtime.NumGoroutine", runtimeMetrics.NumGoroutine) + r.Register("runtime.NumThread", runtimeMetrics.NumThread) r.Register("runtime.ReadMemStats", runtimeMetrics.ReadMemStats) } diff --git a/vendor/github.com/rcrowley/go-metrics/runtime_gccpufraction.go b/vendor/github.com/rcrowley/go-metrics/runtime_gccpufraction.go new file mode 100644 index 000000000..ca12c05ba --- /dev/null +++ b/vendor/github.com/rcrowley/go-metrics/runtime_gccpufraction.go @@ -0,0 +1,9 @@ +// +build go1.5 + +package metrics + +import "runtime" + +func gcCPUFraction(memStats *runtime.MemStats) float64 { + return memStats.GCCPUFraction +} diff --git a/vendor/github.com/rcrowley/go-metrics/runtime_no_gccpufraction.go b/vendor/github.com/rcrowley/go-metrics/runtime_no_gccpufraction.go new file mode 100644 index 000000000..be96aa6f1 --- /dev/null +++ b/vendor/github.com/rcrowley/go-metrics/runtime_no_gccpufraction.go @@ -0,0 +1,9 @@ +// +build !go1.5 + +package metrics + +import "runtime" + +func gcCPUFraction(memStats *runtime.MemStats) float64 { + return 0 +} diff --git a/vendor/github.com/rcrowley/go-metrics/sample.go b/vendor/github.com/rcrowley/go-metrics/sample.go index 5f6a37788..fecee5ef6 100644 --- a/vendor/github.com/rcrowley/go-metrics/sample.go +++ b/vendor/github.com/rcrowley/go-metrics/sample.go @@ -33,7 +33,7 @@ type Sample interface { // priority reservoir. See Cormode et al's "Forward Decay: A Practical Time // Decay Model for Streaming Systems". // -// +// type ExpDecaySample struct { alpha float64 count int64 @@ -302,6 +302,13 @@ type SampleSnapshot struct { values []int64 } +func NewSampleSnapshot(count int64, values []int64) *SampleSnapshot { + return &SampleSnapshot{ + count: count, + values: values, + } +} + // Clear panics. func (*SampleSnapshot) Clear() { panic("Clear called on a SampleSnapshot") diff --git a/vendor/github.com/rcrowley/go-metrics/timer.go b/vendor/github.com/rcrowley/go-metrics/timer.go index 17db8f8d2..d6ec4c626 100644 --- a/vendor/github.com/rcrowley/go-metrics/timer.go +++ b/vendor/github.com/rcrowley/go-metrics/timer.go @@ -19,6 +19,7 @@ type Timer interface { RateMean() float64 Snapshot() Timer StdDev() float64 + Stop() Sum() int64 Time(func()) Update(time.Duration) @@ -28,6 +29,8 @@ type Timer interface { // GetOrRegisterTimer returns an existing Timer or constructs and registers a // new StandardTimer. +// Be sure to unregister the meter from the registry once it is of no use to +// allow for garbage collection. func GetOrRegisterTimer(name string, r Registry) Timer { if nil == r { r = DefaultRegistry @@ -36,6 +39,7 @@ func GetOrRegisterTimer(name string, r Registry) Timer { } // NewCustomTimer constructs a new StandardTimer from a Histogram and a Meter. +// Be sure to call Stop() once the timer is of no use to allow for garbage collection. func NewCustomTimer(h Histogram, m Meter) Timer { if UseNilMetrics { return NilTimer{} @@ -47,6 +51,8 @@ func NewCustomTimer(h Histogram, m Meter) Timer { } // NewRegisteredTimer constructs and registers a new StandardTimer. +// Be sure to unregister the meter from the registry once it is of no use to +// allow for garbage collection. func NewRegisteredTimer(name string, r Registry) Timer { c := NewTimer() if nil == r { @@ -58,6 +64,7 @@ func NewRegisteredTimer(name string, r Registry) Timer { // NewTimer constructs a new StandardTimer using an exponentially-decaying // sample with the same reservoir size and alpha as UNIX load averages. +// Be sure to call Stop() once the timer is of no use to allow for garbage collection. func NewTimer() Timer { if UseNilMetrics { return NilTimer{} @@ -112,6 +119,9 @@ func (NilTimer) Snapshot() Timer { return NilTimer{} } // StdDev is a no-op. func (NilTimer) StdDev() float64 { return 0.0 } +// Stop is a no-op. +func (NilTimer) Stop() {} + // Sum is a no-op. func (NilTimer) Sum() int64 { return 0 } @@ -201,6 +211,11 @@ func (t *StandardTimer) StdDev() float64 { return t.histogram.StdDev() } +// Stop stops the meter. +func (t *StandardTimer) Stop() { + t.meter.Stop() +} + // Sum returns the sum in the sample. func (t *StandardTimer) Sum() int64 { return t.histogram.Sum() @@ -288,6 +303,9 @@ func (t *TimerSnapshot) Snapshot() Timer { return t } // was taken. func (t *TimerSnapshot) StdDev() float64 { return t.histogram.StdDev() } +// Stop is a no-op. +func (t *TimerSnapshot) Stop() {} + // Sum returns the sum at the time the snapshot was taken. func (t *TimerSnapshot) Sum() int64 { return t.histogram.Sum() } diff --git a/vendor/github.com/rcrowley/go-metrics/validate.sh b/vendor/github.com/rcrowley/go-metrics/validate.sh index f6499982e..c4ae91e64 100755 --- a/vendor/github.com/rcrowley/go-metrics/validate.sh +++ b/vendor/github.com/rcrowley/go-metrics/validate.sh @@ -7,4 +7,4 @@ GOFMT_LINES=`gofmt -l . | wc -l | xargs` test $GOFMT_LINES -eq 0 || echo "gofmt needs to be run, ${GOFMT_LINES} files have issues" # run the tests for the root package -go test . +go test -race . diff --git a/vendor/vendor.json b/vendor/vendor.json index 2df73f2ef..dec0970af 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -40,7 +40,7 @@ {"path":"github.com/pascaldekloe/goe/verify","checksumSHA1":"4tr8yNUt5DB8GXc5y+uq6J7TJ54=","revision":"f99183613f483cd9b8c79359d572836e243e0763","revisionTime":"2015-07-19T21:56:08+02:00"}, {"path":"github.com/pkg/profile","checksumSHA1":"C3yiSMdTQxSY3xqKJzMV9T+KnIc=","revision":"5b67d428864e92711fcbd2f8629456121a56d91f","revisionTime":"2017-05-09T09:25:25Z"}, {"path":"github.com/pubnub/go-metrics-statsd","checksumSHA1":"xiXWXI7iNZ4Vkr/ayWU3KsGnRo0=","revision":"7da61f429d6bdaa79d5d5746998e0db9622c56fc","revisionTime":"2017-01-24T01:40:03Z"}, - {"path":"github.com/rcrowley/go-metrics","checksumSHA1":"ODTWX4h8f+DW3oWZFT0yTmfHzdg=","revision":"3e5e593311103d49927c8d2b0fd93ccdfe4a525c","revisionTime":"2015-07-19T09:56:14-07:00"}, + {"path":"github.com/rcrowley/go-metrics","checksumSHA1":"an5RM8wjgPPloolUUYkvEncbHu4=","revision":"e2704e165165ec55d062f5919b4b29494e9fa790","revisionTime":"2018-05-03T17:46:38Z"}, {"path":"github.com/rogpeppe/fastuuid","checksumSHA1":"ehRkDJisGCCSYdNgyvs1gSywSPE=","revision":"6724a57986aff9bff1a1770e9347036def7c89f6","revisionTime":"2015-01-06T09:31:45Z"}, {"path":"github.com/sergi/go-diff/diffmatchpatch","checksumSHA1":"iWCtyR1TkJ22Bi/ygzfKDvOQdQY=","revision":"24e2351369ec4949b2ed0dc5c477afdd4c4034e8","revisionTime":"2017-01-18T13:12:30Z"}, {"path":"golang.org/x/net/http2","checksumSHA1":"N1akwAdrHVfPPrsFOhG2ouP21VA=","revision":"f2499483f923065a842d38eb4c7f1927e6fc6e6d","revisionTime":"2017-01-14T04:22:49Z"},