1
0
mirror of https://github.com/golang/go synced 2024-10-02 04:18:33 -06:00

runtime/pprof: harden CPU profile test against smart backend

A couple of the CPU profiling testpoints make calls to helper
functions (cpuHog1, for example) where the computed value is always
thrown away by the caller without being used. A smart compiler back
end (in this case LLVM) can detect this fact and delete the contents
of the called function, which can cause tests to fail. Harden the test
slighly by passing in a value read from a global and insuring that the
caller stores the value back to a global; this prevents any optimizer
mischief.

Change-Id: Icbd6e3e32ff299c68a6397dc1404a52b21eaeaab
Reviewed-on: https://go-review.googlesource.com/76230
Run-TryBot: Than McIntosh <thanm@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
Than McIntosh 2017-11-06 15:33:31 -05:00
parent 36323e4313
commit 83a1a2ba63

View File

@ -26,16 +26,18 @@ import (
"time" "time"
) )
func cpuHogger(f func() int, dur time.Duration) { func cpuHogger(f func(x int) int, y *int, dur time.Duration) {
// We only need to get one 100 Hz clock tick, so we've got // We only need to get one 100 Hz clock tick, so we've got
// a large safety buffer. // a large safety buffer.
// But do at least 500 iterations (which should take about 100ms), // But do at least 500 iterations (which should take about 100ms),
// otherwise TestCPUProfileMultithreaded can fail if only one // otherwise TestCPUProfileMultithreaded can fail if only one
// thread is scheduled during the testing period. // thread is scheduled during the testing period.
t0 := time.Now() t0 := time.Now()
accum := *y
for i := 0; i < 500 || time.Since(t0) < dur; i++ { for i := 0; i < 500 || time.Since(t0) < dur; i++ {
f() accum = f(accum)
} }
*y = accum
} }
var ( var (
@ -46,8 +48,8 @@ var (
// The actual CPU hogging function. // The actual CPU hogging function.
// Must not call other functions nor access heap/globals in the loop, // Must not call other functions nor access heap/globals in the loop,
// otherwise under race detector the samples will be in the race runtime. // otherwise under race detector the samples will be in the race runtime.
func cpuHog1() int { func cpuHog1(x int) int {
foo := salt1 foo := x
for i := 0; i < 1e5; i++ { for i := 0; i < 1e5; i++ {
if foo > 0 { if foo > 0 {
foo *= foo foo *= foo
@ -58,8 +60,8 @@ func cpuHog1() int {
return foo return foo
} }
func cpuHog2() int { func cpuHog2(x int) int {
foo := salt2 foo := x
for i := 0; i < 1e5; i++ { for i := 0; i < 1e5; i++ {
if foo > 0 { if foo > 0 {
foo *= foo foo *= foo
@ -72,7 +74,7 @@ func cpuHog2() int {
func TestCPUProfile(t *testing.T) { func TestCPUProfile(t *testing.T) {
testCPUProfile(t, []string{"runtime/pprof.cpuHog1"}, func(dur time.Duration) { testCPUProfile(t, []string{"runtime/pprof.cpuHog1"}, func(dur time.Duration) {
cpuHogger(cpuHog1, dur) cpuHogger(cpuHog1, &salt1, dur)
}) })
} }
@ -81,29 +83,29 @@ func TestCPUProfileMultithreaded(t *testing.T) {
testCPUProfile(t, []string{"runtime/pprof.cpuHog1", "runtime/pprof.cpuHog2"}, func(dur time.Duration) { testCPUProfile(t, []string{"runtime/pprof.cpuHog1", "runtime/pprof.cpuHog2"}, func(dur time.Duration) {
c := make(chan int) c := make(chan int)
go func() { go func() {
cpuHogger(cpuHog1, dur) cpuHogger(cpuHog1, &salt1, dur)
c <- 1 c <- 1
}() }()
cpuHogger(cpuHog2, dur) cpuHogger(cpuHog2, &salt2, dur)
<-c <-c
}) })
} }
func TestCPUProfileInlining(t *testing.T) { func TestCPUProfileInlining(t *testing.T) {
testCPUProfile(t, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.inlinedCaller"}, func(dur time.Duration) { testCPUProfile(t, []string{"runtime/pprof.inlinedCallee", "runtime/pprof.inlinedCaller"}, func(dur time.Duration) {
cpuHogger(inlinedCaller, dur) cpuHogger(inlinedCaller, &salt1, dur)
}) })
} }
func inlinedCaller() int { func inlinedCaller(x int) int {
inlinedCallee() x = inlinedCallee(x)
return 0 return x
} }
func inlinedCallee() { func inlinedCallee(x int) int {
// We could just use cpuHog1, but for loops prevent inlining // We could just use cpuHog1, but for loops prevent inlining
// right now. :( // right now. :(
foo := salt1 foo := x
i := 0 i := 0
loop: loop:
if foo > 0 { if foo > 0 {
@ -114,7 +116,7 @@ loop:
if i++; i < 1e5 { if i++; i < 1e5 {
goto loop goto loop
} }
salt1 = foo return foo
} }
func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []*profile.Location, map[string][]string)) { func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []*profile.Location, map[string][]string)) {
@ -843,7 +845,7 @@ func TestEmptyCallStack(t *testing.T) {
func TestCPUProfileLabel(t *testing.T) { func TestCPUProfileLabel(t *testing.T) {
testCPUProfile(t, []string{"runtime/pprof.cpuHogger;key=value"}, func(dur time.Duration) { testCPUProfile(t, []string{"runtime/pprof.cpuHogger;key=value"}, func(dur time.Duration) {
Do(context.Background(), Labels("key", "value"), func(context.Context) { Do(context.Background(), Labels("key", "value"), func(context.Context) {
cpuHogger(cpuHog1, dur) cpuHogger(cpuHog1, &salt1, dur)
}) })
}) })
} }
@ -856,14 +858,15 @@ func TestLabelRace(t *testing.T) {
start := time.Now() start := time.Now()
var wg sync.WaitGroup var wg sync.WaitGroup
for time.Since(start) < dur { for time.Since(start) < dur {
var salts [10]int
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
wg.Add(1) wg.Add(1)
go func() { go func(j int) {
Do(context.Background(), Labels("key", "value"), func(context.Context) { Do(context.Background(), Labels("key", "value"), func(context.Context) {
cpuHogger(cpuHog1, time.Millisecond) cpuHogger(cpuHog1, &salts[j], time.Millisecond)
}) })
wg.Done() wg.Done()
}() }(i)
} }
wg.Wait() wg.Wait()
} }