-
Notifications
You must be signed in to change notification settings - Fork 491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add Stop() to Meter #197
add Stop() to Meter #197
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ type Meter interface { | |
Rate15() float64 | ||
RateMean() float64 | ||
Snapshot() Meter | ||
Stop() | ||
} | ||
|
||
// GetOrRegisterMeter returns an existing Meter or constructs and registers a | ||
|
@@ -34,7 +35,8 @@ func NewMeter() Meter { | |
m := newStandardMeter() | ||
arbiter.Lock() | ||
defer arbiter.Unlock() | ||
arbiter.meters = append(arbiter.meters, m) | ||
m.id = arbiter.newID() | ||
arbiter.meters[m.id] = m | ||
if !arbiter.started { | ||
arbiter.started = true | ||
go arbiter.tick() | ||
|
@@ -86,6 +88,9 @@ func (m *MeterSnapshot) RateMean() float64 { return 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 +115,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 | ||
snapshot *MeterSnapshot | ||
a1, a5, a15 EWMA | ||
startTime time.Time | ||
stopped bool | ||
id int64 | ||
} | ||
|
||
func newStandardMeter() *StandardMeter { | ||
|
@@ -128,6 +138,14 @@ func newStandardMeter() *StandardMeter { | |
} | ||
} | ||
|
||
// Stop stops the meter, Mark() will panic if you use it after being stopped. | ||
func (m *StandardMeter) Stop() { | ||
arbiter.Lock() | ||
defer arbiter.Unlock() | ||
m.stopped = true | ||
delete(arbiter.meters, m.id) | ||
} | ||
|
||
// Count returns the number of events recorded. | ||
func (m *StandardMeter) Count() int64 { | ||
m.lock.RLock() | ||
|
@@ -140,6 +158,9 @@ func (m *StandardMeter) Count() int64 { | |
func (m *StandardMeter) Mark(n int64) { | ||
m.lock.Lock() | ||
defer m.lock.Unlock() | ||
if m.stopped { | ||
panic("Mark called on a stopped Meter") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should really return error instead of panicking? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and change the interface for that? That's like the Mark() on a snapshot which also panics There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Up until now there is no panic on anything else except snapshots and the like, which are unconditional panics. If we do this here, extreme caution must be taken while doing unregister+Mark concurrently. Being able to safely ignore (no-op) this would be better IMHO |
||
} | ||
m.snapshot.count += n | ||
m.a1.Update(n) | ||
m.a5.Update(n) | ||
|
@@ -208,11 +229,12 @@ func (m *StandardMeter) tick() { | |
type meterArbiter struct { | ||
sync.RWMutex | ||
started bool | ||
meters []*StandardMeter | ||
meters map[int64]*StandardMeter | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about using a set of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think that would be a better solution than adding the ID generation |
||
ticker *time.Ticker | ||
id int64 | ||
} | ||
|
||
var arbiter = meterArbiter{ticker: time.NewTicker(5e9)} | ||
var arbiter = meterArbiter{ticker: time.NewTicker(5e9), meters: make(map[int64]*StandardMeter)} | ||
|
||
// Ticks meters on the scheduled interval | ||
func (ma *meterArbiter) tick() { | ||
|
@@ -224,6 +246,12 @@ func (ma *meterArbiter) tick() { | |
} | ||
} | ||
|
||
// should only be called with Lock() held | ||
func (ma *meterArbiter) newID() int64 { | ||
ma.id++ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Someone, somewhere will overflow this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That also made me a bit concerned. |
||
return ma.id | ||
} | ||
|
||
func (ma *meterArbiter) tickMeters() { | ||
ma.RLock() | ||
defer ma.RUnlock() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add the same method on
Timer
as they rely on aMeter
under the hood.I am even wondering if having an interface
Stoppable
implemented byTimer
andMeter
and invokingStop
inRegistry.Unregister(string)
when the metric implements such an interface is the proper way to clean those metrics that can leak (potentially supporting similar behavior for custom metrics).What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rephrasing just so I understand: you're suggesting we add the interface and implementations, then leave it up to the user to call
Unregister
when a metric has outlived its usefulness, correct? I suppose there's not really a magical way we can know when it's safe to do so internally.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mihasya Absolutely.
Unregister
(andUnregisterAll
by extension) would check if the underlyinginterface{}
metric implementsStoppable
and callStop()
as like you said there is no way to know when those metrics are not used anymore.