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