| // 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/goexperiment" |
| "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", 112}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoCreate, []frame{ |
| {"runtime/trace_test.TestTraceSymbolize", 41}, |
| {"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", 114}, |
| {"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", 115}, |
| {"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", 116}, |
| {"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", 121}, |
| {"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.startTheWorldGC", 0}, |
| {"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.ignoringEINTRIO", 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 goexperiment.ExecTracer2 { |
| // An equivalent test exists in internal/trace/v2. |
| t.Skip("skipping because this test is incompatible with the new tracer") |
| } |
| 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() |
| } |