github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/autoprofile/internal/allocation_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 "runtime/pprof" 10 11 "github.com/instana/go-sensor/autoprofile/internal/pprof/profile" 12 ) 13 14 // AllocationSampler collects information about the number of memory allocations 15 type AllocationSampler struct{} 16 17 // NewAllocationSampler initializes a new allocation sampler 18 func NewAllocationSampler() *AllocationSampler { 19 return &AllocationSampler{} 20 } 21 22 // Reset is a no-op for allocation sampler 23 func (as *AllocationSampler) Reset() {} 24 25 // Start is a no-op for allocation sampler 26 func (as *AllocationSampler) Start() error { return nil } 27 28 // Stop is a no-op for allocation sampler 29 func (as *AllocationSampler) Stop() error { return nil } 30 31 // Profile retrieves the head profile and converts it to the profile.Profile 32 func (as *AllocationSampler) Profile(duration int64, timespan int64) (*Profile, error) { 33 hp, err := as.readHeapProfile() 34 if err != nil { 35 return nil, err 36 } 37 38 if hp == nil { 39 return nil, errors.New("no profile returned") 40 } 41 42 top, err := as.createAllocationCallGraph(hp) 43 if err != nil { 44 return nil, err 45 } 46 47 roots := make([]*CallSite, 0) 48 for _, child := range top.children { 49 roots = append(roots, child) 50 } 51 52 return NewProfile(CategoryMemory, TypeMemoryAllocation, UnitByte, roots, duration, timespan), nil 53 } 54 55 func (as *AllocationSampler) createAllocationCallGraph(p *profile.Profile) (*CallSite, error) { 56 // find "inuse_space" type index 57 inuseSpaceTypeIndex := -1 58 for i, s := range p.SampleType { 59 if s.Type == "inuse_space" { 60 inuseSpaceTypeIndex = i 61 break 62 } 63 } 64 65 // find "inuse_objects" type index 66 inuseObjectsTypeIndex := -1 67 for i, s := range p.SampleType { 68 if s.Type == "inuse_objects" { 69 inuseObjectsTypeIndex = i 70 break 71 } 72 } 73 74 if inuseSpaceTypeIndex == -1 || inuseObjectsTypeIndex == -1 { 75 return nil, errors.New("unrecognized profile data") 76 } 77 78 // build call graph 79 top := NewCallSite("", "", 0) 80 81 for _, s := range p.Sample { 82 if shouldSkipStack(s) { 83 continue 84 } 85 86 value := s.Value[inuseSpaceTypeIndex] 87 if value == 0 { 88 continue 89 } 90 91 count := s.Value[inuseObjectsTypeIndex] 92 current := top 93 for i := len(s.Location) - 1; i >= 0; i-- { 94 l := s.Location[i] 95 funcName, fileName, fileLine := readFuncInfo(l) 96 97 current = current.FindOrAddChild(funcName, fileName, fileLine) 98 } 99 100 current.Increment(float64(value), int64(count)) 101 } 102 103 return top, nil 104 } 105 106 func (as *AllocationSampler) readHeapProfile() (*profile.Profile, error) { 107 buf := bytes.NewBuffer(nil) 108 if err := pprof.WriteHeapProfile(buf); err != nil { 109 return nil, err 110 } 111 112 p, err := profile.Parse(buf) 113 if err != nil { 114 return nil, err 115 } 116 117 if err := symbolizeProfile(p); err != nil { 118 return nil, err 119 } 120 121 if err := p.CheckValid(); err != nil { 122 return nil, err 123 } 124 125 return p, nil 126 }