github.com/grafana/pyroscope-go/godeltaprof@v0.1.8-0.20240513050943-1b1f97373e2a/block.go (about) 1 package godeltaprof 2 3 import ( 4 "io" 5 "runtime" 6 "sort" 7 "sync" 8 9 "github.com/grafana/pyroscope-go/godeltaprof/internal/pprof" 10 ) 11 12 // BlockProfiler is a stateful profiler for goroutine blocking events and mutex contention in Go programs. 13 // Depending on the function used to create the BlockProfiler, it uses either runtime.BlockProfile or runtime.MutexProfile. 14 // The BlockProfiler provides similar functionality to pprof.Lookup("block").WriteTo and pprof.Lookup("mutex").WriteTo, 15 // but with some key differences. 16 // 17 // The BlockProfiler tracks the delta of blocking events or mutex contention since the last 18 // profile was written, effectively providing a snapshot of the changes 19 // between two points in time. This is in contrast to the 20 // pprof.Lookup functions, which accumulate profiling data 21 // and result in profiles that represent the entire lifetime of the program. 22 // 23 // The BlockProfiler is safe for concurrent use, as it serializes access to 24 // its internal state using a sync.Mutex. This ensures that multiple goroutines 25 // can call the Profile method without causing any data race issues. 26 type BlockProfiler struct { 27 impl pprof.DeltaMutexProfiler 28 mutex sync.Mutex 29 runtimeProfile func([]runtime.BlockProfileRecord) (int, bool) 30 scaleProfile pprof.MutexProfileScaler 31 } 32 33 // NewMutexProfiler creates a new BlockProfiler instance for profiling mutex contention. 34 // The resulting BlockProfiler uses runtime.MutexProfile as its data source. 35 // 36 // Usage: 37 // 38 // mp := godeltaprof.NewMutexProfiler() 39 // ... 40 // err := mp.Profile(someWriter) 41 func NewMutexProfiler() *BlockProfiler { 42 return &BlockProfiler{ 43 runtimeProfile: runtime.MutexProfile, 44 scaleProfile: pprof.ScalerMutexProfile, 45 impl: pprof.DeltaMutexProfiler{ 46 Options: pprof.ProfileBuilderOptions{ 47 GenericsFrames: true, 48 LazyMapping: true, 49 }, 50 }, 51 } 52 } 53 54 func NewMutexProfilerWithOptions(options ProfileOptions) *BlockProfiler { 55 return &BlockProfiler{ 56 runtimeProfile: runtime.MutexProfile, 57 scaleProfile: pprof.ScalerMutexProfile, 58 impl: pprof.DeltaMutexProfiler{ 59 Options: pprof.ProfileBuilderOptions{ 60 GenericsFrames: options.GenericsFrames, 61 LazyMapping: options.LazyMappings, 62 }, 63 }, 64 } 65 } 66 67 // NewBlockProfiler creates a new BlockProfiler instance for profiling goroutine blocking events. 68 // The resulting BlockProfiler uses runtime.BlockProfile as its data source. 69 // 70 // Usage: 71 // 72 // bp := godeltaprof.NewBlockProfiler() 73 // ... 74 // err := bp.Profile(someWriter) 75 func NewBlockProfiler() *BlockProfiler { 76 return &BlockProfiler{ 77 runtimeProfile: runtime.BlockProfile, 78 scaleProfile: pprof.ScalerBlockProfile, 79 impl: pprof.DeltaMutexProfiler{ 80 Options: pprof.ProfileBuilderOptions{ 81 GenericsFrames: true, 82 LazyMapping: true, 83 }, 84 }, 85 } 86 } 87 88 func NewBlockProfilerWithOptions(options ProfileOptions) *BlockProfiler { 89 return &BlockProfiler{ 90 runtimeProfile: runtime.BlockProfile, 91 scaleProfile: pprof.ScalerBlockProfile, 92 impl: pprof.DeltaMutexProfiler{ 93 Options: pprof.ProfileBuilderOptions{ 94 GenericsFrames: options.GenericsFrames, 95 LazyMapping: options.LazyMappings, 96 }, 97 }, 98 } 99 } 100 101 func (d *BlockProfiler) Profile(w io.Writer) error { 102 d.mutex.Lock() 103 defer d.mutex.Unlock() 104 105 var p []runtime.BlockProfileRecord 106 n, ok := d.runtimeProfile(nil) 107 for { 108 p = make([]runtime.BlockProfileRecord, n+50) 109 n, ok = d.runtimeProfile(p) 110 if ok { 111 p = p[:n] 112 break 113 } 114 } 115 116 sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles }) 117 118 return d.impl.PrintCountCycleProfile(w, "contentions", "delay", d.scaleProfile, p) 119 }