cmd/trace: move goroutine analysis code to internal/trace
This allows to test goroutine analysis code in runtime/pprof tests.
Also fix a nil-deref crash in goroutine analysis code that happens on runtime/pprof tests.
Change-Id: Id7884aa29f7fe4a8d7042482a86fe434e030461e
Reviewed-on: https://go-review.googlesource.com/7301
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Andrew Gerrand <adg@golang.org>
diff --git a/src/cmd/trace/goroutines.go b/src/cmd/trace/goroutines.go
index f8d1289..f5a4ddb 100644
--- a/src/cmd/trace/goroutines.go
+++ b/src/cmd/trace/goroutines.go
@@ -13,6 +13,7 @@
"net/http"
"sort"
"strconv"
+ "sync"
)
func init() {
@@ -42,34 +43,7 @@
l[i], l[j] = l[j], l[i]
}
-// gdesc desribes a single goroutine.
-type gdesc struct {
- ID uint64
- Name string
- PC uint64
- CreateTime int64
- StartTime int64
- EndTime int64
- LastStart int64
-
- ExecTime int64
- SchedWaitTime int64
- IOTime int64
- BlockTime int64
- SyscallTime int64
- GCTime int64
- SweepTime int64
- TotalTime int64
-
- blockNetTime int64
- blockSyncTime int64
- blockSyscallTime int64
- blockSweepTime int64
- blockGCTime int64
- blockSchedTime int64
-}
-
-type gdescList []*gdesc
+type gdescList []*trace.GDesc
func (l gdescList) Len() int {
return len(l)
@@ -83,126 +57,16 @@
l[i], l[j] = l[j], l[i]
}
-var gs = make(map[uint64]*gdesc)
+var (
+ gsInit sync.Once
+ gs map[uint64]*trace.GDesc
+)
-// analyzeGoroutines generates list gdesc's from the trace and stores it in gs.
+// analyzeGoroutines generates statistics about execution of all goroutines and stores them in gs.
func analyzeGoroutines(events []*trace.Event) {
- if len(gs) > 0 { //!!! racy
- return
- }
- var lastTs int64
- var gcStartTime int64
- for _, ev := range events {
- lastTs = ev.Ts
- switch ev.Type {
- case trace.EvGoCreate:
- g := &gdesc{CreateTime: ev.Ts}
- g.blockSchedTime = ev.Ts
- gs[ev.Args[0]] = g
- case trace.EvGoStart:
- g := gs[ev.G]
- if g.PC == 0 {
- g.PC = ev.Stk[0].PC
- g.Name = ev.Stk[0].Fn
- }
- g.LastStart = ev.Ts
- if g.StartTime == 0 {
- g.StartTime = ev.Ts
- }
- if g.blockSchedTime != 0 {
- g.SchedWaitTime += ev.Ts - g.blockSchedTime
- g.blockSchedTime = 0
- }
- case trace.EvGoEnd, trace.EvGoStop:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.LastStart
- g.TotalTime = ev.Ts - g.CreateTime
- g.EndTime = ev.Ts
- case trace.EvGoBlockSend, trace.EvGoBlockRecv, trace.EvGoBlockSelect,
- trace.EvGoBlockSync, trace.EvGoBlockCond:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.LastStart
- g.blockSyncTime = ev.Ts
- case trace.EvGoSched, trace.EvGoPreempt:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.LastStart
- g.blockSchedTime = ev.Ts
- case trace.EvGoSleep, trace.EvGoBlock:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.LastStart
- case trace.EvGoBlockNet:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.LastStart
- g.blockNetTime = ev.Ts
- case trace.EvGoUnblock:
- g := gs[ev.Args[0]]
- if g.blockNetTime != 0 {
- g.IOTime += ev.Ts - g.blockNetTime
- g.blockNetTime = 0
- }
- if g.blockSyncTime != 0 {
- g.BlockTime += ev.Ts - g.blockSyncTime
- g.blockSyncTime = 0
- }
- g.blockSchedTime = ev.Ts
- case trace.EvGoSysBlock:
- g := gs[ev.G]
- g.ExecTime += ev.Ts - g.LastStart
- g.blockSyscallTime = ev.Ts
- case trace.EvGoSysExit:
- g := gs[ev.G]
- if g.blockSyscallTime != 0 {
- g.SyscallTime += ev.Ts - g.blockSyscallTime
- g.blockSyscallTime = 0
- }
- g.blockSchedTime = ev.Ts
- case trace.EvGCSweepStart:
- g := gs[ev.G]
- if g != nil {
- // Sweep can happen during GC on system goroutine.
- g.blockSweepTime = ev.Ts
- }
- case trace.EvGCSweepDone:
- g := gs[ev.G]
- if g != nil && g.blockSweepTime != 0 {
- g.SweepTime += ev.Ts - g.blockSweepTime
- g.blockSweepTime = 0
- }
- case trace.EvGCStart:
- gcStartTime = ev.Ts
- case trace.EvGCDone:
- for _, g := range gs {
- if g.EndTime == 0 {
- g.GCTime += ev.Ts - gcStartTime
- }
- }
- }
- }
-
- for _, g := range gs {
- if g.TotalTime == 0 {
- g.TotalTime = lastTs - g.CreateTime
- }
- if g.EndTime == 0 {
- g.EndTime = lastTs
- }
- if g.blockNetTime != 0 {
- g.IOTime += lastTs - g.blockNetTime
- g.blockNetTime = 0
- }
- if g.blockSyncTime != 0 {
- g.BlockTime += lastTs - g.blockSyncTime
- g.blockSyncTime = 0
- }
- if g.blockSyscallTime != 0 {
- g.SyscallTime += lastTs - g.blockSyscallTime
- g.blockSyscallTime = 0
- }
- if g.blockSchedTime != 0 {
- g.SchedWaitTime += lastTs - g.blockSchedTime
- g.blockSchedTime = 0
- }
- }
+ gsInit.Do(func() {
+ gs = trace.GoroutineStats(events)
+ })
}
// httpGoroutines serves list of goroutine groups.
@@ -256,11 +120,10 @@
}
analyzeGoroutines(events)
var glist gdescList
- for gid, g := range gs {
+ for _, g := range gs {
if g.PC != pc || g.ExecTime == 0 {
continue
}
- g.ID = gid
glist = append(glist, g)
}
sort.Sort(glist)
@@ -303,26 +166,3 @@
</body>
</html>
`))
-
-// relatedGoroutines finds set of related goroutines that we need to include
-// into trace for goroutine goid.
-func relatedGoroutines(events []*trace.Event, goid uint64) map[uint64]bool {
- // BFS of depth 2 over "unblock" edges
- // (what goroutines unblock goroutine goid?).
- gmap := make(map[uint64]bool)
- gmap[goid] = true
- for i := 0; i < 2; i++ {
- gmap1 := make(map[uint64]bool)
- for g := range gmap {
- gmap1[g] = true
- }
- for _, ev := range events {
- if ev.Type == trace.EvGoUnblock && gmap[ev.Args[0]] {
- gmap1[ev.G] = true
- }
- }
- gmap = gmap1
- }
- gmap[0] = true // for GC events
- return gmap
-}
diff --git a/src/cmd/trace/trace.go b/src/cmd/trace/trace.go
index a572ed8..16a20ae 100644
--- a/src/cmd/trace/trace.go
+++ b/src/cmd/trace/trace.go
@@ -90,7 +90,7 @@
params.startTime = g.StartTime
params.endTime = g.EndTime
params.maing = goid
- params.gs = relatedGoroutines(events, goid)
+ params.gs = trace.RelatedGoroutines(events, goid)
}
err = json.NewEncoder(w).Encode(generateTrace(params))
diff --git a/src/internal/trace/goroutines.go b/src/internal/trace/goroutines.go
new file mode 100644
index 0000000..f8673e2
--- /dev/null
+++ b/src/internal/trace/goroutines.go
@@ -0,0 +1,180 @@
+// 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
+
+// GDesc contains statistics about execution of a single goroutine.
+type GDesc struct {
+ ID uint64
+ Name string
+ PC uint64
+ CreationTime int64
+ StartTime int64
+ EndTime int64
+
+ ExecTime int64
+ SchedWaitTime int64
+ IOTime int64
+ BlockTime int64
+ SyscallTime int64
+ GCTime int64
+ SweepTime int64
+ TotalTime int64
+
+ *gdesc // private part
+}
+
+// gdesc is a private part of GDesc that is required only during analysis.
+type gdesc struct {
+ lastStartTime int64
+ blockNetTime int64
+ blockSyncTime int64
+ blockSyscallTime int64
+ blockSweepTime int64
+ blockGCTime int64
+ blockSchedTime int64
+}
+
+// GoroutineStats generates statistics for all goroutines in the trace.
+func GoroutineStats(events []*Event) map[uint64]*GDesc {
+ gs := make(map[uint64]*GDesc)
+ var lastTs int64
+ var gcStartTime int64
+ for _, ev := range events {
+ lastTs = ev.Ts
+ switch ev.Type {
+ case EvGoCreate:
+ g := &GDesc{ID: ev.Args[0], CreationTime: ev.Ts, gdesc: new(gdesc)}
+ g.blockSchedTime = ev.Ts
+ gs[g.ID] = g
+ case EvGoStart:
+ g := gs[ev.G]
+ if g.PC == 0 {
+ g.PC = ev.Stk[0].PC
+ g.Name = ev.Stk[0].Fn
+ }
+ g.lastStartTime = ev.Ts
+ if g.StartTime == 0 {
+ g.StartTime = ev.Ts
+ }
+ if g.blockSchedTime != 0 {
+ g.SchedWaitTime += ev.Ts - g.blockSchedTime
+ g.blockSchedTime = 0
+ }
+ case EvGoEnd, EvGoStop:
+ g := gs[ev.G]
+ g.ExecTime += ev.Ts - g.lastStartTime
+ g.TotalTime = ev.Ts - g.CreationTime
+ g.EndTime = ev.Ts
+ case EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect,
+ EvGoBlockSync, EvGoBlockCond:
+ g := gs[ev.G]
+ g.ExecTime += ev.Ts - g.lastStartTime
+ g.blockSyncTime = ev.Ts
+ case EvGoSched, EvGoPreempt:
+ g := gs[ev.G]
+ g.ExecTime += ev.Ts - g.lastStartTime
+ g.blockSchedTime = ev.Ts
+ case EvGoSleep, EvGoBlock:
+ g := gs[ev.G]
+ g.ExecTime += ev.Ts - g.lastStartTime
+ case EvGoBlockNet:
+ g := gs[ev.G]
+ g.ExecTime += ev.Ts - g.lastStartTime
+ g.blockNetTime = ev.Ts
+ case EvGoUnblock:
+ g := gs[ev.Args[0]]
+ if g.blockNetTime != 0 {
+ g.IOTime += ev.Ts - g.blockNetTime
+ g.blockNetTime = 0
+ }
+ if g.blockSyncTime != 0 {
+ g.BlockTime += ev.Ts - g.blockSyncTime
+ g.blockSyncTime = 0
+ }
+ g.blockSchedTime = ev.Ts
+ case EvGoSysBlock:
+ g := gs[ev.G]
+ g.ExecTime += ev.Ts - g.lastStartTime
+ g.blockSyscallTime = ev.Ts
+ case EvGoSysExit:
+ g := gs[ev.G]
+ if g.blockSyscallTime != 0 {
+ g.SyscallTime += ev.Ts - g.blockSyscallTime
+ g.blockSyscallTime = 0
+ }
+ g.blockSchedTime = ev.Ts
+ case EvGCSweepStart:
+ g := gs[ev.G]
+ if g != nil {
+ // Sweep can happen during GC on system goroutine.
+ g.blockSweepTime = ev.Ts
+ }
+ case EvGCSweepDone:
+ g := gs[ev.G]
+ if g != nil && g.blockSweepTime != 0 {
+ g.SweepTime += ev.Ts - g.blockSweepTime
+ g.blockSweepTime = 0
+ }
+ case EvGCStart:
+ gcStartTime = ev.Ts
+ case EvGCDone:
+ for _, g := range gs {
+ if g.EndTime == 0 {
+ g.GCTime += ev.Ts - gcStartTime
+ }
+ }
+ }
+ }
+
+ for _, g := range gs {
+ if g.TotalTime == 0 {
+ g.TotalTime = lastTs - g.CreationTime
+ }
+ if g.EndTime == 0 {
+ g.EndTime = lastTs
+ }
+ if g.blockNetTime != 0 {
+ g.IOTime += lastTs - g.blockNetTime
+ g.blockNetTime = 0
+ }
+ if g.blockSyncTime != 0 {
+ g.BlockTime += lastTs - g.blockSyncTime
+ g.blockSyncTime = 0
+ }
+ if g.blockSyscallTime != 0 {
+ g.SyscallTime += lastTs - g.blockSyscallTime
+ g.blockSyscallTime = 0
+ }
+ if g.blockSchedTime != 0 {
+ g.SchedWaitTime += lastTs - g.blockSchedTime
+ g.blockSchedTime = 0
+ }
+ g.gdesc = nil
+ }
+
+ return gs
+}
+
+// RelatedGoroutines finds a set of goroutines related to goroutine goid.
+func RelatedGoroutines(events []*Event, goid uint64) map[uint64]bool {
+ // BFS of depth 2 over "unblock" edges
+ // (what goroutines unblock goroutine goid?).
+ gmap := make(map[uint64]bool)
+ gmap[goid] = true
+ for i := 0; i < 2; i++ {
+ gmap1 := make(map[uint64]bool)
+ for g := range gmap {
+ gmap1[g] = true
+ }
+ for _, ev := range events {
+ if ev.Type == EvGoUnblock && gmap[ev.Args[0]] {
+ gmap1[ev.G] = true
+ }
+ }
+ gmap = gmap1
+ }
+ gmap[0] = true // for GC events
+ return gmap
+}
diff --git a/src/internal/trace/parser.go b/src/internal/trace/parser.go
index 44a2e66..3c93e78 100644
--- a/src/internal/trace/parser.go
+++ b/src/internal/trace/parser.go
@@ -277,9 +277,10 @@
gWaiting
)
type gdesc struct {
- state int
- ev *Event
- evStart *Event
+ state int
+ ev *Event
+ evStart *Event
+ evCreate *Event
}
type pdesc struct {
running bool
@@ -371,7 +372,7 @@
if _, ok := gs[ev.Args[0]]; ok {
return fmt.Errorf("g %v already exists (offset %v, time %v)", ev.Args[0], ev.Off, ev.Ts)
}
- gs[ev.Args[0]] = gdesc{state: gRunnable, ev: ev}
+ gs[ev.Args[0]] = gdesc{state: gRunnable, ev: ev, evCreate: ev}
case EvGoStart:
if g.state != gRunnable {
return fmt.Errorf("g %v is not runnable before start (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
@@ -382,11 +383,13 @@
g.state = gRunning
g.evStart = ev
p.g = ev.G
+ if g.evCreate != nil {
+ // +1 because symblizer expects return pc.
+ ev.Stk = []*Frame{&Frame{PC: g.evCreate.Args[1] + 1}}
+ g.evCreate = nil
+ }
+
if g.ev != nil {
- if g.ev.Type == EvGoCreate {
- // +1 because symblizer expects return pc.
- ev.Stk = []*Frame{&Frame{PC: g.ev.Args[1] + 1}}
- }
g.ev.Link = ev
g.ev = nil
}
diff --git a/src/runtime/pprof/trace_stack_test.go b/src/runtime/pprof/trace_stack_test.go
index ef55d52..edb0a24 100644
--- a/src/runtime/pprof/trace_stack_test.go
+++ b/src/runtime/pprof/trace_stack_test.go
@@ -122,7 +122,7 @@
wp.Write(data[:])
StopTrace()
- events, err := trace.Parse(buf)
+ events, _, err := parseTrace(buf)
if err != nil {
t.Fatalf("failed to parse trace: %v", err)
}
diff --git a/src/runtime/pprof/trace_test.go b/src/runtime/pprof/trace_test.go
index c57f4b8..3753e3c 100644
--- a/src/runtime/pprof/trace_test.go
+++ b/src/runtime/pprof/trace_test.go
@@ -7,6 +7,7 @@
import (
"bytes"
"internal/trace"
+ "io"
"net"
"os"
"runtime"
@@ -72,6 +73,20 @@
}
}
+func parseTrace(r io.Reader) ([]*trace.Event, map[uint64]*trace.GDesc, error) {
+ events, err := trace.Parse(r)
+ if err != nil {
+ return nil, nil, err
+ }
+ gs := trace.GoroutineStats(events)
+ for goid := range gs {
+ // We don't do any particular checks on the result at the moment.
+ // But still check that RelatedGoroutines does not crash, hang, etc.
+ _ = trace.RelatedGoroutines(events, goid)
+ }
+ return events, gs, nil
+}
+
func TestTraceStress(t *testing.T) {
skipTraceTestsIfNeeded(t)
@@ -199,7 +214,7 @@
runtime.GOMAXPROCS(procs)
StopTrace()
- _, err = trace.Parse(buf)
+ _, _, err = parseTrace(buf)
if err != nil {
t.Fatalf("failed to parse trace: %v", err)
}
@@ -339,7 +354,7 @@
}
time.Sleep(time.Millisecond)
StopTrace()
- if _, err := trace.Parse(buf); err != nil {
+ if _, _, err := parseTrace(buf); err != nil {
t.Fatalf("failed to parse trace: %v", err)
}
}