|  | // 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() | 
|  | } |