summaryrefslogtreecommitdiffstats
path: root/src/runtime/trace/trace_stack_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:14:23 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:14:23 +0000
commit73df946d56c74384511a194dd01dbe099584fd1a (patch)
treefd0bcea490dd81327ddfbb31e215439672c9a068 /src/runtime/trace/trace_stack_test.go
parentInitial commit. (diff)
downloadgolang-1.16-upstream.tar.xz
golang-1.16-upstream.zip
Adding upstream version 1.16.10.upstream/1.16.10upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/runtime/trace/trace_stack_test.go')
-rw-r--r--src/runtime/trace/trace_stack_test.go333
1 files changed, 333 insertions, 0 deletions
diff --git a/src/runtime/trace/trace_stack_test.go b/src/runtime/trace/trace_stack_test.go
new file mode 100644
index 0000000..be3adc9
--- /dev/null
+++ b/src/runtime/trace/trace_stack_test.go
@@ -0,0 +1,333 @@
+// 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.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 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()
+}