Source file
src/cmd/trace/annotations.go
1
2
3
4
5 package main
6
7 import (
8 "bytes"
9 "fmt"
10 "html/template"
11 "internal/trace"
12 "internal/trace/traceviewer"
13 "log"
14 "net/http"
15 "net/url"
16 "reflect"
17 "sort"
18 "strconv"
19 "strings"
20 "time"
21 )
22
23 func init() {
24 http.HandleFunc("/usertasks", httpUserTasks)
25 http.HandleFunc("/usertask", httpUserTask)
26 http.HandleFunc("/userregions", httpUserRegions)
27 http.HandleFunc("/userregion", httpUserRegion)
28 }
29
30
31 func httpUserTasks(w http.ResponseWriter, r *http.Request) {
32 res, err := analyzeAnnotations()
33 if err != nil {
34 http.Error(w, err.Error(), http.StatusInternalServerError)
35 return
36 }
37
38 tasks := res.tasks
39 summary := make(map[string]taskStats)
40 for _, task := range tasks {
41 stats, ok := summary[task.name]
42 if !ok {
43 stats.Type = task.name
44 }
45
46 stats.add(task)
47 summary[task.name] = stats
48 }
49
50
51 userTasks := make([]taskStats, 0, len(summary))
52 for _, stats := range summary {
53 userTasks = append(userTasks, stats)
54 }
55 sort.Slice(userTasks, func(i, j int) bool {
56 return userTasks[i].Type < userTasks[j].Type
57 })
58
59
60 err = templUserTaskTypes.Execute(w, userTasks)
61 if err != nil {
62 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
63 return
64 }
65 }
66
67 func httpUserRegions(w http.ResponseWriter, r *http.Request) {
68 res, err := analyzeAnnotations()
69 if err != nil {
70 http.Error(w, err.Error(), http.StatusInternalServerError)
71 return
72 }
73 allRegions := res.regions
74
75 summary := make(map[regionTypeID]regionStats)
76 for id, regions := range allRegions {
77 stats, ok := summary[id]
78 if !ok {
79 stats.regionTypeID = id
80 }
81 for _, s := range regions {
82 stats.add(s)
83 }
84 summary[id] = stats
85 }
86
87 userRegions := make([]regionStats, 0, len(summary))
88 for _, stats := range summary {
89 userRegions = append(userRegions, stats)
90 }
91 sort.Slice(userRegions, func(i, j int) bool {
92 if userRegions[i].Type != userRegions[j].Type {
93 return userRegions[i].Type < userRegions[j].Type
94 }
95 return userRegions[i].Frame.PC < userRegions[j].Frame.PC
96 })
97
98 err = templUserRegionTypes.Execute(w, userRegions)
99 if err != nil {
100 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
101 return
102 }
103 }
104
105 func httpUserRegion(w http.ResponseWriter, r *http.Request) {
106 filter, err := newRegionFilter(r)
107 if err != nil {
108 http.Error(w, err.Error(), http.StatusBadRequest)
109 return
110 }
111 res, err := analyzeAnnotations()
112 if err != nil {
113 http.Error(w, err.Error(), http.StatusInternalServerError)
114 return
115 }
116 allRegions := res.regions
117
118 var data []regionDesc
119
120 var maxTotal int64
121 for id, regions := range allRegions {
122 for _, s := range regions {
123 if !filter.match(id, s) {
124 continue
125 }
126 data = append(data, s)
127 if maxTotal < s.TotalTime {
128 maxTotal = s.TotalTime
129 }
130 }
131 }
132
133 sortby := r.FormValue("sortby")
134 _, ok := reflect.TypeOf(regionDesc{}).FieldByNameFunc(func(s string) bool {
135 return s == sortby
136 })
137 if !ok {
138 sortby = "TotalTime"
139 }
140 sort.Slice(data, func(i, j int) bool {
141 ival := reflect.ValueOf(data[i]).FieldByName(sortby).Int()
142 jval := reflect.ValueOf(data[j]).FieldByName(sortby).Int()
143 return ival > jval
144 })
145
146 err = templUserRegionType.Execute(w, struct {
147 MaxTotal int64
148 Data []regionDesc
149 Name string
150 Filter *regionFilter
151 }{
152 MaxTotal: maxTotal,
153 Data: data,
154 Name: filter.name,
155 Filter: filter,
156 })
157 if err != nil {
158 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
159 return
160 }
161 }
162
163
164 func httpUserTask(w http.ResponseWriter, r *http.Request) {
165 filter, err := newTaskFilter(r)
166 if err != nil {
167 http.Error(w, err.Error(), http.StatusBadRequest)
168 return
169 }
170
171 res, err := analyzeAnnotations()
172 if err != nil {
173 http.Error(w, err.Error(), http.StatusInternalServerError)
174 return
175 }
176 tasks := res.tasks
177
178 type event struct {
179 WhenString string
180 Elapsed time.Duration
181 Go uint64
182 What string
183
184 }
185 type entry struct {
186 WhenString string
187 ID uint64
188 Duration time.Duration
189 Complete bool
190 Events []event
191 Start, End time.Duration
192 GCTime time.Duration
193 }
194
195 base := time.Duration(firstTimestamp()) * time.Nanosecond
196
197 var data []entry
198
199 for _, task := range tasks {
200 if !filter.match(task) {
201 continue
202 }
203
204 rawEvents := append([]*trace.Event{}, task.events...)
205 for _, s := range task.regions {
206 if s.Start != nil {
207 rawEvents = append(rawEvents, s.Start)
208 }
209 }
210 sort.SliceStable(rawEvents, func(i, j int) bool { return rawEvents[i].Ts < rawEvents[j].Ts })
211
212 var events []event
213 var last time.Duration
214 for i, ev := range rawEvents {
215 when := time.Duration(ev.Ts)*time.Nanosecond - base
216 elapsed := time.Duration(ev.Ts)*time.Nanosecond - last
217 if i == 0 {
218 elapsed = 0
219 }
220
221 what := describeEvent(ev)
222 if what != "" {
223 events = append(events, event{
224 WhenString: fmt.Sprintf("%2.9f", when.Seconds()),
225 Elapsed: elapsed,
226 What: what,
227 Go: ev.G,
228 })
229 last = time.Duration(ev.Ts) * time.Nanosecond
230 }
231 }
232
233 data = append(data, entry{
234 WhenString: fmt.Sprintf("%2.9fs", (time.Duration(task.firstTimestamp())*time.Nanosecond - base).Seconds()),
235 Duration: task.duration(),
236 ID: task.id,
237 Complete: task.complete(),
238 Events: events,
239 Start: time.Duration(task.firstTimestamp()) * time.Nanosecond,
240 End: time.Duration(task.endTimestamp()) * time.Nanosecond,
241 GCTime: task.overlappingGCDuration(res.gcEvents),
242 })
243 }
244 sort.Slice(data, func(i, j int) bool {
245 return data[i].Duration < data[j].Duration
246 })
247
248
249 err = templUserTaskType.Execute(w, struct {
250 Name string
251 Entry []entry
252 }{
253 Name: filter.name,
254 Entry: data,
255 })
256 if err != nil {
257 log.Printf("failed to execute template: %v", err)
258 http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
259 return
260 }
261 }
262
263 type annotationAnalysisResult struct {
264 tasks map[uint64]*taskDesc
265 regions map[regionTypeID][]regionDesc
266 gcEvents []*trace.Event
267 }
268
269 type regionTypeID struct {
270 Frame trace.Frame
271 Type string
272 }
273
274
275
276 func analyzeAnnotations() (annotationAnalysisResult, error) {
277 res, err := parseTrace()
278 if err != nil {
279 return annotationAnalysisResult{}, fmt.Errorf("failed to parse trace: %v", err)
280 }
281
282 events := res.Events
283 if len(events) == 0 {
284 return annotationAnalysisResult{}, fmt.Errorf("empty trace")
285 }
286
287 tasks := allTasks{}
288 regions := map[regionTypeID][]regionDesc{}
289 var gcEvents []*trace.Event
290
291 for _, ev := range events {
292 switch typ := ev.Type; typ {
293 case trace.EvUserTaskCreate, trace.EvUserTaskEnd, trace.EvUserLog:
294 taskid := ev.Args[0]
295 task := tasks.task(taskid)
296 task.addEvent(ev)
297
298
299 if typ == trace.EvUserTaskCreate {
300 if parentID := ev.Args[1]; parentID != 0 {
301 parentTask := tasks.task(parentID)
302 task.parent = parentTask
303 if parentTask != nil {
304 parentTask.children = append(parentTask.children, task)
305 }
306 }
307 }
308
309 case trace.EvGCStart:
310 gcEvents = append(gcEvents, ev)
311 }
312 }
313
314 analyzeGoroutines(events)
315 for goid, stats := range gs {
316
317
318
319 for _, s := range stats.Regions {
320 if s.TaskID != 0 {
321 task := tasks.task(s.TaskID)
322 task.goroutines[goid] = struct{}{}
323 task.regions = append(task.regions, regionDesc{UserRegionDesc: s, G: goid})
324 }
325 var frame trace.Frame
326 if s.Start != nil {
327 frame = *s.Start.Stk[0]
328 }
329 id := regionTypeID{Frame: frame, Type: s.Name}
330 regions[id] = append(regions[id], regionDesc{UserRegionDesc: s, G: goid})
331 }
332 }
333
334
335 for _, task := range tasks {
336 sort.SliceStable(task.regions, func(i, j int) bool {
337 si, sj := task.regions[i].firstTimestamp(), task.regions[j].firstTimestamp()
338 if si != sj {
339 return si < sj
340 }
341 return task.regions[i].lastTimestamp() < task.regions[j].lastTimestamp()
342 })
343 }
344 return annotationAnalysisResult{tasks: tasks, regions: regions, gcEvents: gcEvents}, nil
345 }
346
347
348 type taskDesc struct {
349 name string
350 id uint64
351 events []*trace.Event
352 regions []regionDesc
353 goroutines map[uint64]struct{}
354
355 create *trace.Event
356 end *trace.Event
357
358 parent *taskDesc
359 children []*taskDesc
360 }
361
362 func newTaskDesc(id uint64) *taskDesc {
363 return &taskDesc{
364 id: id,
365 goroutines: make(map[uint64]struct{}),
366 }
367 }
368
369 func (task *taskDesc) String() string {
370 if task == nil {
371 return "task <nil>"
372 }
373 wb := new(strings.Builder)
374 fmt.Fprintf(wb, "task %d:\t%s\n", task.id, task.name)
375 fmt.Fprintf(wb, "\tstart: %v end: %v complete: %t\n", task.firstTimestamp(), task.endTimestamp(), task.complete())
376 fmt.Fprintf(wb, "\t%d goroutines\n", len(task.goroutines))
377 fmt.Fprintf(wb, "\t%d regions:\n", len(task.regions))
378 for _, s := range task.regions {
379 fmt.Fprintf(wb, "\t\t%s(goid=%d)\n", s.Name, s.G)
380 }
381 if task.parent != nil {
382 fmt.Fprintf(wb, "\tparent: %s\n", task.parent.name)
383 }
384 fmt.Fprintf(wb, "\t%d children:\n", len(task.children))
385 for _, c := range task.children {
386 fmt.Fprintf(wb, "\t\t%s\n", c.name)
387 }
388
389 return wb.String()
390 }
391
392
393 type regionDesc struct {
394 *trace.UserRegionDesc
395 G uint64
396 }
397
398 type allTasks map[uint64]*taskDesc
399
400 func (tasks allTasks) task(taskID uint64) *taskDesc {
401 if taskID == 0 {
402 return nil
403 }
404
405 t, ok := tasks[taskID]
406 if ok {
407 return t
408 }
409
410 t = newTaskDesc(taskID)
411 tasks[taskID] = t
412 return t
413 }
414
415 func (task *taskDesc) addEvent(ev *trace.Event) {
416 if task == nil {
417 return
418 }
419
420 task.events = append(task.events, ev)
421 task.goroutines[ev.G] = struct{}{}
422
423 switch typ := ev.Type; typ {
424 case trace.EvUserTaskCreate:
425 task.name = ev.SArgs[0]
426 task.create = ev
427 case trace.EvUserTaskEnd:
428 task.end = ev
429 }
430 }
431
432
433
434 func (task *taskDesc) complete() bool {
435 if task == nil {
436 return false
437 }
438 return task.create != nil && task.end != nil
439 }
440
441
442 func (task *taskDesc) descendants() []*taskDesc {
443 if task == nil {
444 return nil
445 }
446 res := []*taskDesc{task}
447 for i := 0; len(res[i:]) > 0; i++ {
448 t := res[i]
449 res = append(res, t.children...)
450 }
451 return res
452 }
453
454
455
456
457 func (task *taskDesc) firstTimestamp() int64 {
458 if task != nil && task.create != nil {
459 return task.create.Ts
460 }
461 return firstTimestamp()
462 }
463
464
465
466
467 func (task *taskDesc) lastTimestamp() int64 {
468 endTs := task.endTimestamp()
469 if last := task.lastEvent(); last != nil && last.Ts > endTs {
470 return last.Ts
471 }
472 return endTs
473 }
474
475
476
477
478 func (task *taskDesc) endTimestamp() int64 {
479 if task != nil && task.end != nil {
480 return task.end.Ts
481 }
482 return lastTimestamp()
483 }
484
485 func (task *taskDesc) duration() time.Duration {
486 return time.Duration(task.endTimestamp()-task.firstTimestamp()) * time.Nanosecond
487 }
488
489 func (region *regionDesc) duration() time.Duration {
490 return time.Duration(region.lastTimestamp()-region.firstTimestamp()) * time.Nanosecond
491 }
492
493
494 func (task *taskDesc) overlappingGCDuration(evs []*trace.Event) (overlapping time.Duration) {
495 for _, ev := range evs {
496
497 if typ := ev.Type; typ != trace.EvGCStart {
498 continue
499 }
500
501 if o, overlapped := task.overlappingDuration(ev); overlapped {
502 overlapping += o
503 }
504 }
505 return overlapping
506 }
507
508
509
510
511 func (task *taskDesc) overlappingInstant(ev *trace.Event) bool {
512 if _, ok := isUserAnnotationEvent(ev); ok && task.id != ev.Args[0] {
513 return false
514 }
515
516 ts := ev.Ts
517 taskStart := task.firstTimestamp()
518 taskEnd := task.endTimestamp()
519 if ts < taskStart || taskEnd < ts {
520 return false
521 }
522 if ev.P == trace.GCP {
523 return true
524 }
525
526
527 goid := ev.G
528 for _, region := range task.regions {
529 if region.G != goid {
530 continue
531 }
532 if region.firstTimestamp() <= ts && ts <= region.lastTimestamp() {
533 return true
534 }
535 }
536 return false
537 }
538
539
540
541
542
543 func (task *taskDesc) overlappingDuration(ev *trace.Event) (time.Duration, bool) {
544 start := ev.Ts
545 end := lastTimestamp()
546 if ev.Link != nil {
547 end = ev.Link.Ts
548 }
549
550 if start > end {
551 return 0, false
552 }
553
554 goid := ev.G
555 goid2 := ev.G
556 if ev.Link != nil {
557 goid2 = ev.Link.G
558 }
559
560
561 if ev.P == trace.GCP {
562 taskStart := task.firstTimestamp()
563 taskEnd := task.endTimestamp()
564 o := overlappingDuration(taskStart, taskEnd, start, end)
565 return o, o > 0
566 }
567
568
569 var overlapping time.Duration
570 var lastRegionEnd int64
571 for _, region := range task.regions {
572 if region.G != goid && region.G != goid2 {
573 continue
574 }
575 regionStart, regionEnd := region.firstTimestamp(), region.lastTimestamp()
576 if regionStart < lastRegionEnd {
577 continue
578 }
579
580 if o := overlappingDuration(regionStart, regionEnd, start, end); o > 0 {
581
582 lastRegionEnd = regionEnd
583 overlapping += o
584 }
585 }
586 return overlapping, overlapping > 0
587 }
588
589
590
591
592 func overlappingDuration(start1, end1, start2, end2 int64) time.Duration {
593
594 if end1 < start2 || end2 < start1 {
595 return 0
596 }
597
598 if start1 < start2 {
599 start1 = start2
600 }
601 if end1 > end2 {
602 end1 = end2
603 }
604 return time.Duration(end1 - start1)
605 }
606
607 func (task *taskDesc) lastEvent() *trace.Event {
608 if task == nil {
609 return nil
610 }
611
612 if n := len(task.events); n > 0 {
613 return task.events[n-1]
614 }
615 return nil
616 }
617
618
619
620
621 func (region *regionDesc) firstTimestamp() int64 {
622 if region.Start != nil {
623 return region.Start.Ts
624 }
625 return firstTimestamp()
626 }
627
628
629
630
631 func (region *regionDesc) lastTimestamp() int64 {
632 if region.End != nil {
633 return region.End.Ts
634 }
635 return lastTimestamp()
636 }
637
638
639
640
641
642 func (task *taskDesc) RelatedGoroutines(events []*trace.Event, depth int) map[uint64]bool {
643 start, end := task.firstTimestamp(), task.endTimestamp()
644
645 gmap := map[uint64]bool{}
646 for k := range task.goroutines {
647 gmap[k] = true
648 }
649
650 for i := 0; i < depth; i++ {
651 gmap1 := make(map[uint64]bool)
652 for g := range gmap {
653 gmap1[g] = true
654 }
655 for _, ev := range events {
656 if ev.Ts < start || ev.Ts > end {
657 continue
658 }
659 if ev.Type == trace.EvGoUnblock && gmap[ev.Args[0]] {
660 gmap1[ev.G] = true
661 }
662 gmap = gmap1
663 }
664 }
665 gmap[0] = true
666 return gmap
667 }
668
669 type taskFilter struct {
670 name string
671 cond []func(*taskDesc) bool
672 }
673
674 func (f *taskFilter) match(t *taskDesc) bool {
675 if t == nil {
676 return false
677 }
678 for _, c := range f.cond {
679 if !c(t) {
680 return false
681 }
682 }
683 return true
684 }
685
686 func newTaskFilter(r *http.Request) (*taskFilter, error) {
687 if err := r.ParseForm(); err != nil {
688 return nil, err
689 }
690
691 var name []string
692 var conditions []func(*taskDesc) bool
693
694 param := r.Form
695 if typ, ok := param["type"]; ok && len(typ) > 0 {
696 name = append(name, "type="+typ[0])
697 conditions = append(conditions, func(t *taskDesc) bool {
698 return t.name == typ[0]
699 })
700 }
701 if complete := r.FormValue("complete"); complete == "1" {
702 name = append(name, "complete")
703 conditions = append(conditions, func(t *taskDesc) bool {
704 return t.complete()
705 })
706 } else if complete == "0" {
707 name = append(name, "incomplete")
708 conditions = append(conditions, func(t *taskDesc) bool {
709 return !t.complete()
710 })
711 }
712 if lat, err := time.ParseDuration(r.FormValue("latmin")); err == nil {
713 name = append(name, fmt.Sprintf("latency >= %s", lat))
714 conditions = append(conditions, func(t *taskDesc) bool {
715 return t.complete() && t.duration() >= lat
716 })
717 }
718 if lat, err := time.ParseDuration(r.FormValue("latmax")); err == nil {
719 name = append(name, fmt.Sprintf("latency <= %s", lat))
720 conditions = append(conditions, func(t *taskDesc) bool {
721 return t.complete() && t.duration() <= lat
722 })
723 }
724 if text := r.FormValue("logtext"); text != "" {
725 name = append(name, fmt.Sprintf("log contains %q", text))
726 conditions = append(conditions, func(t *taskDesc) bool {
727 return taskMatches(t, text)
728 })
729 }
730
731 return &taskFilter{name: strings.Join(name, ","), cond: conditions}, nil
732 }
733
734 func taskMatches(t *taskDesc, text string) bool {
735 for _, ev := range t.events {
736 switch ev.Type {
737 case trace.EvUserTaskCreate, trace.EvUserRegion, trace.EvUserLog:
738 for _, s := range ev.SArgs {
739 if strings.Contains(s, text) {
740 return true
741 }
742 }
743 }
744 }
745 return false
746 }
747
748 type regionFilter struct {
749 name string
750 params url.Values
751 cond []func(regionTypeID, regionDesc) bool
752 }
753
754 func (f *regionFilter) match(id regionTypeID, s regionDesc) bool {
755 for _, c := range f.cond {
756 if !c(id, s) {
757 return false
758 }
759 }
760 return true
761 }
762
763 func newRegionFilter(r *http.Request) (*regionFilter, error) {
764 if err := r.ParseForm(); err != nil {
765 return nil, err
766 }
767
768 var name []string
769 var conditions []func(regionTypeID, regionDesc) bool
770 filterParams := make(url.Values)
771
772 param := r.Form
773 if typ, ok := param["type"]; ok && len(typ) > 0 {
774 name = append(name, "type="+typ[0])
775 conditions = append(conditions, func(id regionTypeID, s regionDesc) bool {
776 return id.Type == typ[0]
777 })
778 filterParams.Add("type", typ[0])
779 }
780 if pc, err := strconv.ParseUint(r.FormValue("pc"), 16, 64); err == nil {
781 encPC := fmt.Sprintf("%x", pc)
782 name = append(name, "pc="+encPC)
783 conditions = append(conditions, func(id regionTypeID, s regionDesc) bool {
784 return id.Frame.PC == pc
785 })
786 filterParams.Add("pc", encPC)
787 }
788
789 if lat, err := time.ParseDuration(r.FormValue("latmin")); err == nil {
790 name = append(name, fmt.Sprintf("latency >= %s", lat))
791 conditions = append(conditions, func(_ regionTypeID, s regionDesc) bool {
792 return s.duration() >= lat
793 })
794 filterParams.Add("latmin", lat.String())
795 }
796 if lat, err := time.ParseDuration(r.FormValue("latmax")); err == nil {
797 name = append(name, fmt.Sprintf("latency <= %s", lat))
798 conditions = append(conditions, func(_ regionTypeID, s regionDesc) bool {
799 return s.duration() <= lat
800 })
801 filterParams.Add("latmax", lat.String())
802 }
803
804 return ®ionFilter{
805 name: strings.Join(name, ","),
806 cond: conditions,
807 params: filterParams,
808 }, nil
809 }
810
811 type regionStats struct {
812 regionTypeID
813 Histogram traceviewer.TimeHistogram
814 }
815
816 func (s *regionStats) UserRegionURL() func(min, max time.Duration) string {
817 return func(min, max time.Duration) string {
818 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))
819 }
820 }
821
822 func (s *regionStats) add(region regionDesc) {
823 s.Histogram.Add(region.duration())
824 }
825
826 var templUserRegionTypes = template.Must(template.New("").Parse(`
827 <html>
828 <style type="text/css">
829 .histoTime {
830 width: 20%;
831 white-space:nowrap;
832 }
833
834 </style>
835 <body>
836 <table border="1" sortable="1">
837 <tr>
838 <th>Region type</th>
839 <th>Count</th>
840 <th>Duration distribution (complete tasks)</th>
841 </tr>
842 {{range $}}
843 <tr>
844 <td>{{.Type}}<br>{{.Frame.Fn}}<br>{{.Frame.File}}:{{.Frame.Line}}</td>
845 <td><a href="/userregion?type={{.Type}}&pc={{.Frame.PC | printf "%x"}}">{{.Histogram.Count}}</a></td>
846 <td>{{.Histogram.ToHTML (.UserRegionURL)}}</td>
847 </tr>
848 {{end}}
849 </table>
850 </body>
851 </html>
852 `))
853
854 type taskStats struct {
855 Type string
856 Count int
857 Histogram traceviewer.TimeHistogram
858 }
859
860 func (s *taskStats) UserTaskURL(complete bool) func(min, max time.Duration) string {
861 return func(min, max time.Duration) string {
862 return fmt.Sprintf("/usertask?type=%s&complete=%v&latmin=%v&latmax=%v", template.URLQueryEscaper(s.Type), template.URLQueryEscaper(complete), template.URLQueryEscaper(min), template.URLQueryEscaper(max))
863 }
864 }
865
866 func (s *taskStats) add(task *taskDesc) {
867 s.Count++
868 if task.complete() {
869 s.Histogram.Add(task.duration())
870 }
871 }
872
873 var templUserTaskTypes = template.Must(template.New("").Parse(`
874 <html>
875 <style type="text/css">
876 .histoTime {
877 width: 20%;
878 white-space:nowrap;
879 }
880
881 </style>
882 <body>
883 Search log text: <form action="/usertask"><input name="logtext" type="text"><input type="submit"></form><br>
884 <table border="1" sortable="1">
885 <tr>
886 <th>Task type</th>
887 <th>Count</th>
888 <th>Duration distribution (complete tasks)</th>
889 </tr>
890 {{range $}}
891 <tr>
892 <td>{{.Type}}</td>
893 <td><a href="/usertask?type={{.Type}}">{{.Count}}</a></td>
894 <td>{{.Histogram.ToHTML (.UserTaskURL true)}}</td>
895 </tr>
896 {{end}}
897 </table>
898 </body>
899 </html>
900 `))
901
902 var templUserTaskType = template.Must(template.New("userTask").Funcs(template.FuncMap{
903 "elapsed": elapsed,
904 "asMillisecond": asMillisecond,
905 "trimSpace": strings.TrimSpace,
906 }).Parse(`
907 <html>
908 <head> <title>User Task: {{.Name}} </title> </head>
909 <style type="text/css">
910 body {
911 font-family: sans-serif;
912 }
913 table#req-status td.family {
914 padding-right: 2em;
915 }
916 table#req-status td.active {
917 padding-right: 1em;
918 }
919 table#req-status td.empty {
920 color: #aaa;
921 }
922 table#reqs {
923 margin-top: 1em;
924 }
925 table#reqs tr.first {
926 font-weight: bold;
927 }
928 table#reqs td {
929 font-family: monospace;
930 }
931 table#reqs td.when {
932 text-align: right;
933 white-space: nowrap;
934 }
935 table#reqs td.elapsed {
936 padding: 0 0.5em;
937 text-align: right;
938 white-space: pre;
939 width: 10em;
940 }
941 address {
942 font-size: smaller;
943 margin-top: 5em;
944 }
945 </style>
946 <body>
947
948 <h2>User Task: {{.Name}}</h2>
949
950 Search log text: <form onsubmit="window.location.search+='&logtext='+window.logtextinput.value; return false">
951 <input name="logtext" id="logtextinput" type="text"><input type="submit">
952 </form><br>
953
954 <table id="reqs">
955 <tr><th>When</th><th>Elapsed</th><th>Goroutine ID</th><th>Events</th></tr>
956 {{range $el := $.Entry}}
957 <tr class="first">
958 <td class="when">{{$el.WhenString}}</td>
959 <td class="elapsed">{{$el.Duration}}</td>
960 <td></td>
961 <td>
962 <a href="/trace?focustask={{$el.ID}}#{{asMillisecond $el.Start}}:{{asMillisecond $el.End}}">Task {{$el.ID}}</a>
963 <a href="/trace?taskid={{$el.ID}}#{{asMillisecond $el.Start}}:{{asMillisecond $el.End}}">(goroutine view)</a>
964 ({{if .Complete}}complete{{else}}incomplete{{end}})</td>
965 </tr>
966 {{range $el.Events}}
967 <tr>
968 <td class="when">{{.WhenString}}</td>
969 <td class="elapsed">{{elapsed .Elapsed}}</td>
970 <td class="goid">{{.Go}}</td>
971 <td>{{.What}}</td>
972 </tr>
973 {{end}}
974 <tr>
975 <td></td>
976 <td></td>
977 <td></td>
978 <td>GC:{{$el.GCTime}}</td>
979 {{end}}
980 </body>
981 </html>
982 `))
983
984 func elapsed(d time.Duration) string {
985 b := fmt.Appendf(nil, "%.9f", d.Seconds())
986
987
988
989 if d < time.Second {
990 dot := bytes.IndexByte(b, '.')
991 for i := 0; i < dot; i++ {
992 b[i] = ' '
993 }
994 for i := dot + 1; i < len(b); i++ {
995 if b[i] == '0' {
996 b[i] = ' '
997 } else {
998 break
999 }
1000 }
1001 }
1002
1003 return string(b)
1004 }
1005
1006 func asMillisecond(d time.Duration) float64 {
1007 return float64(d.Nanoseconds()) / 1e6
1008 }
1009
1010 func formatUserLog(ev *trace.Event) string {
1011 k, v := ev.SArgs[0], ev.SArgs[1]
1012 if k == "" {
1013 return v
1014 }
1015 if v == "" {
1016 return k
1017 }
1018 return fmt.Sprintf("%v=%v", k, v)
1019 }
1020
1021 func describeEvent(ev *trace.Event) string {
1022 switch ev.Type {
1023 case trace.EvGoCreate:
1024 goid := ev.Args[0]
1025 return fmt.Sprintf("new goroutine %d: %s", goid, gs[goid].Name)
1026 case trace.EvGoEnd, trace.EvGoStop:
1027 return "goroutine stopped"
1028 case trace.EvUserLog:
1029 return formatUserLog(ev)
1030 case trace.EvUserRegion:
1031 if ev.Args[1] == 0 {
1032 duration := "unknown"
1033 if ev.Link != nil {
1034 duration = (time.Duration(ev.Link.Ts-ev.Ts) * time.Nanosecond).String()
1035 }
1036 return fmt.Sprintf("region %s started (duration: %v)", ev.SArgs[0], duration)
1037 }
1038 return fmt.Sprintf("region %s ended", ev.SArgs[0])
1039 case trace.EvUserTaskCreate:
1040 return fmt.Sprintf("task %v (id %d, parent %d) created", ev.SArgs[0], ev.Args[0], ev.Args[1])
1041
1042 case trace.EvUserTaskEnd:
1043 return "task end"
1044 }
1045 return ""
1046 }
1047
1048 func isUserAnnotationEvent(ev *trace.Event) (taskID uint64, ok bool) {
1049 switch ev.Type {
1050 case trace.EvUserLog, trace.EvUserRegion, trace.EvUserTaskCreate, trace.EvUserTaskEnd:
1051 return ev.Args[0], true
1052 }
1053 return 0, false
1054 }
1055
1056 var templUserRegionType = template.Must(template.New("").Funcs(template.FuncMap{
1057 "prettyDuration": func(nsec int64) template.HTML {
1058 d := time.Duration(nsec) * time.Nanosecond
1059 return template.HTML(d.String())
1060 },
1061 "percent": func(dividend, divisor int64) template.HTML {
1062 if divisor == 0 {
1063 return ""
1064 }
1065 return template.HTML(fmt.Sprintf("(%.1f%%)", float64(dividend)/float64(divisor)*100))
1066 },
1067 "barLen": func(dividend, divisor int64) template.HTML {
1068 if divisor == 0 {
1069 return "0"
1070 }
1071 return template.HTML(fmt.Sprintf("%.2f%%", float64(dividend)/float64(divisor)*100))
1072 },
1073 "unknownTime": func(desc regionDesc) int64 {
1074 sum := desc.ExecTime + desc.IOTime + desc.BlockTime + desc.SyscallTime + desc.SchedWaitTime
1075 if sum < desc.TotalTime {
1076 return desc.TotalTime - sum
1077 }
1078 return 0
1079 },
1080 "filterParams": func(f *regionFilter) template.URL {
1081 return template.URL(f.params.Encode())
1082 },
1083 }).Parse(`
1084 <!DOCTYPE html>
1085 <title>User Region {{.Name}}</title>
1086 <style>
1087 th {
1088 background-color: #050505;
1089 color: #fff;
1090 }
1091 th.total-time,
1092 th.exec-time,
1093 th.io-time,
1094 th.block-time,
1095 th.syscall-time,
1096 th.sched-time,
1097 th.sweep-time,
1098 th.pause-time {
1099 cursor: pointer;
1100 }
1101 table {
1102 border-collapse: collapse;
1103 }
1104 .details tr:hover {
1105 background-color: #f2f2f2;
1106 }
1107 .details td {
1108 text-align: right;
1109 border: 1px solid #000;
1110 }
1111 .details td.id {
1112 text-align: left;
1113 }
1114 .stacked-bar-graph {
1115 width: 300px;
1116 height: 10px;
1117 color: #414042;
1118 white-space: nowrap;
1119 font-size: 5px;
1120 }
1121 .stacked-bar-graph span {
1122 display: inline-block;
1123 width: 100%;
1124 height: 100%;
1125 box-sizing: border-box;
1126 float: left;
1127 padding: 0;
1128 }
1129 .unknown-time { background-color: #636363; }
1130 .exec-time { background-color: #d7191c; }
1131 .io-time { background-color: #fdae61; }
1132 .block-time { background-color: #d01c8b; }
1133 .syscall-time { background-color: #7b3294; }
1134 .sched-time { background-color: #2c7bb6; }
1135 </style>
1136
1137 <script>
1138 function reloadTable(key, value) {
1139 let params = new URLSearchParams(window.location.search);
1140 params.set(key, value);
1141 window.location.search = params.toString();
1142 }
1143 </script>
1144
1145 <h2>{{.Name}}</h2>
1146
1147 {{ with $p := filterParams .Filter}}
1148 <table class="summary">
1149 <tr><td>Network Wait Time:</td><td> <a href="/regionio?{{$p}}">graph</a><a href="/regionio?{{$p}}&raw=1" download="io.profile">(download)</a></td></tr>
1150 <tr><td>Sync Block Time:</td><td> <a href="/regionblock?{{$p}}">graph</a><a href="/regionblock?{{$p}}&raw=1" download="block.profile">(download)</a></td></tr>
1151 <tr><td>Blocking Syscall Time:</td><td> <a href="/regionsyscall?{{$p}}">graph</a><a href="/regionsyscall?{{$p}}&raw=1" download="syscall.profile">(download)</a></td></tr>
1152 <tr><td>Scheduler Wait Time:</td><td> <a href="/regionsched?{{$p}}">graph</a><a href="/regionsched?{{$p}}&raw=1" download="sched.profile">(download)</a></td></tr>
1153 </table>
1154 {{ end }}
1155 <p>
1156 <table class="details">
1157 <tr>
1158 <th> Goroutine </th>
1159 <th> Task </th>
1160 <th onclick="reloadTable('sortby', 'TotalTime')" class="total-time"> Total</th>
1161 <th></th>
1162 <th onclick="reloadTable('sortby', 'ExecTime')" class="exec-time"> Execution</th>
1163 <th onclick="reloadTable('sortby', 'IOTime')" class="io-time"> Network wait</th>
1164 <th onclick="reloadTable('sortby', 'BlockTime')" class="block-time"> Sync block </th>
1165 <th onclick="reloadTable('sortby', 'SyscallTime')" class="syscall-time"> Blocking syscall</th>
1166 <th onclick="reloadTable('sortby', 'SchedWaitTime')" class="sched-time"> Scheduler wait</th>
1167 <th onclick="reloadTable('sortby', 'SweepTime')" class="sweep-time"> GC sweeping</th>
1168 <th onclick="reloadTable('sortby', 'GCTime')" class="pause-time"> GC pause</th>
1169 </tr>
1170 {{range .Data}}
1171 <tr>
1172 <td> <a href="/trace?goid={{.G}}">{{.G}}</a> </td>
1173 <td> {{if .TaskID}}<a href="/trace?focustask={{.TaskID}}">{{.TaskID}}</a>{{end}} </td>
1174 <td> {{prettyDuration .TotalTime}} </td>
1175 <td>
1176 <div class="stacked-bar-graph">
1177 {{if unknownTime .}}<span style="width:{{barLen (unknownTime .) $.MaxTotal}}" class="unknown-time"> </span>{{end}}
1178 {{if .ExecTime}}<span style="width:{{barLen .ExecTime $.MaxTotal}}" class="exec-time"> </span>{{end}}
1179 {{if .IOTime}}<span style="width:{{barLen .IOTime $.MaxTotal}}" class="io-time"> </span>{{end}}
1180 {{if .BlockTime}}<span style="width:{{barLen .BlockTime $.MaxTotal}}" class="block-time"> </span>{{end}}
1181 {{if .SyscallTime}}<span style="width:{{barLen .SyscallTime $.MaxTotal}}" class="syscall-time"> </span>{{end}}
1182 {{if .SchedWaitTime}}<span style="width:{{barLen .SchedWaitTime $.MaxTotal}}" class="sched-time"> </span>{{end}}
1183 </div>
1184 </td>
1185 <td> {{prettyDuration .ExecTime}}</td>
1186 <td> {{prettyDuration .IOTime}}</td>
1187 <td> {{prettyDuration .BlockTime}}</td>
1188 <td> {{prettyDuration .SyscallTime}}</td>
1189 <td> {{prettyDuration .SchedWaitTime}}</td>
1190 <td> {{prettyDuration .SweepTime}} {{percent .SweepTime .TotalTime}}</td>
1191 <td> {{prettyDuration .GCTime}} {{percent .GCTime .TotalTime}}</td>
1192 </tr>
1193 {{end}}
1194 </table>
1195 </p>
1196 `))
1197
View as plain text