github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/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/pprof/profile"
    13  	"internal/testenv"
    14  	"math/big"
    15  	"os"
    16  	"os/exec"
    17  	"regexp"
    18  	"runtime"
    19  	. "runtime/pprof"
    20  	"strings"
    21  	"sync"
    22  	"testing"
    23  	"time"
    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, valBytes []byte, f func(uintptr, []uintptr)) {
    90  	p, err := profile.Parse(bytes.NewReader(valBytes))
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  	for _, sample := range p.Sample {
    95  		count := uintptr(sample.Value[0])
    96  		stk := make([]uintptr, len(sample.Location))
    97  		for i := range sample.Location {
    98  			stk[i] = uintptr(sample.Location[i].Address)
    99  		}
   100  		f(count, stk)
   101  	}
   102  }
   103  
   104  func testCPUProfile(t *testing.T, need []string, f func(dur time.Duration)) {
   105  	switch runtime.GOOS {
   106  	case "darwin":
   107  		switch runtime.GOARCH {
   108  		case "arm", "arm64":
   109  			// nothing
   110  		default:
   111  			out, err := exec.Command("uname", "-a").CombinedOutput()
   112  			if err != nil {
   113  				t.Fatal(err)
   114  			}
   115  			vers := string(out)
   116  			t.Logf("uname -a: %v", vers)
   117  		}
   118  	case "plan9":
   119  		t.Skip("skipping on plan9")
   120  	}
   121  
   122  	const maxDuration = 5 * time.Second
   123  	// If we're running a long test, start with a long duration
   124  	// for tests that try to make sure something *doesn't* happen.
   125  	duration := 5 * time.Second
   126  	if testing.Short() {
   127  		duration = 200 * time.Millisecond
   128  	}
   129  
   130  	// Profiling tests are inherently flaky, especially on a
   131  	// loaded system, such as when this test is running with
   132  	// several others under go test std. If a test fails in a way
   133  	// that could mean it just didn't run long enough, try with a
   134  	// longer duration.
   135  	for duration <= maxDuration {
   136  		var prof bytes.Buffer
   137  		if err := StartCPUProfile(&prof); err != nil {
   138  			t.Fatal(err)
   139  		}
   140  		f(duration)
   141  		StopCPUProfile()
   142  
   143  		if profileOk(t, need, prof, duration) {
   144  			return
   145  		}
   146  
   147  		duration *= 2
   148  		if duration <= maxDuration {
   149  			t.Logf("retrying with %s duration", duration)
   150  		}
   151  	}
   152  
   153  	if badOS[runtime.GOOS] {
   154  		t.Skipf("ignoring failure on %s; see golang.org/issue/13841", runtime.GOOS)
   155  		return
   156  	}
   157  	// Ignore the failure if the tests are running in a QEMU-based emulator,
   158  	// QEMU is not perfect at emulating everything.
   159  	// IN_QEMU environmental variable is set by some of the Go builders.
   160  	// IN_QEMU=1 indicates that the tests are running in QEMU. See issue 9605.
   161  	if os.Getenv("IN_QEMU") == "1" {
   162  		t.Skip("ignore the failure in QEMU; see golang.org/issue/9605")
   163  		return
   164  	}
   165  	t.FailNow()
   166  }
   167  
   168  func profileOk(t *testing.T, need []string, prof bytes.Buffer, duration time.Duration) (ok bool) {
   169  	ok = true
   170  
   171  	// Check that profile is well formed and contains need.
   172  	have := make([]uintptr, len(need))
   173  	var samples uintptr
   174  	parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr) {
   175  		samples += count
   176  		for _, pc := range stk {
   177  			f := runtime.FuncForPC(pc)
   178  			if f == nil {
   179  				continue
   180  			}
   181  			for i, name := range need {
   182  				if strings.Contains(f.Name(), name) {
   183  					have[i] += count
   184  				}
   185  			}
   186  		}
   187  	})
   188  	t.Logf("total %d CPU profile samples collected", samples)
   189  
   190  	if samples < 10 && runtime.GOOS == "windows" {
   191  		// On some windows machines we end up with
   192  		// not enough samples due to coarse timer
   193  		// resolution. Let it go.
   194  		t.Log("too few samples on Windows (golang.org/issue/10842)")
   195  		return false
   196  	}
   197  
   198  	// Check that we got a reasonable number of samples.
   199  	// We used to always require at least ideal/4 samples,
   200  	// but that is too hard to guarantee on a loaded system.
   201  	// Now we accept 10 or more samples, which we take to be
   202  	// enough to show that at least some profiling is occurring.
   203  	if ideal := uintptr(duration * 100 / time.Second); samples == 0 || (samples < ideal/4 && samples < 10) {
   204  		t.Logf("too few samples; got %d, want at least %d, ideally %d", samples, ideal/4, ideal)
   205  		ok = false
   206  	}
   207  
   208  	if len(need) == 0 {
   209  		return ok
   210  	}
   211  
   212  	var total uintptr
   213  	for i, name := range need {
   214  		total += have[i]
   215  		t.Logf("%s: %d\n", name, have[i])
   216  	}
   217  	if total == 0 {
   218  		t.Logf("no samples in expected functions")
   219  		ok = false
   220  	}
   221  	// We'd like to check a reasonable minimum, like
   222  	// total / len(have) / smallconstant, but this test is
   223  	// pretty flaky (see bug 7095).  So we'll just test to
   224  	// make sure we got at least one sample.
   225  	min := uintptr(1)
   226  	for i, name := range need {
   227  		if have[i] < min {
   228  			t.Logf("%s has %d samples out of %d, want at least %d, ideally %d", name, have[i], total, min, total/uintptr(len(have)))
   229  			ok = false
   230  		}
   231  	}
   232  	return ok
   233  }
   234  
   235  // Fork can hang if preempted with signals frequently enough (see issue 5517).
   236  // Ensure that we do not do this.
   237  func TestCPUProfileWithFork(t *testing.T) {
   238  	testenv.MustHaveExec(t)
   239  
   240  	heap := 1 << 30
   241  	if runtime.GOOS == "android" {
   242  		// Use smaller size for Android to avoid crash.
   243  		heap = 100 << 20
   244  	}
   245  	if testing.Short() {
   246  		heap = 100 << 20
   247  	}
   248  	// This makes fork slower.
   249  	garbage := make([]byte, heap)
   250  	// Need to touch the slice, otherwise it won't be paged in.
   251  	done := make(chan bool)
   252  	go func() {
   253  		for i := range garbage {
   254  			garbage[i] = 42
   255  		}
   256  		done <- true
   257  	}()
   258  	<-done
   259  
   260  	var prof bytes.Buffer
   261  	if err := StartCPUProfile(&prof); err != nil {
   262  		t.Fatal(err)
   263  	}
   264  	defer StopCPUProfile()
   265  
   266  	for i := 0; i < 10; i++ {
   267  		exec.Command(os.Args[0], "-h").CombinedOutput()
   268  	}
   269  }
   270  
   271  // Test that profiler does not observe runtime.gogo as "user" goroutine execution.
   272  // If it did, it would see inconsistent state and would either record an incorrect stack
   273  // or crash because the stack was malformed.
   274  func TestGoroutineSwitch(t *testing.T) {
   275  	// How much to try. These defaults take about 1 seconds
   276  	// on a 2012 MacBook Pro. The ones in short mode take
   277  	// about 0.1 seconds.
   278  	tries := 10
   279  	count := 1000000
   280  	if testing.Short() {
   281  		tries = 1
   282  	}
   283  	for try := 0; try < tries; try++ {
   284  		var prof bytes.Buffer
   285  		if err := StartCPUProfile(&prof); err != nil {
   286  			t.Fatal(err)
   287  		}
   288  		for i := 0; i < count; i++ {
   289  			runtime.Gosched()
   290  		}
   291  		StopCPUProfile()
   292  
   293  		// Read profile to look for entries for runtime.gogo with an attempt at a traceback.
   294  		// The special entry
   295  		parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr) {
   296  			// An entry with two frames with 'System' in its top frame
   297  			// exists to record a PC without a traceback. Those are okay.
   298  			if len(stk) == 2 {
   299  				f := runtime.FuncForPC(stk[1])
   300  				if f != nil && (f.Name() == "runtime._System" || f.Name() == "runtime._ExternalCode" || f.Name() == "runtime._GC") {
   301  					return
   302  				}
   303  			}
   304  
   305  			// Otherwise, should not see runtime.gogo.
   306  			// The place we'd see it would be the inner most frame.
   307  			f := runtime.FuncForPC(stk[0])
   308  			if f != nil && f.Name() == "runtime.gogo" {
   309  				var buf bytes.Buffer
   310  				for _, pc := range stk {
   311  					f := runtime.FuncForPC(pc)
   312  					if f == nil {
   313  						fmt.Fprintf(&buf, "%#x ?:0\n", pc)
   314  					} else {
   315  						file, line := f.FileLine(pc)
   316  						fmt.Fprintf(&buf, "%#x %s:%d\n", pc, file, line)
   317  					}
   318  				}
   319  				t.Fatalf("found profile entry for runtime.gogo:\n%s", buf.String())
   320  			}
   321  		})
   322  	}
   323  }
   324  
   325  // Test that profiling of division operations is okay, especially on ARM. See issue 6681.
   326  func TestMathBigDivide(t *testing.T) {
   327  	testCPUProfile(t, nil, func(duration time.Duration) {
   328  		t := time.After(duration)
   329  		pi := new(big.Int)
   330  		for {
   331  			for i := 0; i < 100; i++ {
   332  				n := big.NewInt(2646693125139304345)
   333  				d := big.NewInt(842468587426513207)
   334  				pi.Div(n, d)
   335  			}
   336  			select {
   337  			case <-t:
   338  				return
   339  			default:
   340  			}
   341  		}
   342  	})
   343  }
   344  
   345  // Operating systems that are expected to fail the tests. See issue 13841.
   346  var badOS = map[string]bool{
   347  	"darwin":    true,
   348  	"netbsd":    true,
   349  	"plan9":     true,
   350  	"dragonfly": true,
   351  	"solaris":   true,
   352  }
   353  
   354  func TestBlockProfile(t *testing.T) {
   355  	type TestCase struct {
   356  		name string
   357  		f    func()
   358  		re   string
   359  	}
   360  	tests := [...]TestCase{
   361  		{"chan recv", blockChanRecv, `
   362  [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]+
   363  #	0x[0-9,a-f]+	runtime\.chanrecv1\+0x[0-9,a-f]+	.*/src/runtime/chan.go:[0-9]+
   364  #	0x[0-9,a-f]+	runtime/pprof_test\.blockChanRecv\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   365  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   366  `},
   367  		{"chan send", blockChanSend, `
   368  [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]+
   369  #	0x[0-9,a-f]+	runtime\.chansend1\+0x[0-9,a-f]+	.*/src/runtime/chan.go:[0-9]+
   370  #	0x[0-9,a-f]+	runtime/pprof_test\.blockChanSend\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   371  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   372  `},
   373  		{"chan close", blockChanClose, `
   374  [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]+
   375  #	0x[0-9,a-f]+	runtime\.chanrecv1\+0x[0-9,a-f]+	.*/src/runtime/chan.go:[0-9]+
   376  #	0x[0-9,a-f]+	runtime/pprof_test\.blockChanClose\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   377  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   378  `},
   379  		{"select recv async", blockSelectRecvAsync, `
   380  [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]+
   381  #	0x[0-9,a-f]+	runtime\.selectgo\+0x[0-9,a-f]+	.*/src/runtime/select.go:[0-9]+
   382  #	0x[0-9,a-f]+	runtime/pprof_test\.blockSelectRecvAsync\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   383  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   384  `},
   385  		{"select send sync", blockSelectSendSync, `
   386  [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]+
   387  #	0x[0-9,a-f]+	runtime\.selectgo\+0x[0-9,a-f]+	.*/src/runtime/select.go:[0-9]+
   388  #	0x[0-9,a-f]+	runtime/pprof_test\.blockSelectSendSync\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   389  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   390  `},
   391  		{"mutex", blockMutex, `
   392  [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]+
   393  #	0x[0-9,a-f]+	sync\.\(\*Mutex\)\.Lock\+0x[0-9,a-f]+	.*/src/sync/mutex\.go:[0-9]+
   394  #	0x[0-9,a-f]+	runtime/pprof_test\.blockMutex\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   395  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   396  `},
   397  		{"cond", blockCond, `
   398  [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]+
   399  #	0x[0-9,a-f]+	sync\.\(\*Cond\)\.Wait\+0x[0-9,a-f]+	.*/src/sync/cond\.go:[0-9]+
   400  #	0x[0-9,a-f]+	runtime/pprof_test\.blockCond\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   401  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   402  `},
   403  	}
   404  
   405  	runtime.SetBlockProfileRate(1)
   406  	defer runtime.SetBlockProfileRate(0)
   407  	for _, test := range tests {
   408  		test.f()
   409  	}
   410  	var w bytes.Buffer
   411  	Lookup("block").WriteTo(&w, 1)
   412  	prof := w.String()
   413  
   414  	if !strings.HasPrefix(prof, "--- contention:\ncycles/second=") {
   415  		t.Fatalf("Bad profile header:\n%v", prof)
   416  	}
   417  
   418  	if strings.HasSuffix(prof, "#\t0x0\n\n") {
   419  		t.Errorf("Useless 0 suffix:\n%v", prof)
   420  	}
   421  
   422  	for _, test := range tests {
   423  		if !regexp.MustCompile(strings.Replace(test.re, "\t", "\t+", -1)).MatchString(prof) {
   424  			t.Fatalf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof)
   425  		}
   426  	}
   427  }
   428  
   429  const blockDelay = 10 * time.Millisecond
   430  
   431  func blockChanRecv() {
   432  	c := make(chan bool)
   433  	go func() {
   434  		time.Sleep(blockDelay)
   435  		c <- true
   436  	}()
   437  	<-c
   438  }
   439  
   440  func blockChanSend() {
   441  	c := make(chan bool)
   442  	go func() {
   443  		time.Sleep(blockDelay)
   444  		<-c
   445  	}()
   446  	c <- true
   447  }
   448  
   449  func blockChanClose() {
   450  	c := make(chan bool)
   451  	go func() {
   452  		time.Sleep(blockDelay)
   453  		close(c)
   454  	}()
   455  	<-c
   456  }
   457  
   458  func blockSelectRecvAsync() {
   459  	const numTries = 3
   460  	c := make(chan bool, 1)
   461  	c2 := make(chan bool, 1)
   462  	go func() {
   463  		for i := 0; i < numTries; i++ {
   464  			time.Sleep(blockDelay)
   465  			c <- true
   466  		}
   467  	}()
   468  	for i := 0; i < numTries; i++ {
   469  		select {
   470  		case <-c:
   471  		case <-c2:
   472  		}
   473  	}
   474  }
   475  
   476  func blockSelectSendSync() {
   477  	c := make(chan bool)
   478  	c2 := make(chan bool)
   479  	go func() {
   480  		time.Sleep(blockDelay)
   481  		<-c
   482  	}()
   483  	select {
   484  	case c <- true:
   485  	case c2 <- true:
   486  	}
   487  }
   488  
   489  func blockMutex() {
   490  	var mu sync.Mutex
   491  	mu.Lock()
   492  	go func() {
   493  		time.Sleep(blockDelay)
   494  		mu.Unlock()
   495  	}()
   496  	mu.Lock()
   497  }
   498  
   499  func blockCond() {
   500  	var mu sync.Mutex
   501  	c := sync.NewCond(&mu)
   502  	mu.Lock()
   503  	go func() {
   504  		time.Sleep(blockDelay)
   505  		mu.Lock()
   506  		c.Signal()
   507  		mu.Unlock()
   508  	}()
   509  	c.Wait()
   510  	mu.Unlock()
   511  }
   512  
   513  func TestMutexProfile(t *testing.T) {
   514  	old := runtime.SetMutexProfileFraction(1)
   515  	defer runtime.SetMutexProfileFraction(old)
   516  	if old != 0 {
   517  		t.Fatalf("need MutexProfileRate 0, got %d", old)
   518  	}
   519  
   520  	blockMutex()
   521  
   522  	var w bytes.Buffer
   523  	Lookup("mutex").WriteTo(&w, 1)
   524  	prof := w.String()
   525  
   526  	if !strings.HasPrefix(prof, "--- mutex:\ncycles/second=") {
   527  		t.Errorf("Bad profile header:\n%v", prof)
   528  	}
   529  	prof = strings.Trim(prof, "\n")
   530  	lines := strings.Split(prof, "\n")
   531  	if len(lines) != 6 {
   532  		t.Errorf("expected 6 lines, got %d %q\n%s", len(lines), prof, prof)
   533  	}
   534  	if len(lines) < 6 {
   535  		return
   536  	}
   537  	// checking that the line is like "35258904 1 @ 0x48288d 0x47cd28 0x458931"
   538  	r2 := `^\d+ 1 @(?: 0x[[:xdigit:]]+)+`
   539  	//r2 := "^[0-9]+ 1 @ 0x[0-9a-f x]+$"
   540  	if ok, err := regexp.MatchString(r2, lines[3]); err != nil || !ok {
   541  		t.Errorf("%q didn't match %q", lines[3], r2)
   542  	}
   543  	r3 := "^#.*runtime/pprof_test.blockMutex.*$"
   544  	if ok, err := regexp.MatchString(r3, lines[5]); err != nil || !ok {
   545  		t.Errorf("%q didn't match %q", lines[5], r3)
   546  	}
   547  }
   548  
   549  func func1(c chan int) { <-c }
   550  func func2(c chan int) { <-c }
   551  func func3(c chan int) { <-c }
   552  func func4(c chan int) { <-c }
   553  
   554  func TestGoroutineCounts(t *testing.T) {
   555  	if runtime.GOOS == "openbsd" {
   556  		testenv.SkipFlaky(t, 15156)
   557  	}
   558  	c := make(chan int)
   559  	for i := 0; i < 100; i++ {
   560  		if i%10 == 0 {
   561  			go func1(c)
   562  			continue
   563  		}
   564  		if i%2 == 0 {
   565  			go func2(c)
   566  			continue
   567  		}
   568  		go func3(c)
   569  	}
   570  	time.Sleep(10 * time.Millisecond) // let goroutines block on channel
   571  
   572  	var w bytes.Buffer
   573  	goroutineProf := Lookup("goroutine")
   574  
   575  	// Check debug profile
   576  	goroutineProf.WriteTo(&w, 1)
   577  	prof := w.String()
   578  
   579  	if !containsInOrder(prof, "\n50 @ ", "\n40 @", "\n10 @", "\n1 @") {
   580  		t.Errorf("expected sorted goroutine counts:\n%s", prof)
   581  	}
   582  
   583  	// Check proto profile
   584  	w.Reset()
   585  	goroutineProf.WriteTo(&w, 0)
   586  	p, err := profile.Parse(&w)
   587  	if err != nil {
   588  		t.Errorf("error parsing protobuf profile: %v", err)
   589  	}
   590  	if err := p.CheckValid(); err != nil {
   591  		t.Errorf("protobuf profile is invalid: %v", err)
   592  	}
   593  	if !containsCounts(p, []int64{50, 40, 10, 1}) {
   594  		t.Errorf("expected count profile to contain goroutines with counts %v, got %v",
   595  			[]int64{50, 40, 10, 1}, p)
   596  	}
   597  
   598  	close(c)
   599  
   600  	time.Sleep(10 * time.Millisecond) // let goroutines exit
   601  }
   602  
   603  func containsInOrder(s string, all ...string) bool {
   604  	for _, t := range all {
   605  		i := strings.Index(s, t)
   606  		if i < 0 {
   607  			return false
   608  		}
   609  		s = s[i+len(t):]
   610  	}
   611  	return true
   612  }
   613  
   614  func containsCounts(prof *profile.Profile, counts []int64) bool {
   615  	m := make(map[int64]int)
   616  	for _, c := range counts {
   617  		m[c]++
   618  	}
   619  	for _, s := range prof.Sample {
   620  		// The count is the single value in the sample
   621  		if len(s.Value) != 1 {
   622  			return false
   623  		}
   624  		m[s.Value[0]]--
   625  	}
   626  	for _, n := range m {
   627  		if n > 0 {
   628  			return false
   629  		}
   630  	}
   631  	return true
   632  }