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  }