Skip to content

Commit

Permalink
Remove locks from the hot-path in meter.go and add parallel benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
Ralph Caraveo committed Apr 5, 2018
1 parent b08b742 commit 9073533
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 38 deletions.
69 changes: 31 additions & 38 deletions meter.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package metrics

import (
"math"
"sync"
"sync/atomic"
"time"
)

Expand Down Expand Up @@ -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.
Expand All @@ -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 }
Expand Down Expand Up @@ -124,7 +126,8 @@ 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
Expand Down Expand Up @@ -156,10 +159,7 @@ 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.
Expand All @@ -178,56 +178,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()
Expand Down
10 changes: 10 additions & 0 deletions meter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ 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)
}
})
}

func TestGetOrRegisterMeter(t *testing.T) {
r := NewRegistry()
NewRegisteredMeter("foo", r).Mark(47)
Expand Down

0 comments on commit 9073533

Please sign in to comment.