aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/runtime/pprof/pprof_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/runtime/pprof/pprof_test.go')
-rw-r--r--libgo/go/runtime/pprof/pprof_test.go153
1 files changed, 119 insertions, 34 deletions
diff --git a/libgo/go/runtime/pprof/pprof_test.go b/libgo/go/runtime/pprof/pprof_test.go
index 73d7aaa..ab96b0c 100644
--- a/libgo/go/runtime/pprof/pprof_test.go
+++ b/libgo/go/runtime/pprof/pprof_test.go
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+//go:build !js
// +build !js
package pprof
@@ -13,6 +14,7 @@ import (
"internal/profile"
"internal/testenv"
"io"
+ "math"
"math/big"
"os"
"os/exec"
@@ -23,6 +25,7 @@ import (
"sync/atomic"
"testing"
"time"
+ _ "unsafe"
)
func cpuHogger(f func(x int) int, y *int, dur time.Duration) {
@@ -257,40 +260,43 @@ func parseProfile(t *testing.T, valBytes []byte, f func(uintptr, []*profile.Loca
return p
}
+func cpuProfilingBroken() bool {
+ switch runtime.GOOS {
+ case "plan9":
+ // Profiling unimplemented.
+ return true
+ case "aix":
+ // See https://golang.org/issue/45170.
+ return true
+ case "ios", "dragonfly", "netbsd", "illumos", "solaris":
+ // See https://golang.org/issue/13841.
+ return true
+ case "openbsd":
+ if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
+ // See https://golang.org/issue/13841.
+ return true
+ }
+ }
+
+ return false
+}
+
// testCPUProfile runs f under the CPU profiler, checking for some conditions specified by need,
// as interpreted by matches, and returns the parsed profile.
func testCPUProfile(t *testing.T, matches matchFunc, need []string, avoid []string, f func(dur time.Duration)) *profile.Profile {
switch runtime.GOOS {
- case "darwin", "ios":
- switch runtime.GOARCH {
- case "arm64":
- // nothing
- default:
- out, err := exec.Command("uname", "-a").CombinedOutput()
- if err != nil {
- t.Fatal(err)
- }
- vers := string(out)
- t.Logf("uname -a: %v", vers)
+ case "darwin":
+ out, err := exec.Command("uname", "-a").CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
}
+ vers := string(out)
+ t.Logf("uname -a: %v", vers)
case "plan9":
t.Skip("skipping on plan9")
}
- broken := false
- switch runtime.GOOS {
- // See https://golang.org/issue/45170 for AIX.
- case "darwin", "ios", "dragonfly", "netbsd", "illumos", "solaris", "aix":
- broken = true
- case "openbsd":
- if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
- broken = true
- }
- case "windows":
- if runtime.GOARCH == "arm" {
- broken = true // See https://golang.org/issues/42862
- }
- }
+ broken := cpuProfilingBroken()
maxDuration := 5 * time.Second
if testing.Short() && broken {
@@ -524,8 +530,10 @@ func TestGoroutineSwitch(t *testing.T) {
}
StopCPUProfile()
- // Read profile to look for entries for runtime.gogo with an attempt at a traceback.
- // The special entry
+ // Read profile to look for entries for gogo with an attempt at a traceback.
+ // "runtime.gogo" is OK, because that's the part of the context switch
+ // before the actual switch begins. But we should not see "gogo",
+ // aka "gogo<>(SB)", which does the actual switch and is marked SPWRITE.
parseProfile(t, prof.Bytes(), func(count uintptr, stk []*profile.Location, _ map[string][]string) {
// An entry with two frames with 'System' in its top frame
// exists to record a PC without a traceback. Those are okay.
@@ -536,13 +544,19 @@ func TestGoroutineSwitch(t *testing.T) {
}
}
- // Otherwise, should not see runtime.gogo.
+ // An entry with just one frame is OK too:
+ // it knew to stop at gogo.
+ if len(stk) == 1 {
+ return
+ }
+
+ // Otherwise, should not see gogo.
// The place we'd see it would be the inner most frame.
name := stk[0].Line[0].Function.Name
- if name == "runtime.gogo" {
+ if name == "gogo" {
var buf bytes.Buffer
fprintStack(&buf, stk)
- t.Fatalf("found profile entry for runtime.gogo:\n%s", buf.String())
+ t.Fatalf("found profile entry for gogo:\n%s", buf.String())
}
})
}
@@ -616,17 +630,20 @@ func TestMorestack(t *testing.T) {
//go:noinline
func growstack1() {
- growstack()
+ growstack(10)
}
//go:noinline
-func growstack() {
- var buf [8 << 10]byte
+func growstack(n int) {
+ var buf [8 << 18]byte
use(buf)
+ if n > 0 {
+ growstack(n - 1)
+ }
}
//go:noinline
-func use(x [8 << 10]byte) {}
+func use(x [8 << 18]byte) {}
func TestBlockProfile(t *testing.T) {
t.Skip("lots of details are different for gccgo; FIXME")
@@ -905,6 +922,74 @@ func blockCond() {
mu.Unlock()
}
+// See http://golang.org/cl/299991.
+func TestBlockProfileBias(t *testing.T) {
+ rate := int(1000) // arbitrary value
+ runtime.SetBlockProfileRate(rate)
+ defer runtime.SetBlockProfileRate(0)
+
+ // simulate blocking events
+ blockFrequentShort(rate)
+ blockInfrequentLong(rate)
+
+ var w bytes.Buffer
+ Lookup("block").WriteTo(&w, 0)
+ p, err := profile.Parse(&w)
+ if err != nil {
+ t.Fatalf("failed to parse profile: %v", err)
+ }
+ t.Logf("parsed proto: %s", p)
+
+ il := float64(-1) // blockInfrequentLong duration
+ fs := float64(-1) // blockFrequentShort duration
+ for _, s := range p.Sample {
+ for _, l := range s.Location {
+ for _, line := range l.Line {
+ if len(s.Value) < 2 {
+ t.Fatal("block profile has less than 2 sample types")
+ }
+
+ if line.Function.Name == "runtime/pprof.blockInfrequentLong" {
+ il = float64(s.Value[1])
+ } else if line.Function.Name == "runtime/pprof.blockFrequentShort" {
+ fs = float64(s.Value[1])
+ }
+ }
+ }
+ }
+ if il == -1 || fs == -1 {
+ t.Fatal("block profile is missing expected functions")
+ }
+
+ // stddev of bias from 100 runs on local machine multiplied by 10x
+ const threshold = 0.2
+ if bias := (il - fs) / il; math.Abs(bias) > threshold {
+ t.Fatalf("bias: abs(%f) > %f", bias, threshold)
+ } else {
+ t.Logf("bias: abs(%f) < %f", bias, threshold)
+ }
+}
+
+// blockFrequentShort produces 100000 block events with an average duration of
+// rate / 10.
+func blockFrequentShort(rate int) {
+ for i := 0; i < 100000; i++ {
+ blockevent(int64(rate/10), 1)
+ }
+}
+
+// blockFrequentShort produces 10000 block events with an average duration of
+// rate.
+func blockInfrequentLong(rate int) {
+ for i := 0; i < 10000; i++ {
+ blockevent(int64(rate), 1)
+ }
+}
+
+// Used by TestBlockProfileBias.
+//go:linkname blockevent runtime.blockevent
+func blockevent(cycles int64, skip int)
+
func TestMutexProfile(t *testing.T) {
// Generate mutex profile