diff options
Diffstat (limited to 'src/cmd/trace/goroutines.go')
-rw-r--r-- | src/cmd/trace/goroutines.go | 328 |
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 +} |