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 }