-
Notifications
You must be signed in to change notification settings - Fork 112
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 CachedReporter interface that supports pre allocation. #22
Conversation
3 similar comments
// CachedCapabilities returns a description of metrics reporting capabilities | ||
CachedCapabilities() Capabilities | ||
|
||
// CachedFlush is expected to be called by a Scope when it completes a round or reporting |
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.
Might be worth fixing the doc string here, the "or" is meant to be "of". Worth fixing on line 39 too I should think.
I think it might be worth renaming CachedCapabilities
and CachedFlush
to just Capabilities
and Flush
and then creating a new internal only interface:
type baseStatsReporter interface {
Capabilities() Capabilities()
Flush()
}
That way you can add a field to the scope:
reporter StatsReporter // existing
cachedReporter CachedStatsReporter // existing
baseReporter baseStatsReporter // new
And when calling capabilities or flush:
// capabilities
s.baseReporter.Capabilities()
// flush
s.baseReporter.Flush()
Just to avoid the if/elses there.
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.
Fixed. However this means that the Flush()
method name classes and a single struct can no longer implement both StatsReporter and CachedStatsReporter, but that's probably a good thing.
@@ -305,6 +376,9 @@ func (s *scope) Close() error { | |||
if closer, ok := s.reporter.(io.Closer); ok { | |||
return closer.Close() | |||
} | |||
if closer, ok := s.cachedReporter.(io.Closer); ok { | |||
return closer.Close() | |||
} |
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.
If we were using the baseStatsReporter approach this could also just be:
if closer, ok := s.baseReporter.(io.Closer); ok {
return closer.Close()
}
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.
Fixed.
if prev == curr { | ||
return | ||
} | ||
atomic.StoreInt64(&c.prev, curr) |
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.
Might be best to avoid repeating this code by adding a new method that takes the value?
func (c *counter) value() int64 {
curr := atomic.LoadInt64(&c.curr)
prev := atomic.LoadInt64(&c.prev)
if prev == curr {
return 0
}
atomic.StoreInt64(&c.prev, curr)
return curr - prev
}
Then we don't have to repeat this code twice.
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.
Fixed.
func (g *gauge) cachedReport() { | ||
if atomic.SwapUint64(&g.updated, 0) == 1 { | ||
g.cachedGauge.ReportGauge( | ||
math.Float64frombits(atomic.LoadUint64(&g.curr)), |
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.
Maybe same for the gauge, something that just performs the math.Float64frombits(atomic.LoadUint64(&g.curr))
. The compiler will likely inline it so will stay fast but is more concise to read.
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.
Fixed.
t.cachedTimer.ReportTimer(interval) | ||
} else { | ||
t.reporter.ReportTimer(t.name, t.tags, interval) | ||
} |
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.
Might be worth binding this method to avoid the if/else jump at Record
call time:
type timer struct {
// ...
reportFn func(interval time.Duration)
}
func newTimer(/*...*/) {
t := &timer{/*...*/}
if cachedTimer != nil {
t.reportFn = t.cacheTimer.ReportTimer
} else {
t.reportFn = t.report
}
}
func (t *timer) Record(interval time.Duration) {
t.reportFn(interval)
}
func (t *timer) report(interval time.Duration) {
t.reporter.ReportTimer(t.name, t.tags, interval)
}
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 tried this.
raynos at raynos-ThinkPad-T440p
~/gocode/src/github.com/uber-go/tally on cached-reporter*
$ go test -bench BenchmarkTimerReport -run _NONE_ -cpuprofile cpuprofile.out -benchmem -benchtime 7s -cpu 2 .
BenchmarkTimerReport-2 2000000000 6.02 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/uber-go/tally 12.633s
raynos at raynos-ThinkPad-T440p
~/gocode/src/github.com/uber-go/tally on cached-reporter*
$ go test -bench BenchmarkTimerReport -run _NONE_ -cpuprofile cpuprofile.out -benchmem -benchtime 7s -cpu 2 .
BenchmarkTimerReport-2 1000000000 9.24 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/uber-go/tally 10.196s
It looks like the inline if statement is faster then the bound function call. Adding an extra function call takes 9/10ns instead of 5/6ns.
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.
Interesting, sounds good. Thanks for the breakdown 👍
if s.cachedReporter != nil { | ||
return s.cachedReporter.CachedCapabilities() | ||
} | ||
return capabilitiesNone |
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.
If we were using the baseStatsReporter approach this could also just be:
if s.baseReporter != nil [
return s.baseReporter.Capabilities()
}
return capabilitiesNone
@@ -22,8 +22,15 @@ package tally | |||
|
|||
import "time" | |||
|
|||
type baseStatsReporter interface { |
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.
Might be worth making this an exported interface on second thoughts since its strange to export an interface that extends a non-exported defined interface.
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.
Fixed.
Other than final comment LGTM |
After I land this we should cut a proper version and use glide versioning in tally. Since I'm going to be using uber-go/tally in prod, I'm happy to cut 1.0.0 and lock down the interface ;) Alternatively we can version it as 0.1.0 if there are known things that need to change. |
3 similar comments
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.
LGTM - @Raynos sounds good, I'm fine with cutting 1.0.0
We do need to add native histogram types soon but we can make that a 2.0.0 release.
Ok, will wait until monday for landing, to catch more reviewers. |
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.
LGTM
I've merged this and then started using semantic versioning in this repository, starting at v1.0.0 |
This PR adds a new method
NewCachedRootScope
and a newCachedReporter
interface.This
CachedReporter
interface allows for onetime allocationof counters, gauges & timers. This allows for the CachedReporter
to cached the tags map and do pre-computation.
The actual runtime interface after all the scopes & metrics are
allocated at startup just involves
ReportCount(int64)
and friends.This enables a reporter backend to implement a more involved BUT
more performant interface.
I've got a benchmark using an internal reporter where I was measuring the
overhead of adding 3 timers to a HTTP server.
This is a 2x improvement and allows us to have more timers in our
applications.
r: @robskillington @martin-mao
cc: @Matt-Esch @zhenghuiwang