New Upstream Snapshot - golang-github-rcrowley-go-metrics

Ready changes

Summary

Merged new upstream version: 0.0~git20201226.cf1acfc (was: 0.0~git20180125.8732c61).

Resulting package

Built on 2022-11-19T18:56 (took 5m13s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-snapshots golang-github-rcrowley-go-metrics-dev

Lintian Result

Diff

diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 83c8f82..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,9 +0,0 @@
-*.[68]
-*.a
-*.out
-*.swp
-_obj
-_testmain.go
-cmd/metrics-bench/metrics-bench
-cmd/metrics-example/metrics-example
-cmd/never-read/never-read
diff --git a/.travis.yml b/.travis.yml
index f8b3b2e..ce9afea 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,19 @@
 language: go
 
 go:
-    - 1.2
-    - 1.3
-    - 1.4
-    - 1.5
-    - 1.6
-    - 1.7
-    - 1.8
-    - 1.9
+    - "1.3"
+    - "1.4"
+    - "1.5"
+    - "1.6"
+    - "1.7"
+    - "1.8"
+    - "1.9"
+    - "1.10"
+    - "1.11"
+    - "1.12"
+    - "1.13"
+    - "1.14"
+    - "1.15"
 
 script:
     - ./validate.sh
diff --git a/README.md b/README.md
index 17cea76..27ddfee 100644
--- a/README.md
+++ b/README.md
@@ -157,6 +157,7 @@ Publishing Metrics
 
 Clients are available for the following destinations:
 
+* AppOptics - https://github.com/ysamlan/go-metrics-appoptics
 * Librato - https://github.com/mihasya/go-metrics-librato
 * Graphite - https://github.com/cyberdelia/go-metrics-graphite
 * InfluxDB - https://github.com/vrischmann/go-metrics-influxdb
@@ -165,3 +166,6 @@ Clients are available for the following destinations:
 * 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
+* Open-Falcon - https://github.com/g4zhuj/go-metrics-falcon
+* AWS CloudWatch - [https://github.com/savaki/cloudmetrics](https://github.com/savaki/cloudmetrics)
diff --git a/debian/changelog b/debian/changelog
index d07a7f0..40e263a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-rcrowley-go-metrics (0.0~git20201226.cf1acfc-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 19 Nov 2022 18:53:05 -0000
+
 golang-github-rcrowley-go-metrics (0.0~git20180125.8732c61-3) unstable; urgency=medium
 
   * Team upload.
diff --git a/debian/patches/precision.patch b/debian/patches/precision.patch
index b7e32f4..350b6e2 100644
--- a/debian/patches/precision.patch
+++ b/debian/patches/precision.patch
@@ -10,9 +10,11 @@ NB: I've stripped out the optimizations as they are not merged upstream. I did,
 however, keep in the parts that fix the bitwise float comparison numerics. The
 test-code now does a delta test instead which should work more reliably.
 
---- a/ewma_test.go
-+++ b/ewma_test.go
-@@ -15,67 +15,67 @@
+Index: golang-github-rcrowley-go-metrics.git/ewma_test.go
+===================================================================
+--- golang-github-rcrowley-go-metrics.git.orig/ewma_test.go
++++ golang-github-rcrowley-go-metrics.git/ewma_test.go
+@@ -48,67 +48,67 @@ func TestEWMA1(t *testing.T) {
  	a := NewEWMA1()
  	a.Update(3)
  	a.Tick()
@@ -96,7 +98,7 @@ test-code now does a delta test instead which should work more reliably.
  		t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate)
  	}
  }
-@@ -84,67 +84,67 @@
+@@ -117,67 +117,67 @@ func TestEWMA5(t *testing.T) {
  	a := NewEWMA5()
  	a.Update(3)
  	a.Tick()
@@ -180,7 +182,7 @@ test-code now does a delta test instead which should work more reliably.
  		t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate)
  	}
  }
-@@ -153,67 +153,67 @@
+@@ -186,67 +186,67 @@ func TestEWMA15(t *testing.T) {
  	a := NewEWMA15()
  	a.Update(3)
  	a.Tick()
@@ -264,9 +266,11 @@ test-code now does a delta test instead which should work more reliably.
  		t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate)
  	}
  }
