github.com/d4l3k/go@v0.0.0-20151015000803-65fc379daeda/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()) {
    27  	// We only need to get one 100 Hz clock tick, so we've got
    28  	// a 25x 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 250ms period.
    32  	t0 := time.Now()
    33  	for i := 0; i < 500 || time.Since(t0) < 250*time.Millisecond; 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() {
    72  		cpuHogger(cpuHog1)
    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() {
    79  		c := make(chan int)
    80  		go func() {
    81  			cpuHogger(cpuHog1)
    82  			c <- 1
    83  		}()
    84  		cpuHogger(cpuHog2)
    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, 2 for the per-sample header on at least one sample, 3 for the trailer.
    96  	if l < 5+2+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/6047", 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()) {
   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  	var prof bytes.Buffer
   142  	if err := StartCPUProfile(&prof); err != nil {
   143  		t.Fatal(err)
   144  	}
   145  	f()
   146  	StopCPUProfile()
   147  
   148  	// Check that profile is well formed and contains need.
   149  	have := make([]uintptr, len(need))
   150  	var samples uintptr
   151  	parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr) {
   152  		samples += count
   153  		for _, pc := range stk {
   154  			f := runtime.FuncForPC(pc)
   155  			if f == nil {
   156  				continue
   157  			}
   158  			for i, name := range need {
   159  				if strings.Contains(f.Name(), name) {
   160  					have[i] += count
   161  				}
   162  			}
   163  		}
   164  	})
   165  	t.Logf("total %d CPU profile samples collected", samples)
   166  
   167  	if samples < 10 && runtime.GOOS == "windows" {
   168  		// On some windows machines we end up with
   169  		// not enough samples due to coarse timer
   170  		// resolution. Let it go.
   171  		t.Skip("too few samples on Windows (golang.org/issue/10842)")
   172  	}
   173  
   174  	if len(need) == 0 {
   175  		return
   176  	}
   177  
   178  	var total uintptr
   179  	for i, name := range need {
   180  		total += have[i]
   181  		t.Logf("%s: %d\n", name, have[i])
   182  	}
   183  	ok := true
   184  	if total == 0 {
   185  		t.Logf("no CPU profile samples collected")
   186  		ok = false
   187  	}
   188  	// We'd like to check a reasonable minimum, like
   189  	// total / len(have) / smallconstant, but this test is
   190  	// pretty flaky (see bug 7095).  So we'll just test to
   191  	// make sure we got at least one sample.
   192  	min := uintptr(1)
   193  	for i, name := range need {
   194  		if have[i] < min {
   195  			t.Logf("%s has %d samples out of %d, want at least %d, ideally %d", name, have[i], total, min, total/uintptr(len(have)))
   196  			ok = false
   197  		}
   198  	}
   199  
   200  	if !ok {
   201  		if badOS[runtime.GOOS] {
   202  			t.Skipf("ignoring failure on %s; see golang.org/issue/6047", runtime.GOOS)
   203  			return
   204  		}
   205  		// Ignore the failure if the tests are running in a QEMU-based emulator,
   206  		// QEMU is not perfect at emulating everything.
   207  		// IN_QEMU environmental variable is set by some of the Go builders.
   208  		// IN_QEMU=1 indicates that the tests are running in QEMU. See issue 9605.
   209  		if os.Getenv("IN_QEMU") == "1" {
   210  			t.Skip("ignore the failure in QEMU; see golang.org/issue/9605")
   211  			return
   212  		}
   213  		t.FailNow()
   214  	}
   215  }
   216  
   217  // Fork can hang if preempted with signals frequently enough (see issue 5517).
   218  // Ensure that we do not do this.
   219  func TestCPUProfileWithFork(t *testing.T) {
   220  	testenv.MustHaveExec(t)
   221  
   222  	heap := 1 << 30
   223  	if runtime.GOOS == "android" {
   224  		// Use smaller size for Android to avoid crash.
   225  		heap = 100 << 20
   226  	}
   227  	if testing.Short() {
   228  		heap = 100 << 20
   229  	}
   230  	// This makes fork slower.
   231  	garbage := make([]byte, heap)
   232  	// Need to touch the slice, otherwise it won't be paged in.
   233  	done := make(chan bool)
   234  	go func() {
   235  		for i := range garbage {
   236  			garbage[i] = 42
   237  		}
   238  		done <- true
   239  	}()
   240  	<-done
   241  
   242  	var prof bytes.Buffer
   243  	if err := StartCPUProfile(&prof); err != nil {
   244  		t.Fatal(err)
   245  	}
   246  	defer StopCPUProfile()
   247  
   248  	for i := 0; i < 10; i++ {
   249  		exec.Command(os.Args[0], "-h").CombinedOutput()
   250  	}
   251  }
   252  
   253  // Test that profiler does not observe runtime.gogo as "user" goroutine execution.
   254  // If it did, it would see inconsistent state and would either record an incorrect stack
   255  // or crash because the stack was malformed.
   256  func TestGoroutineSwitch(t *testing.T) {
   257  	// How much to try. These defaults take about 1 seconds
   258  	// on a 2012 MacBook Pro. The ones in short mode take
   259  	// about 0.1 seconds.
   260  	tries := 10
   261  	count := 1000000
   262  	if testing.Short() {
   263  		tries = 1
   264  	}
   265  	for try := 0; try < tries; try++ {
   266  		var prof bytes.Buffer
   267  		if err := StartCPUProfile(&prof); err != nil {
   268  			t.Fatal(err)
   269  		}
   270  		for i := 0; i < count; i++ {
   271  			runtime.Gosched()
   272  		}
   273  		StopCPUProfile()
   274  
   275  		// Read profile to look for entries for runtime.gogo with an attempt at a traceback.
   276  		// The special entry
   277  		parseProfile(t, prof.Bytes(), func(count uintptr, stk []uintptr) {
   278  			// An entry with two frames with 'System' in its top frame
   279  			// exists to record a PC without a traceback. Those are okay.
   280  			if len(stk) == 2 {
   281  				f := runtime.FuncForPC(stk[1])
   282  				if f != nil && (f.Name() == "runtime._System" || f.Name() == "runtime._ExternalCode" || f.Name() == "runtime._GC") {
   283  					return
   284  				}
   285  			}
   286  
   287  			// Otherwise, should not see runtime.gogo.
   288  			// The place we'd see it would be the inner most frame.
   289  			f := runtime.FuncForPC(stk[0])
   290  			if f != nil && f.Name() == "runtime.gogo" {
   291  				var buf bytes.Buffer
   292  				for _, pc := range stk {
   293  					f := runtime.FuncForPC(pc)
   294  					if f == nil {
   295  						fmt.Fprintf(&buf, "%#x ?:0\n", pc)
   296  					} else {
   297  						file, line := f.FileLine(pc)
   298  						fmt.Fprintf(&buf, "%#x %s:%d\n", pc, file, line)
   299  					}
   300  				}
   301  				t.Fatalf("found profile entry for runtime.gogo:\n%s", buf.String())
   302  			}
   303  		})
   304  	}
   305  }
   306  
   307  // Test that profiling of division operations is okay, especially on ARM. See issue 6681.
   308  func TestMathBigDivide(t *testing.T) {
   309  	testCPUProfile(t, nil, func() {
   310  		t := time.After(5 * time.Second)
   311  		pi := new(big.Int)
   312  		for {
   313  			for i := 0; i < 100; i++ {
   314  				n := big.NewInt(2646693125139304345)
   315  				d := big.NewInt(842468587426513207)
   316  				pi.Div(n, d)
   317  			}
   318  			select {
   319  			case <-t:
   320  				return
   321  			default:
   322  			}
   323  		}
   324  	})
   325  }
   326  
   327  // Operating systems that are expected to fail the tests. See issue 6047.
   328  var badOS = map[string]bool{
   329  	"darwin": true,
   330  	"netbsd": true,
   331  	"plan9":  true,
   332  }
   333  
   334  func TestBlockProfile(t *testing.T) {
   335  	type TestCase struct {
   336  		name string
   337  		f    func()
   338  		re   string
   339  	}
   340  	tests := [...]TestCase{
   341  		{"chan recv", blockChanRecv, `
   342  [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]+
   343  #	0x[0-9,a-f]+	runtime\.chanrecv1\+0x[0-9,a-f]+	.*/src/runtime/chan.go:[0-9]+
   344  #	0x[0-9,a-f]+	runtime/pprof_test\.blockChanRecv\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   345  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   346  `},
   347  		{"chan send", blockChanSend, `
   348  [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]+
   349  #	0x[0-9,a-f]+	runtime\.chansend1\+0x[0-9,a-f]+	.*/src/runtime/chan.go:[0-9]+
   350  #	0x[0-9,a-f]+	runtime/pprof_test\.blockChanSend\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   351  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   352  `},
   353  		{"chan close", blockChanClose, `
   354  [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]+
   355  #	0x[0-9,a-f]+	runtime\.chanrecv1\+0x[0-9,a-f]+	.*/src/runtime/chan.go:[0-9]+
   356  #	0x[0-9,a-f]+	runtime/pprof_test\.blockChanClose\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   357  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   358  `},
   359  		{"select recv async", blockSelectRecvAsync, `
   360  [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]+
   361  #	0x[0-9,a-f]+	runtime\.selectgo\+0x[0-9,a-f]+	.*/src/runtime/select.go:[0-9]+
   362  #	0x[0-9,a-f]+	runtime/pprof_test\.blockSelectRecvAsync\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   363  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   364  `},
   365  		{"select send sync", blockSelectSendSync, `
   366  [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]+
   367  #	0x[0-9,a-f]+	runtime\.selectgo\+0x[0-9,a-f]+	.*/src/runtime/select.go:[0-9]+
   368  #	0x[0-9,a-f]+	runtime/pprof_test\.blockSelectSendSync\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   369  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   370  `},
   371  		{"mutex", blockMutex, `
   372  [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]+
   373  #	0x[0-9,a-f]+	sync\.\(\*Mutex\)\.Lock\+0x[0-9,a-f]+	.*/src/sync/mutex\.go:[0-9]+
   374  #	0x[0-9,a-f]+	runtime/pprof_test\.blockMutex\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   375  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   376  `},
   377  		{"cond", blockCond, `
   378  [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]+
   379  #	0x[0-9,a-f]+	sync\.\(\*Cond\)\.Wait\+0x[0-9,a-f]+	.*/src/sync/cond\.go:[0-9]+
   380  #	0x[0-9,a-f]+	runtime/pprof_test\.blockCond\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   381  #	0x[0-9,a-f]+	runtime/pprof_test\.TestBlockProfile\+0x[0-9,a-f]+	.*/src/runtime/pprof/pprof_test.go:[0-9]+
   382  `},
   383  	}
   384  
   385  	runtime.SetBlockProfileRate(1)
   386  	defer runtime.SetBlockProfileRate(0)
   387  	for _, test := range tests {
   388  		test.f()
   389  	}
   390  	var w bytes.Buffer
   391  	Lookup("block").WriteTo(&w, 1)
   392  	prof := w.String()
   393  
   394  	if !strings.HasPrefix(prof, "--- contention:\ncycles/second=") {
   395  		t.Fatalf("Bad profile header:\n%v", prof)
   396  	}
   397  
   398  	for _, test := range tests {
   399  		if !regexp.MustCompile(strings.Replace(test.re, "\t", "\t+", -1)).MatchString(prof) {
   400  			t.Fatalf("Bad %v entry, expect:\n%v\ngot:\n%v", test.name, test.re, prof)
   401  		}
   402  	}
   403  }
   404  
   405  const blockDelay = 10 * time.Millisecond
   406  
   407  func blockChanRecv() {
   408  	c := make(chan bool)
   409  	go func() {
   410  		time.Sleep(blockDelay)
   411  		c <- true
   412  	}()
   413  	<-c
   414  }
   415  
   416  func blockChanSend() {
   417  	c := make(chan bool)
   418  	go func() {
   419  		time.Sleep(blockDelay)
   420  		<-c
   421  	}()
   422  	c <- true
   423  }
   424  
   425  func blockChanClose() {
   426  	c := make(chan bool)
   427  	go func() {
   428  		time.Sleep(blockDelay)
   429  		close(c)
   430  	}()
   431  	<-c
   432  }
   433  
   434  func blockSelectRecvAsync() {
   435  	c := make(chan bool, 1)
   436  	c2 := make(chan bool, 1)
   437  	go func() {
   438  		time.Sleep(blockDelay)
   439  		c <- true
   440  	}()
   441  	select {
   442  	case <-c:
   443  	case <-c2:
   444  	}
   445  }
   446  
   447  func blockSelectSendSync() {
   448  	c := make(chan bool)
   449  	c2 := make(chan bool)
   450  	go func() {
   451  		time.Sleep(blockDelay)
   452  		<-c
   453  	}()
   454  	select {
   455  	case c <- true:
   456  	case c2 <- true:
   457  	}
   458  }
   459  
   460  func blockMutex() {
   461  	var mu sync.Mutex
   462  	mu.Lock()
   463  	go func() {
   464  		time.Sleep(blockDelay)
   465  		mu.Unlock()
   466  	}()
   467  	mu.Lock()
   468  }
   469  
   470  func blockCond() {
   471  	var mu sync.Mutex
   472  	c := sync.NewCond(&mu)
   473  	mu.Lock()
   474  	go func() {
   475  		time.Sleep(blockDelay)
   476  		mu.Lock()
   477  		c.Signal()
   478  		mu.Unlock()
   479  	}()
   480  	c.Wait()
   481  	mu.Unlock()
   482  }