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