Source file
src/cmd/trace/goroutines.go
1
2
3
4
5
6
7 package main
8
9 import (
10 "fmt"
11 "html/template"
12 "internal/trace"
13 "log"
14 "net/http"
15 "reflect"
16 "sort"
17 "strconv"
18 "sync"
19 "time"
20 )
21
22 func init() {
23 http.HandleFunc("/goroutines", httpGoroutines)
24 http.HandleFunc("/goroutine", httpGoroutine)
25 }
26
27
28 type gtype struct {
29 ID uint64
30 Name string
31 N int
32 ExecTime int64
33 }
34
35 var (
36 gsInit sync.Once
37 gs map[uint64]*trace.GDesc
38 )
39
40
41 func analyzeGoroutines(events []*trace.Event) {
42 gsInit.Do(func() {
43 gs = trace.GoroutineStats(events)
44 })
45 }
46
47
48 func httpGoroutines(w http.ResponseWriter, r *http.Request) {
49 events, err := parseEvents()
50 if err != nil {
51 http.Error(w, err.Error(), http.StatusInternalServerError)
52 return
53 }
54 analyzeGoroutines(events)
55 gss := make(map[uint64]gtype)
56 for _, g := range gs {
57 gs1 := gss[g.PC]
58 gs1.ID = g.PC
59 gs1.Name = g.Name
60 gs1.N++
61 gs1.ExecTime += g.ExecTime
62 gss[g.PC] = gs1
63 }
64 var glist []gtype
65 for k, v := range gss {
66 v.ID = k
67
68
69 if v.ID == 0 && v.Name == "" {
70 v.Name = "(Inactive, no stack trace sampled)"
71 }
72 glist = append(glist, v)
73 }
74 sort.Slice(glist, func(i, j int) bool { return glist[i].ExecTime > glist[j].ExecTime })
75 w.Header().Set("Content-Type", "text/html;charset=utf-8")
76 if err := templGoroutines.Execute(w, glist); err != nil {
77 log.Printf("failed to execute template: %v", err)
78 return
79 }
80 }
81
82 var templGoroutines = template.Must(template.New("").Parse(`
83 <html>
84 <body>
85 Goroutines: <br>
86 {{range $}}
87 <a href="/goroutine?id={{.ID}}">{{.Name}}</a> N={{.N}} <br>
88 {{end}}
89 </body>
90 </html>
91 `))
92
93
94 func httpGoroutine(w http.ResponseWriter, r *http.Request) {
95
96
97 events, err := parseEvents()
98 if err != nil {
99 http.Error(w, err.Error(), http.StatusInternalServerError)
100 return
101 }
102
103 pc, err := strconv.ParseUint(r.FormValue("id"), 10, 64)
104 if err != nil {
105 http.Error(w, fmt.Sprintf("failed to parse id parameter '%v': %v", r.FormValue("id"), err), http.StatusInternalServerError)
106 return
107 }
108 analyzeGoroutines(events)
109 var (
110 glist []*trace.GDesc
111 name string
112 totalExecTime, execTime int64
113 maxTotalTime int64
114 )
115
116 for _, g := range gs {
117 totalExecTime += g.ExecTime
118
119 if g.PC != pc {
120 continue
121 }
122 glist = append(glist, g)
123 name = g.Name
124 execTime += g.ExecTime
125 if maxTotalTime < g.TotalTime {
126 maxTotalTime = g.TotalTime
127 }
128 }
129
130 execTimePercent := ""
131 if totalExecTime > 0 {
132 execTimePercent = fmt.Sprintf("%.2f%%", float64(execTime)/float64(totalExecTime)*100)
133 }
134
135 sortby := r.FormValue("sortby")
136 _, ok := reflect.TypeOf(trace.GDesc{}).FieldByNameFunc(func(s string) bool {
137 return s == sortby
138 })
139 if !ok {
140 sortby = "TotalTime"
141 }
142
143 sort.Slice(glist, func(i, j int) bool {
144 ival := reflect.ValueOf(glist[i]).Elem().FieldByName(sortby).Int()
145 jval := reflect.ValueOf(glist[j]).Elem().FieldByName(sortby).Int()
146 return ival > jval
147 })
148
149 err = templGoroutine.Execute(w, struct {
150 Name string
151 PC uint64
152 N int
153 ExecTimePercent string
154 MaxTotal int64
155 GList []*trace.GDesc
156 }{
157 Name: name,
158 PC: pc,
159 N: len(glist),
160 ExecTimePercent: execTimePercent,
161 MaxTotal: maxTotalTime,
162 GList: glist})
163 if err != nil {
164 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
165 return
166 }
167 }
168
169 var templGoroutine = template.Must(template.New("").Funcs(template.FuncMap{
170 "prettyDuration": func(nsec int64) template.HTML {
171 d := time.Duration(nsec) * time.Nanosecond
172 return template.HTML(d.String())
173 },
174 "percent": func(dividend, divisor int64) template.HTML {
175 if divisor == 0 {
176 return ""
177 }
178 return template.HTML(fmt.Sprintf("(%.1f%%)", float64(dividend)/float64(divisor)*100))
179 },
180 "barLen": func(dividend, divisor int64) template.HTML {
181 if divisor == 0 {
182 return "0"
183 }
184 return template.HTML(fmt.Sprintf("%.2f%%", float64(dividend)/float64(divisor)*100))
185 },
186 "unknownTime": func(desc *trace.GDesc) int64 {
187 sum := desc.ExecTime + desc.IOTime + desc.BlockTime + desc.SyscallTime + desc.SchedWaitTime
188 if sum < desc.TotalTime {
189 return desc.TotalTime - sum
190 }
191 return 0
192 },
193 }).Parse(`
194 <!DOCTYPE html>
195 <title>Goroutine {{.Name}}</title>
196 <style>
197 th {
198 background-color: #050505;
199 color: #fff;
200 }
201 th.total-time,
202 th.exec-time,
203 th.io-time,
204 th.block-time,
205 th.syscall-time,
206 th.sched-time,
207 th.sweep-time,
208 th.pause-time {
209 cursor: pointer;
210 }
211 table {
212 border-collapse: collapse;
213 }
214 .details tr:hover {
215 background-color: #f2f2f2;
216 }
217 .details td {
218 text-align: right;
219 border: 1px solid black;
220 }
221 .details td.id {
222 text-align: left;
223 }
224 .stacked-bar-graph {
225 width: 300px;
226 height: 10px;
227 color: #414042;
228 white-space: nowrap;
229 font-size: 5px;
230 }
231 .stacked-bar-graph span {
232 display: inline-block;
233 width: 100%;
234 height: 100%;
235 box-sizing: border-box;
236 float: left;
237 padding: 0;
238 }
239 .unknown-time { background-color: #636363; }
240 .exec-time { background-color: #d7191c; }
241 .io-time { background-color: #fdae61; }
242 .block-time { background-color: #d01c8b; }
243 .syscall-time { background-color: #7b3294; }
244 .sched-time { background-color: #2c7bb6; }
245 </style>
246
247 <script>
248 function reloadTable(key, value) {
249 let params = new URLSearchParams(window.location.search);
250 params.set(key, value);
251 window.location.search = params.toString();
252 }
253 </script>
254
255 <table class="summary">
256 <tr><td>Goroutine Name:</td><td>{{.Name}}</td></tr>
257 <tr><td>Number of Goroutines:</td><td>{{.N}}</td></tr>
258 <tr><td>Execution Time:</td><td>{{.ExecTimePercent}} of total program execution time </td> </tr>
259 <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>
260 <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>
261 <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>
262 <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>
263 </table>
264 <p>
265 <table class="details">
266 <tr>
267 <th> Goroutine</th>
268 <th onclick="reloadTable('sortby', 'TotalTime')" class="total-time"> Total</th>
269 <th></th>
270 <th onclick="reloadTable('sortby', 'ExecTime')" class="exec-time"> Execution</th>
271 <th onclick="reloadTable('sortby', 'IOTime')" class="io-time"> Network wait</th>
272 <th onclick="reloadTable('sortby', 'BlockTime')" class="block-time"> Sync block </th>
273 <th onclick="reloadTable('sortby', 'SyscallTime')" class="syscall-time"> Blocking syscall</th>
274 <th onclick="reloadTable('sortby', 'SchedWaitTime')" class="sched-time"> Scheduler wait</th>
275 <th onclick="reloadTable('sortby', 'SweepTime')" class="sweep-time"> GC sweeping</th>
276 <th onclick="reloadTable('sortby', 'GCTime')" class="pause-time"> GC pause</th>
277 </tr>
278 {{range .GList}}
279 <tr>
280 <td> <a href="/trace?goid={{.ID}}">{{.ID}}</a> </td>
281 <td> {{prettyDuration .TotalTime}} </td>
282 <td>
283 <div class="stacked-bar-graph">
284 {{if unknownTime .}}<span style="width:{{barLen (unknownTime .) $.MaxTotal}}" class="unknown-time"> </span>{{end}}
285 {{if .ExecTime}}<span style="width:{{barLen .ExecTime $.MaxTotal}}" class="exec-time"> </span>{{end}}
286 {{if .IOTime}}<span style="width:{{barLen .IOTime $.MaxTotal}}" class="io-time"> </span>{{end}}
287 {{if .BlockTime}}<span style="width:{{barLen .BlockTime $.MaxTotal}}" class="block-time"> </span>{{end}}
288 {{if .SyscallTime}}<span style="width:{{barLen .SyscallTime $.MaxTotal}}" class="syscall-time"> </span>{{end}}
289 {{if .SchedWaitTime}}<span style="width:{{barLen .SchedWaitTime $.MaxTotal}}" class="sched-time"> </span>{{end}}
290 </div>
291 </td>
292 <td> {{prettyDuration .ExecTime}}</td>
293 <td> {{prettyDuration .IOTime}}</td>
294 <td> {{prettyDuration .BlockTime}}</td>
295 <td> {{prettyDuration .SyscallTime}}</td>
296 <td> {{prettyDuration .SchedWaitTime}}</td>
297 <td> {{prettyDuration .SweepTime}} {{percent .SweepTime .TotalTime}}</td>
298 <td> {{prettyDuration .GCTime}} {{percent .GCTime .TotalTime}}</td>
299 </tr>
300 {{end}}
301 </table>
302 `))
303
View as plain text