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  }