1
2
3
4
5 package traceviewer
6
7 import (
8 "encoding/json"
9 "fmt"
10 "internal/trace"
11 "internal/trace/traceviewer/format"
12 "io"
13 "strconv"
14 "time"
15 )
16
17 type TraceConsumer struct {
18 ConsumeTimeUnit func(unit string)
19 ConsumeViewerEvent func(v *format.Event, required bool)
20 ConsumeViewerFrame func(key string, f format.Frame)
21 Flush func()
22 }
23
24
25
26
27 func ViewerDataTraceConsumer(w io.Writer, startIdx, endIdx int64) TraceConsumer {
28 allFrames := make(map[string]format.Frame)
29 requiredFrames := make(map[string]format.Frame)
30 enc := json.NewEncoder(w)
31 written := 0
32 index := int64(-1)
33
34 io.WriteString(w, "{")
35 return TraceConsumer{
36 ConsumeTimeUnit: func(unit string) {
37 io.WriteString(w, `"displayTimeUnit":`)
38 enc.Encode(unit)
39 io.WriteString(w, ",")
40 },
41 ConsumeViewerEvent: func(v *format.Event, required bool) {
42 index++
43 if !required && (index < startIdx || index > endIdx) {
44
45 return
46 }
47 WalkStackFrames(allFrames, v.Stack, func(id int) {
48 s := strconv.Itoa(id)
49 requiredFrames[s] = allFrames[s]
50 })
51 WalkStackFrames(allFrames, v.EndStack, func(id int) {
52 s := strconv.Itoa(id)
53 requiredFrames[s] = allFrames[s]
54 })
55 if written == 0 {
56 io.WriteString(w, `"traceEvents": [`)
57 }
58 if written > 0 {
59 io.WriteString(w, ",")
60 }
61 enc.Encode(v)
62
63
64 written++
65 },
66 ConsumeViewerFrame: func(k string, v format.Frame) {
67 allFrames[k] = v
68 },
69 Flush: func() {
70 io.WriteString(w, `], "stackFrames":`)
71 enc.Encode(requiredFrames)
72 io.WriteString(w, `}`)
73 },
74 }
75 }
76
77 func SplittingTraceConsumer(max int) (*splitter, TraceConsumer) {
78 type eventSz struct {
79 Time float64
80 Sz int
81 Frames []int
82 }
83
84 var (
85
86 data = format.Data{Frames: make(map[string]format.Frame)}
87
88 allFrames = make(map[string]format.Frame)
89
90 sizes []eventSz
91 cw countingWriter
92 )
93
94 s := new(splitter)
95
96 return s, TraceConsumer{
97 ConsumeTimeUnit: func(unit string) {
98 data.TimeUnit = unit
99 },
100 ConsumeViewerEvent: func(v *format.Event, required bool) {
101 if required {
102
103
104
105 data.Events = append(data.Events, v)
106 WalkStackFrames(allFrames, v.Stack, func(id int) {
107 s := strconv.Itoa(id)
108 data.Frames[s] = allFrames[s]
109 })
110 WalkStackFrames(allFrames, v.EndStack, func(id int) {
111 s := strconv.Itoa(id)
112 data.Frames[s] = allFrames[s]
113 })
114 return
115 }
116 enc := json.NewEncoder(&cw)
117 enc.Encode(v)
118 size := eventSz{Time: v.Time, Sz: cw.size + 1}
119
120
121 WalkStackFrames(allFrames, v.Stack, func(id int) {
122 size.Frames = append(size.Frames, id)
123 })
124 WalkStackFrames(allFrames, v.EndStack, func(id int) {
125 size.Frames = append(size.Frames, id)
126 })
127 sizes = append(sizes, size)
128 cw.size = 0
129 },
130 ConsumeViewerFrame: func(k string, v format.Frame) {
131 allFrames[k] = v
132 },
133 Flush: func() {
134
135
136
137 cw.size = 0
138 enc := json.NewEncoder(&cw)
139 enc.Encode(data)
140 requiredSize := cw.size
141
142
143
144
145
146
147 var (
148 start = 0
149
150 eventsSize = 0
151
152 frames = make(map[string]format.Frame)
153 framesSize = 0
154 )
155 for i, ev := range sizes {
156 eventsSize += ev.Sz
157
158
159
160 for _, id := range ev.Frames {
161 s := strconv.Itoa(id)
162 _, ok := frames[s]
163 if ok {
164 continue
165 }
166 f := allFrames[s]
167 frames[s] = f
168 framesSize += stackFrameEncodedSize(uint(id), f)
169 }
170
171 total := requiredSize + framesSize + eventsSize
172 if total < max {
173 continue
174 }
175
176
177
178 startTime := time.Duration(sizes[start].Time * 1000)
179 endTime := time.Duration(ev.Time * 1000)
180 s.Ranges = append(s.Ranges, Range{
181 Name: fmt.Sprintf("%v-%v", startTime, endTime),
182 Start: start,
183 End: i + 1,
184 StartTime: int64(startTime),
185 EndTime: int64(endTime),
186 })
187 start = i + 1
188 frames = make(map[string]format.Frame)
189 framesSize = 0
190 eventsSize = 0
191 }
192 if len(s.Ranges) <= 1 {
193 s.Ranges = nil
194 return
195 }
196
197 if end := len(sizes) - 1; start < end {
198 s.Ranges = append(s.Ranges, Range{
199 Name: fmt.Sprintf("%v-%v", time.Duration(sizes[start].Time*1000), time.Duration(sizes[end].Time*1000)),
200 Start: start,
201 End: end,
202 StartTime: int64(sizes[start].Time * 1000),
203 EndTime: int64(sizes[end].Time * 1000),
204 })
205 }
206 },
207 }
208 }
209
210 type splitter struct {
211 Ranges []Range
212 }
213
214 type countingWriter struct {
215 size int
216 }
217
218 func (cw *countingWriter) Write(data []byte) (int, error) {
219 cw.size += len(data)
220 return len(data), nil
221 }
222
223 func stackFrameEncodedSize(id uint, f format.Frame) int {
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238 const (
239 baseSize = len(`"`) + len(`":{"name":"`) + len(`"},`)
240
241
242
243 parentBaseSize = len(`,"parent":`)
244 )
245
246 size := baseSize
247
248 size += len(f.Name)
249
250
251 for id > 0 {
252 size += 1
253 id /= 10
254 }
255
256 if f.Parent > 0 {
257 size += parentBaseSize
258
259 for f.Parent > 0 {
260 size += 1
261 f.Parent /= 10
262 }
263 }
264
265 return size
266 }
267
268
269 func WalkStackFrames(allFrames map[string]format.Frame, id int, fn func(id int)) {
270 for id != 0 {
271 f, ok := allFrames[strconv.Itoa(id)]
272 if !ok {
273 break
274 }
275 fn(id)
276 id = f.Parent
277 }
278 }
279
280 type Mode int
281
282 const (
283 ModeGoroutineOriented Mode = 1 << iota
284 ModeTaskOriented
285 ModeThreadOriented
286 )
287
288
289
290 func NewEmitter(c TraceConsumer, rangeStart, rangeEnd time.Duration) *Emitter {
291 c.ConsumeTimeUnit("ns")
292
293 return &Emitter{
294 c: c,
295 rangeStart: rangeStart,
296 rangeEnd: rangeEnd,
297 frameTree: frameNode{children: make(map[uint64]frameNode)},
298 resources: make(map[uint64]string),
299 tasks: make(map[uint64]task),
300 }
301 }
302
303 type Emitter struct {
304 c TraceConsumer
305 rangeStart time.Duration
306 rangeEnd time.Duration
307
308 heapStats, prevHeapStats heapStats
309 gstates, prevGstates [gStateCount]int64
310 threadStats, prevThreadStats [threadStateCount]int64
311 gomaxprocs uint64
312 frameTree frameNode
313 frameSeq int
314 arrowSeq uint64
315 filter func(uint64) bool
316 resourceType string
317 resources map[uint64]string
318 focusResource uint64
319 tasks map[uint64]task
320 asyncSliceSeq uint64
321 }
322
323 type task struct {
324 name string
325 sortIndex int
326 }
327
328 func (e *Emitter) Gomaxprocs(v uint64) {
329 if v > e.gomaxprocs {
330 e.gomaxprocs = v
331 }
332 }
333
334 func (e *Emitter) Resource(id uint64, name string) {
335 if e.filter != nil && !e.filter(id) {
336 return
337 }
338 e.resources[id] = name
339 }
340
341 func (e *Emitter) SetResourceType(name string) {
342 e.resourceType = name
343 }
344
345 func (e *Emitter) SetResourceFilter(filter func(uint64) bool) {
346 e.filter = filter
347 }
348
349 func (e *Emitter) Task(id uint64, name string, sortIndex int) {
350 e.tasks[id] = task{name, sortIndex}
351 }
352
353 func (e *Emitter) Slice(s SliceEvent) {
354 if e.filter != nil && !e.filter(s.Resource) {
355 return
356 }
357 e.slice(s, format.ProcsSection, "")
358 }
359
360 func (e *Emitter) TaskSlice(s SliceEvent) {
361 e.slice(s, format.TasksSection, pickTaskColor(s.Resource))
362 }
363
364 func (e *Emitter) slice(s SliceEvent, sectionID uint64, cname string) {
365 if !e.tsWithinRange(s.Ts) && !e.tsWithinRange(s.Ts+s.Dur) {
366 return
367 }
368 e.OptionalEvent(&format.Event{
369 Name: s.Name,
370 Phase: "X",
371 Time: viewerTime(s.Ts),
372 Dur: viewerTime(s.Dur),
373 PID: sectionID,
374 TID: s.Resource,
375 Stack: s.Stack,
376 EndStack: s.EndStack,
377 Arg: s.Arg,
378 Cname: cname,
379 })
380 }
381
382 type SliceEvent struct {
383 Name string
384 Ts time.Duration
385 Dur time.Duration
386 Resource uint64
387 Stack int
388 EndStack int
389 Arg any
390 }
391
392 func (e *Emitter) AsyncSlice(s AsyncSliceEvent) {
393 if !e.tsWithinRange(s.Ts) && !e.tsWithinRange(s.Ts+s.Dur) {
394 return
395 }
396 if e.filter != nil && !e.filter(s.Resource) {
397 return
398 }
399 cname := ""
400 if s.TaskColorIndex != 0 {
401 cname = pickTaskColor(s.TaskColorIndex)
402 }
403 e.asyncSliceSeq++
404 e.OptionalEvent(&format.Event{
405 Category: s.Category,
406 Name: s.Name,
407 Phase: "b",
408 Time: viewerTime(s.Ts),
409 TID: s.Resource,
410 ID: e.asyncSliceSeq,
411 Scope: s.Scope,
412 Stack: s.Stack,
413 Cname: cname,
414 })
415 e.OptionalEvent(&format.Event{
416 Category: s.Category,
417 Name: s.Name,
418 Phase: "e",
419 Time: viewerTime(s.Ts + s.Dur),
420 TID: s.Resource,
421 ID: e.asyncSliceSeq,
422 Scope: s.Scope,
423 Stack: s.EndStack,
424 Arg: s.Arg,
425 Cname: cname,
426 })
427 }
428
429 type AsyncSliceEvent struct {
430 SliceEvent
431 Category string
432 Scope string
433 TaskColorIndex uint64
434 }
435
436 func (e *Emitter) Instant(i InstantEvent) {
437 if !e.tsWithinRange(i.Ts) {
438 return
439 }
440 if e.filter != nil && !e.filter(i.Resource) {
441 return
442 }
443 cname := ""
444 e.OptionalEvent(&format.Event{
445 Name: i.Name,
446 Category: i.Category,
447 Phase: "I",
448 Scope: "t",
449 Time: viewerTime(i.Ts),
450 PID: format.ProcsSection,
451 TID: i.Resource,
452 Stack: i.Stack,
453 Cname: cname,
454 Arg: i.Arg,
455 })
456 }
457
458 type InstantEvent struct {
459 Ts time.Duration
460 Name string
461 Category string
462 Resource uint64
463 Stack int
464 Arg any
465 }
466
467 func (e *Emitter) Arrow(a ArrowEvent) {
468 if e.filter != nil && (!e.filter(a.FromResource) || !e.filter(a.ToResource)) {
469 return
470 }
471 e.arrow(a, format.ProcsSection)
472 }
473
474 func (e *Emitter) TaskArrow(a ArrowEvent) {
475 e.arrow(a, format.TasksSection)
476 }
477
478 func (e *Emitter) arrow(a ArrowEvent, sectionID uint64) {
479 if !e.tsWithinRange(a.Start) || !e.tsWithinRange(a.End) {
480 return
481 }
482 e.arrowSeq++
483 e.OptionalEvent(&format.Event{
484 Name: a.Name,
485 Phase: "s",
486 TID: a.FromResource,
487 PID: sectionID,
488 ID: e.arrowSeq,
489 Time: viewerTime(a.Start),
490 Stack: a.FromStack,
491 })
492 e.OptionalEvent(&format.Event{
493 Name: a.Name,
494 Phase: "t",
495 TID: a.ToResource,
496 PID: sectionID,
497 ID: e.arrowSeq,
498 Time: viewerTime(a.End),
499 })
500 }
501
502 type ArrowEvent struct {
503 Name string
504 Start time.Duration
505 End time.Duration
506 FromResource uint64
507 FromStack int
508 ToResource uint64
509 }
510
511 func (e *Emitter) Event(ev *format.Event) {
512 e.c.ConsumeViewerEvent(ev, true)
513 }
514
515 func (e *Emitter) HeapAlloc(ts time.Duration, v uint64) {
516 e.heapStats.heapAlloc = v
517 e.emitHeapCounters(ts)
518 }
519
520 func (e *Emitter) Focus(id uint64) {
521 e.focusResource = id
522 }
523
524 func (e *Emitter) GoroutineTransition(ts time.Duration, from, to GState) {
525 e.gstates[from]--
526 e.gstates[to]++
527 if e.prevGstates == e.gstates {
528 return
529 }
530 if e.tsWithinRange(ts) {
531 e.OptionalEvent(&format.Event{
532 Name: "Goroutines",
533 Phase: "C",
534 Time: viewerTime(ts),
535 PID: 1,
536 Arg: &format.GoroutineCountersArg{
537 Running: uint64(e.gstates[GRunning]),
538 Runnable: uint64(e.gstates[GRunnable]),
539 GCWaiting: uint64(e.gstates[GWaitingGC]),
540 },
541 })
542 }
543 e.prevGstates = e.gstates
544 }
545
546 func (e *Emitter) IncThreadStateCount(ts time.Duration, state ThreadState, delta int64) {
547 e.threadStats[state] += delta
548 if e.prevThreadStats == e.threadStats {
549 return
550 }
551 if e.tsWithinRange(ts) {
552 e.OptionalEvent(&format.Event{
553 Name: "Threads",
554 Phase: "C",
555 Time: viewerTime(ts),
556 PID: 1,
557 Arg: &format.ThreadCountersArg{
558 Running: int64(e.threadStats[ThreadStateRunning]),
559 InSyscall: int64(e.threadStats[ThreadStateInSyscall]),
560
561 },
562 })
563 }
564 e.prevThreadStats = e.threadStats
565 }
566
567 func (e *Emitter) HeapGoal(ts time.Duration, v uint64) {
568
569
570
571 const PB = 1 << 50
572 if v > PB {
573 v = 0
574 }
575 e.heapStats.nextGC = v
576 e.emitHeapCounters(ts)
577 }
578
579 func (e *Emitter) emitHeapCounters(ts time.Duration) {
580 if e.prevHeapStats == e.heapStats {
581 return
582 }
583 diff := uint64(0)
584 if e.heapStats.nextGC > e.heapStats.heapAlloc {
585 diff = e.heapStats.nextGC - e.heapStats.heapAlloc
586 }
587 if e.tsWithinRange(ts) {
588 e.OptionalEvent(&format.Event{
589 Name: "Heap",
590 Phase: "C",
591 Time: viewerTime(ts),
592 PID: 1,
593 Arg: &format.HeapCountersArg{Allocated: e.heapStats.heapAlloc, NextGC: diff},
594 })
595 }
596 e.prevHeapStats = e.heapStats
597 }
598
599
600 func (e *Emitter) Err() error {
601 if e.gstates[GRunnable] < 0 || e.gstates[GRunning] < 0 || e.threadStats[ThreadStateInSyscall] < 0 || e.threadStats[ThreadStateInSyscallRuntime] < 0 {
602 return fmt.Errorf(
603 "runnable=%d running=%d insyscall=%d insyscallRuntime=%d",
604 e.gstates[GRunnable],
605 e.gstates[GRunning],
606 e.threadStats[ThreadStateInSyscall],
607 e.threadStats[ThreadStateInSyscallRuntime],
608 )
609 }
610 return nil
611 }
612
613 func (e *Emitter) tsWithinRange(ts time.Duration) bool {
614 return e.rangeStart <= ts && ts <= e.rangeEnd
615 }
616
617
618
619 func (e *Emitter) OptionalEvent(ev *format.Event) {
620 e.c.ConsumeViewerEvent(ev, false)
621 }
622
623 func (e *Emitter) Flush() {
624 e.processMeta(format.StatsSection, "STATS", 0)
625
626 if len(e.tasks) != 0 {
627 e.processMeta(format.TasksSection, "TASKS", 1)
628 }
629 for id, task := range e.tasks {
630 e.threadMeta(format.TasksSection, id, task.name, task.sortIndex)
631 }
632
633 e.processMeta(format.ProcsSection, e.resourceType, 2)
634
635 e.threadMeta(format.ProcsSection, trace.GCP, "GC", -6)
636 e.threadMeta(format.ProcsSection, trace.NetpollP, "Network", -5)
637 e.threadMeta(format.ProcsSection, trace.TimerP, "Timers", -4)
638 e.threadMeta(format.ProcsSection, trace.SyscallP, "Syscalls", -3)
639
640 for id, name := range e.resources {
641 priority := int(id)
642 if e.focusResource != 0 && id == e.focusResource {
643
644 priority = -2
645 }
646 e.threadMeta(format.ProcsSection, id, name, priority)
647 }
648
649 e.c.Flush()
650 }
651
652 func (e *Emitter) threadMeta(sectionID, tid uint64, name string, priority int) {
653 e.Event(&format.Event{
654 Name: "thread_name",
655 Phase: "M",
656 PID: sectionID,
657 TID: tid,
658 Arg: &format.NameArg{Name: name},
659 })
660 e.Event(&format.Event{
661 Name: "thread_sort_index",
662 Phase: "M",
663 PID: sectionID,
664 TID: tid,
665 Arg: &format.SortIndexArg{Index: priority},
666 })
667 }
668
669 func (e *Emitter) processMeta(sectionID uint64, name string, priority int) {
670 e.Event(&format.Event{
671 Name: "process_name",
672 Phase: "M",
673 PID: sectionID,
674 Arg: &format.NameArg{Name: name},
675 })
676 e.Event(&format.Event{
677 Name: "process_sort_index",
678 Phase: "M",
679 PID: sectionID,
680 Arg: &format.SortIndexArg{Index: priority},
681 })
682 }
683
684
685
686 func (e *Emitter) Stack(stk []*trace.Frame) int {
687 return e.buildBranch(e.frameTree, stk)
688 }
689
690
691 func (e *Emitter) buildBranch(parent frameNode, stk []*trace.Frame) int {
692 if len(stk) == 0 {
693 return parent.id
694 }
695 last := len(stk) - 1
696 frame := stk[last]
697 stk = stk[:last]
698
699 node, ok := parent.children[frame.PC]
700 if !ok {
701 e.frameSeq++
702 node.id = e.frameSeq
703 node.children = make(map[uint64]frameNode)
704 parent.children[frame.PC] = node
705 e.c.ConsumeViewerFrame(strconv.Itoa(node.id), format.Frame{Name: fmt.Sprintf("%v:%v", frame.Fn, frame.Line), Parent: parent.id})
706 }
707 return e.buildBranch(node, stk)
708 }
709
710 type heapStats struct {
711 heapAlloc uint64
712 nextGC uint64
713 }
714
715 func viewerTime(t time.Duration) float64 {
716 return float64(t) / float64(time.Microsecond)
717 }
718
719 type GState int
720
721 const (
722 GDead GState = iota
723 GRunnable
724 GRunning
725 GWaiting
726 GWaitingGC
727
728 gStateCount
729 )
730
731 type ThreadState int
732
733 const (
734 ThreadStateInSyscall ThreadState = iota
735 ThreadStateInSyscallRuntime
736 ThreadStateRunning
737
738 threadStateCount
739 )
740
741 type frameNode struct {
742 id int
743 children map[uint64]frameNode
744 }
745
746
747
748
749 const (
750 colorLightMauve = "thread_state_uninterruptible"
751 colorOrange = "thread_state_iowait"
752 colorSeafoamGreen = "thread_state_running"
753 colorVistaBlue = "thread_state_runnable"
754 colorTan = "thread_state_unknown"
755 colorIrisBlue = "background_memory_dump"
756 colorMidnightBlue = "light_memory_dump"
757 colorDeepMagenta = "detailed_memory_dump"
758 colorBlue = "vsync_highlight_color"
759 colorGrey = "generic_work"
760 colorGreen = "good"
761 colorDarkGoldenrod = "bad"
762 colorPeach = "terrible"
763 colorBlack = "black"
764 colorLightGrey = "grey"
765 colorWhite = "white"
766 colorYellow = "yellow"
767 colorOlive = "olive"
768 colorCornflowerBlue = "rail_response"
769 colorSunsetOrange = "rail_animation"
770 colorTangerine = "rail_idle"
771 colorShamrockGreen = "rail_load"
772 colorGreenishYellow = "startup"
773 colorDarkGrey = "heap_dump_stack_frame"
774 colorTawny = "heap_dump_child_node_arrow"
775 colorLemon = "cq_build_running"
776 colorLime = "cq_build_passed"
777 colorPink = "cq_build_failed"
778 colorSilver = "cq_build_abandoned"
779 colorManzGreen = "cq_build_attempt_runnig"
780 colorKellyGreen = "cq_build_attempt_passed"
781 colorAnotherGrey = "cq_build_attempt_failed"
782 )
783
784 var colorForTask = []string{
785 colorLightMauve,
786 colorOrange,
787 colorSeafoamGreen,
788 colorVistaBlue,
789 colorTan,
790 colorMidnightBlue,
791 colorIrisBlue,
792 colorDeepMagenta,
793 colorGreen,
794 colorDarkGoldenrod,
795 colorPeach,
796 colorOlive,
797 colorCornflowerBlue,
798 colorSunsetOrange,
799 colorTangerine,
800 colorShamrockGreen,
801 colorTawny,
802 colorLemon,
803 colorLime,
804 colorPink,
805 colorSilver,
806 colorManzGreen,
807 colorKellyGreen,
808 }
809
810 func pickTaskColor(id uint64) string {
811 idx := id % uint64(len(colorForTask))
812 return colorForTask[idx]
813 }
814
View as plain text