Black Lives Matter. Support the Equal Justice Initiative.

Source file src/runtime/trace/trace_stack_test.go

Documentation: runtime/trace

     1  // Copyright 2014 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  package trace_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"internal/testenv"
    11  	"internal/trace"
    12  	"net"
    13  	"os"
    14  	"runtime"
    15  	. "runtime/trace"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  	"text/tabwriter"
    20  	"time"
    21  )
    22  
    23  // TestTraceSymbolize tests symbolization and that events has proper stacks.
    24  // In particular that we strip bottom uninteresting frames like goexit,
    25  // top uninteresting frames (runtime guts).
    26  func TestTraceSymbolize(t *testing.T) {
    27  	skipTraceSymbolizeTestIfNecessary(t)
    28  
    29  	buf := new(bytes.Buffer)
    30  	if err := Start(buf); err != nil {
    31  		t.Fatalf("failed to start tracing: %v", err)
    32  	}
    33  	defer Stop() // in case of early return
    34  
    35  	// Now we will do a bunch of things for which we verify stacks later.
    36  	// It is impossible to ensure that a goroutine has actually blocked
    37  	// on a channel, in a select or otherwise. So we kick off goroutines
    38  	// that need to block first in the hope that while we are executing
    39  	// the rest of the test, they will block.
    40  	go func() { // func1
    41  		select {}
    42  	}()
    43  	go func() { // func2
    44  		var c chan int
    45  		c <- 0
    46  	}()
    47  	go func() { // func3
    48  		var c chan int
    49  		<-c
    50  	}()
    51  	done1 := make(chan bool)
    52  	go func() { // func4
    53  		<-done1
    54  	}()
    55  	done2 := make(chan bool)
    56  	go func() { // func5
    57  		done2 <- true
    58  	}()
    59  	c1 := make(chan int)
    60  	c2 := make(chan int)
    61  	go func() { // func6
    62  		select {
    63  		case <-c1:
    64  		case <-c2:
    65  		}
    66  	}()
    67  	var mu sync.Mutex
    68  	mu.Lock()
    69  	go func() { // func7
    70  		mu.Lock()
    71  		mu.Unlock()
    72  	}()
    73  	var wg sync.WaitGroup
    74  	wg.Add(1)
    75  	go func() { // func8
    76  		wg.Wait()
    77  	}()
    78  	cv := sync.NewCond(&sync.Mutex{})
    79  	go func() { // func9
    80  		cv.L.Lock()
    81  		cv.Wait()
    82  		cv.L.Unlock()
    83  	}()
    84  	ln, err := net.Listen("tcp", "127.0.0.1:0")
    85  	if err != nil {
    86  		t.Fatalf("failed to listen: %v", err)
    87  	}
    88  	go func() { // func10
    89  		c, err := ln.Accept()
    90  		if err != nil {
    91  			t.Errorf("failed to accept: %v", err)
    92  			return
    93  		}
    94  		c.Close()
    95  	}()
    96  	rp, wp, err := os.Pipe()
    97  	if err != nil {
    98  		t.Fatalf("failed to create a pipe: %v", err)
    99  	}
   100  	defer rp.Close()
   101  	defer wp.Close()
   102  	pipeReadDone := make(chan bool)
   103  	go func() { // func11
   104  		var data [1]byte
   105  		rp.Read(data[:])
   106  		pipeReadDone <- true
   107  	}()
   108  
   109  	time.Sleep(100 * time.Millisecond)
   110  	runtime.GC()
   111  	runtime.Gosched()
   112  	time.Sleep(100 * time.Millisecond) // the last chance for the goroutines above to block
   113  	done1 <- true
   114  	<-done2
   115  	select {
   116  	case c1 <- 0:
   117  	case c2 <- 0:
   118  	}
   119  	mu.Unlock()
   120  	wg.Done()
   121  	cv.Signal()
   122  	c, err := net.Dial("tcp", ln.Addr().String())
   123  	if err != nil {
   124  		t.Fatalf("failed to dial: %v", err)
   125  	}
   126  	c.Close()
   127  	var data [1]byte
   128  	wp.Write(data[:])
   129  	<-pipeReadDone
   130  
   131  	oldGoMaxProcs := runtime.GOMAXPROCS(0)
   132  	runtime.GOMAXPROCS(oldGoMaxProcs + 1)
   133  
   134  	Stop()
   135  
   136  	runtime.GOMAXPROCS(oldGoMaxProcs)
   137  
   138  	events, _ := parseTrace(t, buf)
   139  
   140  	// Now check that the stacks are correct.
   141  	type eventDesc struct {
   142  		Type byte
   143  		Stk  []frame
   144  	}
   145  	want := []eventDesc{
   146  		{trace.EvGCStart, []frame{
   147  			{"runtime.GC", 0},
   148  			{"runtime/trace_test.TestTraceSymbolize", 0},
   149  			{"testing.tRunner", 0},
   150  		}},
   151  		{trace.EvGoStart, []frame{
   152  			{"runtime/trace_test.TestTraceSymbolize.func1", 0},
   153  		}},
   154  		{trace.EvGoSched, []frame{
   155  			{"runtime/trace_test.TestTraceSymbolize", 111},
   156  			{"testing.tRunner", 0},
   157  		}},
   158  		{trace.EvGoCreate, []frame{
   159  			{"runtime/trace_test.TestTraceSymbolize", 40},
   160  			{"testing.tRunner", 0},
   161  		}},
   162  		{trace.EvGoStop, []frame{
   163  			{"runtime.block", 0},
   164  			{"runtime/trace_test.TestTraceSymbolize.func1", 0},
   165  		}},
   166  		{trace.EvGoStop, []frame{
   167  			{"runtime.chansend1", 0},
   168  			{"runtime/trace_test.TestTraceSymbolize.func2", 0},
   169  		}},
   170  		{trace.EvGoStop, []frame{
   171  			{"runtime.chanrecv1", 0},
   172  			{"runtime/trace_test.TestTraceSymbolize.func3", 0},
   173  		}},
   174  		{trace.EvGoBlockRecv, []frame{
   175  			{"runtime.chanrecv1", 0},
   176  			{"runtime/trace_test.TestTraceSymbolize.func4", 0},
   177  		}},
   178  		{trace.EvGoUnblock, []frame{
   179  			{"runtime.chansend1", 0},
   180  			{"runtime/trace_test.TestTraceSymbolize", 113},
   181  			{"testing.tRunner", 0},
   182  		}},
   183  		{trace.EvGoBlockSend, []frame{
   184  			{"runtime.chansend1", 0},
   185  			{"runtime/trace_test.TestTraceSymbolize.func5", 0},
   186  		}},
   187  		{trace.EvGoUnblock, []frame{
   188  			{"runtime.chanrecv1", 0},
   189  			{"runtime/trace_test.TestTraceSymbolize", 114},
   190  			{"testing.tRunner", 0},
   191  		}},
   192  		{trace.EvGoBlockSelect, []frame{
   193  			{"runtime.selectgo", 0},
   194  			{"runtime/trace_test.TestTraceSymbolize.func6", 0},
   195  		}},
   196  		{trace.EvGoUnblock, []frame{
   197  			{"runtime.selectgo", 0},
   198  			{"runtime/trace_test.TestTraceSymbolize", 115},
   199  			{"testing.tRunner", 0},
   200  		}},
   201  		{trace.EvGoBlockSync, []frame{
   202  			{"sync.(*Mutex).Lock", 0},
   203  			{"runtime/trace_test.TestTraceSymbolize.func7", 0},
   204  		}},
   205  		{trace.EvGoUnblock, []frame{
   206  			{"sync.(*Mutex).Unlock", 0},
   207  			{"runtime/trace_test.TestTraceSymbolize", 0},
   208  			{"testing.tRunner", 0},
   209  		}},
   210  		{trace.EvGoBlockSync, []frame{
   211  			{"sync.(*WaitGroup).Wait", 0},
   212  			{"runtime/trace_test.TestTraceSymbolize.func8", 0},
   213  		}},
   214  		{trace.EvGoUnblock, []frame{
   215  			{"sync.(*WaitGroup).Add", 0},
   216  			{"sync.(*WaitGroup).Done", 0},
   217  			{"runtime/trace_test.TestTraceSymbolize", 120},
   218  			{"testing.tRunner", 0},
   219  		}},
   220  		{trace.EvGoBlockCond, []frame{
   221  			{"sync.(*Cond).Wait", 0},
   222  			{"runtime/trace_test.TestTraceSymbolize.func9", 0},
   223  		}},
   224  		{trace.EvGoUnblock, []frame{
   225  			{"sync.(*Cond).Signal", 0},
   226  			{"runtime/trace_test.TestTraceSymbolize", 0},
   227  			{"testing.tRunner", 0},
   228  		}},
   229  		{trace.EvGoSleep, []frame{
   230  			{"time.Sleep", 0},
   231  			{"runtime/trace_test.TestTraceSymbolize", 0},
   232  			{"testing.tRunner", 0},
   233  		}},
   234  		{trace.EvGomaxprocs, []frame{
   235  			{"runtime.startTheWorld", 0}, // this is when the current gomaxprocs is logged.
   236  			{"runtime.startTheWorldGC", 0},
   237  			{"runtime.GOMAXPROCS", 0},
   238  			{"runtime/trace_test.TestTraceSymbolize", 0},
   239  			{"testing.tRunner", 0},
   240  		}},
   241  	}
   242  	// Stacks for the following events are OS-dependent due to OS-specific code in net package.
   243  	if runtime.GOOS != "windows" && runtime.GOOS != "plan9" {
   244  		want = append(want, []eventDesc{
   245  			{trace.EvGoBlockNet, []frame{
   246  				{"internal/poll.(*FD).Accept", 0},
   247  				{"net.(*netFD).accept", 0},
   248  				{"net.(*TCPListener).accept", 0},
   249  				{"net.(*TCPListener).Accept", 0},
   250  				{"runtime/trace_test.TestTraceSymbolize.func10", 0},
   251  			}},
   252  			{trace.EvGoSysCall, []frame{
   253  				{"syscall.read", 0},
   254  				{"syscall.Read", 0},
   255  				{"internal/poll.ignoringEINTRIO", 0},
   256  				{"internal/poll.(*FD).Read", 0},
   257  				{"os.(*File).read", 0},
   258  				{"os.(*File).Read", 0},
   259  				{"runtime/trace_test.TestTraceSymbolize.func11", 0},
   260  			}},
   261  		}...)
   262  	}
   263  	matched := make([]bool, len(want))
   264  	for _, ev := range events {
   265  	wantLoop:
   266  		for i, w := range want {
   267  			if matched[i] || w.Type != ev.Type || len(w.Stk) != len(ev.Stk) {
   268  				continue
   269  			}
   270  
   271  			for fi, f := range ev.Stk {
   272  				wf := w.Stk[fi]
   273  				if wf.Fn != f.Fn || wf.Line != 0 && wf.Line != f.Line {
   274  					continue wantLoop
   275  				}
   276  			}
   277  			matched[i] = true
   278  		}
   279  	}
   280  	for i, w := range want {
   281  		if matched[i] {
   282  			continue
   283  		}
   284  		seen, n := dumpEventStacks(w.Type, events)
   285  		t.Errorf("Did not match event %v with stack\n%s\nSeen %d events of the type\n%s",
   286  			trace.EventDescriptions[w.Type].Name, dumpFrames(w.Stk), n, seen)
   287  	}
   288  }
   289  
   290  func skipTraceSymbolizeTestIfNecessary(t *testing.T) {
   291  	testenv.MustHaveGoBuild(t)
   292  	if IsEnabled() {
   293  		t.Skip("skipping because -test.trace is set")
   294  	}
   295  }
   296  
   297  func dumpEventStacks(typ byte, events []*trace.Event) ([]byte, int) {
   298  	matched := 0
   299  	o := new(bytes.Buffer)
   300  	tw := tabwriter.NewWriter(o, 0, 8, 0, '\t', 0)
   301  	for _, ev := range events {
   302  		if ev.Type != typ {
   303  			continue
   304  		}
   305  		matched++
   306  		fmt.Fprintf(tw, "Offset %d\n", ev.Off)
   307  		for _, f := range ev.Stk {
   308  			fname := f.File
   309  			if idx := strings.Index(fname, "/go/src/"); idx > 0 {
   310  				fname = fname[idx:]
   311  			}
   312  			fmt.Fprintf(tw, "  %v\t%s:%d\n", f.Fn, fname, f.Line)
   313  		}
   314  	}
   315  	tw.Flush()
   316  	return o.Bytes(), matched
   317  }
   318  
   319  type frame struct {
   320  	Fn   string
   321  	Line int
   322  }
   323  
   324  func dumpFrames(frames []frame) []byte {
   325  	o := new(bytes.Buffer)
   326  	tw := tabwriter.NewWriter(o, 0, 8, 0, '\t', 0)
   327  
   328  	for _, f := range frames {
   329  		fmt.Fprintf(tw, "  %v\t :%d\n", f.Fn, f.Line)
   330  	}
   331  	tw.Flush()
   332  	return o.Bytes()
   333  }
   334  

View as plain text