-
Notifications
You must be signed in to change notification settings - Fork 78
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
internal/prometheusbp: Add HighWatermarkValue and HighWatermarkGauge #554
Conversation
prometheusbp/high_watermark_gauge.go
Outdated
|
||
// HighWatermarkGauge implements an int64 gauge with high watermark value. | ||
type HighWatermarkGauge struct { | ||
lock sync.RWMutex |
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.
benchmark result with RWMutex
:
goos: linux
goarch: amd64
pkg: github.com/reddit/baseplate.go/prometheusbp
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkHighWatermarkGauge-12 20724597 55.75 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/reddit/baseplate.go/prometheusbp 1.221s
benchmark result if we use Mutex
instead:
goos: linux
goarch: amd64
pkg: github.com/reddit/baseplate.go/prometheusbp
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkHighWatermarkGauge-12 12081015 103.0 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/reddit/baseplate.go/prometheusbp 1.351s
e34e775
to
1004bac
Compare
prometheusbp/high_watermark.go
Outdated
} | ||
|
||
// Inc increases the gauge value by 1. | ||
func (hwv *HighWatermarkValue) Inc() { |
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 think this should have more of the methods from Gauge:
- Inc/Dec
- Add/Sub
- Set
(I don't really care about SetToCurrentTime)
prometheusbp/high_watermark.go
Outdated
"github.com/prometheus/client_golang/prometheus" | ||
) | ||
|
||
// HighWatermarkValue implements an int64 gauge with high watermark value. |
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 think this exposes too much of this API surface and makes it too hard to use.
I would use prometheus.Histogram as a guide here. It's exposed as a single API but when collected it produces more than one metric with a fixed naming scheme.
Expose:
func NewHighWaterMarkGauge(HighWatermarkGaugeArgs) *HighWatermarkGauge
func NewHighWaterMarkGaugeVec(HighWatermarkGaugeArgs, labels) *HighWatermarkGaugeVec
type HighWatermarkGaugeVec
- With / WithLabels
type HighWatermarkGauge
- Add / Sub / Set
- Inc / Dec
- Collect
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 strongly prefer to separate the API for the value and api for the actual prometheus metrics (the 2 gauges). There's no guarantee that we would always define the same set of labels for the 2 gauges, so an API like prometheus.Histogram
can be very brittle.
As for Add
/Sub
, I just don't really see a use case for them yet. If we do have the use case for them later we can add them when then.
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.
There's no guarantee that we would always define the same set of labels for the 2 gauges
I would approach this from the other side. Why would we not want them to have the same labels? Unlike a histogram where you may want a counter with additional labels, we're not talking about exploding cardinality here, and it would be pretty confusing to have different labels for the instant and high watermark metrics.
prometheusbp/high_watermark.go
Outdated
|
||
// HighWatermarkGauge implements a prometheus.Collector that reports up to 2 | ||
// gauges backed by a HighWatermarkValue. | ||
type HighWatermarkGauge struct { |
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.
Constructor to help making one of these? This looks like it would be a beast to construct and I feel like there are a lot of implicit / undocumented assumptions this is making that might trip users up. These types (prometheus.Desc) also are probably not super obvious for users how to construct.
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.
So the actual gauge api is mostly just a sugar for the common use cases we have in the spec implementations. It's a very thin wrapper around the value api that's trivial to implement, so I initially did not include this part, but then thought that it's probably better to save some repetitions.
What do you think if I move the gauge api to internal
and only leave the value api as public?
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 think the valuable thing to export is an easy-to-use API. If this is just used for internal things, then maybe we don't expose any of it.
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.
ok moved both to internal.
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.
RC'ing to appease harold.
b.ResetTimer() | ||
b.RunParallel(func(pb *testing.PB) { | ||
for pb.Next() { | ||
n := int(atomic.AddInt64(&counter, 1)) % len(operations) |
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 feel like having a goroutine-local counter in a var just above the for pb.next loop will work fine -- they don't need to rotate through the operations in lockstep for it to make sense. That will remove the atomic overhead, which is non-negligible when you're benchmarking a mutex
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.
good point. will do.
Benchmark result: $ go test -bench . goos: linux goarch: amd64 pkg: github.com/reddit/baseplate.go/internal/prometheusbp cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz BenchmarkHighWatermarkValue-12 24221392 48.11 ns/op 0 B/op 0 allocs/op PASS ok github.com/reddit/baseplate.go/internal/prometheusbp 1.221s
5b13bae
to
e56de02
Compare
b.ResetTimer() | ||
b.RunParallel(func(pb *testing.PB) { | ||
for pb.Next() { | ||
n := int(atomic.AddInt64(&counter, 1)) % len(operations) |
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.
good point. will do.
|
||
// HighWatermarkValue implements an int64 gauge with high watermark value. | ||
type HighWatermarkValue struct { | ||
lock sync.RWMutex |
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.
Benchmark result with RWMutex
:
$ go test -bench .
goos: linux
goarch: amd64
pkg: github.com/reddit/baseplate.go/internal/prometheusbp
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkHighWatermarkValue-12 25154269 47.51 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/reddit/baseplate.go/internal/prometheusbp 1.250s
benchmark result with Mutex
:
$ git diff
diff --git a/internal/prometheusbp/high_watermark.go b/internal/prometheusbp/high_watermark.go
index ccfa193..6f12a0e 100644
--- a/internal/prometheusbp/high_watermark.go
+++ b/internal/prometheusbp/high_watermark.go
@@ -8,7 +8,7 @@ import (
// HighWatermarkValue implements an int64 gauge with high watermark value.
type HighWatermarkValue struct {
- lock sync.RWMutex
+ lock sync.Mutex
curr int64
max int64
}
@@ -45,23 +45,23 @@ func (hwv *HighWatermarkValue) Set(v int64) {
// Get gets the current gauge value.
func (hwv *HighWatermarkValue) Get() int64 {
- hwv.lock.RLock()
- defer hwv.lock.RUnlock()
+ hwv.lock.Lock()
+ defer hwv.lock.Unlock()
return hwv.curr
}
// Max returns the max gauge value (the high watermark).
func (hwv *HighWatermarkValue) Max() int64 {
- hwv.lock.RLock()
- defer hwv.lock.RUnlock()
+ hwv.lock.Lock()
+ defer hwv.lock.Unlock()
return hwv.max
}
func (hwv *HighWatermarkValue) getBoth() (curr, max int64) {
- hwv.lock.RLock()
- defer hwv.lock.RUnlock()
+ hwv.lock.Lock()
+ defer hwv.lock.Unlock()
return hwv.curr, hwv.max
}
$ go test -bench .
goos: linux
goarch: amd64
pkg: github.com/reddit/baseplate.go/internal/prometheusbp
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkHighWatermarkValue-12 15068430 76.76 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/reddit/baseplate.go/internal/prometheusbp 1.244s
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.
Could we add some comments in code or the git commit that explains what a high watermark value is?
@@ -0,0 +1,3 @@ | |||
// Package prometheusbp provides common code used by Baseplate.go regarding |
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.
// Package prometheusbp provides common code used by Baseplate.go regarding | |
// Package prometheusbp provides common code used by baseplate.go regarding |
🔕 is the official name all lowercase?
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.
it's not :)
|
||
// HighWatermarkValue implements an int64 gauge with high watermark value. | ||
type HighWatermarkValue struct { | ||
lock sync.RWMutex |
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.
lock sync.RWMutex | |
mu sync.RWMutex |
Is mu to refer to the lock more common?
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.
why do you want to use mu to refer to lock instead of using lock to refer to lock though :)
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.
For no good reason other than convention. I see it mostly being called mu
in Go https://go.dev/tour/concurrency/9
Maybe because lock.RLock()
reads a bit repetitive 🤷
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.
lock
is noun and Lock
/RLock
is verb :)
Benchmark result:
cc @nanassito