---- a/histogram_test.go
-+++ b/histogram_test.go
-@@ -76,20 +76,20 @@
+Index: golang-github-rcrowley-go-metrics.git/histogram_test.go
+===================================================================
+--- golang-github-rcrowley-go-metrics.git.orig/histogram_test.go
++++ golang-github-rcrowley-go-metrics.git/histogram_test.go
+@@ -76,20 +76,20 @@ func testHistogram10000(t *testing.T, h
  	if max := h.Max(); 10000 != max {
  		t.Errorf("h.Max(): 10000 != %v\n", max)
  	}
@@ -292,11 +296,13 @@ test-code now does a delta test instead which should work more reliably.
  		t.Errorf("99th percentile: 9900.99 != %v\n", ps[2])
  	}
  }
---- a/sample_test.go
-+++ b/sample_test.go
+Index: golang-github-rcrowley-go-metrics.git/sample_test.go
+===================================================================
+--- golang-github-rcrowley-go-metrics.git.orig/sample_test.go
++++ golang-github-rcrowley-go-metrics.git/sample_test.go
 @@ -1,12 +1,21 @@
  package metrics
-
+ 
  import (
 +	"math"
  	"math/rand"
@@ -304,7 +310,7 @@ test-code now does a delta test instead which should work more reliably.
  	"testing"
  	"time"
  )
-
+ 
 +func float64NotEqual(a, b float64) bool {
 +	v := math.Abs(a - b)
 +	if b == 0.0 && v > 0.00001 {
@@ -316,7 +322,7 @@ test-code now does a delta test instead which should work more reliably.
  // Benchmark{Compute,Copy}{1000,1000000} demonstrate that, even for relatively
  // expensive computations like Variance, the cost of copying the Sample, as
  // approximated by a make and copy, is much greater than the cost of the
-@@ -285,20 +336,20 @@
+@@ -285,20 +294,20 @@ func testExpDecaySampleStatistics(t *tes
  	if max := s.Max(); 10000 != max {
  		t.Errorf("s.Max(): 10000 != %v\n", max)
  	}
@@ -342,7 +348,7 @@ test-code now does a delta test instead which should work more reliably.
  		t.Errorf("99th percentile: 9998.99 != %v\n", ps[2])
  	}
  }
-@@ -313,20 +364,20 @@
+@@ -313,20 +322,20 @@ func testUniformSampleStatistics(t *test
  	if max := s.Max(); 9989 != max {
  		t.Errorf("s.Max(): 9989 != %v\n", max)
  	}
@@ -368,9 +374,11 @@ test-code now does a delta test instead which should work more reliably.
  		t.Errorf("99th percentile: 9986.429999999998 != %v\n", ps[2])
  	}
  }
---- a/timer_test.go
-+++ b/timer_test.go
-@@ -27,7 +27,7 @@
+Index: golang-github-rcrowley-go-metrics.git/timer_test.go
+===================================================================
+--- golang-github-rcrowley-go-metrics.git.orig/timer_test.go
++++ golang-github-rcrowley-go-metrics.git/timer_test.go
+@@ -27,7 +27,7 @@ func TestTimerExtremes(t *testing.T) {
  	tm := NewTimer()
  	tm.Update(math.MaxInt64)
  	tm.Update(0)
diff --git a/debug.go b/debug.go
index 043ccef..179e5aa 100644
--- a/debug.go
+++ b/debug.go
@@ -2,6 +2,7 @@ package metrics
 
 import (
 	"runtime/debug"
+	"sync"
 	"time"
 )
 
@@ -16,7 +17,8 @@ var (
 		}
 		ReadGCStats Timer
 	}
-	gcStats debug.GCStats
+	gcStats                  debug.GCStats
+	registerDebugMetricsOnce = sync.Once{}
 )
 
 // Capture new values for the Go garbage collector statistics exported in
