github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/autoprofile/internal/block_sampler.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  package internal
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"runtime"
    11  	"runtime/pprof"
    12  
    13  	"github.com/instana/go-sensor/autoprofile/internal/pprof/profile"
    14  )
    15  
    16  type blockValues struct {
    17  	delay       float64
    18  	contentions int64
    19  }
    20  
    21  // BlockSampler collects information about goroutine blocking events, such as waiting on
    22  // synchronization primitives. This sampler uses the runtime blocking profiler, enabling and
    23  // disabling it for a period of time.
    24  type BlockSampler struct {
    25  	top            *CallSite
    26  	prevValues     map[string]blockValues
    27  	partialProfile *pprof.Profile
    28  }
    29  
    30  // NewBlockSampler initializes a new blocking events sampler
    31  func NewBlockSampler() *BlockSampler {
    32  	bs := &BlockSampler{
    33  		top:            nil,
    34  		prevValues:     make(map[string]blockValues),
    35  		partialProfile: nil,
    36  	}
    37  
    38  	return bs
    39  }
    40  
    41  // Reset resets the state of a BlockSampler, starting a new call tree
    42  func (bs *BlockSampler) Reset() {
    43  	bs.top = NewCallSite("", "", 0)
    44  }
    45  
    46  // Start enables the reporting of blocking events
    47  func (bs *BlockSampler) Start() error {
    48  	bs.partialProfile = pprof.Lookup("block")
    49  	if bs.partialProfile == nil {
    50  		return errors.New("No block profile found")
    51  	}
    52  
    53  	runtime.SetBlockProfileRate(1e6)
    54  
    55  	return nil
    56  }
    57  
    58  // Stop disables the reporting of blocking events and gathers the collected information
    59  // into a profile
    60  func (bs *BlockSampler) Stop() error {
    61  	runtime.SetBlockProfileRate(0)
    62  
    63  	p, err := bs.collectProfile()
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	if p == nil {
    69  		return errors.New("no profile returned")
    70  	}
    71  
    72  	if err := bs.updateBlockProfile(p); err != nil {
    73  		return err
    74  	}
    75  
    76  	return nil
    77  }
    78  
    79  // Profile return the collected profile for a given time span
    80  func (bs *BlockSampler) Profile(duration, timespan int64) (*Profile, error) {
    81  	roots := make([]*CallSite, 0)
    82  	for _, child := range bs.top.children {
    83  		roots = append(roots, child)
    84  	}
    85  	p := NewProfile(CategoryTime, TypeBlockingCalls, UnitMillisecond, roots, duration, timespan)
    86  	return p, nil
    87  }
    88  
    89  func (bs *BlockSampler) updateBlockProfile(p *profile.Profile) error {
    90  	contentionIndex := -1
    91  	delayIndex := -1
    92  	for i, s := range p.SampleType {
    93  		if s.Type == "contentions" {
    94  			contentionIndex = i
    95  		} else if s.Type == "delay" {
    96  			delayIndex = i
    97  		}
    98  	}
    99  
   100  	if contentionIndex == -1 || delayIndex == -1 {
   101  		return errors.New("Unrecognized profile data")
   102  	}
   103  
   104  	for _, s := range p.Sample {
   105  		if shouldSkipStack(s) {
   106  			continue
   107  		}
   108  
   109  		delay := float64(s.Value[delayIndex])
   110  		contentions := s.Value[contentionIndex]
   111  
   112  		valueKey := generateValueKey(s)
   113  		delay, contentions = bs.getValueChange(valueKey, delay, contentions)
   114  
   115  		if contentions == 0 || delay == 0 {
   116  			continue
   117  		}
   118  
   119  		// to milliseconds
   120  		delay = delay / 1e6
   121  
   122  		current := bs.top
   123  		for i := len(s.Location) - 1; i >= 0; i-- {
   124  			l := s.Location[i]
   125  			funcName, fileName, fileLine := readFuncInfo(l)
   126  
   127  			current = current.FindOrAddChild(funcName, fileName, fileLine)
   128  		}
   129  		current.Increment(delay, contentions)
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  func generateValueKey(s *profile.Sample) string {
   136  	var key string
   137  	for _, l := range s.Location {
   138  		key += fmt.Sprintf("%v:", l.Address)
   139  	}
   140  
   141  	return key
   142  }
   143  
   144  func (bs *BlockSampler) getValueChange(key string, delay float64, contentions int64) (float64, int64) {
   145  	pv := bs.prevValues[key]
   146  
   147  	delayChange := delay - pv.delay
   148  	contentionsChange := contentions - pv.contentions
   149  
   150  	pv.delay = delay
   151  	pv.contentions = contentions
   152  	bs.prevValues[key] = pv
   153  
   154  	return delayChange, contentionsChange
   155  }
   156  
   157  func (bs *BlockSampler) collectProfile() (*profile.Profile, error) {
   158  	buf := bytes.NewBuffer(nil)
   159  
   160  	if err := bs.partialProfile.WriteTo(buf, 0); err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	p, err := profile.Parse(buf)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	if err := symbolizeProfile(p); err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	if err := p.CheckValid(); err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	return p, nil
   178  }