Source file
src/cmd/trace/regions.go
1
2
3
4
5 package main
6
7 import (
8 "cmp"
9 "fmt"
10 "html/template"
11 "internal/trace"
12 "internal/trace/traceviewer"
13 "net/http"
14 "net/url"
15 "slices"
16 "sort"
17 "strconv"
18 "strings"
19 "time"
20 )
21
22
23 func UserRegionsHandlerFunc(t *parsedTrace) http.HandlerFunc {
24 return func(w http.ResponseWriter, r *http.Request) {
25
26 summary := make(map[regionFingerprint]regionStats)
27 for _, g := range t.summary.Goroutines {
28 for _, r := range g.Regions {
29 id := fingerprintRegion(r)
30 stats, ok := summary[id]
31 if !ok {
32 stats.regionFingerprint = id
33 }
34 stats.add(t, r)
35 summary[id] = stats
36 }
37 }
38
39 userRegions := make([]regionStats, 0, len(summary))
40 for _, stats := range summary {
41 userRegions = append(userRegions, stats)
42 }
43 slices.SortFunc(userRegions, func(a, b regionStats) int {
44 if c := cmp.Compare(a.Type, b.Type); c != 0 {
45 return c
46 }
47 return cmp.Compare(a.Frame.PC, b.Frame.PC)
48 })
49
50 err := templUserRegionTypes.Execute(w, userRegions)
51 if err != nil {
52 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
53 return
54 }
55 }
56 }
57
58
59
60 type regionFingerprint struct {
61 Frame trace.StackFrame
62 Type string
63 }
64
65 func fingerprintRegion(r *trace.UserRegionSummary) regionFingerprint {
66 return regionFingerprint{
67 Frame: regionTopStackFrame(r),
68 Type: r.Name,
69 }
70 }
71
72 func regionTopStackFrame(r *trace.UserRegionSummary) trace.StackFrame {
73 var frame trace.StackFrame
74 if r.Start != nil && r.Start.Stack() != trace.NoStack {
75 for f := range r.Start.Stack().Frames() {
76 frame = f
77 }
78 }
79 return frame
80 }
81
82 type regionStats struct {
83 regionFingerprint
84 Histogram traceviewer.TimeHistogram
85 }
86
87 func (s *regionStats) UserRegionURL() func(min, max time.Duration) string {
88 return func(min, max time.Duration) string {
89 return fmt.Sprintf("/userregion?type=%s&pc=%x&latmin=%v&latmax=%v", template.URLQueryEscaper(s.Type), s.Frame.PC, template.URLQueryEscaper(min), template.URLQueryEscaper(max))
90 }
91 }
92
93 func (s *regionStats) add(t *parsedTrace, region *trace.UserRegionSummary) {
94 s.Histogram.Add(regionInterval(t, region).duration())
95 }
96
97 var templUserRegionTypes = template.Must(template.New("").Parse(`
98 <!DOCTYPE html>
99 <title>Regions</title>
100 <style>` + traceviewer.CommonStyle + `
101 .histoTime {
102 width: 20%;
103 white-space:nowrap;
104 }
105 th {
106 background-color: #050505;
107 color: #fff;
108 }
109 table {
110 border-collapse: collapse;
111 }
112 td,
113 th {
114 padding-left: 8px;
115 padding-right: 8px;
116 padding-top: 4px;
117 padding-bottom: 4px;
118 }
119 </style>
120 <body>
121 <h1>Regions</h1>
122
123 Below is a table containing a summary of all the user-defined regions in the trace.
124 Regions are grouped by the region type and the point at which the region started.
125 The rightmost column of the table contains a latency histogram for each region group.
126 Note that this histogram only counts regions that began and ended within the traced
127 period.
128 However, the "Count" column includes all regions, including those that only started
129 or ended during the traced period.
130 Regions that were active through the trace period were not recorded, and so are not
131 accounted for at all.
132 Click on the links to explore a breakdown of time spent for each region by goroutine
133 and user-defined task.
134 <br>
135 <br>
136
137 <table border="1" sortable="1">
138 <tr>
139 <th>Region type</th>
140 <th>Count</th>
141 <th>Duration distribution (complete tasks)</th>
142 </tr>
143 {{range $}}
144 <tr>
145 <td><pre>{{printf "%q" .Type}}<br>{{.Frame.Func}} @ {{printf "0x%x" .Frame.PC}}<br>{{.Frame.File}}:{{.Frame.Line}}</pre></td>
146 <td><a href="/userregion?type={{.Type}}&pc={{.Frame.PC | printf "%x"}}">{{.Histogram.Count}}</a></td>
147 <td>{{.Histogram.ToHTML (.UserRegionURL)}}</td>
148 </tr>
149 {{end}}
150 </table>
151 </body>
152 </html>
153 `))
154
155
156 func UserRegionHandlerFunc(t *parsedTrace) http.HandlerFunc {
157 return func(w http.ResponseWriter, r *http.Request) {
158
159 filter, err := newRegionFilter(r)
160 if err != nil {
161 http.Error(w, err.Error(), http.StatusBadRequest)
162 return
163 }
164
165
166 type region struct {
167 *trace.UserRegionSummary
168 Goroutine trace.GoID
169 NonOverlappingStats map[string]time.Duration
170 HasRangeTime bool
171 }
172 var regions []region
173 var maxTotal time.Duration
174 validNonOverlappingStats := make(map[string]struct{})
175 validRangeStats := make(map[string]struct{})
176 for _, g := range t.summary.Goroutines {
177 for _, r := range g.Regions {
178 if !filter.match(t, r) {
179 continue
180 }
181 nonOverlappingStats := r.NonOverlappingStats()
182 for name := range nonOverlappingStats {
183 validNonOverlappingStats[name] = struct{}{}
184 }
185 var totalRangeTime time.Duration
186 for name, dt := range r.RangeTime {
187 validRangeStats[name] = struct{}{}
188 totalRangeTime += dt
189 }
190 regions = append(regions, region{
191 UserRegionSummary: r,
192 Goroutine: g.ID,
193 NonOverlappingStats: nonOverlappingStats,
194 HasRangeTime: totalRangeTime != 0,
195 })
196 if maxTotal < r.TotalTime {
197 maxTotal = r.TotalTime
198 }
199 }
200 }
201
202
203 sortBy := r.FormValue("sortby")
204 if _, ok := validNonOverlappingStats[sortBy]; ok {
205 slices.SortFunc(regions, func(a, b region) int {
206 return cmp.Compare(b.NonOverlappingStats[sortBy], a.NonOverlappingStats[sortBy])
207 })
208 } else {
209
210 slices.SortFunc(regions, func(a, b region) int {
211 return cmp.Compare(b.TotalTime, a.TotalTime)
212 })
213 }
214
215
216 allNonOverlappingStats := make([]string, 0, len(validNonOverlappingStats))
217 for name := range validNonOverlappingStats {
218 allNonOverlappingStats = append(allNonOverlappingStats, name)
219 }
220 slices.SortFunc(allNonOverlappingStats, func(a, b string) int {
221 if a == b {
222 return 0
223 }
224 if a == "Execution time" {
225 return -1
226 }
227 if b == "Execution time" {
228 return 1
229 }
230 return cmp.Compare(a, b)
231 })
232
233
234 allRangeStats := make([]string, 0, len(validRangeStats))
235 for name := range validRangeStats {
236 allRangeStats = append(allRangeStats, name)
237 }
238 sort.Strings(allRangeStats)
239
240 err = templUserRegionType.Execute(w, struct {
241 MaxTotal time.Duration
242 Regions []region
243 Name string
244 Filter *regionFilter
245 NonOverlappingStats []string
246 RangeStats []string
247 }{
248 MaxTotal: maxTotal,
249 Regions: regions,
250 Name: filter.name,
251 Filter: filter,
252 NonOverlappingStats: allNonOverlappingStats,
253 RangeStats: allRangeStats,
254 })
255 if err != nil {
256 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
257 return
258 }
259 }
260 }
261
262 var templUserRegionType = template.Must(template.New("").Funcs(template.FuncMap{
263 "headerStyle": func(statName string) template.HTMLAttr {
264 return template.HTMLAttr(fmt.Sprintf("style=\"background-color: %s;\"", stat2Color(statName)))
265 },
266 "barStyle": func(statName string, dividend, divisor time.Duration) template.HTMLAttr {
267 width := "0"
268 if divisor != 0 {
269 width = fmt.Sprintf("%.2f%%", float64(dividend)/float64(divisor)*100)
270 }
271 return template.HTMLAttr(fmt.Sprintf("style=\"width: %s; background-color: %s;\"", width, stat2Color(statName)))
272 },
273 "filterParams": func(f *regionFilter) template.URL {
274 return template.URL(f.params.Encode())
275 },
276 }).Parse(`
277 <!DOCTYPE html>
278 <title>Regions: {{.Name}}</title>
279 <style>` + traceviewer.CommonStyle + `
280 th {
281 background-color: #050505;
282 color: #fff;
283 }
284 th.link {
285 cursor: pointer;
286 }
287 table {
288 border-collapse: collapse;
289 }
290 td,
291 th {
292 padding-left: 8px;
293 padding-right: 8px;
294 padding-top: 4px;
295 padding-bottom: 4px;
296 }
297 .details tr:hover {
298 background-color: #f2f2f2;
299 }
300 .details td {
301 text-align: right;
302 border: 1px solid #000;
303 }
304 .details td.id {
305 text-align: left;
306 }
307 .stacked-bar-graph {
308 width: 300px;
309 height: 10px;
310 color: #414042;
311 white-space: nowrap;
312 font-size: 5px;
313 }
314 .stacked-bar-graph span {
315 display: inline-block;
316 width: 100%;
317 height: 100%;
318 box-sizing: border-box;
319 float: left;
320 padding: 0;
321 }
322 </style>
323
324 <script>
325 function reloadTable(key, value) {
326 let params = new URLSearchParams(window.location.search);
327 params.set(key, value);
328 window.location.search = params.toString();
329 }
330 </script>
331
332 <h1>Regions: {{.Name}}</h1>
333
334 Table of contents
335 <ul>
336 <li><a href="#summary">Summary</a></li>
337 <li><a href="#breakdown">Breakdown</a></li>
338 <li><a href="#ranges">Special ranges</a></li>
339 </ul>
340
341 <h3 id="summary">Summary</h3>
342
343 {{ with $p := filterParams .Filter}}
344 <table class="summary">
345 <tr>
346 <td>Network wait profile:</td>
347 <td> <a href="/regionio?{{$p}}">graph</a> <a href="/regionio?{{$p}}&raw=1" download="io.profile">(download)</a></td>
348 </tr>
349 <tr>
350 <td>Sync block profile:</td>
351 <td> <a href="/regionblock?{{$p}}">graph</a> <a href="/regionblock?{{$p}}&raw=1" download="block.profile">(download)</a></td>
352 </tr>
353 <tr>
354 <td>Syscall profile:</td>
355 <td> <a href="/regionsyscall?{{$p}}">graph</a> <a href="/regionsyscall?{{$p}}&raw=1" download="syscall.profile">(download)</a></td>
356 </tr>
357 <tr>
358 <td>Scheduler wait profile:</td>
359 <td> <a href="/regionsched?{{$p}}">graph</a> <a href="/regionsched?{{$p}}&raw=1" download="sched.profile">(download)</a></td>
360 </tr>
361 </table>
362 {{ end }}
363
364 <h3 id="breakdown">Breakdown</h3>
365
366 The table below breaks down where each goroutine is spent its time during the
367 traced period.
368 All of the columns except total time are non-overlapping.
369 <br>
370 <br>
371
372 <table class="details">
373 <tr>
374 <th> Goroutine </th>
375 <th> Task </th>
376 <th class="link" onclick="reloadTable('sortby', 'Total time')"> Total</th>
377 <th></th>
378 {{range $.NonOverlappingStats}}
379 <th class="link" onclick="reloadTable('sortby', '{{.}}')" {{headerStyle .}}> {{.}}</th>
380 {{end}}
381 </tr>
382 {{range .Regions}}
383 <tr>
384 <td> <a href="/trace?goid={{.Goroutine}}">{{.Goroutine}}</a> </td>
385 <td> {{if .TaskID}}<a href="/trace?focustask={{.TaskID}}">{{.TaskID}}</a>{{end}} </td>
386 <td> {{ .TotalTime.String }} </td>
387 <td>
388 <div class="stacked-bar-graph">
389 {{$Region := .}}
390 {{range $.NonOverlappingStats}}
391 {{$Time := index $Region.NonOverlappingStats .}}
392 {{if $Time}}
393 <span {{barStyle . $Time $.MaxTotal}}> </span>
394 {{end}}
395 {{end}}
396 </div>
397 </td>
398 {{$Region := .}}
399 {{range $.NonOverlappingStats}}
400 {{$Time := index $Region.NonOverlappingStats .}}
401 <td> {{$Time.String}}</td>
402 {{end}}
403 </tr>
404 {{end}}
405 </table>
406
407 <h3 id="ranges">Special ranges</h3>
408
409 The table below describes how much of the traced period each goroutine spent in
410 certain special time ranges.
411 If a goroutine has spent no time in any special time ranges, it is excluded from
412 the table.
413 For example, how much time it spent helping the GC. Note that these times do
414 overlap with the times from the first table.
415 In general the goroutine may not be executing in these special time ranges.
416 For example, it may have blocked while trying to help the GC.
417 This must be taken into account when interpreting the data.
418 <br>
419 <br>
420
421 <table class="details">
422 <tr>
423 <th> Goroutine</th>
424 <th> Task </th>
425 <th> Total</th>
426 {{range $.RangeStats}}
427 <th {{headerStyle .}}> {{.}}</th>
428 {{end}}
429 </tr>
430 {{range .Regions}}
431 {{if .HasRangeTime}}
432 <tr>
433 <td> <a href="/trace?goid={{.Goroutine}}">{{.Goroutine}}</a> </td>
434 <td> {{if .TaskID}}<a href="/trace?focustask={{.TaskID}}">{{.TaskID}}</a>{{end}} </td>
435 <td> {{ .TotalTime.String }} </td>
436 {{$Region := .}}
437 {{range $.RangeStats}}
438 {{$Time := index $Region.RangeTime .}}
439 <td> {{$Time.String}}</td>
440 {{end}}
441 </tr>
442 {{end}}
443 {{end}}
444 </table>
445 `))
446
447
448 type regionFilter struct {
449 name string
450 params url.Values
451 cond []func(*parsedTrace, *trace.UserRegionSummary) bool
452 }
453
454
455
456 func (f *regionFilter) match(t *parsedTrace, s *trace.UserRegionSummary) bool {
457 for _, c := range f.cond {
458 if !c(t, s) {
459 return false
460 }
461 }
462 return true
463 }
464
465
466 func newRegionFilter(r *http.Request) (*regionFilter, error) {
467 if err := r.ParseForm(); err != nil {
468 return nil, err
469 }
470
471 var name []string
472 var conditions []func(*parsedTrace, *trace.UserRegionSummary) bool
473 filterParams := make(url.Values)
474
475 param := r.Form
476 if typ, ok := param["type"]; ok && len(typ) > 0 {
477 name = append(name, fmt.Sprintf("%q", typ[0]))
478 conditions = append(conditions, func(_ *parsedTrace, r *trace.UserRegionSummary) bool {
479 return r.Name == typ[0]
480 })
481 filterParams.Add("type", typ[0])
482 }
483 if pc, err := strconv.ParseUint(r.FormValue("pc"), 16, 64); err == nil {
484 encPC := fmt.Sprintf("0x%x", pc)
485 name = append(name, "@ "+encPC)
486 conditions = append(conditions, func(_ *parsedTrace, r *trace.UserRegionSummary) bool {
487 return regionTopStackFrame(r).PC == pc
488 })
489 filterParams.Add("pc", encPC)
490 }
491
492 if lat, err := time.ParseDuration(r.FormValue("latmin")); err == nil {
493 name = append(name, fmt.Sprintf("(latency >= %s)", lat))
494 conditions = append(conditions, func(t *parsedTrace, r *trace.UserRegionSummary) bool {
495 return regionInterval(t, r).duration() >= lat
496 })
497 filterParams.Add("latmin", lat.String())
498 }
499 if lat, err := time.ParseDuration(r.FormValue("latmax")); err == nil {
500 name = append(name, fmt.Sprintf("(latency <= %s)", lat))
501 conditions = append(conditions, func(t *parsedTrace, r *trace.UserRegionSummary) bool {
502 return regionInterval(t, r).duration() <= lat
503 })
504 filterParams.Add("latmax", lat.String())
505 }
506
507 return ®ionFilter{
508 name: strings.Join(name, " "),
509 cond: conditions,
510 params: filterParams,
511 }, nil
512 }
513
514 func regionInterval(t *parsedTrace, s *trace.UserRegionSummary) interval {
515 var i interval
516 if s.Start != nil {
517 i.start = s.Start.Time()
518 } else {
519 i.start = t.startTime()
520 }
521 if s.End != nil {
522 i.end = s.End.Time()
523 } else {
524 i.end = t.endTime()
525 }
526 return i
527 }
528
View as plain text