github.com/bir3/gocompiler@v0.9.2202/src/internal/trace/summary.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package trace
     6  
     7  import (
     8  	tracev2 "github.com/bir3/gocompiler/src/internal/trace/v2"
     9  	"sort"
    10  	"time"
    11  )
    12  
    13  // Summary is the analysis result produced by the summarizer.
    14  type Summary struct {
    15  	Goroutines	map[tracev2.GoID]*GoroutineSummary
    16  	Tasks		map[tracev2.TaskID]*UserTaskSummary
    17  }
    18  
    19  // GoroutineSummary contains statistics and execution details of a single goroutine.
    20  // (For v2 traces.)
    21  type GoroutineSummary struct {
    22  	ID		tracev2.GoID
    23  	Name		string		// A non-unique human-friendly identifier for the goroutine.
    24  	PC		uint64		// The start PC of the goroutine.
    25  	CreationTime	tracev2.Time	// Timestamp of the first appearance in the trace.
    26  	StartTime	tracev2.Time	// Timestamp of the first time it started running. 0 if the goroutine never ran.
    27  	EndTime		tracev2.Time	// Timestamp of when the goroutine exited. 0 if the goroutine never exited.
    28  
    29  	// List of regions in the goroutine, sorted based on the start time.
    30  	Regions	[]*UserRegionSummary
    31  
    32  	// Statistics of execution time during the goroutine execution.
    33  	GoroutineExecStats
    34  
    35  	// goroutineSummary is state used just for computing this structure.
    36  	// It's dropped before being returned to the caller.
    37  	//
    38  	// More specifically, if it's nil, it indicates that this summary has
    39  	// already been finalized.
    40  	*goroutineSummary
    41  }
    42  
    43  // UserTaskSummary represents a task in the trace.
    44  type UserTaskSummary struct {
    45  	ID		tracev2.TaskID
    46  	Name		string
    47  	Parent		*UserTaskSummary	// nil if the parent is unknown.
    48  	Children	[]*UserTaskSummary
    49  
    50  	// Task begin event. An EventTaskBegin event or nil.
    51  	Start	*tracev2.Event
    52  
    53  	// End end event. Normally EventTaskEnd event or nil.
    54  	End	*tracev2.Event
    55  
    56  	// Logs is a list of tracev2.EventLog events associated with the task.
    57  	Logs	[]*tracev2.Event
    58  
    59  	// List of regions in the task, sorted based on the start time.
    60  	Regions	[]*UserRegionSummary
    61  
    62  	// Goroutines is the set of goroutines associated with this task.
    63  	Goroutines	map[tracev2.GoID]*GoroutineSummary
    64  }
    65  
    66  // Complete returns true if we have complete information about the task
    67  // from the trace: both a start and an end.
    68  func (s *UserTaskSummary) Complete() bool {
    69  	return s.Start != nil && s.End != nil
    70  }
    71  
    72  // Descendents returns a slice consisting of itself (always the first task returned),
    73  // and the transitive closure of all of its children.
    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  // UserRegionSummary represents a region and goroutine execution stats
    83  // while the region was active. (For v2 traces.)
    84  type UserRegionSummary struct {
    85  	TaskID	tracev2.TaskID
    86  	Name	string
    87  
    88  	// Region start event. Normally EventRegionBegin event or nil,
    89  	// but can be a state transition event from NotExist or Undetermined
    90  	// if the region is a synthetic region representing task inheritance
    91  	// from the parent goroutine.
    92  	Start	*tracev2.Event
    93  
    94  	// Region end event. Normally EventRegionEnd event or nil,
    95  	// but can be a state transition event to NotExist if the goroutine
    96  	// terminated without explicitly ending the region.
    97  	End	*tracev2.Event
    98  
    99  	GoroutineExecStats
   100  }
   101  
   102  // GoroutineExecStats contains statistics about a goroutine's execution
   103  // during a period of time.
   104  type GoroutineExecStats struct {
   105  	// These stats are all non-overlapping.
   106  	ExecTime		time.Duration
   107  	SchedWaitTime		time.Duration
   108  	BlockTimeByReason	map[string]time.Duration
   109  	SyscallTime		time.Duration
   110  	SyscallBlockTime	time.Duration
   111  
   112  	// TotalTime is the duration of the goroutine's presence in the trace.
   113  	// Necessarily overlaps with other stats.
   114  	TotalTime	time.Duration
   115  
   116  	// Total time the goroutine spent in certain ranges; may overlap
   117  	// with other stats.
   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  	// N.B. Don't include RangeTime or TotalTime; they overlap with these other
   133  	// stats.
   134  	return stats
   135  }
   136  
   137  // UnknownTime returns whatever isn't accounted for in TotalTime.
   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  	// N.B. Don't include range time. Ranges overlap with
   145  	// other stats, whereas these stats are non-overlapping.
   146  	if sum < s.TotalTime {
   147  		return s.TotalTime - sum
   148  	}
   149  	return 0
   150  }
   151  
   152  // sub returns the stats v-s.
   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  // snapshotStat returns the snapshot of the goroutine execution statistics.
   183  // This is called as we process the ordered trace event stream. lastTs is used
   184  // to process pending statistics if this is called before any goroutine end event.
   185  func (g *GoroutineSummary) snapshotStat(lastTs tracev2.Time) (ret GoroutineExecStats) {
   186  	ret = g.GoroutineExecStats.clone()
   187  
   188  	if g.goroutineSummary == nil {
   189  		return ret	// Already finalized; no pending state.
   190  	}
   191  
   192  	// Set the total time if necessary.
   193  	if g.TotalTime == 0 {
   194  		ret.TotalTime = lastTs.Sub(g.CreationTime)
   195  	}
   196  
   197  	// Add in time since lastTs.
   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  // finalize is called when processing a goroutine end event or at
   220  // the end of trace processing. This finalizes the execution stat
   221  // and any active regions in the goroutine, in which case trigger is nil.
   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  	// System goroutines are never part of regions, even though they
   231  	// "inherit" a task due to creation (EvGoCreate) from within a region.
   232  	// This may happen e.g. if the first GC is triggered within a region,
   233  	// starting the GC worker goroutines.
   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  // goroutineSummary is a private part of GoroutineSummary that is required only during analysis.
   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	// stack of active regions
   254  }
   255  
   256  // Summarizer constructs per-goroutine time statistics for v2 traces.
   257  type Summarizer struct {
   258  	// gs contains the map of goroutine summaries we're building up to return to the caller.
   259  	gs	map[tracev2.GoID]*GoroutineSummary
   260  
   261  	// tasks contains the map of task summaries we're building up to return to the caller.
   262  	tasks	map[tracev2.TaskID]*UserTaskSummary
   263  
   264  	// syscallingP and syscallingG represent a binding between a P and G in a syscall.
   265  	// Used to correctly identify and clean up after syscalls (blocking or otherwise).
   266  	syscallingP	map[tracev2.ProcID]tracev2.GoID
   267  	syscallingG	map[tracev2.GoID]tracev2.ProcID
   268  
   269  	// rangesP is used for optimistic tracking of P-based ranges for goroutines.
   270  	//
   271  	// It's a best-effort mapping of an active range on a P to the goroutine we think
   272  	// is associated with it.
   273  	rangesP	map[rangeP]tracev2.GoID
   274  
   275  	lastTs	tracev2.Time	// timestamp of the last event processed.
   276  	syncTs	tracev2.Time	// timestamp of the last sync event processed (or the first timestamp in the trace).
   277  }
   278  
   279  // NewSummarizer creates a new struct to build goroutine stats from a trace.
   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  // Event feeds a single event into the stats summarizer.
   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  	// Record sync time for the RangeActive events.
   304  	case tracev2.EventSync:
   305  		s.syncTs = ev.Time()
   306  
   307  	// Handle state transitions.
   308  	case tracev2.EventStateTransition:
   309  		st := ev.StateTransition()
   310  		switch st.Resource.Kind {
   311  		// Handle goroutine transitions, which are the meat of this computation.
   312  		case tracev2.ResourceGoroutine:
   313  			id := st.Resource.Goroutine()
   314  			old, new := st.Goroutine()
   315  			if old == new {
   316  				// Skip these events; they're not telling us anything new.
   317  				break
   318  			}
   319  
   320  			// Handle transition out.
   321  			g := s.gs[id]
   322  			switch old {
   323  			case tracev2.GoUndetermined, tracev2.GoNotExist:
   324  				g = &GoroutineSummary{ID: id, goroutineSummary: &goroutineSummary{}}
   325  				// If we're coming out of GoUndetermined, then the creation time is the
   326  				// time of the last sync.
   327  				if old == tracev2.GoUndetermined {
   328  					g.CreationTime = s.syncTs
   329  				} else {
   330  					g.CreationTime = ev.Time()
   331  				}
   332  				// The goroutine is being created, or it's being named for the first time.
   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  				// When a goroutine is newly created, inherit the task
   338  				// of the active region. For ease handling of this
   339  				// case, we create a fake region description with the
   340  				// task id. This isn't strictly necessary as this
   341  				// goroutine may not be associated with the task, but
   342  				// it can be convenient to see all children created
   343  				// during a region.
   344  				//
   345  				// N.B. ev.Goroutine() will always be NoGoroutine for the
   346  				// Undetermined case, so this is will simply not fire.
   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  				// Record execution time as we transition out of running
   355  				g.ExecTime += ev.Time().Sub(g.lastStartTime)
   356  				g.lastStartTime = 0
   357  			case tracev2.GoWaiting:
   358  				// Record block time as we transition out of waiting.
   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  				// Record sched latency time as we transition out of runnable.
   365  				if g.lastRunnableTime != 0 {
   366  					g.SchedWaitTime += ev.Time().Sub(g.lastRunnableTime)
   367  					g.lastRunnableTime = 0
   368  				}
   369  			case tracev2.GoSyscall:
   370  				// Record syscall execution time and syscall block time as we transition out of syscall.
   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  					// Clear the syscall map.
   382  					delete(s.syscallingP, s.syscallingG[id])
   383  					delete(s.syscallingG, id)
   384  				}
   385  			}
   386  
   387  			// The goroutine hasn't been identified yet. Take the transition stack
   388  			// and identify the goroutine by the bottom-most frame of that stack.
   389  			// This bottom-most frame will be identical for all transitions on this
   390  			// goroutine, because it represents its immutable start point.
   391  			if g.PC == 0 {
   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 false
   400  					})
   401  					if ok {
   402  						g.PC = frame.PC
   403  						g.Name = frame.Func
   404  					}
   405  				}
   406  			}
   407  
   408  			// Handle transition in.
   409  			switch new {
   410  			case tracev2.GoRunning:
   411  				// We started running. Record it.
   412  				g.lastStartTime = ev.Time()
   413  				if g.StartTime == 0 {
   414  					g.StartTime = ev.Time()
   415  				}
   416  			case tracev2.GoRunnable:
   417  				g.lastRunnableTime = ev.Time()
   418  			case tracev2.GoWaiting:
   419  				if st.Reason != "forever" {
   420  					g.lastBlockTime = ev.Time()
   421  					g.lastBlockReason = st.Reason
   422  					break
   423  				}
   424  				// "Forever" is like goroutine death.
   425  				fallthrough
   426  			case tracev2.GoNotExist:
   427  				g.finalize(ev.Time(), ev)
   428  			case tracev2.GoSyscall:
   429  				s.syscallingP[ev.Proc()] = id
   430  				s.syscallingG[id] = ev.Proc()
   431  				g.lastSyscallTime = ev.Time()
   432  			}
   433  
   434  		// Handle procs to detect syscall blocking, which si identifiable as a
   435  		// proc going idle while the goroutine it was attached to is in a syscall.
   436  		case tracev2.ResourceProc:
   437  			id := st.Resource.Proc()
   438  			old, new := st.Proc()
   439  			if old != new && new == tracev2.ProcIdle {
   440  				if goid, ok := s.syscallingP[id]; ok {
   441  					g := s.gs[goid]
   442  					g.lastSyscallBlockTime = ev.Time()
   443  					delete(s.syscallingP, id)
   444  				}
   445  			}
   446  		}
   447  
   448  	// Handle ranges of all kinds.
   449  	case tracev2.EventRangeBegin, tracev2.EventRangeActive:
   450  		r := ev.Range()
   451  		var g *GoroutineSummary
   452  		switch r.Scope.Kind {
   453  		case tracev2.ResourceGoroutine:
   454  			// Simple goroutine range. We attribute the entire range regardless of
   455  			// goroutine stats. Lots of situations are still identifiable, e.g. a
   456  			// goroutine blocked often in mark assist will have both high mark assist
   457  			// and high block times. Those interested in a deeper view can look at the
   458  			// trace viewer.
   459  			g = s.gs[r.Scope.Goroutine()]
   460  		case tracev2.ResourceProc:
   461  			// N.B. These ranges are not actually bound to the goroutine, they're
   462  			// bound to the P. But if we happen to be on the P the whole time, let's
   463  			// try to attribute it to the goroutine. (e.g. GC sweeps are here.)
   464  			g = s.gs[ev.Goroutine()]
   465  			if g != nil {
   466  				s.rangesP[rangeP{id: r.Scope.Proc(), name: r.Name}] = ev.Goroutine()
   467  			}
   468  		}
   469  		if g == nil {
   470  			break
   471  		}
   472  		if ev.Kind() == tracev2.EventRangeActive {
   473  			if ts := g.lastRangeTime[r.Name]; ts != 0 {
   474  				g.RangeTime[r.Name] += s.syncTs.Sub(ts)
   475  			}
   476  			g.lastRangeTime[r.Name] = s.syncTs
   477  		} else {
   478  			g.lastRangeTime[r.Name] = ev.Time()
   479  		}
   480  	case tracev2.EventRangeEnd:
   481  		r := ev.Range()
   482  		var g *GoroutineSummary
   483  		switch r.Scope.Kind {
   484  		case tracev2.ResourceGoroutine:
   485  			g = s.gs[r.Scope.Goroutine()]
   486  		case tracev2.ResourceProc:
   487  			rp := rangeP{id: r.Scope.Proc(), name: r.Name}
   488  			if goid, ok := s.rangesP[rp]; ok {
   489  				if goid == ev.Goroutine() {
   490  					// As the comment in the RangeBegin case states, this is only OK
   491  					// if we finish on the same goroutine we started on.
   492  					g = s.gs[goid]
   493  				}
   494  				delete(s.rangesP, rp)
   495  			}
   496  		}
   497  		if g == nil {
   498  			break
   499  		}
   500  		ts := g.lastRangeTime[r.Name]
   501  		if ts == 0 {
   502  			break
   503  		}
   504  		g.RangeTime[r.Name] += ev.Time().Sub(ts)
   505  		delete(g.lastRangeTime, r.Name)
   506  
   507  	// Handle user-defined regions.
   508  	case tracev2.EventRegionBegin:
   509  		g := s.gs[ev.Goroutine()]
   510  		r := ev.Region()
   511  		region := &UserRegionSummary{
   512  			Name:			r.Type,
   513  			TaskID:			r.Task,
   514  			Start:			ev,
   515  			GoroutineExecStats:	g.snapshotStat(ev.Time()),
   516  		}
   517  		g.activeRegions = append(g.activeRegions, region)
   518  		// Associate the region and current goroutine to the task.
   519  		task := s.getOrAddTask(r.Task)
   520  		task.Regions = append(task.Regions, region)
   521  		task.Goroutines[g.ID] = g
   522  	case tracev2.EventRegionEnd:
   523  		g := s.gs[ev.Goroutine()]
   524  		r := ev.Region()
   525  		var sd *UserRegionSummary
   526  		if regionStk := g.activeRegions; len(regionStk) > 0 {
   527  			// Pop the top region from the stack since that's what must have ended.
   528  			n := len(regionStk)
   529  			sd = regionStk[n-1]
   530  			regionStk = regionStk[:n-1]
   531  			g.activeRegions = regionStk
   532  			// N.B. No need to add the region to a task; the EventRegionBegin already handled it.
   533  		} else {
   534  			// This is an "end" without a start. Just fabricate the region now.
   535  			sd = &UserRegionSummary{Name: r.Type, TaskID: r.Task}
   536  			// Associate the region and current goroutine to the task.
   537  			task := s.getOrAddTask(r.Task)
   538  			task.Goroutines[g.ID] = g
   539  			task.Regions = append(task.Regions, sd)
   540  		}
   541  		sd.GoroutineExecStats = g.snapshotStat(ev.Time()).sub(sd.GoroutineExecStats)
   542  		sd.End = ev
   543  		g.Regions = append(g.Regions, sd)
   544  
   545  	// Handle tasks and logs.
   546  	case tracev2.EventTaskBegin, tracev2.EventTaskEnd:
   547  		// Initialize the task.
   548  		t := ev.Task()
   549  		task := s.getOrAddTask(t.ID)
   550  		task.Name = t.Type
   551  		task.Goroutines[ev.Goroutine()] = s.gs[ev.Goroutine()]
   552  		if ev.Kind() == tracev2.EventTaskBegin {
   553  			task.Start = ev
   554  		} else {
   555  			task.End = ev
   556  		}
   557  		// Initialize the parent, if one exists and it hasn't been done yet.
   558  		// We need to avoid doing it twice, otherwise we could appear twice
   559  		// in the parent's Children list.
   560  		if t.Parent != tracev2.NoTask && task.Parent == nil {
   561  			parent := s.getOrAddTask(t.Parent)
   562  			task.Parent = parent
   563  			parent.Children = append(parent.Children, task)
   564  		}
   565  	case tracev2.EventLog:
   566  		log := ev.Log()
   567  		// Just add the log to the task. We'll create the task if it
   568  		// doesn't exist (it's just been mentioned now).
   569  		task := s.getOrAddTask(log.Task)
   570  		task.Goroutines[ev.Goroutine()] = s.gs[ev.Goroutine()]
   571  		task.Logs = append(task.Logs, ev)
   572  	}
   573  }
   574  
   575  func (s *Summarizer) getOrAddTask(id tracev2.TaskID) *UserTaskSummary {
   576  	task := s.tasks[id]
   577  	if task == nil {
   578  		task = &UserTaskSummary{ID: id, Goroutines: make(map[tracev2.GoID]*GoroutineSummary)}
   579  		s.tasks[id] = task
   580  	}
   581  	return task
   582  }
   583  
   584  // Finalize indicates to the summarizer that we're done processing the trace.
   585  // It cleans up any remaining state and returns the full summary.
   586  func (s *Summarizer) Finalize() *Summary {
   587  	for _, g := range s.gs {
   588  		g.finalize(s.lastTs, nil)
   589  
   590  		// Sort based on region start time.
   591  		sort.Slice(g.Regions, func(i, j int) bool {
   592  			x := g.Regions[i].Start
   593  			y := g.Regions[j].Start
   594  			if x == nil {
   595  				return true
   596  			}
   597  			if y == nil {
   598  				return false
   599  			}
   600  			return x.Time() < y.Time()
   601  		})
   602  		g.goroutineSummary = nil
   603  	}
   604  	return &Summary{
   605  		Goroutines:	s.gs,
   606  		Tasks:		s.tasks,
   607  	}
   608  }
   609  
   610  // RelatedGoroutinesV2 finds a set of goroutines related to goroutine goid for v2 traces.
   611  // The association is based on whether they have synchronized with each other in the Go
   612  // scheduler (one has unblocked another).
   613  func RelatedGoroutinesV2(events []tracev2.Event, goid tracev2.GoID) map[tracev2.GoID]struct{} {
   614  	// Process all the events, looking for transitions of goroutines
   615  	// out of GoWaiting. If there was an active goroutine when this
   616  	// happened, then we know that active goroutine unblocked another.
   617  	// Scribble all these down so we can process them.
   618  	type unblockEdge struct {
   619  		operator	tracev2.GoID
   620  		operand		tracev2.GoID
   621  	}
   622  	var unblockEdges []unblockEdge
   623  	for _, ev := range events {
   624  		if ev.Goroutine() == tracev2.NoGoroutine {
   625  			continue
   626  		}
   627  		if ev.Kind() != tracev2.EventStateTransition {
   628  			continue
   629  		}
   630  		st := ev.StateTransition()
   631  		if st.Resource.Kind != tracev2.ResourceGoroutine {
   632  			continue
   633  		}
   634  		id := st.Resource.Goroutine()
   635  		old, new := st.Goroutine()
   636  		if old == new || old != tracev2.GoWaiting {
   637  			continue
   638  		}
   639  		unblockEdges = append(unblockEdges, unblockEdge{
   640  			operator:	ev.Goroutine(),
   641  			operand:	id,
   642  		})
   643  	}
   644  	// Compute the transitive closure of depth 2 of goroutines that have unblocked each other
   645  	// (starting from goid).
   646  	gmap := make(map[tracev2.GoID]struct{})
   647  	gmap[goid] = struct{}{}
   648  	for i := 0; i < 2; i++ {
   649  		// Copy the map.
   650  		gmap1 := make(map[tracev2.GoID]struct{})
   651  		for g := range gmap {
   652  			gmap1[g] = struct{}{}
   653  		}
   654  		for _, edge := range unblockEdges {
   655  			if _, ok := gmap[edge.operand]; ok {
   656  				gmap1[edge.operator] = struct{}{}
   657  			}
   658  		}
   659  		gmap = gmap1
   660  	}
   661  	return gmap
   662  }