github.com/miolini/go@v0.0.0-20160405192216-fca68c8cb408/src/runtime/pprof/pprof_test.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build !nacl
     6  
     7  package pprof_test
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"internal/testenv"
    13  	"math/big"
    14  	"os"
    15  	"os/exec"
    16  	"regexp"
    17  	"runtime"
    18  	. "runtime/pprof"
    19  	"strings"
    20  	"sync"
    21  	"testing"
    22  	"time"
    23  	"unsafe"
    24  )
    25  
    26  func cpuHogger(f func(), dur time.Duration) {
    27  	// We only need to get one 100 Hz clock tick, so we've got
    28  	// a large safety buffer.
    29  	// But do at least 500 iterations (which should take about 100ms),
    30  	// otherwise TestCPUProfileMultithreaded can fail if only one
    31  	// thread is scheduled during the testing period.
    32  	t0 := time.Now()
    33  	for i := 0; i < 500 || time.Since(t0) < dur; i++ {
    34  		f()
    35  	}
    36  }
    37  
    38  var (
    39  	salt1 = 0
    40  	salt2 = 0
    41  )
    42  
    43  // The actual CPU hogging function.
    44  // Must not call other functions nor access heap/globals in the loop,
    45  // otherwise under race detector the samples will be in the race runtime.
    46  func cpuHog1() {
    47  	foo := salt1
    48  	for i := 0; i < 1e5; i++ {
    49  		if foo > 0 {
    50  			foo *= foo
    51  		} else {
    52  			foo *= foo + 1
    53  		}
    54  	}
    55  	salt1 = foo
    56  }
    57  
    58  func cpuHog2() {
    59  	foo := salt2
    60  	for i := 0; i < 1e5; i++ {
    61  		if foo > 0 {
    62  			foo *= foo
    63  		} else {
    64  			foo *= foo + 2
    65  		}
    66  	}
    67  	salt2 = foo
    68  }
    69  
    70  func TestCPUProfile(t *testing.T) {
    71  	testCPUProfile(t, []string{"runtime/pprof_test.cpuHog1"}, func(dur time.Duration) {
    72  		cpuHogger(cpuHog1, dur)
    73  	})
    74  }
    75  
    76  func TestCPUProfileMultithreaded(t *testing.T) {
    77  	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
    78  	testCPUProfile(t, []string{"runtime/pprof_test.cpuHog1", "runtime/pprof_test.cpuHog2"}, func(dur time.Duration) {
    79  		c := make(chan int)
    80  		go func() {
    81  			cpuHogger(cpuHog1, dur)
    82  			c <- 1
    83  		}()
    84  		cpuHogger(cpuHog2, dur)
    85  		<-c
    86  	})
    87  }
    88  
    89  func parseProfile(t *testing.T, bytes []byte, f func(uintptr, []uintptr)) {
    90  	// Convert []byte to []uintptr.
    91  	l := len(bytes) / int(unsafe.Sizeof(uintptr(0)))
    92  	val := *(*[]uintptr)(unsafe.Pointer(&bytes))
    93  	val = val[:l]
    94  
    95  	// 5 for the header, 3 for the trailer.
    96  	if l < 5+3 {
    97  		t.Logf("profile too short: %#x", val)
    98  		if badOS[runtime.GOOS] {
    99  			t.Skipf("ignoring failure on %s; see golang.org/issue/13841", runtime.GOOS)
   100  			return
   101  		}
   102  		t.FailNow()
   103  	}
   104  
   105  	hd, val, tl := val[:5], val[5:l-3], val[l-3:]
   106  	if hd[0] != 0 || hd[1] != 3 || hd[2] != 0 || hd[3] != 1e6/100 || hd[4] != 0 {
   107  		t.Fatalf("unexpected header %#x", hd)
   108  	}
   109  
   110  	if tl[0] != 0 || tl[1] != 1 || tl[2] != 0 {
   111  		t.Fatalf("malformed end-of-data marker %#x", tl)
   112  	}
   113  
   114  	for len(val) > 0 {
   115  		if len(val) < 2 || val[0] < 1 || val[1] < 1 || uintptr(len(val)) < 2+val[1] {
   116  			t.Fatalf("malformed profile.  leftover: %#x", val)
   117  		}
   118  		f(val[0], val[2:2+val[1]])
   119  		val = val[2+val[1]:]
   120  	}
   121  }
   122  
   123  func testCPUProfile(t *testing.T, need []string, f func(dur time.Duration)) {
   124  	switch runtime.GOOS {
   125  	case "darwin":
   126  		switch runtime.GOARCH {
   127  		case "arm", "arm64":
   128  			// nothing
   129  		default:
   130  			out, err := exec.Command("uname", "-a").CombinedOutput()
   131  			if err != nil {
   132  				t.Fatal(err)
   133  			}
   134  			vers := string(out)
   135  			t.Logf("uname -a: %v", vers)
   136  		}
   137  	case "plan9":
   138  		t.Skip("skipping on plan9")
   139  	}
   140  
   141  	const maxDuration = 5 * time.Second
   142  	// If we're running a long test, start with a long duration
   143  	// because some of the tests (e.g., TestStackBarrierProfiling)
   144  	// are trying to make sure something *doesn't* happen.
   145  	duration := 5 * time.Second
   146  	if testing.Short() {
   147  		duration = 200 * time.Millisecond
   148  	}
   149  
   150  	// Profiling tests are inherently flaky, especially on a
   151  	// loaded system, such as when this test is running with
   152  	// several others under go test std. If a test fails in a way
   153  	// that could mean it just didn't run long enough, try with a
   154  	// longer duration.
   155  	for duration <= maxDuration {
   156  		var prof bytes.Buffer
   157  		if err := StartCPUProfile(&prof); err != nil {
   158  			t.Fatal(err)
   159  		}
   160  		f(duration)
   161  		StopCPUProfile()
   162  
   163  		if profileOk(t, need, prof, duration) {
   164  			return
   165  		}
   166  
   167  		duration *= 2
   168  		if duration <= maxDuration {
   169  			t.Logf("retrying with %s duration", duration)
   170  		}
   171  	}
   172  
   173  	if badOS[runtime.GOOS] {
   174  		t.Skipf("ignoring failure on %s; see golang.org/issue/13841", runtime.GOOS)
   175  		return
   176  	}
   177  	// Ignore the failure if the tests are running in a QEMU-based emulator,
   178  	// QEMU is not perfect at emulating everything.
   179  	// IN_QEMU environmental variable is set by some of the Go builders.
   180  	// IN_QEMU=1 indicates that the tests are running in QEMU. See issue 9605.
   181  	if os.Getenv("IN_QEMU") == "1" {
   182  		t.Skip("ignore the failure in QEMU; see golang.org/issue/9605")
   183  		return
   184  	}
   185  	t.FailNow()
   186  }
   187  
   188  func profileOk(t *testing.T, need []string, prof bytes.Buffer, duration time.Duration) (ok bool) {
   189  	ok = true
   190  
   191  	// Check that profile is well formed and contains need.
   192  	have := make([]uintptr, len(need))
   193  	var samples uintptr
   194  	parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr) {
   195  		samples += count
   196  		for _, pc := range stk {
   197  			f := runtime.FuncForPC(pc)
   198  			if f == nil {
   199  				continue
   200  			}
   201  			for i, name := range need {
   202  				if strings.Contains(f.Name(), name) {
   203  					have[i] += count
   204  				}
   205  			}
   206  			if strings.Contains(f.Name(), "stackBarrier") {
   207  				// The runtime should have unwound this.
   208  				t.Fatalf("profile includes stackBarrier")
   209  			}
   210  		}
   211  	})
   212  	t.Logf("total %d CPU profile samples collected", samples)
   213  
   214  	if samples < 10 && runtime.GOOS == "windows" {
   215  		// On some windows machines we end up with
   216  		// not enough samples due to coarse timer
   217  		// resolution. Let it go.
   218  		t.Log("too few samples on Windows (golang.org/issue/10842)")
   219  		return false
   220  	}
   221  
   222  	// Check that we got a reasonable number of samples.
   223  	if ideal := uintptr(duration * 100 / time.Second); samples == 0 || samples < ideal/4 {
   224  		t.Logf("too few samples; got %d, want at least %d, ideally %d", samples, ideal/4, ideal)
   225  		ok = false
   226  	}
   227  
   228  	if len(need) == 0 {
   229  		return ok
   230  	}
   231  
   232  	var total uintptr
   233  	for i, name := range need {
   234  		total += have[i]
   235  		t.Logf("%s: %d\n", name, have[i])
   236  	}
   237  	if total == 0 {
   238  		t.Logf("no samples in expected functions")
   239  		ok = false
   240  	}
   241  	// We'd like to check a reasonable minimum, like
   242  	// total / len(have) / smallconstant, but this test is
   243  	// pretty flaky (see bug 7095).  So we'll just test to
   244  	// make sure we got at least one sample.
   245  	min := uintptr(1)
   246  	for i, name := range need {
   247  		if have[i] < min {
   248  			t.Logf("%s has %d samples out of %d, want at least %d, ideally %d", name, have[i], total, min, total/uintptr(len(have)))
   249  			ok = false
   250  		}
   251  	}
   252  	return ok
   253  }
   254  
   255  // Fork can hang if preempted with signals frequently enough (see issue 5517).
   256  // Ensure that we do not do this.
   257  func TestCPUProfileWithFork(t *testing.T) {
   258  	testenv.MustHaveExec(t)
   259  
   260  	heap := 1 << 30
   261  	if runtime.GOOS == "android" {
   262  		// Use smaller size for Android to avoid crash.
   263  		heap = 100 << 20
   264  	}
   265  	if testing.Short() {
   266  		heap = 100 << 20
   267  	}
   268  	// This makes fork slower.
   269  	garbage := make([]byte, heap)
   270  	// Need to touch the slice, otherwise it won't be paged in.
   271  	done := make(chan bool)
   272  	go func() {
   273  		for i := range garbage {
   274  			garbage[i] = 42
   275  		}
   276  		done <- true
   277  	}()
   278  	<-done
   279  
   280  	var prof bytes.Buffer
   281  	if err := StartCPUProfile(&prof); err != nil {
   282  		t.Fatal(err)
   283  	}
   284  	defer StopCPUProfile()
   285  
   286  	for i := 0; i < 10; i++ {
   287  		exec.Command(os.Args[0], "-h").CombinedOutput()
   288  	}
   289  }
   290  
   291  // Test that profiler does not observe runtime.gogo as "user" goroutine execution.
   292  // If it did, it would see inconsistent state and would either record an incorrect stack
   293  // or crash because the stack was malformed.
   294  func TestGoroutineSwitch(t *testing.T) {
   295  	// How much to try. These defaults take about 1 seconds
   296  	// on a 2012 MacBook Pro. The ones in short mode take
   297  	// about 0.1 seconds.
   298  	tries := 10
   299  	count := 1000000
   300  	if testing.Short() {
   301  		tries = 1
   302  	}
   303  	for try := 0; try < tries; try++ {
   304  		var prof bytes.Buffer
   305  		if err := StartCPUProfile(&prof); err != nil {
   306  			t.Fatal(err)
   307  		}
   308  		for i := 0; i < count; i++ {
   309  			runtime.Gosched()
   310  		}
   311  		StopCPUProfile()
   312  
   313  		// Read profile to look for entries for runtime.gogo with an attempt at a traceback.
   314  		// The special entry
   315  		parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr) {
   316  			// An entry with two frames with 'System' in its top frame
   317  			// exists to record a PC without a traceback. Those are okay.
   318  			if len(stk) == 2 {
   319  				f := runtime.FuncForPC(stk[1])
   320  				if f != nil && (f.Name() == "runtime._System" || f.Name() == "runtime._ExternalCode" || f.Name() == "runtime._GC") {
   321  					return
   322  				}
   323  			}
   324  
   325  			// Otherwise, should not see runtime.gogo.
   326  			// The place we'd see it would be the inner most frame.
   327  			f := runtime.FuncForPC(stk[0])
   328  			if f != nil && f.Name() == "runtime.gogo" {
   329  				var buf bytes.Buffer
   330  				for _, pc := range stk {
   331  					f := runtime.FuncForPC(pc)
   332  					if f == nil {
   333  						fmt.Fprintf(&buf, "%#x ?:0\n", pc)
   334  					} else {
   335  						file, line := f.FileLine(pc)
   336  						fmt.Fprintf(&buf, "%#x %s:%d\n", pc, file, line)
   337  					}
   338  				}
   339  				t.Fatalf("found profile entry for runtime.gogo:\n%s", buf.String())
   340  			}
   341  		})
   342  	}
   343  }
   344  
   345  // Test that profiling of division operations is okay, especially on ARM. See issue 6681.
   346  func TestMathBigDivide(t *testing.T) {
   347  	testCPUProfile(t, nil, func(duration time.Duration) {
   348  		t := time.After(duration)
   349  		pi := new(big.Int)
   350  		for {
   351  			for i := 0; i < 100; i++ {
   352  				n := big.NewInt(2646693125139304345)
   353  				d := big.NewInt(842468587426513207)
   354  				pi.Div(n, d)
   355  			}
   356  			select {
   357  			case <-t:
   358  				return
   359  			default:
   360  			}
   361  		}
   362  	})
   363  }
   364  
   365  func TestStackBarrierProfiling(t *testing.T) {
   366  	if (runtime.GOOS == "linux" && runtime.GOARCH == "arm") || runtime.GOOS == "openbsd" || runtime.GOOS == "solaris" || runtime.GOOS == "dragonfly" || runtime.GOOS == "freebsd" {
   367  		// This test currently triggers a large number of
   368  		// usleep(100)s. These kernels/arches have poor
   369  		// resolution timers, so this gives up a whole
   370  		// scheduling quantum. On Linux and the BSDs (and
   371  		// probably Solaris), profiling signals are only
   372  		// generated when a process completes a whole
   373  		// scheduling quantum, so this test often gets zero
   374  		// profiling signals and fails.
   375  		t.Skipf("low resolution timers inhibit profiling signals (golang.org/issue/13405)")
   376  		return
   377  	}
   378  
   379  	if !strings.Contains(os.Getenv("GODEBUG"), "gcstackbarrierall=1") {
   380  		// Re-execute this test with constant GC and stack
   381  		// barriers at every frame.
   382  		testenv.MustHaveExec(t)
   383  		if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" {
   384  			t.Skip("gcstackbarrierall doesn't work on ppc64")
   385  		}
   386  		args := []string{"-test.run=TestStackBarrierProfiling"}
   387  		if testing.Short() {
   388  			args = append(args, "-test.short")
   389  		}
   390  		cmd := exec.Command(os.Args[0], args...)
   391  		cmd.Env = append([]string{"GODEBUG=gcstackbarrierall=1", "GOGC=1"}, os.Environ()...)
   392  		if out, err := cmd.CombinedOutput(); err != nil {
   393  			t.Fatalf("subprocess failed with %v:\n%s", err, out)
   394  		}
   395  		return
   396  	}
   397  
   398  	testCPUProfile(t, nil, func(duration time.Duration) {
   399  		// In long mode, we're likely to get one or two
   400  		// samples in stackBarrier.
   401  		t := time.After(duration)
   402  		for {
   403  			deepStack(1000)
   404  			select {
   405  			case <-t:
   406  				return
   407  			default:
   408  			}
   409  		}
   410  	})
   411  }
   412  
   413  var x []byte
   414  
   415  func deepStack(depth int) int {
   416  	if depth == 0 {
   417  		return 0
   418  	}
   419  	x = make([]byte, 1024)
   420  	return deepStack(depth-1) + 1
   421  }
   422  
   423  // Operating systems that are expected to fail the tests. See issue 13841.
   424  var badOS = map[string]bool{
   425  	"darwin":    true,
   426  	"netbsd":    true,
   427  	"plan9":     true,
   428  	"dragonfly": true,
   429  	"solaris":   true,
   430  }
   431  
   432  func TestBlockProfile(t *testing.T) {
   433  	type TestCase struct {
   434  		name string
   435  		f    func()
   436  		re   string
   437  	}
   438  	tests := [...]TestCase{
   439  		{"chan recv", blockChanRecv, `
   440  [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   441  #	0x[0-9,a-f]+	runtime\.chanrecv1\+0x[0-9,a-f]+	.*/src/runtime/chan.go:[0-9]+
   442  #	0x[0-9,a-f]+	runtime/pprof_test\.blockChanRecv\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   443  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   444  `},
   445  		{"chan send", blockChanSend, `
   446  [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   447  #	0x[0-9,a-f]+	runtime\.chansend1\+0x[0-9,a-f]+	.*/src/runtime/chan.go:[0-9]+
   448  #	0x[0-9,a-f]+	runtime/pprof_test\.blockChanSend\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   449  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   450  `},
   451  		{"chan close", blockChanClose, `
   452  [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   453  #	0x[0-9,a-f]+	runtime\.chanrecv1\+0x[0-9,a-f]+	.*/src/runtime/chan.go:[0-9]+
   454  #	0x[0-9,a-f]+	runtime/pprof_test\.blockChanClose\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   455  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   456  `},
   457  		{"select recv async", blockSelectRecvAsync, `
   458  [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   459  #	0x[0-9,a-f]+	runtime\.selectgo\+0x[0-9,a-f]+	.*/src/runtime/select.go:[0-9]+
   460  #	0x[0-9,a-f]+	runtime/pprof_test\.blockSelectRecvAsync\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   461  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   462  `},
   463  		{"select send sync", blockSelectSendSync, `
   464  [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   465  #	0x[0-9,a-f]+	runtime\.selectgo\+0x[0-9,a-f]+	.*/src/runtime/select.go:[0-9]+
   466  #	0x[0-9,a-f]+	runtime/pprof_test\.blockSelectSendSync\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   467  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   468  `},
   469  		{"mutex", blockMutex, `
   470  [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   471  #	0x[0-9,a-f]+	sync\.\(\*Mutex\)\.Lock\+0x[0-9,a-f]+	.*/src/sync/mutex\.go:[0-9]+
   472  #	0x[0-9,a-f]+	runtime/pprof_test\.blockMutex\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   473  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   474  `},
   475  		{"cond", blockCond, `
   476  [0-9]+ [0-9]+ @ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+ 0x[0-9,a-f]+
   477  #	0x[0-9,a-f]+	sync\.\(\*Cond\)\.Wait\+0x[0-9,a-f]+	.*/src/sync/cond\.go:[0-9]+
   478  #	0x[0-9,a-f]+	runtime/pprof_test\.blockCond\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   479  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   480  `},
   481  	}
   482  
   483  	runtime.SetBlockProfileRate(1)
   484  	defer runtime.SetBlockProfileRate(0)
   485  	for _, test := range tests {
   486  		test.f()
   487  	}
   488  	var w bytes.Buffer
   489  	Lookup("block").WriteTo(&w, 1)
   490  	prof := w.String()
   491  
   492  	if !strings.HasPrefix(prof, "--- contention:\ncycles/second=") {
   493  		t.Fatalf("Bad profile header:\n%v", prof)
   494  	}
   495  
   496  	for _, test := range tests {
   497  		if !regexp.MustCompile(strings.Replace(test.re, "\t", "\t+", -1)).MatchString(prof) {
   498  			t.Fatalf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof)
   499  		}
   500  	}
   501  }
   502  
   503  const blockDelay = 10 * time.Millisecond
   504  
   505  func blockChanRecv() {
   506  	c := make(chan bool)
   507  	go func() {
   508  		time.Sleep(blockDelay)
   509  		c <- true
   510  	}()
   511  	<-c
   512  }
   513  
   514  func blockChanSend() {
   515  	c := make(chan bool)
   516  	go func() {
   517  		time.Sleep(blockDelay)
   518  		<-c
   519  	}()
   520  	c <- true
   521  }
   522  
   523  func blockChanClose() {
   524  	c := make(chan bool)
   525  	go func() {
   526  		time.Sleep(blockDelay)
   527  		close(c)
   528  	}()
   529  	<-c
   530  }
   531  
   532  func blockSelectRecvAsync() {
   533  	c := make(chan bool, 1)
   534  	c2 := make(chan bool, 1)
   535  	go func() {
   536  		time.Sleep(blockDelay)
   537  		c <- true
   538  	}()
   539  	select {
   540  	case <-c:
   541  	case <-c2:
   542  	}
   543  }
   544  
   545  func blockSelectSendSync() {
   546  	c := make(chan bool)
   547  	c2 := make(chan bool)
   548  	go func() {
   549  		time.Sleep(blockDelay)
   550  		<-c
   551  	}()
   552  	select {
   553  	case c <- true:
   554  	case c2 <- true:
   555  	}
   556  }
   557  
   558  func blockMutex() {
   559  	var mu sync.Mutex
   560  	mu.Lock()
   561  	go func() {
   562  		time.Sleep(blockDelay)
   563  		mu.Unlock()
   564  	}()
   565  	mu.Lock()
   566  }
   567  
   568  func blockCond() {
   569  	var mu sync.Mutex
   570  	c := sync.NewCond(&mu)
   571  	mu.Lock()
   572  	go func() {
   573  		time.Sleep(blockDelay)
   574  		mu.Lock()
   575  		c.Signal()
   576  		mu.Unlock()
   577  	}()
   578  	c.Wait()
   579  	mu.Unlock()
   580  }
   581  
   582  func func1(c chan int) { <-c }
   583  func func2(c chan int) { <-c }
   584  func func3(c chan int) { <-c }
   585  func func4(c chan int) { <-c }
   586  
   587  func TestGoroutineCounts(t *testing.T) {
   588  	c := make(chan int)
   589  	for i := 0; i < 100; i++ {
   590  		if i%10 == 0 {
   591  			go func1(c)
   592  			continue
   593  		}
   594  		if i%2 == 0 {
   595  			go func2(c)
   596  			continue
   597  		}
   598  		go func3(c)
   599  	}
   600  	time.Sleep(10 * time.Millisecond) // let goroutines block on channel
   601  
   602  	var w bytes.Buffer
   603  	Lookup("goroutine").WriteTo(&w, 1)
   604  	prof := w.String()
   605  
   606  	if !containsInOrder(prof, "\n50 @ ", "\n40 @", "\n10 @", "\n1 @") {
   607  		t.Errorf("expected sorted goroutine counts:\n%s", prof)
   608  	}
   609  
   610  	close(c)
   611  
   612  	time.Sleep(10 * time.Millisecond) // let goroutines exit
   613  }
   614  
   615  func containsInOrder(s string, all ...string) bool {
   616  	for _, t := range all {
   617  		i := strings.Index(s, t)
   618  		if i < 0 {
   619  			return false
   620  		}
   621  		s = s[i+len(t):]
   622  	}
   623  	return true
   624  }