|  | // 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 pprof_test | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "internal/trace" | 
|  | "net" | 
|  | "os" | 
|  | "runtime" | 
|  | . "runtime/pprof" | 
|  | "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) { | 
|  | skipTraceTestsIfNeeded(t) | 
|  | if runtime.GOOS == "nacl" { | 
|  | t.Skip("skipping: nacl tests fail with 'failed to symbolize trace: failed to start addr2line'") | 
|  | } | 
|  | buf := new(bytes.Buffer) | 
|  | if err := StartTrace(buf); err != nil { | 
|  | t.Fatalf("failed to start tracing: %v", err) | 
|  | } | 
|  |  | 
|  | // 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", "localhost:0") | 
|  | if err != nil { | 
|  | t.Fatalf("failed to listen: %v", err) | 
|  | } | 
|  | go func() { | 
|  | c, err := ln.Accept() | 
|  | if err != nil { | 
|  | t.Fatalf("failed to accept: %v", err) | 
|  | } | 
|  | 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(time.Millisecond) | 
|  | runtime.GC() | 
|  | runtime.Gosched() | 
|  | time.Sleep(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 | 
|  |  | 
|  | StopTrace() | 
|  | events, _, err := parseTrace(buf) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to parse trace: %v", err) | 
|  | } | 
|  | err = trace.Symbolize(events, os.Args[0]) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to symbolize trace: %v", err) | 
|  | } | 
|  |  | 
|  | // Now check that the stacks are correct. | 
|  | type frame struct { | 
|  | Fn   string | 
|  | Line int | 
|  | } | 
|  | type eventDesc struct { | 
|  | Type byte | 
|  | Stk  []frame | 
|  | } | 
|  | want := []eventDesc{ | 
|  | eventDesc{trace.EvGCStart, []frame{ | 
|  | frame{"runtime.GC", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 106}, | 
|  | frame{"testing.tRunner", 0}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoSched, []frame{ | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 107}, | 
|  | frame{"testing.tRunner", 0}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoCreate, []frame{ | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 39}, | 
|  | frame{"testing.tRunner", 0}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoStop, []frame{ | 
|  | frame{"runtime.block", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func1", 38}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoStop, []frame{ | 
|  | frame{"runtime.chansend1", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func2", 42}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoStop, []frame{ | 
|  | frame{"runtime.chanrecv1", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func3", 46}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoBlockRecv, []frame{ | 
|  | frame{"runtime.chanrecv1", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func4", 50}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoUnblock, []frame{ | 
|  | frame{"runtime.chansend1", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 109}, | 
|  | frame{"testing.tRunner", 0}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoBlockSend, []frame{ | 
|  | frame{"runtime.chansend1", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func5", 54}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoUnblock, []frame{ | 
|  | frame{"runtime.chanrecv1", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 110}, | 
|  | frame{"testing.tRunner", 0}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoBlockSelect, []frame{ | 
|  | frame{"runtime.selectgo", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func6", 59}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoUnblock, []frame{ | 
|  | frame{"runtime.selectgo", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 111}, | 
|  | frame{"testing.tRunner", 0}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoBlockSync, []frame{ | 
|  | frame{"sync.(*Mutex).Lock", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func7", 67}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoUnblock, []frame{ | 
|  | frame{"sync.(*Mutex).Unlock", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 115}, | 
|  | frame{"testing.tRunner", 0}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoBlockSync, []frame{ | 
|  | frame{"sync.(*WaitGroup).Wait", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func8", 73}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoUnblock, []frame{ | 
|  | frame{"sync.(*WaitGroup).Add", 0}, | 
|  | frame{"sync.(*WaitGroup).Done", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 116}, | 
|  | frame{"testing.tRunner", 0}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoBlockCond, []frame{ | 
|  | frame{"sync.(*Cond).Wait", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func9", 78}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoUnblock, []frame{ | 
|  | frame{"sync.(*Cond).Signal", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 117}, | 
|  | frame{"testing.tRunner", 0}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoSleep, []frame{ | 
|  | frame{"time.Sleep", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize", 108}, | 
|  | frame{"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{ | 
|  | eventDesc{trace.EvGoBlockNet, []frame{ | 
|  | frame{"net.(*netFD).accept", 0}, | 
|  | frame{"net.(*TCPListener).AcceptTCP", 0}, | 
|  | frame{"net.(*TCPListener).Accept", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func10", 86}, | 
|  | }}, | 
|  | eventDesc{trace.EvGoSysCall, []frame{ | 
|  | frame{"syscall.read", 0}, | 
|  | frame{"syscall.Read", 0}, | 
|  | frame{"os.(*File).read", 0}, | 
|  | frame{"os.(*File).Read", 0}, | 
|  | frame{"runtime/pprof_test.TestTraceSymbolize.func11", 101}, | 
|  | }}, | 
|  | }...) | 
|  | } | 
|  | 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:%v", f.Fn, f.Line) | 
|  | } | 
|  | t.Logf("---") | 
|  | } | 
|  | } | 
|  | } |