mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			332 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2014 The Go Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package trace_test
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"internal/testenv"
 | |
| 	"internal/trace"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"runtime"
 | |
| 	. "runtime/trace"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"testing"
 | |
| 	"text/tabwriter"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // TestTraceSymbolize tests symbolization and that events has proper stacks.
 | |
| // In particular that we strip bottom uninteresting frames like goexit,
 | |
| // top uninteresting frames (runtime guts).
 | |
| func TestTraceSymbolize(t *testing.T) {
 | |
| 	skipTraceSymbolizeTestIfNecessary(t)
 | |
| 
 | |
| 	buf := new(bytes.Buffer)
 | |
| 	if err := Start(buf); err != nil {
 | |
| 		t.Fatalf("failed to start tracing: %v", err)
 | |
| 	}
 | |
| 	defer Stop() // in case of early return
 | |
| 
 | |
| 	// Now we will do a bunch of things for which we verify stacks later.
 | |
| 	// It is impossible to ensure that a goroutine has actually blocked
 | |
| 	// on a channel, in a select or otherwise. So we kick off goroutines
 | |
| 	// that need to block first in the hope that while we are executing
 | |
| 	// the rest of the test, they will block.
 | |
| 	go func() { // func1
 | |
| 		select {}
 | |
| 	}()
 | |
| 	go func() { // func2
 | |
| 		var c chan int
 | |
| 		c <- 0
 | |
| 	}()
 | |
| 	go func() { // func3
 | |
| 		var c chan int
 | |
| 		<-c
 | |
| 	}()
 | |
| 	done1 := make(chan bool)
 | |
| 	go func() { // func4
 | |
| 		<-done1
 | |
| 	}()
 | |
| 	done2 := make(chan bool)
 | |
| 	go func() { // func5
 | |
| 		done2 <- true
 | |
| 	}()
 | |
| 	c1 := make(chan int)
 | |
| 	c2 := make(chan int)
 | |
| 	go func() { // func6
 | |
| 		select {
 | |
| 		case <-c1:
 | |
| 		case <-c2:
 | |
| 		}
 | |
| 	}()
 | |
| 	var mu sync.Mutex
 | |
| 	mu.Lock()
 | |
| 	go func() { // func7
 | |
| 		mu.Lock()
 | |
| 		mu.Unlock()
 | |
| 	}()
 | |
| 	var wg sync.WaitGroup
 | |
| 	wg.Add(1)
 | |
| 	go func() { // func8
 | |
| 		wg.Wait()
 | |
| 	}()
 | |
| 	cv := sync.NewCond(&sync.Mutex{})
 | |
| 	go func() { // func9
 | |
| 		cv.L.Lock()
 | |
| 		cv.Wait()
 | |
| 		cv.L.Unlock()
 | |
| 	}()
 | |
| 	ln, err := net.Listen("tcp", "127.0.0.1:0")
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("failed to listen: %v", err)
 | |
| 	}
 | |
| 	go func() { // func10
 | |
| 		c, err := ln.Accept()
 | |
| 		if err != nil {
 | |
| 			t.Errorf("failed to accept: %v", err)
 | |
| 			return
 | |
| 		}
 | |
| 		c.Close()
 | |
| 	}()
 | |
| 	rp, wp, err := os.Pipe()
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("failed to create a pipe: %v", err)
 | |
| 	}
 | |
| 	defer rp.Close()
 | |
| 	defer wp.Close()
 | |
| 	pipeReadDone := make(chan bool)
 | |
| 	go func() { // func11
 | |
| 		var data [1]byte
 | |
| 		rp.Read(data[:])
 | |
| 		pipeReadDone <- true
 | |
| 	}()
 | |
| 
 | |
| 	time.Sleep(100 * time.Millisecond)
 | |
| 	runtime.GC()
 | |
| 	runtime.Gosched()
 | |
