aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/trace/goroutines.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/trace/goroutines.go')
-rw-r--r--src/cmd/trace/goroutines.go328
1 files changed, 328 insertions, 0 deletions
diff --git a/src/cmd/trace/goroutines.go b/src/cmd/trace/goroutines.go
new file mode 100644
index 0000000000..f8d1289c29
--- /dev/null
+++ b/src/cmd/trace/goroutines.go
@@ -0,0 +1,328 @@
+// 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.
+
+// Goroutine-related profiles.
+
+package main
+
+import (
+ "fmt"
+ "html/template"
+ "internal/trace"
+ "net/http"
+ "sort"
+ "strconv"
+)
+
+func init() {
+ http.HandleFunc("/goroutines", httpGoroutines)
+ http.HandleFunc("/goroutine", httpGoroutine)
+}
+
+// gtype describes a group of goroutines grouped by start PC.
+type gtype struct {
+ ID uint64 // Unique identifier (PC).
+ Name string // Start function.
+ N int // Total number of goroutines in this group.
+ ExecTime int64 // Total execution time of all goroutines in this group.
+}
+
+type gtypeList []gtype
+
+func (l gtypeList) Len() int {
+ return len(l)
+}
+
+func (l gtypeList) Less(i, j int) bool {
+ return l[i].ExecTime > l[j].ExecTime
+}
+
+func (l gtypeList) Swap(i, j int) {
+ 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
+
+func (l gdescList) Len() int {
+ return len(l)
+}
+
+func (l gdescList) Less(i, j int) bool {
+ return l[i].TotalTime > l[j].TotalTime
+}
+
+func (l gdescList) Swap(i, j int) {
+ l[i], l[j] = l[j], l[i]
+}
+
+var gs = make(map[uint64]*gdesc)
+
+// analyzeGoroutines generates list gdesc's from the trace and stores it 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
+ }
+ }
+}
+
+// httpGoroutines serves list of goroutine groups.
+func httpGoroutines(w http.ResponseWriter, r *http.Request) {
+ events, err := parseEvents()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ analyzeGoroutines(events)
+ gss := make(map[uint64]gtype)
+ for _, g := range gs {
+ gs1 := gss[g.PC]
+ gs1.ID = g.PC
+ gs1.Name = g.Name
+ gs1.N++
+ gs1.ExecTime += g.ExecTime
+ gss[g.PC] = gs1
+ }
+ var glist gtypeList
+ for k, v := range gss {
+ v.ID = k
+ glist = append(glist, v)
+ }
+ sort.Sort(glist)
+ templGoroutines.Execute(w, glist)
+}
+
+var templGoroutines = template.Must(template.New("").Parse(`
+<html>
+<body>
+Goroutines: <br>
+{{range $}}
+ <a href="/goroutine?id={{.ID}}">{{.Name}}</a> N={{.N}} <br>
+{{end}}
+</body>
+</html>
+`))
+
+// httpGoroutine serves list of goroutines in a particular group.
+func httpGoroutine(w http.ResponseWriter, r *http.Request) {
+ events, err := parseEvents()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ pc, err := strconv.ParseUint(r.FormValue("id"), 10, 64)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("failed to parse id parameter '%v': %v", r.FormValue("id"), err), http.StatusInternalServerError)
+ return
+ }
+ analyzeGoroutines(events)
+ var glist gdescList
+ for gid, g := range gs {
+ if g.PC != pc || g.ExecTime == 0 {
+ continue
+ }
+ g.ID = gid
+ glist = append(glist, g)
+ }
+ sort.Sort(glist)
+ err = templGoroutine.Execute(w, glist)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
+ return
+ }
+}
+
+var templGoroutine = template.Must(template.New("").Parse(`
+<html>
+<body>
+<table border="1" sortable="1">
+<tr>
+<th> Goroutine </th>
+<th> Total time, ns </th>
+<th> Execution time, ns </th>
+<th> Network wait time, ns </th>
+<th> Sync block time, ns </th>
+<th> Blocking syscall time, ns </th>
+<th> Scheduler wait time, ns </th>
+<th> GC sweeping time, ns </th>
+<th> GC pause time, ns </th>
+</tr>
+{{range $}}
+ <tr>
+ <td> <a href="/trace?goid={{.ID}}">{{.ID}}</a> </td>
+ <td> {{.TotalTime}} </td>
+ <td> {{.ExecTime}} </td>
+ <td> {{.IOTime}} </td>
+ <td> {{.BlockTime}} </td>
+ <td> {{.SyscallTime}} </td>
+ <td> {{.SchedWaitTime}} </td>
+ <td> {{.SweepTime}} </td>
+ <td> {{.GCTime}} </td>
+ </tr>
+{{end}}
+</table>
+</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
+}