aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/mprof.go
diff options
context:
space:
mode:
authorFelix Geisendörfer <felix.geisendoerfer@datadoghq.com>2021-02-26 14:41:19 +0100
committerMichael Pratt <mpratt@google.com>2021-04-27 18:54:49 +0000
commit0ae9c3b98bf01862f11d764ee379bebc29a76431 (patch)
tree519de0afcb3e0158524171286d5e31a6729ea24d /src/runtime/mprof.go
parent8e0023b81b81352c1f8ea5cd58eea91939924f9d (diff)
downloadgo-0ae9c3b98bf01862f11d764ee379bebc29a76431.tar.gz
go-0ae9c3b98bf01862f11d764ee379bebc29a76431.zip
runtime/pprof: fix block profile bias
Block profiles were biased towards infrequent long events over frequent short events. This fix corrects the bias by aggregating shorter events as longer but less frequent in the profiles. As a result their cumulative duration will be accurately represented in the profile without skewing their sample mean (duration/count). Credit to @dvyukov for suggesting to adjust the count in the saveblockevent function. Fixes #44192. Change-Id: I71a99d7f6ebdb2d484d44890a2517863cceb4004 Reviewed-on: https://go-review.googlesource.com/c/go/+/299991 Trust: Michael Pratt <mpratt@google.com> Trust: Ian Lance Taylor <iant@golang.org> Run-TryBot: Michael Pratt <mpratt@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Michael Pratt <mpratt@google.com>
Diffstat (limited to 'src/runtime/mprof.go')
-rw-r--r--src/runtime/mprof.go35
1 files changed, 25 insertions, 10 deletions
diff --git a/src/runtime/mprof.go b/src/runtime/mprof.go
index 1156329615..5235b898e4 100644
--- a/src/runtime/mprof.go
+++ b/src/runtime/mprof.go
@@ -133,7 +133,7 @@ func (a *memRecordCycle) add(b *memRecordCycle) {
// A blockRecord is the bucket data for a bucket of type blockProfile,
// which is used in blocking and mutex profiles.
type blockRecord struct {
- count int64
+ count float64
cycles int64
}
@@ -398,20 +398,23 @@ func blockevent(cycles int64, skip int) {
if cycles <= 0 {
cycles = 1
}
- if blocksampled(cycles) {
- saveblockevent(cycles, skip+1, blockProfile)
+
+ rate := int64(atomic.Load64(&blockprofilerate))
+ if blocksampled(cycles, rate) {
+ saveblockevent(cycles, rate, skip+1, blockProfile)
}
}
-func blocksampled(cycles int64) bool {
- rate := int64(atomic.Load64(&blockprofilerate))
+// blocksampled returns true for all events where cycles >= rate. Shorter
+// events have a cycles/rate random chance of returning true.
+func blocksampled(cycles, rate int64) bool {
if rate <= 0 || (rate > cycles && int64(fastrand())%rate > cycles) {
return false
}
return true
}
-func saveblockevent(cycles int64, skip int, which bucketType) {
+func saveblockevent(cycles, rate int64, skip int, which bucketType) {
gp := getg()
var nstk int
var stk [maxStack]uintptr
@@ -422,8 +425,15 @@ func saveblockevent(cycles int64, skip int, which bucketType) {
}
lock(&proflock)
b := stkbucket(which, 0, stk[:nstk], true)
- b.bp().count++
- b.bp().cycles += cycles
+
+ if which == blockProfile && cycles < rate {
+ // Remove sampling bias, see discussion on http://golang.org/cl/299991.
+ b.bp().count += float64(rate) / float64(cycles)
+ b.bp().cycles += rate
+ } else {
+ b.bp().count++
+ b.bp().cycles += cycles
+ }
unlock(&proflock)
}
@@ -454,7 +464,7 @@ func mutexevent(cycles int64, skip int) {
// TODO(pjw): measure impact of always calling fastrand vs using something
// like malloc.go:nextSample()
if rate > 0 && int64(fastrand())%rate == 0 {
- saveblockevent(cycles, skip+1, mutexProfile)
+ saveblockevent(cycles, rate, skip+1, mutexProfile)
}
}
@@ -656,7 +666,12 @@ func BlockProfile(p []BlockProfileRecord) (n int, ok bool) {
for b := bbuckets; b != nil; b = b.allnext {
bp := b.bp()
r := &p[0]
- r.Count = bp.count
+ r.Count = int64(bp.count)
+ // Prevent callers from having to worry about division by zero errors.
+ // See discussion on http://golang.org/cl/299991.
+ if r.Count == 0 {
+ r.Count = 1
+ }
r.Cycles = bp.cycles
if raceenabled {
racewriterangepc(unsafe.Pointer(&r.Stack0[0]), unsafe.Sizeof(r.Stack0), getcallerpc(), funcPC(BlockProfile))