| 	time.Sleep(100 * time.Millisecond) // the last chance for the goroutines above to block
 | |
| 	done1 <- true
 | |
| 	<-done2
 | |
| 	select {
 | |
| 	case c1 <- 0:
 | |
| 	case c2 <- 0:
 | |
| 	}
 | |
| 	mu.Unlock()
 | |
| 	wg.Done()
 | |
| 	cv.Signal()
 | |
| 	c, err := net.Dial("tcp", ln.Addr().String())
 | |
| 	if err != nil {
 | |
| 		t.Fatalf("failed to dial: %v", err)
 | |
| 	}
 | |
| 	c.Close()
 | |
| 	var data [1]byte
 | |
| 	wp.Write(data[:])
 | |
| 	<-pipeReadDone
 | |
| 
 | |
| 	oldGoMaxProcs := runtime.GOMAXPROCS(0)
 | |
| 	runtime.GOMAXPROCS(oldGoMaxProcs + 1)
 | |
| 
 | |
| 	Stop()
 | |
| 
 | |
| 	runtime.GOMAXPROCS(oldGoMaxProcs)
 | |
| 
 | |
| 	events, _ := parseTrace(t, buf)
 | |
| 
 | |
| 	// Now check that the stacks are correct.
 | |
| 	type eventDesc struct {
 | |
| 		Type byte
 | |
| 		Stk  []frame
 | |
| 	}
 | |
