github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/tree/profile_finder.go (about) 1 package tree 2 3 import ( 4 "sort" 5 ) 6 7 // NewFinder creates an efficient finder for functions or locations in a profile. 8 // 9 // It exists to abstract the details of how functions and locations exist in a pprof profile, 10 // and make it easy to provide an efficient implementation depending on the actual profile format, 11 // as location and function finding is a recurrent operation while processing pprof profiles. 12 // 13 // The [pprof format description](https://github.com/google/pprof/tree/master/proto#general-structure-of-a-profile) 14 // describes that both locations and functions have unique nonzero ids. 15 // A [comment in the proto file](https://github.com/google/pprof/blob/master/proto/profile.proto#L164-L166) 16 // goes further: _A profile could use instruction addresses or any integer sequence as ids_. 17 // 18 // Based on this, any uint64 value (except 0) can appear as ids, and a map based cache can be used in that case. 19 // In practice, [go runtime](https://github.com/golang/go/blob/master/src/runtime/pprof/proto.go#L537) 20 // generates profiles where locations and functions use consecutive IDs starting from 1, 21 // making optimized access possible. 22 // 23 // Taking advantage of this, the finder will try to: 24 // - Use direct access to functions and locations indexed by IDs when possible 25 // (sorting location and function sequences if needed). 26 // - Use a map based cache otherwise. 27 func NewFinder(p *Profile) Finder { 28 return &finder{p: p, lf: nil, ff: nil} 29 } 30 31 type Finder interface { 32 FunctionFinder 33 LocationFinder 34 } 35 36 // Find location in a profile based on its ID 37 type LocationFinder interface { 38 FindLocation(id uint64) (*Location, bool) 39 } 40 41 // Find function in a profile based on its ID 42 type FunctionFinder interface { 43 FindFunction(id uint64) (*Function, bool) 44 } 45 46 type sliceLocationFinder []*Location 47 48 func (f sliceLocationFinder) FindLocation(id uint64) (*Location, bool) { 49 if id == 0 || id > uint64(len(f)) { 50 return nil, false 51 } 52 return f[id-1], true 53 } 54 55 type mapLocationFinder map[uint64]*Location 56 57 func (f mapLocationFinder) FindLocation(id uint64) (*Location, bool) { 58 loc, ok := f[id] 59 return loc, ok 60 } 61 62 type sliceFunctionFinder []*Function 63 64 func (f sliceFunctionFinder) FindFunction(id uint64) (*Function, bool) { 65 if id == 0 || id > uint64(len(f)) { 66 return nil, false 67 } 68 return f[id-1], true 69 } 70 71 type mapFunctionFinder map[uint64]*Function 72 73 func (f mapFunctionFinder) FindFunction(id uint64) (*Function, bool) { 74 fun, ok := f[id] 75 return fun, ok 76 } 77 78 // finder is a lazy implementation of Finder, that will be using the most efficient function and location finder. 79 type finder struct { 80 p *Profile 81 lf LocationFinder 82 ff FunctionFinder 83 } 84 85 func (f *finder) FindLocation(id uint64) (*Location, bool) { 86 if f.lf == nil { 87 var ok bool 88 f.lf, ok = locationSlice(f.p) 89 if !ok { 90 f.lf = locationMap(f.p) 91 } 92 } 93 return f.lf.FindLocation(id) 94 } 95 96 func (f *finder) FindFunction(id uint64) (*Function, bool) { 97 if f.ff == nil { 98 var ok bool 99 f.ff, ok = functionSlice(f.p) 100 if !ok { 101 f.ff = functionMap(f.p) 102 } 103 } 104 return f.ff.FindFunction(id) 105 } 106 107 func locationSlice(p *Profile) (sliceLocationFinder, bool) { 108 // Check if it's already sorted first 109 max := uint64(0) 110 sorted := true 111 for i, l := range p.Location { 112 if l.Id != uint64(i+1) { 113 sorted = false 114 if l.Id > max { 115 max = l.Id 116 } 117 } 118 } 119 if max > uint64(len(p.Location)) { 120 // IDs are not consecutive numbers starting at 1, a slice is not good enough 121 return nil, false 122 } 123 if !sorted { 124 sort.Slice(p.Location, func(i, j int) bool { 125 return p.Location[i].Id < p.Location[j].Id 126 }) 127 } 128 return sliceLocationFinder(p.Location), true 129 } 130 131 func locationMap(p *Profile) mapLocationFinder { 132 m := make(map[uint64]*Location, len(p.Location)) 133 for _, l := range p.Location { 134 m[l.Id] = l 135 } 136 return mapLocationFinder(m) 137 } 138 139 func functionSlice(p *Profile) (sliceFunctionFinder, bool) { 140 // Check if it's already sorted first 141 max := uint64(0) 142 sorted := true 143 for i, f := range p.Function { 144 if f.Id != uint64(i+1) { 145 sorted = false 146 if f.Id > max { 147 max = f.Id 148 } 149 } 150 } 151 if max > uint64(len(p.Function)) { 152 // IDs are not consecutive numbers starting at one, this won't work 153 return nil, false 154 } 155 if !sorted { 156 sort.Slice(p.Function, func(i, j int) bool { 157 return p.Function[i].Id < p.Function[j].Id 158 }) 159 } 160 return sliceFunctionFinder(p.Function), true 161 } 162 163 func functionMap(p *Profile) mapFunctionFinder { 164 m := make(map[uint64]*Function, len(p.Function)) 165 for _, f := range p.Function { 166 m[f.Id] = f 167 } 168 return m 169 }