aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/pprof
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime/pprof')
-rw-r--r--src/runtime/pprof/pprof_test.go74
1 files changed, 58 insertions, 16 deletions
diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go
index 1ade860441..9480240615 100644
--- a/src/runtime/pprof/pprof_test.go
+++ b/src/runtime/pprof/pprof_test.go
@@ -1023,7 +1023,7 @@ func containsStack(got [][]string, want []string) bool {
// awaitBlockedGoroutine spins on runtime.Gosched until a runtime stack dump
// shows a goroutine in the given state with a stack frame in
// runtime/pprof.<fName>.
-func awaitBlockedGoroutine(t *testing.T, state, fName string) {
+func awaitBlockedGoroutine(t *testing.T, state, fName string, count int) {
re := fmt.Sprintf(`(?m)^goroutine \d+ \[%s\]:\n(?:.+\n\t.+\n)*runtime/pprof\.%s`, regexp.QuoteMeta(state), fName)
r := regexp.MustCompile(re)
@@ -1047,7 +1047,7 @@ func awaitBlockedGoroutine(t *testing.T, state, fName string) {
buf = make([]byte, 2*len(buf))
continue
}
- if r.Match(buf[:n]) {
+ if len(r.FindAll(buf[:n], -1)) >= count {
return
}
}
@@ -1056,7 +1056,7 @@ func awaitBlockedGoroutine(t *testing.T, state, fName string) {
func blockChanRecv(t *testing.T) {
c := make(chan bool)
go func() {
- awaitBlockedGoroutine(t, "chan receive", "blockChanRecv")
+ awaitBlockedGoroutine(t, "chan receive", "blockChanRecv", 1)
c <- true
}()
<-c
@@ -1065,7 +1065,7 @@ func blockChanRecv(t *testing.T) {
func blockChanSend(t *testing.T) {
c := make(chan bool)
go func() {
- awaitBlockedGoroutine(t, "chan send", "blockChanSend")
+ awaitBlockedGoroutine(t, "chan send", "blockChanSend", 1)
<-c
}()
c <- true
@@ -1074,7 +1074,7 @@ func blockChanSend(t *testing.T) {
func blockChanClose(t *testing.T) {
c := make(chan bool)
go func() {
- awaitBlockedGoroutine(t, "chan receive", "blockChanClose")
+ awaitBlockedGoroutine(t, "chan receive", "blockChanClose", 1)
close(c)
}()
<-c
@@ -1086,7 +1086,7 @@ func blockSelectRecvAsync(t *testing.T) {
c2 := make(chan bool, 1)
go func() {
for i := 0; i < numTries; i++ {
- awaitBlockedGoroutine(t, "select", "blockSelectRecvAsync")
+ awaitBlockedGoroutine(t, "select", "blockSelectRecvAsync", 1)
c <- true
}
}()
@@ -1102,7 +1102,7 @@ func blockSelectSendSync(t *testing.T) {
c := make(chan bool)
c2 := make(chan bool)
go func() {
- awaitBlockedGoroutine(t, "select", "blockSelectSendSync")
+ awaitBlockedGoroutine(t, "select", "blockSelectSendSync", 1)
<-c
}()
select {
@@ -1115,7 +1115,7 @@ func blockMutex(t *testing.T) {
var mu sync.Mutex
mu.Lock()
go func() {
- awaitBlockedGoroutine(t, "sync.Mutex.Lock", "blockMutex")
+ awaitBlockedGoroutine(t, "sync.Mutex.Lock", "blockMutex", 1)
mu.Unlock()
}()
// Note: Unlock releases mu before recording the mutex event,
@@ -1125,12 +1125,36 @@ func blockMutex(t *testing.T) {
mu.Lock()
}
+func blockMutexN(t *testing.T, n int, d time.Duration) {
+ var wg sync.WaitGroup
+ var mu sync.Mutex
+ mu.Lock()
+ go func() {
+ awaitBlockedGoroutine(t, "sync.Mutex.Lock", "blockMutex", n)
+ time.Sleep(d)
+ mu.Unlock()
+ }()
+ // Note: Unlock releases mu before recording the mutex event,
+ // so it's theoretically possible for this to proceed and
+ // capture the profile before the event is recorded. As long
+ // as this is blocked before the unlock happens, it's okay.
+ for i := 0; i < n; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ mu.Lock()
+ mu.Unlock()
+ }()
+ }
+ wg.Wait()
+}
+
func blockCond(t *testing.T) {
var mu sync.Mutex
c := sync.NewCond(&mu)
mu.Lock()
go func() {
- awaitBlockedGoroutine(t, "sync.Cond.Wait", "blockCond")
+ awaitBlockedGoroutine(t, "sync.Cond.Wait", "blockCond", 1)
mu.Lock()
c.Signal()
mu.Unlock()
@@ -1217,7 +1241,11 @@ func TestMutexProfile(t *testing.T) {
t.Fatalf("need MutexProfileRate 0, got %d", old)
}
- blockMutex(t)
+ const (
+ N = 100
+ D = 100 * time.Millisecond
+ )
+ blockMutexN(t, N, D)
t.Run("debug=1", func(t *testing.T) {
var w strings.Builder
@@ -1230,15 +1258,11 @@ func TestMutexProfile(t *testing.T) {
}
prof = strings.Trim(prof, "\n")
lines := strings.Split(prof, "\n")
- if len(lines) != 6 {
- t.Errorf("expected 6 lines, got %d %q\n%s", len(lines), prof, prof)
- }
if len(lines) < 6 {
- return
+ t.Fatalf("expected >=6 lines, got %d %q\n%s", len(lines), prof, prof)
}
// checking that the line is like "35258904 1 @ 0x48288d 0x47cd28 0x458931"
r2 := `^\d+ \d+ @(?: 0x[[:xdigit:]]+)+`
- //r2 := "^[0-9]+ 1 @ 0x[0-9a-f x]+$"
if ok, err := regexp.MatchString(r2, lines[3]); err != nil || !ok {
t.Errorf("%q didn't match %q", lines[3], r2)
}
@@ -1263,12 +1287,30 @@ func TestMutexProfile(t *testing.T) {
stks := stacks(p)
for _, want := range [][]string{
- {"sync.(*Mutex).Unlock", "runtime/pprof.blockMutex.func1"},
+ {"sync.(*Mutex).Unlock", "runtime/pprof.blockMutexN.func1"},
} {
if !containsStack(stks, want) {
t.Errorf("No matching stack entry for %+v", want)
}
}
+
+ i := 0
+ for ; i < len(p.SampleType); i++ {
+ if p.SampleType[i].Unit == "nanoseconds" {
+ break
+ }
+ }
+ if i >= len(p.SampleType) {
+ t.Fatalf("profile did not contain nanoseconds sample")
+ }
+ total := int64(0)
+ for _, s := range p.Sample {
+ total += s.Value[i]
+ }
+ d := time.Duration(total)
+ if d < N*D*9/10 || d > N*D*2 { // want N*D but allow [0.9,2.0]*that.
+ t.Fatalf("profile samples total %v, want %v", d, N*D)
+ }
})
}