| 	want := []eventDesc{
 | |
| 		{trace.EvGCStart, []frame{
 | |
| 			{"runtime.GC", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 0},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoStart, []frame{
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func1", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoSched, []frame{
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 111},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoCreate, []frame{
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 40},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoStop, []frame{
 | |
| 			{"runtime.block", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func1", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoStop, []frame{
 | |
| 			{"runtime.chansend1", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func2", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoStop, []frame{
 | |
| 			{"runtime.chanrecv1", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func3", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoBlockRecv, []frame{
 | |
| 			{"runtime.chanrecv1", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func4", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoUnblock, []frame{
 | |
| 			{"runtime.chansend1", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 113},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoBlockSend, []frame{
 | |
| 			{"runtime.chansend1", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func5", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoUnblock, []frame{
 | |
| 			{"runtime.chanrecv1", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 114},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoBlockSelect, []frame{
 | |
| 			{"runtime.selectgo", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func6", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoUnblock, []frame{
 | |
| 			{"runtime.selectgo", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 115},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoBlockSync, []frame{
 | |
| 			{"sync.(*Mutex).Lock", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func7", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoUnblock, []frame{
 | |
| 			{"sync.(*Mutex).Unlock", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 0},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoBlockSync, []frame{
 | |
| 			{"sync.(*WaitGroup).Wait", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func8", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoUnblock, []frame{
 | |
| 			{"sync.(*WaitGroup).Add", 0},
 | |
| 			{"sync.(*WaitGroup).Done", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 120},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoBlockCond, []frame{
 | |
| 			{"sync.(*Cond).Wait", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize.func9", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoUnblock, []frame{
 | |
| 			{"sync.(*Cond).Signal", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 0},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGoSleep, []frame{
 | |
| 			{"time.Sleep", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 0},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 		{trace.EvGomaxprocs, []frame{
 | |
| 			{"runtime.startTheWorld", 0}, // this is when the current gomaxprocs is logged.
 | |
| 			{"runtime.GOMAXPROCS", 0},
 | |
| 			{"runtime/trace_test.TestTraceSymbolize", 0},
 | |
| 			{"testing.tRunner", 0},
 | |
| 		}},
 | |
| 	}
 | |
| 	// Stacks for the following events are OS-dependent due to OS-specific code in net package.
 | |
| 	if runtime.GOOS != "windows" && runtime.GOOS != "plan9" {
 | |
| 		want = append(want, []eventDesc{
 | |
| 			{trace.EvGoBlockNet, []frame{
 | |
| 				{"internal/poll.(*FD).Accept", 0},
 | |
| 				{"net.(*netFD).accept", 0},
 | |
| 				{"net.(*TCPListener).accept", 0},
 | |
| 				{"net.(*TCPListener).Accept", 0},
 | |
| 				{"runtime/trace_test.TestTraceSymbolize.func10", 0},
 | |
| 			}},
 | |
| 			{trace.EvGoSysCall, []frame{
 | |
| 				{"syscall.read", 0},
 | |
| 				{"syscall.Read", 0},
 | |
| 				{"internal/poll.(*FD).Read", 0},
 | |
| 				{"os.(*File).read", 0},
 | |
| 				{"os.(*File).Read", 0},
 | |
| 				{"runtime/trace_test.TestTraceSymbolize.func11", 0},
 | |
| 			}},
 | |
| 		}...)
 | |
| 	}
 | |
| 	matched := make([]bool, len(want))
 | |
| 	for _, ev := range events {
 | |
| 	wantLoop:
 | |
| 		for i, w := range want {
 | |
| 			if matched[i] || w.Type != ev.Type || len(w.Stk) != len(ev.Stk) {
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			for fi, f := range ev.Stk {
 | |
| 				wf := w.Stk[fi]
 | |
| 				if wf.Fn != f.Fn || wf.Line != 0 && wf.Line != f.Line {
 | |
| 					continue wantLoop
 | |
| 				}
 | |
| 			}
 | |
| 			matched[i] = true
 | |
| 		}
 | |
| 	}
 | |
| 	for i, w := range want {
 | |
| 		if matched[i] {
 | |
| 			continue
 | |
| 		}
 | |
| 		seen, n := dumpEventStacks(w.Type, events)
 | |
| 		t.Errorf("Did not match event %v with stack\n%s\nSeen %d events of the type\n%s",
 | |
| 			trace.EventDescriptions[w.Type].Name, dumpFrames(w.Stk), n, seen)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func skipTraceSymbolizeTestIfNecessary(t *testing.T) {
 | |
| 	testenv.MustHaveGoBuild(t)
 | |
| 	if IsEnabled() {
 | |
| 		t.Skip("skipping because -test.trace is set")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func dumpEventStacks(typ byte, events []*trace.Event) ([]byte, int) {
 | |
| 	matched := 0
 | |
| 	o := new(bytes.Buffer)
 | |
| 	tw := tabwriter.NewWriter(o, 0, 8, 0, '\t', 0)
 | |
| 	for _, ev := range events {
 | |
| 		if ev.Type != typ {
 | |
| 			continue
 | |
| 		}
 | |
| 		matched++
 | |
| 		fmt.Fprintf(tw, "Offset %d\n", ev.Off)
 | |
| 		for _, f := range ev.Stk {
 | |
| 			fname := f.File
 | |
| 			if idx := strings.Index(fname, "/go/src/"); idx > 0 {
 | |
| 				fname = fname[idx:]
 | |
| 			}
 | |
| 			fmt.Fprintf(tw, "  %v\t%s:%d\n", f.Fn, fname, f.Line)
 | |
| 		}
 | |
| 	}
 | |
| 	tw.Flush()
 | |
| 	return o.Bytes(), matched
 | |
| }
 | |
| 
 | |
| type frame struct {
 | |
| 	Fn   string
 | |
| 	Line int
 | |
| }
 | |
| 
 | |
| func dumpFrames(frames []frame) []byte {
 | |
| 	o := new(bytes.Buffer)
 | |
| 	tw := tabwriter.NewWriter(o, 0, 8, 0, '\t', 0)
 | |
| 
 | |
| 	for _, f := range frames {
 | |
| 		fmt.Fprintf(tw, "  %v\t :%d\n", f.Fn, f.Line)
 | |
| 	}
 | |
| 	tw.Flush()
 | |
| 	return o.Bytes()
 | |
| }
 |