github.com/pyroscope-io/godeltaprof@v0.1.3-0.20230906152420-0d7eeca7b8c1/block.go (about) 1 package godeltaprof 2 3 import ( 4 "github.com/pyroscope-io/godeltaprof/internal/pprof" 5 "io" 6 "runtime" 7 "sort" 8 "sync" 9 ) 10 11 // BlockProfiler is a stateful profiler for goroutine blocking events and mutex contention in Go programs. 12 // Depending on the function used to create the BlockProfiler, it uses either runtime.BlockProfile or runtime.MutexProfile. 13 // The BlockProfiler provides similar functionality to pprof.Lookup("block").WriteTo and pprof.Lookup("mutex").WriteTo, 14 // but with some key differences. 15 // 16 // The BlockProfiler tracks the delta of blocking events or mutex contention since the last 17 // profile was written, effectively providing a snapshot of the changes 18 // between two points in time. This is in contrast to the 19 // pprof.Lookup functions, which accumulate profiling data 20 // and result in profiles that represent the entire lifetime of the program. 21 // 22 // The BlockProfiler is safe for concurrent use, as it serializes access to 23 // its internal state using a sync.Mutex. This ensures that multiple goroutines 24 // can call the Profile method without causing any data race issues. 25 type BlockProfiler struct { 26 impl pprof.DeltaMutexProfiler 27 mutex sync.Mutex 28 runtimeProfile func([]runtime.BlockProfileRecord) (int, bool) 29 scaleProfile func(int64, float64) (int64, float64) 30 } 31 32 // NewMutexProfiler creates a new BlockProfiler instance for profiling mutex contention. 33 // The resulting BlockProfiler uses runtime.MutexProfile as its data source. 34 // 35 // Usage: 36 // 37 // mp := godeltaprof.NewMutexProfiler() 38 // ... 39 // err := mp.Profile(someWriter) 40 func NewMutexProfiler() *BlockProfiler { 41 return &BlockProfiler{runtimeProfile: runtime.MutexProfile, scaleProfile: scaleMutexProfile} 42 } 43 44 // NewBlockProfiler creates a new BlockProfiler instance for profiling goroutine blocking events. 45 // The resulting BlockProfiler uses runtime.BlockProfile as its data source. 46 // 47 // Usage: 48 // 49 // bp := godeltaprof.NewBlockProfiler() 50 // ... 51 // err := bp.Profile(someWriter) 52 func NewBlockProfiler() *BlockProfiler { 53 return &BlockProfiler{runtimeProfile: runtime.BlockProfile, scaleProfile: scaleBlockProfile} 54 } 55 56 func (d *BlockProfiler) Profile(w io.Writer) error { 57 d.mutex.Lock() 58 defer d.mutex.Unlock() 59 60 var p []runtime.BlockProfileRecord 61 n, ok := d.runtimeProfile(nil) 62 for { 63 p = make([]runtime.BlockProfileRecord, n+50) 64 n, ok = d.runtimeProfile(p) 65 if ok { 66 p = p[:n] 67 break 68 } 69 } 70 71 sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles }) 72 73 return d.impl.PrintCountCycleProfile(w, "contentions", "delay", d.scaleProfile, p) 74 } 75 76 func scaleMutexProfile(cnt int64, ns float64) (int64, float64) { 77 period := runtime.SetMutexProfileFraction(-1) 78 return cnt * int64(period), ns * float64(period) 79 } 80 81 func scaleBlockProfile(cnt int64, ns float64) (int64, float64) { 82 // Do nothing. 83 // The current way of block profile sampling makes it 84 // hard to compute the unsampled number. The legacy block 85 // profile parse doesn't attempt to scale or unsample. 86 return cnt, ns 87 }