github.com/mier85/go-sensor@v1.30.1-0.20220920111756-9bf41b3bc7e0/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/mier85/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 }