@@ -54,19 +56,21 @@ func CaptureDebugGCStatsOnce(r Registry) {
 // debug.GCStats.  The metrics are named by their fully-qualified Go symbols,
 // i.e. debug.GCStats.PauseTotal.
 func RegisterDebugGCStats(r Registry) {
-	debugMetrics.GCStats.LastGC = NewGauge()
-	debugMetrics.GCStats.NumGC = NewGauge()
-	debugMetrics.GCStats.Pause = NewHistogram(NewExpDecaySample(1028, 0.015))
-	//debugMetrics.GCStats.PauseQuantiles = NewHistogram(NewExpDecaySample(1028, 0.015))
-	debugMetrics.GCStats.PauseTotal = NewGauge()
-	debugMetrics.ReadGCStats = NewTimer()
+	registerDebugMetricsOnce.Do(func() {
+		debugMetrics.GCStats.LastGC = NewGauge()
+		debugMetrics.GCStats.NumGC = NewGauge()
+		debugMetrics.GCStats.Pause = NewHistogram(NewExpDecaySample(1028, 0.015))
+		//debugMetrics.GCStats.PauseQuantiles = NewHistogram(NewExpDecaySample(1028, 0.015))
+		debugMetrics.GCStats.PauseTotal = NewGauge()
+		debugMetrics.ReadGCStats = NewTimer()
 
-	r.Register("debug.GCStats.LastGC", debugMetrics.GCStats.LastGC)
-	r.Register("debug.GCStats.NumGC", debugMetrics.GCStats.NumGC)
-	r.Register("debug.GCStats.Pause", debugMetrics.GCStats.Pause)
-	//r.Register("debug.GCStats.PauseQuantiles", debugMetrics.GCStats.PauseQuantiles)
-	r.Register("debug.GCStats.PauseTotal", debugMetrics.GCStats.PauseTotal)
-	r.Register("debug.ReadGCStats", debugMetrics.ReadGCStats)
+		r.Register("debug.GCStats.LastGC", debugMetrics.GCStats.LastGC)
+		r.Register("debug.GCStats.NumGC", debugMetrics.GCStats.NumGC)
+		r.Register("debug.GCStats.Pause", debugMetrics.GCStats.Pause)
+		//r.Register("debug.GCStats.PauseQuantiles", debugMetrics.GCStats.PauseQuantiles)
+		r.Register("debug.GCStats.PauseTotal", debugMetrics.GCStats.PauseTotal)
+		r.Register("debug.ReadGCStats", debugMetrics.ReadGCStats)
+	})
 }
 
 // Allocate an initial slice for gcStats.Pause to avoid allocations during
diff --git a/debug_test.go b/debug_test.go
index 07eb867..1c4000b 100644
--- a/debug_test.go
+++ b/debug_test.go
@@ -46,3 +46,26 @@ func testDebugGCStatsBlocking(ch chan int) {
 		}
 	}
 }
+
+func TestDebugGCStatsDoubleRegister(t *testing.T) {
+	r := NewRegistry()
+	RegisterDebugGCStats(r)
+	var storedGauge = (r.Get("debug.GCStats.LastGC")).(Gauge)
+
+	runtime.GC()
+	CaptureDebugGCStatsOnce(r)
+
+	firstGC := storedGauge.Value()
+	if 0 == firstGC {
+		t.Errorf("firstGC got %d, expected > 0", firstGC)
+	}
+
+	time.Sleep(time.Millisecond)
+
+	RegisterDebugGCStats(r)
+	runtime.GC()
+	CaptureDebugGCStatsOnce(r)
+	if lastGC := storedGauge.Value(); firstGC == lastGC {
+		t.Errorf("lastGC got %d, expected a higher timestamp value", lastGC)
+	}
+}
diff --git a/ewma.go b/ewma.go
index 694a1d0..a8183dd 100644
--- a/ewma.go
+++ b/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/ewma_test.go b/ewma_test.go
index 0430fbd..058ae2d 100644
--- a/ewma_test.go
+++ b/ewma_test.go
@@ -1,6 +1,11 @@
 package metrics
 
-import "testing"
+import (
+	"math/rand"
+	"sync"
+	"testing"
+	"time"
+)
 
 func BenchmarkEWMA(b *testing.B) {
 	a := NewEWMA1()
@@ -11,6 +16,34 @@ func BenchmarkEWMA(b *testing.B) {
 	}
 }
 
