aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/trace/main.go5
-rw-r--r--src/cmd/trace/trace.go49
-rw-r--r--src/cmd/trace/trace_test.go62
3 files changed, 96 insertions, 20 deletions
diff --git a/src/cmd/trace/main.go b/src/cmd/trace/main.go
index 1b84d838f0..3611012290 100644
--- a/src/cmd/trace/main.go
+++ b/src/cmd/trace/main.go
@@ -89,7 +89,10 @@ func main() {
events: events,
endTime: int64(1<<63 - 1),
}
- data := generateTrace(params)
+ data, err := generateTrace(params)
+ if err != nil {
+ dief("%v\n", err)
+ }
log.Printf("Splitting trace...")
ranges = splitTrace(data)
diff --git a/src/cmd/trace/trace.go b/src/cmd/trace/trace.go
index d87a5bac38..7670d330e1 100644
--- a/src/cmd/trace/trace.go
+++ b/src/cmd/trace/trace.go
@@ -160,7 +160,11 @@ func httpJsonTrace(w http.ResponseWriter, r *http.Request) {
params.gs = trace.RelatedGoroutines(events, goid)
}
- data := generateTrace(params)
+ data, err := generateTrace(params)
+ if err != nil {
+ log.Printf("failed to generate trace: %v", err)
+ return
+ }
if startStr, endStr := r.FormValue("start"), r.FormValue("end"); startStr != "" && endStr != "" {
// If start/end arguments are present, we are rendering a range of the trace.
@@ -307,7 +311,7 @@ type SortIndexArg struct {
// If gtrace=true, generate trace for goroutine goid, otherwise whole trace.
// startTime, endTime determine part of the trace that we are interested in.
// gset restricts goroutines that are included in the resulting trace.
-func generateTrace(params *traceParams) ViewerData {
+func generateTrace(params *traceParams) (ViewerData, error) {
ctx := &traceContext{traceParams: params}
ctx.frameTree.children = make(map[uint64]frameNode)
ctx.data.Frames = make(map[string]ViewerFrame)
@@ -408,9 +412,10 @@ func generateTrace(params *traceParams) ViewerData {
ctx.grunning--
ctx.emitGoroutineCounters(ev)
case trace.EvGoWaiting:
- ctx.grunnable--
+ ctx.grunnable-- // cancels out the effect of EvGoCreate at the beginning
ctx.emitGoroutineCounters(ev)
case trace.EvGoInSyscall:
+ ctx.grunnable-- // cancels out the effect of EvGoCreate at the beginning
ctx.insyscall++
ctx.emitThreadCounters(ev)
case trace.EvHeapAlloc:
@@ -420,6 +425,9 @@ func generateTrace(params *traceParams) ViewerData {
ctx.nextGC = ev.Args[0]
ctx.emitHeapCounters(ev)
}
+ if ctx.grunnable < 0 || ctx.grunning < 0 || ctx.insyscall < 0 {
+ return ctx.data, fmt.Errorf("invalid state after processing %v: runnable=%d running=%d insyscall=%d", ev, ctx.grunnable, ctx.grunning, ctx.insyscall)
+ }
}
ctx.data.footer = len(ctx.data.Events)
@@ -459,7 +467,7 @@ func generateTrace(params *traceParams) ViewerData {
ctx.emit(&ViewerEvent{Name: "thread_sort_index", Phase: "M", Pid: 0, Tid: 0, Arg: &SortIndexArg{-1}})
}
- return ctx.data
+ return ctx.data, nil
}
func (ctx *traceContext) emit(e *ViewerEvent) {
@@ -491,11 +499,12 @@ func (ctx *traceContext) emitSlice(ev *trace.Event, name string) {
})
}
+type heapCountersArg struct {
+ Allocated uint64
+ NextGC uint64
+}
+
func (ctx *traceContext) emitHeapCounters(ev *trace.Event) {
- type Arg struct {
- Allocated uint64
- NextGC uint64
- }
if ctx.gtrace {
return
}
@@ -503,29 +512,31 @@ func (ctx *traceContext) emitHeapCounters(ev *trace.Event) {
if ctx.nextGC > ctx.heapAlloc {
diff = ctx.nextGC - ctx.heapAlloc
}
- ctx.emit(&ViewerEvent{Name: "Heap", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &Arg{ctx.heapAlloc, diff}})
+ ctx.emit(&ViewerEvent{Name: "Heap", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &heapCountersArg{ctx.heapAlloc, diff}})
+}
+
+type goroutineCountersArg struct {
+ Running uint64
+ Runnable uint64
}
func (ctx *traceContext) emitGoroutineCounters(ev *trace.Event) {
- type Arg struct {
- Running uint64
- Runnable uint64
- }
if ctx.gtrace {
return
}
- ctx.emit(&ViewerEvent{Name: "Goroutines", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &Arg{ctx.grunning, ctx.grunnable}})
+ ctx.emit(&ViewerEvent{Name: "Goroutines", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &goroutineCountersArg{ctx.grunning, ctx.grunnable}})
+}
+
+type threadCountersArg struct {
+ Running uint64
+ InSyscall uint64
}
func (ctx *traceContext) emitThreadCounters(ev *trace.Event) {
- type Arg struct {
- Running uint64
- InSyscall uint64
- }
if ctx.gtrace {
return
}
- ctx.emit(&ViewerEvent{Name: "Threads", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &Arg{ctx.prunning, ctx.insyscall}})
+ ctx.emit(&ViewerEvent{Name: "Threads", Phase: "C", Time: ctx.time(ev), Pid: 1, Arg: &threadCountersArg{ctx.prunning, ctx.insyscall}})
}
func (ctx *traceContext) emitInstant(ev *trace.Event, name string) {
diff --git a/src/cmd/trace/trace_test.go b/src/cmd/trace/trace_test.go
new file mode 100644
index 0000000000..73a2883f1e
--- /dev/null
+++ b/src/cmd/trace/trace_test.go
@@ -0,0 +1,62 @@
+package main
+
+import (
+ "internal/trace"
+ "testing"
+)
+
+// TestGoroutineCount tests runnable/running goroutine counts computed by generateTrace
+// remain in the valid range.
+// - the counts must not be negative. generateTrace will return an error.
+// - the counts must not include goroutines blocked waiting on channels or in syscall.
+func TestGoroutineCount(t *testing.T) {
+ w := trace.NewWriter()
+ w.Emit(trace.EvBatch, 0, 0) // start of per-P batch event [pid, timestamp]
+ w.Emit(trace.EvFrequency, 1) // [ticks per second]
+
+ // In this test, we assume a valid trace contains EvGoWaiting or EvGoInSyscall
+ // event for every blocked goroutine.
+
+ // goroutine 10: blocked
+ w.Emit(trace.EvGoCreate, 1, 10, 1, 1) // [timestamp, new goroutine id, new stack id, stack id]
+ w.Emit(trace.EvGoWaiting, 1, 10) // [timestamp, goroutine id]
+
+ // goroutine 20: in syscall
+ w.Emit(trace.EvGoCreate, 1, 20, 2, 1)
+ w.Emit(trace.EvGoInSyscall, 1, 20) // [timestamp, goroutine id]
+
+ // goroutine 30: runnable
+ w.Emit(trace.EvGoCreate, 1, 30, 5, 1)
+
+ w.Emit(trace.EvProcStart, 2, 0) // [timestamp, thread id]
+
+ // goroutine 40: runnable->running->runnable
+ w.Emit(trace.EvGoCreate, 1, 40, 7, 1)
+ w.Emit(trace.EvGoStartLocal, 1, 40) // [timestamp, goroutine id]
+ w.Emit(trace.EvGoSched, 1, 8) // [timestamp, stack]
+
+ events, err := trace.Parse(w, "")
+ if err != nil {
+ t.Fatalf("failed to parse test trace: %v", err)
+ }
+
+ params := &traceParams{
+ events: events,
+ endTime: int64(1<<63 - 1),
+ }
+
+ // If the counts drop below 0, generateTrace will return an error.
+ viewerData, err := generateTrace(params)
+ if err != nil {
+ t.Fatalf("generateTrace failed: %v", err)
+ }
+ for _, ev := range viewerData.Events {
+ if ev.Name == "Goroutines" {
+ cnt := ev.Arg.(*goroutineCountersArg)
+ if cnt.Runnable+cnt.Running > 2 {
+ t.Errorf("goroutine count=%+v; want no more than 2 goroutines in runnable/running state", cnt)
+ }
+ t.Logf("read %+v %+v", ev, cnt)
+ }
+ }
+}