1
2
3
4
5 package trace
6
7 import (
8 "cmp"
9 "slices"
10 "strings"
11 "time"
12 )
13
14
15 type Summary struct {
16 Goroutines map[GoID]*GoroutineSummary
17 Tasks map[TaskID]*UserTaskSummary
18 }
19
20
21
22 type GoroutineSummary struct {
23 ID GoID
24 Name string
25 PC uint64
26 CreationTime Time
27 StartTime Time
28 EndTime Time
29
30
31 Regions []*UserRegionSummary
32
33
34 GoroutineExecStats
35
36
37
38
39
40
41 *goroutineSummary
42 }
43
44
45 type UserTaskSummary struct {
46 ID TaskID
47 Name string
48 Parent *UserTaskSummary
49 Children []*UserTaskSummary
50
51
52 Start *Event
53
54
55 End *Event
56
57
58 Logs []*Event
59
60
61 Regions []*UserRegionSummary
62
63
64 Goroutines map[GoID]*GoroutineSummary
65 }
66
67
68
69 func (s *UserTaskSummary) Complete() bool {
70 return s.Start != nil && s.End != nil
71 }
72
73
74
75 func (s *UserTaskSummary) Descendents() []*UserTaskSummary {
76 descendents := []*UserTaskSummary{s}
77 for _, child := range s.Children {
78 descendents = append(descendents, child.Descendents()...)
79 }
80 return descendents
81 }
82
83
84
85 type UserRegionSummary struct {
86 TaskID TaskID
87 Name string
88
89
90
91
92
93 Start *Event
94
95
96
97
98 End *Event
99
100 GoroutineExecStats
101 }
102
103
104
105 type GoroutineExecStats struct {
106
107 ExecTime time.Duration
108 SchedWaitTime time.Duration
109 BlockTimeByReason map[string]time.Duration
110 SyscallTime time.Duration
111 SyscallBlockTime time.Duration
112
113
114
115 TotalTime time.Duration
116
117
118
119 RangeTime map[string]time.Duration
120 }
121
122 func (s GoroutineExecStats) NonOverlappingStats() map[string]time.Duration {
123 stats := map[string]time.Duration{
124 "Execution time": s.ExecTime,
125 "Sched wait time": s.SchedWaitTime,
126 "Syscall execution time": s.SyscallTime,
127 "Block time (syscall)": s.SyscallBlockTime,
128 "Unknown time": s.UnknownTime(),
129 }
130 for reason, dt := range s.BlockTimeByReason {
131 stats["Block time ("+reason+")"] += dt
132 }
133
134
135 return stats
136 }
137
138
139 func (s GoroutineExecStats) UnknownTime() time.Duration {
140 sum := s.ExecTime + s.SchedWaitTime + s.SyscallTime +
141 s.SyscallBlockTime
142 for _, dt := range s.BlockTimeByReason {
143 sum += dt
144 }
145
146
147 if sum < s.TotalTime {
148 return s.TotalTime - sum
149 }
150 return 0
151 }
152
153
154 func (s GoroutineExecStats) sub(v GoroutineExecStats) (r GoroutineExecStats) {
155 r = s.clone()
156 r.ExecTime -= v.ExecTime
157 r.SchedWaitTime -= v.SchedWaitTime
158 for reason := range s.BlockTimeByReason {
159 r.BlockTimeByReason[reason] -= v.BlockTimeByReason[reason]
160 }
161 r.SyscallTime -= v.SyscallTime
162 r.SyscallBlockTime -= v.SyscallBlockTime
163 r.TotalTime -= v.TotalTime
164 for name := range s.RangeTime {
165 r.RangeTime[name] -= v.RangeTime[name]
166 }
167 return r
168 }
169
170 func (s GoroutineExecStats) clone() (r GoroutineExecStats) {
171 r = s
172 r.BlockTimeByReason = make(map[string]time.Duration)
173 for reason, dt := range s.BlockTimeByReason {
174 r.BlockTimeByReason[reason] = dt
175 }
176 r.RangeTime = make(map[string]time.Duration)
177 for name, dt := range s.RangeTime {
178 r.RangeTime[name] = dt
179 }
180 return r
181 }
182
183
184
185
186 func (g *GoroutineSummary) snapshotStat(lastTs Time) (ret GoroutineExecStats) {
187 ret = g.GoroutineExecStats.clone()
188
189 if g.goroutineSummary == nil {
190 return ret
191 }
192
193
194 if g.TotalTime == 0 {
195 ret.TotalTime = lastTs.Sub(g.CreationTime)
196 }
197
198
199 if g.lastStartTime != 0 {
200 ret.ExecTime += lastTs.Sub(g.lastStartTime)
201 }
202 if g.lastRunnableTime != 0 {
203 ret.SchedWaitTime += lastTs.Sub(g.lastRunnableTime)
204 }
205 if g.lastBlockTime != 0 {
206 ret.BlockTimeByReason[g.lastBlockReason] += lastTs.Sub(g.lastBlockTime)
207 }
208 if g.lastSyscallTime != 0 {
209 ret.SyscallTime += lastTs.Sub(g.lastSyscallTime)
210 }
211 if g.lastSyscallBlockTime != 0 {
212 ret.SchedWaitTime += lastTs.Sub(g.lastSyscallBlockTime)
213 }
214 for name, ts := range g.lastRangeTime {
215 ret.RangeTime[name] += lastTs.Sub(ts)
216 }
217 return ret
218 }
219
220
221
222
223 func (g *GoroutineSummary) finalize(lastTs Time, trigger *Event) {
224 if trigger != nil {
225 g.EndTime = trigger.Time()
226 }
227 finalStat := g.snapshotStat(lastTs)
228
229 g.GoroutineExecStats = finalStat
230
231
232
233
234
235 if !IsSystemGoroutine(g.Name) {
236 for _, s := range g.activeRegions {
237 s.End = trigger
238 s.GoroutineExecStats = finalStat.sub(s.GoroutineExecStats)
239 g.Regions = append(g.Regions, s)
240 }
241 }
242 *(g.goroutineSummary) = goroutineSummary{}
243 }
244
245
246 type goroutineSummary struct {
247 lastStartTime Time
248 lastRunnableTime Time
249 lastBlockTime Time
250 lastBlockReason string
251 lastSyscallTime Time
252 lastSyscallBlockTime Time
253 lastRangeTime map[string]Time
254 activeRegions []*UserRegionSummary
255 }
256
257
258 type Summarizer struct {
259
260 gs map[GoID]*GoroutineSummary
261
262
263 tasks map[TaskID]*UserTaskSummary
264
265
266
267 syscallingP map[ProcID]GoID
268 syscallingG map[GoID]ProcID
269
270
271
272
273
274 rangesP map[rangeP]GoID
275
276 lastTs Time
277 syncTs Time
278 }
279
280
281 func NewSummarizer() *Summarizer {
282 return &Summarizer{
283 gs: make(map[GoID]*GoroutineSummary),
284 tasks: make(map[TaskID]*UserTaskSummary),
285 syscallingP: make(map[ProcID]GoID),
286 syscallingG: make(map[GoID]ProcID),
287 rangesP: make(map[rangeP]GoID),
288 }
289 }
290
291 type rangeP struct {
292 id ProcID
293 name string
294 }
295
296
297 func (s *Summarizer) Event(ev *Event) {
298 if s.syncTs == 0 {
299 s.syncTs = ev.Time()
300 }
301 s.lastTs = ev.Time()
302
303 switch ev.Kind() {
304
305 case EventSync:
306 s.syncTs = ev.Time()
307
308
309 case EventStateTransition:
310 st := ev.StateTransition()
311 switch st.Resource.Kind {
312
313 case ResourceGoroutine:
314 id := st.Resource.Goroutine()
315 old, new := st.Goroutine()
316 if old == new {
317
318 break
319 }
320
321
322 g := s.gs[id]
323 switch old {
324 case GoUndetermined:
325 g = &GoroutineSummary{ID: id, goroutineSummary: &goroutineSummary{}}
326
327
328
329
330 g.CreationTime = s.syncTs
331 g.lastRangeTime = make(map[string]Time)
332 g.BlockTimeByReason = make(map[string]time.Duration)
333 g.RangeTime = make(map[string]time.Duration)
334
335
336
337
338
339
340 stateTime := ev.Time().Sub(s.syncTs)
341 switch new {
342 case GoRunning:
343 g.ExecTime += stateTime
344 case GoWaiting:
345 g.BlockTimeByReason[st.Reason] += stateTime
346 case GoRunnable:
347 g.SchedWaitTime += stateTime
348 case GoSyscall:
349
350
351
352
353 if ev.Proc() == NoProc {
354 g.SyscallBlockTime += stateTime
355 } else {
356 g.SyscallTime += stateTime
357 }
358 }
359 s.gs[g.ID] = g
360 case GoNotExist:
361 g = &GoroutineSummary{ID: id, goroutineSummary: &goroutineSummary{}}
362
363
364 g.CreationTime = ev.Time()
365 g.lastRangeTime = make(map[string]Time)
366 g.BlockTimeByReason = make(map[string]time.Duration)
367 g.RangeTime = make(map[string]time.Duration)
368
369
370
371
372
373
374
375
376
377
378
379 if creatorG := s.gs[ev.Goroutine()]; creatorG != nil && len(creatorG.activeRegions) > 0 {
380 regions := creatorG.activeRegions
381 s := regions[len(regions)-1]
382 g.activeRegions = []*UserRegionSummary{{TaskID: s.TaskID, Start: ev}}
383 }
384 s.gs[g.ID] = g
385 case GoRunning:
386
387 g.ExecTime += ev.Time().Sub(g.lastStartTime)
388 g.lastStartTime = 0
389 case GoWaiting:
390
391 if g.lastBlockTime != 0 {
392 g.BlockTimeByReason[g.lastBlockReason] += ev.Time().Sub(g.lastBlockTime)
393 g.lastBlockTime = 0
394 }
395 case GoRunnable:
396
397 if g.lastRunnableTime != 0 {
398 g.SchedWaitTime += ev.Time().Sub(g.lastRunnableTime)
399 g.lastRunnableTime = 0
400 }
401 case GoSyscall:
402
403 if g.lastSyscallTime != 0 {
404 if g.lastSyscallBlockTime != 0 {
405 g.SyscallBlockTime += ev.Time().Sub(g.lastSyscallBlockTime)
406 g.SyscallTime += g.lastSyscallBlockTime.Sub(g.lastSyscallTime)
407 } else {
408 g.SyscallTime += ev.Time().Sub(g.lastSyscallTime)
409 }
410 g.lastSyscallTime = 0
411 g.lastSyscallBlockTime = 0
412
413
414 delete(s.syscallingP, s.syscallingG[id])
415 delete(s.syscallingG, id)
416 }
417 }
418
419
420
421
422
423 if g.Name == "" {
424 for frame := range st.Stack.Frames() {
425
426
427
428
429
430 g.PC = frame.PC
431 g.Name = frame.Func
432 }
433 }
434
435
436 switch new {
437 case GoRunning:
438
439 g.lastStartTime = ev.Time()
440 if g.StartTime == 0 {
441 g.StartTime = ev.Time()
442 }
443 case GoRunnable:
444 g.lastRunnableTime = ev.Time()
445 case GoWaiting:
446 if st.Reason != "forever" {
447 g.lastBlockTime = ev.Time()
448 g.lastBlockReason = st.Reason
449 break
450 }
451
452 fallthrough
453 case GoNotExist:
454 g.finalize(ev.Time(), ev)
455 case GoSyscall:
456 s.syscallingP[ev.Proc()] = id
457 s.syscallingG[id] = ev.Proc()
458 g.lastSyscallTime = ev.Time()
459 }
460
461
462
463 case ResourceProc:
464 id := st.Resource.Proc()
465 old, new := st.Proc()
466 if old != new && new == ProcIdle {
467 if goid, ok := s.syscallingP[id]; ok {
468 g := s.gs[goid]
469 g.lastSyscallBlockTime = ev.Time()
470 delete(s.syscallingP, id)
471 }
472 }
473 }
474
475
476 case EventRangeBegin, EventRangeActive:
477 r := ev.Range()
478 var g *GoroutineSummary
479 switch r.Scope.Kind {
480 case ResourceGoroutine:
481
482
483
484
485
486 g = s.gs[r.Scope.Goroutine()]
487 case ResourceProc:
488
489
490
491 g = s.gs[ev.Goroutine()]
492 if g != nil {
493 s.rangesP[rangeP{id: r.Scope.Proc(), name: r.Name}] = ev.Goroutine()
494 }
495 }
496 if g == nil {
497 break
498 }
499 if ev.Kind() == EventRangeActive {
500 if ts := g.lastRangeTime[r.Name]; ts != 0 {
501 g.RangeTime[r.Name] += s.syncTs.Sub(ts)
502 }
503 g.lastRangeTime[r.Name] = s.syncTs
504 } else {
505 g.lastRangeTime[r.Name] = ev.Time()
506 }
507 case EventRangeEnd:
508 r := ev.Range()
509 var g *GoroutineSummary
510 switch r.Scope.Kind {
511 case ResourceGoroutine:
512 g = s.gs[r.Scope.Goroutine()]
513 case ResourceProc:
514 rp := rangeP{id: r.Scope.Proc(), name: r.Name}
515 if goid, ok := s.rangesP[rp]; ok {
516 if goid == ev.Goroutine() {
517
518
519 g = s.gs[goid]
520 }
521 delete(s.rangesP, rp)
522 }
523 }
524 if g == nil {
525 break
526 }
527 ts := g.lastRangeTime[r.Name]
528 if ts == 0 {
529 break
530 }
531 g.RangeTime[r.Name] += ev.Time().Sub(ts)
532 delete(g.lastRangeTime, r.Name)
533
534
535 case EventRegionBegin:
536 g := s.gs[ev.Goroutine()]
537 r := ev.Region()
538 region := &UserRegionSummary{
539 Name: r.Type,
540 TaskID: r.Task,
541 Start: ev,
542 GoroutineExecStats: g.snapshotStat(ev.Time()),
543 }
544 g.activeRegions = append(g.activeRegions, region)
545
546 task := s.getOrAddTask(r.Task)
547 task.Regions = append(task.Regions, region)
548 task.Goroutines[g.ID] = g
549 case EventRegionEnd:
550 g := s.gs[ev.Goroutine()]
551 r := ev.Region()
552 var sd *UserRegionSummary
553 if regionStk := g.activeRegions; len(regionStk) > 0 {
554
555 n := len(regionStk)
556 sd = regionStk[n-1]
557 regionStk = regionStk[:n-1]
558 g.activeRegions = regionStk
559
560 } else {
561
562 sd = &UserRegionSummary{Name: r.Type, TaskID: r.Task}
563
564 task := s.getOrAddTask(r.Task)
565 task.Goroutines[g.ID] = g
566 task.Regions = append(task.Regions, sd)
567 }
568 sd.GoroutineExecStats = g.snapshotStat(ev.Time()).sub(sd.GoroutineExecStats)
569 sd.End = ev
570 g.Regions = append(g.Regions, sd)
571
572
573 case EventTaskBegin, EventTaskEnd:
574
575 t := ev.Task()
576 task := s.getOrAddTask(t.ID)
577 task.Name = t.Type
578 task.Goroutines[ev.Goroutine()] = s.gs[ev.Goroutine()]
579 if ev.Kind() == EventTaskBegin {
580 task.Start = ev
581 } else {
582 task.End = ev
583 }
584
585
586
587 if t.Parent != NoTask && task.Parent == nil {
588 parent := s.getOrAddTask(t.Parent)
589 task.Parent = parent
590 parent.Children = append(parent.Children, task)
591 }
592 case EventLog:
593 log := ev.Log()
594
595
596 task := s.getOrAddTask(log.Task)
597 task.Goroutines[ev.Goroutine()] = s.gs[ev.Goroutine()]
598 task.Logs = append(task.Logs, ev)
599 }
600 }
601
602 func (s *Summarizer) getOrAddTask(id TaskID) *UserTaskSummary {
603 task := s.tasks[id]
604 if task == nil {
605 task = &UserTaskSummary{ID: id, Goroutines: make(map[GoID]*GoroutineSummary)}
606 s.tasks[id] = task
607 }
608 return task
609 }
610
611
612
613 func (s *Summarizer) Finalize() *Summary {
614 for _, g := range s.gs {
615 g.finalize(s.lastTs, nil)
616
617
618 slices.SortFunc(g.Regions, func(a, b *UserRegionSummary) int {
619 x := a.Start
620 y := b.Start
621 if x == nil {
622 if y == nil {
623 return 0
624 }
625 return -1
626 }
627 if y == nil {
628 return +1
629 }
630 return cmp.Compare(x.Time(), y.Time())
631 })
632 g.goroutineSummary = nil
633 }
634 return &Summary{
635 Goroutines: s.gs,
636 Tasks: s.tasks,
637 }
638 }
639
640
641
642
643 func RelatedGoroutinesV2(events []Event, goid GoID) map[GoID]struct{} {
644
645
646
647
648 type unblockEdge struct {
649 operator GoID
650 operand GoID
651 }
652 var unblockEdges []unblockEdge
653 for _, ev := range events {
654 if ev.Goroutine() == NoGoroutine {
655 continue
656 }
657 if ev.Kind() != EventStateTransition {
658 continue
659 }
660 st := ev.StateTransition()
661 if st.Resource.Kind != ResourceGoroutine {
662 continue
663 }
664 id := st.Resource.Goroutine()
665 old, new := st.Goroutine()
666 if old == new || old != GoWaiting {
667 continue
668 }
669 unblockEdges = append(unblockEdges, unblockEdge{
670 operator: ev.Goroutine(),
671 operand: id,
672 })
673 }
674
675
676 gmap := make(map[GoID]struct{})
677 gmap[goid] = struct{}{}
678 for i := 0; i < 2; i++ {
679
680 gmap1 := make(map[GoID]struct{})
681 for g := range gmap {
682 gmap1[g] = struct{}{}
683 }
684 for _, edge := range unblockEdges {
685 if _, ok := gmap[edge.operand]; ok {
686 gmap1[edge.operator] = struct{}{}
687 }
688 }
689 gmap = gmap1
690 }
691 return gmap
692 }
693
694 func IsSystemGoroutine(entryFn string) bool {
695
696
697
698 return entryFn == "" || entryFn != "runtime.main" && strings.HasPrefix(entryFn, "runtime.")
699 }
700
View as plain text