| // 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" |
| "internal/testenv" |
| "internal/trace" |
| "net" |
| "os" |
| "runtime" |
| . "runtime/trace" |
| "sync" |
| "testing" |
| "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) { |
| testenv.MustHaveGoBuild(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() { |
| select {} |
| }() |
| go func() { |
| var c chan int |
| c <- 0 |
| }() |
| go func() { |
| var c chan int |
| <-c |
| }() |
| done1 := make(chan bool) |
| go func() { |
| <-done1 |
| }() |
| done2 := make(chan bool) |
| go func() { |
| done2 <- true |
| }() |
| c1 := make(chan int) |
| c2 := make(chan int) |
| go func() { |
| select { |
| case <-c1: |
| case <-c2: |
| } |
| }() |
| var mu sync.Mutex |
| mu.Lock() |
| go func() { |
| mu.Lock() |
| mu.Unlock() |
| }() |
| var wg sync.WaitGroup |
| wg.Add(1) |
| go func() { |
| wg.Wait() |
| }() |
| cv := sync.NewCond(&sync.Mutex{}) |
| go func() { |
| 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() { |
| 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() { |
| 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 |
| |
| Stop() |
| events, _ := parseTrace(t, buf) |
| |
| // Now check that the stacks are correct. |
| type frame struct { |
| Fn string |
| Line int |
| } |
| type eventDesc struct { |
| Type byte |
| Stk []frame |
| } |
| want := []eventDesc{ |
| {trace.EvGCStart, []frame{ |
| {"runtime.GC", 0}, |
| {"runtime/trace_test.TestTraceSymbolize", 107}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoStart, []frame{ |
| {"runtime/trace_test.TestTraceSymbolize.func1", 37}, |
| }}, |
| {trace.EvGoSched, []frame{ |
| {"runtime/trace_test.TestTraceSymbolize", 108}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoCreate, []frame{ |
| {"runtime/trace_test.TestTraceSymbolize", 37}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoStop, []frame{ |
| {"runtime.block", 0}, |
| {"runtime/trace_test.TestTraceSymbolize.func1", 38}, |
| }}, |
| {trace.EvGoStop, []frame{ |
| {"runtime.chansend1", 0}, |
| {"runtime/trace_test.TestTraceSymbolize.func2", 42}, |
| }}, |
| {trace.EvGoStop, []frame{ |
| {"runtime.chanrecv1", 0}, |
| {"runtime/trace_test.TestTraceSymbolize.func3", 46}, |
| }}, |
| {trace.EvGoBlockRecv, []frame{ |
| {"runtime.chanrecv1", 0}, |
| {"runtime/trace_test.TestTraceSymbolize.func4", 50}, |
| }}, |
| {trace.EvGoUnblock, []frame{ |
| {"runtime.chansend1", 0}, |
| {"runtime/trace_test.TestTraceSymbolize", 110}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoBlockSend, []frame{ |
| {"runtime.chansend1", 0}, |
| {"runtime/trace_test.TestTraceSymbolize.func5", 54}, |
| }}, |
| {trace.EvGoUnblock, []frame{ |
| {"runtime.chanrecv1", 0}, |
| {"runtime/trace_test.TestTraceSymbolize", 111}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoBlockSelect, []frame{ |
| {"runtime.selectgo", 0}, |
| {"runtime/trace_test.TestTraceSymbolize.func6", 59}, |
| }}, |
| {trace.EvGoUnblock, []frame{ |
| {"runtime.selectgo", 0}, |
| {"runtime/trace_test.TestTraceSymbolize", 112}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoBlockSync, []frame{ |
| {"sync.(*Mutex).Lock", 0}, |
| {"runtime/trace_test.TestTraceSymbolize.func7", 67}, |
| }}, |
| {trace.EvGoUnblock, []frame{ |
| {"sync.(*Mutex).Unlock", 0}, |
| {"runtime/trace_test.TestTraceSymbolize", 116}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoBlockSync, []frame{ |
| {"sync.(*WaitGroup).Wait", 0}, |
| {"runtime/trace_test.TestTraceSymbolize.func8", 73}, |
| }}, |
| {trace.EvGoUnblock, []frame{ |
| {"sync.(*WaitGroup).Add", 0}, |
| {"sync.(*WaitGroup).Done", 0}, |
| {"runtime/trace_test.TestTraceSymbolize", 117}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoBlockCond, []frame{ |
| {"sync.(*Cond).Wait", 0}, |
| {"runtime/trace_test.TestTraceSymbolize.func9", 78}, |
| }}, |
| {trace.EvGoUnblock, []frame{ |
| {"sync.(*Cond).Signal", 0}, |
| {"runtime/trace_test.TestTraceSymbolize", 118}, |
| {"testing.tRunner", 0}, |
| }}, |
| {trace.EvGoSleep, []frame{ |
| {"time.Sleep", 0}, |
| {"runtime/trace_test.TestTraceSymbolize", 109}, |
| {"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", 86}, |
| }}, |
| {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", 102}, |
| }}, |
| }...) |
| } |
| 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, m := range matched { |
| if m { |
| continue |
| } |
| w := want[i] |
| t.Errorf("did not match event %v at %v:%v", trace.EventDescriptions[w.Type].Name, w.Stk[0].Fn, w.Stk[0].Line) |
| t.Errorf("seen the following events of this type:") |
| for _, ev := range events { |
| if ev.Type != w.Type { |
| continue |
| } |
| for _, f := range ev.Stk { |
| t.Logf(" %v :: %s:%v", f.Fn, f.File, f.Line) |
| } |
| t.Logf("---") |
| } |
| t.Logf("======") |
| } |
| } |