+func BenchmarkEWMAParallel(b *testing.B) {
+	a := NewEWMA1()
+	b.ResetTimer()
+
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			a.Update(1)
+			a.Tick()
+		}
+	})
+}
+
+// exercise race detector
+func TestEWMAConcurrency(t *testing.T) {
+	rand.Seed(time.Now().Unix())
+	a := NewEWMA1()
+	wg := &sync.WaitGroup{}
+	reps := 100
+	for i := 0; i < reps; i++ {
+		wg.Add(1)
+		go func(ewma EWMA, wg *sync.WaitGroup) {
+			a.Update(rand.Int63())
+			wg.Done()
+		}(a, wg)
+	}
+	wg.Wait()
+}
+
 func TestEWMA1(t *testing.T) {
 	a := NewEWMA1()
 	a.Update(3)
diff --git a/gauge_float64.go b/gauge_float64.go
index 6f93920..3962e6d 100644
--- a/gauge_float64.go
+++ b/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 {
@@ -85,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.
@@ -96,16 +98,12 @@ 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
diff --git a/gauge_float64_test.go b/gauge_float64_test.go
index 99e62a4..6769b95 100644
--- a/gauge_float64_test.go
+++ b/gauge_float64_test.go
@@ -10,6 +10,16 @@ func BenchmarkGuageFloat64(b *testing.B) {
 	}
 }
 
+func BenchmarkGuageFloat64Parallel(b *testing.B) {
+	g := NewGaugeFloat64()
+	b.ResetTimer()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			g.Update(float64(1))
+		}
+	})
+}
+
 func TestGaugeFloat64(t *testing.T) {
 	g := NewGaugeFloat64()
 	g.Update(float64(47.0))
diff --git a/gauge_test.go b/gauge_test.go
index 1f2603d..d54d6f1 100644
--- a/gauge_test.go
+++ b/gauge_test.go
@@ -2,7 +2,10 @@ package metrics
 
 import (
 	"fmt"
+	"math/rand"
+	"sync"
 	"testing"
+	"time"
 )
 
 func BenchmarkGuage(b *testing.B) {
@@ -13,6 +16,22 @@ func BenchmarkGuage(b *testing.B) {
 	}
 }
 
+// exercise race detector
+func TestGaugeConcurrency(t *testing.T) {
+	rand.Seed(time.Now().Unix())
+	g := NewGauge()
+	wg := &sync.WaitGroup{}
+	reps := 100
+	for i := 0; i < reps; i++ {
+		wg.Add(1)
+		go func(g Gauge, wg *sync.WaitGroup) {
+			g.Update(rand.Int63())
+			wg.Done()
+		}(g, wg)
+	}
+	wg.Wait()
+}
+
 func TestGauge(t *testing.T) {
 	g := NewGauge()
 	g.Update(int64(47))
diff --git a/log.go b/log.go
index f8074c0..2614a0a 100644
--- a/log.go
+++ b/log.go
@@ -8,17 +8,37 @@ type Logger interface {
 	Printf(format string, v ...interface{})
 }
 
+// Log outputs each metric in the given registry periodically using the given logger.
 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
+// LogOnCue outputs each metric in the given registry on demand through the channel
+// using the given logger
+func LogOnCue(r Registry, ch chan interface{}, l Logger) {
+	LogScaledOnCue(r, ch, time.Nanosecond, l)
+}
+
+// LogScaled outputs each metric in the given registry periodically using the given
 // logger. Print timings in `scale` units (eg time.Millisecond) rather than nanos.
 func LogScaled(r Registry, freq time.Duration, scale time.Duration, l Logger) {
+	ch := make(chan interface{})
+	go func(channel chan interface{}) {
+		for _ = range time.Tick(freq) {
+			channel <- struct{}{}
+		}
+	}(ch)
+	LogScaledOnCue(r, ch, scale, l)
+}
+
+// LogScaledOnCue outputs each metric in the given registry on demand through the channel
+// using the given logger. Print timings in `scale` units (eg time.Millisecond) rather
+// than nanos.
+func LogScaledOnCue(r Registry, ch chan interface{}, scale time.Duration, l Logger) {
 	du := float64(scale)
 	duSuffix := scale.String()[1:]
 
-	for _ = range time.Tick(freq) {
+	for _ = range ch {
 		r.Each(func(name string, i interface{}) {
 			switch metric := i.(type) {
 			case Counter:
diff --git a/meter.go b/meter.go
index 53ff329..223669b 100644
--- a/meter.go
+++ b/meter.go
@@ -1,7 +1,9 @@
 package metrics
 
 import (
+	"math"
 	"sync"
+	"sync/atomic"
 	"time"
 )
 
@@ -62,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.
@@ -75,19 +77,19 @@ 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 }
@@ -124,11 +126,10 @@ func (NilMeter) Stop() {}
 
 // StandardMeter is the standard implementation of a Meter.
 type StandardMeter struct {
-	lock        sync.RWMutex
 	snapshot    *MeterSnapshot
 	a1, a5, a15 EWMA
 	startTime   time.Time
-	stopped     bool
+	stopped     uint32
 }
 
 func newStandardMeter() *StandardMeter {
@@ -143,11 +144,7 @@ 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 = true
-	m.lock.Unlock()
-	if !stopped {
+	if atomic.CompareAndSwapUint32(&m.stopped, 0, 1) {
 		arbiter.Lock()
 		delete(arbiter.meters, m)
 		arbiter.Unlock()
@@ -156,20 +153,17 @@ func (m *StandardMeter) Stop() {
 
 // 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()
-	if m.stopped {
+	if atomic.LoadUint32(&m.stopped) == 1 {
 		return
 	}
-	m.snapshot.count += n
+
+	atomic.AddInt64(&m.snapshot.count, n)
+
 	m.a1.Update(n)
 	m.a5.Update(n)
 	m.a15.Update(n)
@@ -178,56 +172,49 @@ 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()
diff --git a/meter_test.go b/meter_test.go
index e889222..ecef37d 100644
--- a/meter_test.go
+++ b/meter_test.go
@@ -1,6 +1,8 @@
 package metrics
 
 import (
+	"math/rand"
+	"sync"
 	"testing"
 	"time"
 )
@@ -13,6 +15,43 @@ func BenchmarkMeter(b *testing.B) {
 	}
 }
 
+func BenchmarkMeterParallel(b *testing.B) {
+	m := NewMeter()
+	b.ResetTimer()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			m.Mark(1)
+		}
+	})
+}
+
+// exercise race detector
+func TestMeterConcurrency(t *testing.T) {
+	rand.Seed(time.Now().Unix())
+	ma := meterArbiter{
+		ticker: time.NewTicker(time.Millisecond),
+		meters: make(map[*StandardMeter]struct{}),
+	}
+	m := newStandardMeter()
+	ma.meters[m] = struct{}{}
+	go ma.tick()
+	wg := &sync.WaitGroup{}
+	reps := 100
+	for i := 0; i < reps; i++ {
+		wg.Add(1)
+		go func(m Meter, wg *sync.WaitGroup) {
+			m.Mark(1)
+			wg.Done()
+		}(m, wg)
+		wg.Add(1)
+		go func(m Meter, wg *sync.WaitGroup) {
+			m.Stop()
+			wg.Done()
+		}(m, wg)
+	}
+	wg.Wait()
+}
+
 func TestGetOrRegisterMeter(t *testing.T) {
 	r := NewRegistry()
 	NewRegisteredMeter("foo", r).Mark(47)
diff --git a/registry.go b/registry.go
index 6c0007b..a8e6722 100644
--- a/registry.go
+++ b/registry.go
@@ -54,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.
@@ -64,15 +64,17 @@ func NewRegistry() Registry {
 
 // Call the given function for each registered metric.
 func (r *StandardRegistry) Each(f func(string, interface{})) {
-	for name, i := range r.registered() {
-		f(name, i)
+	metrics := r.registered()
+	for i := range metrics {
+		kv := &metrics[i]
+		f(kv.name, kv.value)
 	}
 }
 
 // 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]
 }
 
@@ -81,6 +83,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 {
@@ -103,8 +114,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()
@@ -202,12 +213,20 @@ func (r *StandardRegistry) register(name string, i interface{}) error {
 	return nil
 }
 
-func (r *StandardRegistry) registered() map[string]interface{} {
-	r.mutex.Lock()
-	defer r.mutex.Unlock()
-	metrics := make(map[string]interface{}, len(r.metrics))
+type metricKV struct {
+	name  string
+	value interface{}
+}
+
+func (r *StandardRegistry) registered() []metricKV {
+	r.mutex.RLock()
+	defer r.mutex.RUnlock()
+	metrics := make([]metricKV, 0, len(r.metrics))
 	for name, i := range r.metrics {
-		metrics[name] = i
+		metrics = append(metrics, metricKV{
+			name:  name,
+			value: i,
+		})
 	}
 	return metrics
 }
diff --git a/registry_test.go b/registry_test.go
index a63e485..c3b0855 100644
--- a/registry_test.go
+++ b/registry_test.go
@@ -1,6 +1,8 @@
 package metrics
 
 import (
+	"fmt"
+	"sync"
 	"testing"
 )
 
@@ -13,6 +15,31 @@ func BenchmarkRegistry(b *testing.B) {
 	}
 }
 
+func BenchmarkHugeRegistry(b *testing.B) {
+	r := NewRegistry()
+	for i := 0; i < 10000; i++ {
+		r.Register(fmt.Sprintf("foo%07d", i), NewCounter())
+	}
+	v := make([]string, 10000)
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		v := v[:0]
+		r.Each(func(k string, _ interface{}) {
+			v = append(v, k)
+		})
+	}
+}
+
+func BenchmarkRegistryParallel(b *testing.B) {
+	r := NewRegistry()
+	b.ResetTimer()
+	b.RunParallel(func(pb *testing.PB) {
+		for pb.Next() {
+			r.GetOrRegister("foo", NewCounter())
+		}
+	})
+}
+
 func TestRegistry(t *testing.T) {
 	r := NewRegistry()
 	r.Register("foo", NewCounter())
@@ -301,5 +328,66 @@ func TestWalkRegistries(t *testing.T) {
 	if "prefix.prefix2." != prefix {
 		t.Fatal(prefix)
 	}
+}
+
+func TestConcurrentRegistryAccess(t *testing.T) {
+	r := NewRegistry()
+
+	counter := NewCounter()
+
+	signalChan := make(chan struct{})
+
+	var wg sync.WaitGroup
+	for i := 0; i < 10; i++ {
+		wg.Add(1)
+		go func(dowork chan struct{}) {
+			defer wg.Done()
+			iface := r.GetOrRegister("foo", counter)
+			retCounter, ok := iface.(Counter)
+			if !ok {
+				t.Fatal("Expected a Counter type")
+			}
+			if retCounter != counter {
+				t.Fatal("Counter references don't match")
+			}
+		}(signalChan)
+	}
+
+	close(signalChan) // Closing will cause all go routines to execute at the same time
+	wg.Wait()         // Wait for all go routines to do their work
+
+	// At the end of the test we should still only have a single "foo" Counter
+	i := 0
+	r.Each(func(name string, iface interface{}) {
+		i++
+		if "foo" != name {
+			t.Fatal(name)
+		}
+		if _, ok := iface.(Counter); !ok {
+			t.Fatal(iface)
+		}
+	})
+	if 1 != i {
+		t.Fatal(i)
+	}
+	r.Unregister("foo")
+	i = 0
+	r.Each(func(string, interface{}) { i++ })
+	if 0 != i {
+		t.Fatal(i)
+	}
+}
 
+// exercise race detector
+func TestRegisterAndRegisteredConcurrency(t *testing.T) {
+	r := NewRegistry()
+	wg := &sync.WaitGroup{}
+	wg.Add(1)
+	go func(r Registry, wg *sync.WaitGroup) {
+		defer wg.Done()
+		r.Each(func(name string, iface interface{}) {
+		})
+	}(r, wg)
+	r.Register("foo", NewCounter())
+	wg.Wait()
 }
diff --git a/runtime.go b/runtime.go
index 11c6b78..4047ab3 100644
--- a/runtime.go
+++ b/runtime.go
@@ -3,6 +3,7 @@ package metrics
 import (
 	"runtime"
 	"runtime/pprof"
+	"sync"
 	"time"
 )
 
@@ -49,7 +50,8 @@ var (
 	numGC       uint32
 	numCgoCalls int64
 
-	threadCreateProfile = pprof.Lookup("threadcreate")
+	threadCreateProfile        = pprof.Lookup("threadcreate")
+	registerRuntimeMetricsOnce = sync.Once{}
 )
 
 // Capture new values for the Go runtime statistics exported in
@@ -146,67 +148,69 @@ func CaptureRuntimeMemStatsOnce(r Registry) {
 // specifically runtime.MemStats.  The runtimeMetrics are named by their
 // fully-qualified Go symbols, i.e. runtime.MemStats.Alloc.
 func RegisterRuntimeMemStats(r Registry) {
-	runtimeMetrics.MemStats.Alloc = NewGauge()
-	runtimeMetrics.MemStats.BuckHashSys = NewGauge()
-	runtimeMetrics.MemStats.DebugGC = NewGauge()
-	runtimeMetrics.MemStats.EnableGC = NewGauge()
-	runtimeMetrics.MemStats.Frees = NewGauge()
-	runtimeMetrics.MemStats.HeapAlloc = NewGauge()
-	runtimeMetrics.MemStats.HeapIdle = NewGauge()
-	runtimeMetrics.MemStats.HeapInuse = NewGauge()
-	runtimeMetrics.MemStats.HeapObjects = NewGauge()
-	runtimeMetrics.MemStats.HeapReleased = NewGauge()
-	runtimeMetrics.MemStats.HeapSys = NewGauge()
-	runtimeMetrics.MemStats.LastGC = NewGauge()
-	runtimeMetrics.MemStats.Lookups = NewGauge()
-	runtimeMetrics.MemStats.Mallocs = NewGauge()
-	runtimeMetrics.MemStats.MCacheInuse = NewGauge()
-	runtimeMetrics.MemStats.MCacheSys = NewGauge()
-	runtimeMetrics.MemStats.MSpanInuse = NewGauge()
-	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()
-	runtimeMetrics.MemStats.StackSys = NewGauge()
-	runtimeMetrics.MemStats.Sys = NewGauge()
-	runtimeMetrics.MemStats.TotalAlloc = NewGauge()
-	runtimeMetrics.NumCgoCall = NewGauge()
-	runtimeMetrics.NumGoroutine = NewGauge()
-	runtimeMetrics.NumThread = NewGauge()
-	runtimeMetrics.ReadMemStats = NewTimer()
+	registerRuntimeMetricsOnce.Do(func() {
+		runtimeMetrics.MemStats.Alloc = NewGauge()
+		runtimeMetrics.MemStats.BuckHashSys = NewGauge()
+		runtimeMetrics.MemStats.DebugGC = NewGauge()
+		runtimeMetrics.MemStats.EnableGC = NewGauge()
+		runtimeMetrics.MemStats.Frees = NewGauge()
+		runtimeMetrics.MemStats.HeapAlloc = NewGauge()
+		runtimeMetrics.MemStats.HeapIdle = NewGauge()
+		runtimeMetrics.MemStats.HeapInuse = NewGauge()
+		runtimeMetrics.MemStats.HeapObjects = NewGauge()
+		runtimeMetrics.MemStats.HeapReleased = NewGauge()
+		runtimeMetrics.MemStats.HeapSys = NewGauge()
+		runtimeMetrics.MemStats.LastGC = NewGauge()
+		runtimeMetrics.MemStats.Lookups = NewGauge()
+		runtimeMetrics.MemStats.Mallocs = NewGauge()
+		runtimeMetrics.MemStats.MCacheInuse = NewGauge()
+		runtimeMetrics.MemStats.MCacheSys = NewGauge()
+		runtimeMetrics.MemStats.MSpanInuse = NewGauge()
+		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()
+		runtimeMetrics.MemStats.StackSys = NewGauge()
+		runtimeMetrics.MemStats.Sys = NewGauge()
+		runtimeMetrics.MemStats.TotalAlloc = NewGauge()
+		runtimeMetrics.NumCgoCall = NewGauge()
+		runtimeMetrics.NumGoroutine = NewGauge()
+		runtimeMetrics.NumThread = NewGauge()
+		runtimeMetrics.ReadMemStats = NewTimer()
 
-	r.Register("runtime.MemStats.Alloc", runtimeMetrics.MemStats.Alloc)
-	r.Register("runtime.MemStats.BuckHashSys", runtimeMetrics.MemStats.BuckHashSys)
-	r.Register("runtime.MemStats.DebugGC", runtimeMetrics.MemStats.DebugGC)
-	r.Register("runtime.MemStats.EnableGC", runtimeMetrics.MemStats.EnableGC)
-	r.Register("runtime.MemStats.Frees", runtimeMetrics.MemStats.Frees)
-	r.Register("runtime.MemStats.HeapAlloc", runtimeMetrics.MemStats.HeapAlloc)
-	r.Register("runtime.MemStats.HeapIdle", runtimeMetrics.MemStats.HeapIdle)
-	r.Register("runtime.MemStats.HeapInuse", runtimeMetrics.MemStats.HeapInuse)
-	r.Register("runtime.MemStats.HeapObjects", runtimeMetrics.MemStats.HeapObjects)
-	r.Register("runtime.MemStats.HeapReleased", runtimeMetrics.MemStats.HeapReleased)
-	r.Register("runtime.MemStats.HeapSys", runtimeMetrics.MemStats.HeapSys)
-	r.Register("runtime.MemStats.LastGC", runtimeMetrics.MemStats.LastGC)
-	r.Register("runtime.MemStats.Lookups", runtimeMetrics.MemStats.Lookups)
-	r.Register("runtime.MemStats.Mallocs", runtimeMetrics.MemStats.Mallocs)
-	r.Register("runtime.MemStats.MCacheInuse", runtimeMetrics.MemStats.MCacheInuse)
-	r.Register("runtime.MemStats.MCacheSys", runtimeMetrics.MemStats.MCacheSys)
-	r.Register("runtime.MemStats.MSpanInuse", runtimeMetrics.MemStats.MSpanInuse)
-	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)
-	r.Register("runtime.MemStats.StackSys", runtimeMetrics.MemStats.StackSys)
-	r.Register("runtime.MemStats.Sys", runtimeMetrics.MemStats.Sys)
-	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)
+		r.Register("runtime.MemStats.Alloc", runtimeMetrics.MemStats.Alloc)
+		r.Register("runtime.MemStats.BuckHashSys", runtimeMetrics.MemStats.BuckHashSys)
+		r.Register("runtime.MemStats.DebugGC", runtimeMetrics.MemStats.DebugGC)
+		r.Register("runtime.MemStats.EnableGC", runtimeMetrics.MemStats.EnableGC)
+		r.Register("runtime.MemStats.Frees", runtimeMetrics.MemStats.Frees)
+		r.Register("runtime.MemStats.HeapAlloc", runtimeMetrics.MemStats.HeapAlloc)
+		r.Register("runtime.MemStats.HeapIdle", runtimeMetrics.MemStats.HeapIdle)
+		r.Register("runtime.MemStats.HeapInuse", runtimeMetrics.MemStats.HeapInuse)
+		r.Register("runtime.MemStats.HeapObjects", runtimeMetrics.MemStats.HeapObjects)
+		r.Register("runtime.MemStats.HeapReleased", runtimeMetrics.MemStats.HeapReleased)
+		r.Register("runtime.MemStats.HeapSys", runtimeMetrics.MemStats.HeapSys)
+		r.Register("runtime.MemStats.LastGC", runtimeMetrics.MemStats.LastGC)
+		r.Register("runtime.MemStats.Lookups", runtimeMetrics.MemStats.Lookups)
+		r.Register("runtime.MemStats.Mallocs", runtimeMetrics.MemStats.Mallocs)
+		r.Register("runtime.MemStats.MCacheInuse", runtimeMetrics.MemStats.MCacheInuse)
+		r.Register("runtime.MemStats.MCacheSys", runtimeMetrics.MemStats.MCacheSys)
+		r.Register("runtime.MemStats.MSpanInuse", runtimeMetrics.MemStats.MSpanInuse)
+		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)
+		r.Register("runtime.MemStats.StackSys", runtimeMetrics.MemStats.StackSys)
+		r.Register("runtime.MemStats.Sys", runtimeMetrics.MemStats.Sys)
+		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/runtime_test.go b/runtime_test.go
index ebbfd50..46a09e5 100644
--- a/runtime_test.go
+++ b/runtime_test.go
@@ -6,6 +6,29 @@ import (
 	"time"
 )
 
+func TestRuntimeMemStatsDoubleRegister(t *testing.T) {
+	r := NewRegistry()
+	RegisterRuntimeMemStats(r)
+	storedGauge := r.Get("runtime.MemStats.LastGC").(Gauge)
+
+	runtime.GC()
+	CaptureRuntimeMemStatsOnce(r)
+
+	firstGC := storedGauge.Value()
+	if 0 == firstGC {
+		t.Errorf("firstGC got %d, expected timestamp > 0", firstGC)
+	}
+
+	time.Sleep(time.Millisecond)
+
+	RegisterRuntimeMemStats(r)
+	runtime.GC()
+	CaptureRuntimeMemStatsOnce(r)
+	if lastGC := storedGauge.Value(); firstGC == lastGC {
+		t.Errorf("lastGC got %d, expected a higher timestamp value", lastGC)
+	}
+}
+
 func BenchmarkRuntimeMemStats(b *testing.B) {
 	r := NewRegistry()
 	RegisterRuntimeMemStats(r)

Debdiff

File lists identical (after any substitutions)

No differences were encountered in the control files

More details

Full run details