summaryrefslogtreecommitdiffstats
path: root/src/cmd/trace/goroutines.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/cmd/trace/goroutines.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 '')
-rw-r--r--src/cmd/trace/goroutines.go297
1 files changed, 297 insertions, 0 deletions
diff --git a/src/cmd/trace/goroutines.go b/src/cmd/trace/goroutines.go
new file mode 100644
index 0000000..74d1b0a
--- /dev/null
+++ b/src/cmd/trace/goroutines.go
@@ -0,0 +1,297 @@
+// 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"
+ "log"
+ "net/http"
+ "reflect"
+ "sort"
+ "strconv"
+ "sync"
+ "time"
+)
+
+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.
+}
+
+var (
+ gsInit sync.Once
+ gs map[uint64]*trace.GDesc
+)
+
+// analyzeGoroutines generates statistics about execution of all goroutines and stores them in gs.
+func analyzeGoroutines(events []*trace.Event) {
+ gsInit.Do(func() {
+ gs = trace.GoroutineStats(events)
+ })
+}
+
+// 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 []gtype
+ for k, v := range gss {
+ v.ID = k
+ glist = append(glist, v)
+ }
+ sort.Slice(glist, func(i, j int) bool { return glist[i].ExecTime > glist[j].ExecTime })
+ w.Header().Set("Content-Type", "text/html;charset=utf-8")
+ if err := templGoroutines.Execute(w, glist); err != nil {
+ log.Printf("failed to execute template: %v", err)
+ return
+ }
+}
+
+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) {
+ // TODO(hyangah): support format=csv (raw data)
+
+ 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 []*trace.GDesc
+ name string
+ totalExecTime, execTime int64
+ maxTotalTime int64
+ )
+
+ for _, g := range gs {
+ totalExecTime += g.ExecTime
+
+ if g.PC != pc {
+ continue
+ }
+ glist = append(glist, g)
+ name = g.Name
+ execTime += g.ExecTime
+ if maxTotalTime < g.TotalTime {
+ maxTotalTime = g.TotalTime
+ }
+ }
+
+ execTimePercent := ""
+ if totalExecTime > 0 {
+ execTimePercent = fmt.Sprintf("%.2f%%", float64(execTime)/float64(totalExecTime)*100)
+ }
+
+ sortby := r.FormValue("sortby")
+ _, ok := reflect.TypeOf(trace.GDesc{}).FieldByNameFunc(func(s string) bool {
+ return s == sortby
+ })
+ if !ok {
+ sortby = "TotalTime"
+ }
+
+ sort.Slice(glist, func(i, j int) bool {
+ ival := reflect.ValueOf(glist[i]).Elem().FieldByName(sortby).Int()
+ jval := reflect.ValueOf(glist[j]).Elem().FieldByName(sortby).Int()
+ return ival > jval
+ })
+
+ err = templGoroutine.Execute(w, struct {
+ Name string
+ PC uint64
+ N int
+ ExecTimePercent string
+ MaxTotal int64
+ GList []*trace.GDesc
+ }{
+ Name: name,
+ PC: pc,
+ N: len(glist),
+ ExecTimePercent: execTimePercent,
+ MaxTotal: maxTotalTime,
+ GList: glist})
+ if err != nil {
+ http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
+ return
+ }
+}
+
+var templGoroutine = template.Must(template.New("").Funcs(template.FuncMap{
+ "prettyDuration": func(nsec int64) template.HTML {
+ d := time.Duration(nsec) * time.Nanosecond
+ return template.HTML(niceDuration(d))
+ },
+ "percent": func(dividend, divisor int64) template.HTML {
+ if divisor == 0 {
+ return ""
+ }
+ return template.HTML(fmt.Sprintf("(%.1f%%)", float64(dividend)/float64(divisor)*100))
+ },
+ "barLen": func(dividend, divisor int64) template.HTML {
+ if divisor == 0 {
+ return "0"
+ }
+ return template.HTML(fmt.Sprintf("%.2f%%", float64(dividend)/float64(divisor)*100))
+ },
+ "unknownTime": func(desc *trace.GDesc) int64 {
+ sum := desc.ExecTime + desc.IOTime + desc.BlockTime + desc.SyscallTime + desc.SchedWaitTime
+ if sum < desc.TotalTime {
+ return desc.TotalTime - sum
+ }
+ return 0
+ },
+}).Parse(`
+<!DOCTYPE html>
+<title>Goroutine {{.Name}}</title>
+<style>
+th {
+ background-color: #050505;
+ color: #fff;
+}
+th.total-time,
+th.exec-time,
+th.io-time,
+th.block-time,
+th.syscall-time,
+th.sched-time,
+th.sweep-time,
+th.pause-time {
+ cursor: pointer;
+}
+table {
+ border-collapse: collapse;
+}
+.details tr:hover {
+ background-color: #f2f2f2;
+}
+.details td {
+ text-align: right;
+ border: 1px solid black;
+}
+.details td.id {
+ text-align: left;
+}
+.stacked-bar-graph {
+ width: 300px;
+ height: 10px;
+ color: #414042;
+ white-space: nowrap;
+ font-size: 5px;
+}
+.stacked-bar-graph span {
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ float: left;
+ padding: 0;
+}
+.unknown-time { background-color: #636363; }
+.exec-time { background-color: #d7191c; }
+.io-time { background-color: #fdae61; }
+.block-time { background-color: #d01c8b; }
+.syscall-time { background-color: #7b3294; }
+.sched-time { background-color: #2c7bb6; }
+</style>
+
+<script>
+function reloadTable(key, value) {
+ let params = new URLSearchParams(window.location.search);
+ params.set(key, value);
+ window.location.search = params.toString();
+}
+</script>
+
+<table class="summary">
+ <tr><td>Goroutine Name:</td><td>{{.Name}}</td></tr>
+ <tr><td>Number of Goroutines:</td><td>{{.N}}</td></tr>
+ <tr><td>Execution Time:</td><td>{{.ExecTimePercent}} of total program execution time </td> </tr>
+ <tr><td>Network Wait Time:</td><td> <a href="/io?id={{.PC}}">graph</a><a href="/io?id={{.PC}}&raw=1" download="io.profile">(download)</a></td></tr>
+ <tr><td>Sync Block Time:</td><td> <a href="/block?id={{.PC}}">graph</a><a href="/block?id={{.PC}}&raw=1" download="block.profile">(download)</a></td></tr>
+ <tr><td>Blocking Syscall Time:</td><td> <a href="/syscall?id={{.PC}}">graph</a><a href="/syscall?id={{.PC}}&raw=1" download="syscall.profile">(download)</a></td></tr>
+ <tr><td>Scheduler Wait Time:</td><td> <a href="/sched?id={{.PC}}">graph</a><a href="/sched?id={{.PC}}&raw=1" download="sched.profile">(download)</a></td></tr>
+</table>
+<p>
+<table class="details">
+<tr>
+<th> Goroutine</th>
+<th onclick="reloadTable('sortby', 'TotalTime')" class="total-time"> Total</th>
+<th></th>
+<th onclick="reloadTable('sortby', 'ExecTime')" class="exec-time"> Execution</th>
+<th onclick="reloadTable('sortby', 'IOTime')" class="io-time"> Network wait</th>
+<th onclick="reloadTable('sortby', 'BlockTime')" class="block-time"> Sync block </th>
+<th onclick="reloadTable('sortby', 'SyscallTime')" class="syscall-time"> Blocking syscall</th>
+<th onclick="reloadTable('sortby', 'SchedWaitTime')" class="sched-time"> Scheduler wait</th>
+<th onclick="reloadTable('sortby', 'SweepTime')" class="sweep-time"> GC sweeping</th>
+<th onclick="reloadTable('sortby', 'GCTime')" class="pause-time"> GC pause</th>
+</tr>
+{{range .GList}}
+ <tr>
+ <td> <a href="/trace?goid={{.ID}}">{{.ID}}</a> </td>
+ <td> {{prettyDuration .TotalTime}} </td>
+ <td>
+ <div class="stacked-bar-graph">
+ {{if unknownTime .}}<span style="width:{{barLen (unknownTime .) $.MaxTotal}}" class="unknown-time">&nbsp;</span>{{end}}
+ {{if .ExecTime}}<span style="width:{{barLen .ExecTime $.MaxTotal}}" class="exec-time">&nbsp;</span>{{end}}
+ {{if .IOTime}}<span style="width:{{barLen .IOTime $.MaxTotal}}" class="io-time">&nbsp;</span>{{end}}
+ {{if .BlockTime}}<span style="width:{{barLen .BlockTime $.MaxTotal}}" class="block-time">&nbsp;</span>{{end}}
+ {{if .SyscallTime}}<span style="width:{{barLen .SyscallTime $.MaxTotal}}" class="syscall-time">&nbsp;</span>{{end}}
+ {{if .SchedWaitTime}}<span style="width:{{barLen .SchedWaitTime $.MaxTotal}}" class="sched-time">&nbsp;</span>{{end}}
+ </div>
+ </td>
+ <td> {{prettyDuration .ExecTime}}</td>
+ <td> {{prettyDuration .IOTime}}</td>
+ <td> {{prettyDuration .BlockTime}}</td>
+ <td> {{prettyDuration .SyscallTime}}</td>
+ <td> {{prettyDuration .SchedWaitTime}}</td>
+ <td> {{prettyDuration .SweepTime}} {{percent .SweepTime .TotalTime}}</td>
+ <td> {{prettyDuration .GCTime}} {{percent .GCTime .TotalTime}}</td>
+ </tr>
+{{end}}
+</table>
+`))