GoroutineProfile returns the stacks of all goroutines currently managed by the scheduler. This includes both goroutines that are currently running (On-CPU), as well as waiting (Off-CPU).
()
| 90 | // the scheduler. This includes both goroutines that are currently running |
| 91 | // (On-CPU), as well as waiting (Off-CPU). |
| 92 | func (p *profiler) GoroutineProfile() []runtime.StackRecord { |
| 93 | if p.selfFrame == nil { |
| 94 | // Determine the runtime.Frame of this func so we can hide it from our |
| 95 | // profiling output. |
| 96 | rpc := make([]uintptr, 1) |
| 97 | n := runtime.Callers(1, rpc) |
| 98 | if n < 1 { |
| 99 | panic("could not determine selfFrame") |
| 100 | } |
| 101 | selfFrame, _ := runtime.CallersFrames(rpc).Next() |
| 102 | p.selfFrame = &selfFrame |
| 103 | } |
| 104 | |
| 105 | // We don't know how many goroutines exist, so we have to grow p.stacks |
| 106 | // dynamically. We overshoot by 10% since it's possible that more goroutines |
| 107 | // are launched in between two calls to GoroutineProfile. Once p.stacks |
| 108 | // reaches the maximum number of goroutines used by the program, it will get |
| 109 | // reused indefinitely, eliminating GoroutineProfile calls and allocations. |
| 110 | // |
| 111 | // TODO(fg) There might be workloads where it would be nice to shrink |
| 112 | // p.stacks dynamically as well, but let's not over-engineer this until we |
| 113 | // understand those cases better. |
| 114 | for { |
| 115 | if nullTerminationWorkaround { |
| 116 | for i := range p.stacks { |
| 117 | p.stacks[i].Stack0 = [32]uintptr{} |
| 118 | } |
| 119 | } |
| 120 | n, ok := runtime.GoroutineProfile(p.stacks) |
| 121 | if !ok { |
| 122 | p.stacks = make([]runtime.StackRecord, int(float64(n)*1.1)) |
| 123 | } else { |
| 124 | return p.stacks[0:n] |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | // SelfFrames returns frames that belong to the profiler so that we can ignore |
| 130 | // them when exporting the final profile. |
no outgoing calls