github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/symdb/resolver_pprof.go (about) 1 package symdb 2 3 import ( 4 "context" 5 6 googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" 7 schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" 8 "github.com/grafana/pyroscope/pkg/slices" 9 ) 10 11 type pprofBuilder interface { 12 StacktraceInserter 13 init(*Symbols, schemav1.Samples) 14 buildPprof() *googlev1.Profile 15 } 16 17 func buildPprof( 18 ctx context.Context, 19 symbols *Symbols, 20 samples schemav1.Samples, 21 maxNodes int64, 22 selection *SelectedStackTraces, 23 ) (*googlev1.Profile, error) { 24 // By default, we use a builder that's optimized for the most 25 // basic case: we take all the source stack traces unchanged. 26 var b pprofBuilder = new(pprofFull) 27 switch { 28 // Go PGO selector; the stack traces are trimmed in the way 29 // that only the first KeepLocations are retained. Optionally, 30 // samples are aggregated by the callee, ignoring the leaf 31 // location line number. 32 case selection.gopgo != nil: 33 b = &pprofGoPGO{pgo: selection.gopgo} 34 // If a stack trace selector is specified, check if such a 35 // profile can exist. Otherwise, build an empty profile. 36 case !selection.HasValidCallSite(): 37 return b.buildPprof(), nil 38 // Truncation is applicable when there is an explicit 39 // limit on the number of the nodes in the profile, or 40 // if stack traces should be filtered by the call site. 41 case maxNodes > 0 || len(selection.callSite) > 0: 42 b = &pprofTree{maxNodes: maxNodes, selection: selection} 43 } 44 b.init(symbols, samples) 45 if err := symbols.Stacktraces.ResolveStacktraceLocations(ctx, b, samples.StacktraceIDs); err != nil { 46 return nil, err 47 } 48 return b.buildPprof(), nil 49 } 50 51 func createSampleTypeStub(profile *googlev1.Profile) { 52 profile.PeriodType = new(googlev1.ValueType) 53 profile.SampleType = []*googlev1.ValueType{new(googlev1.ValueType)} 54 } 55 56 func copyLocations(profile *googlev1.Profile, symbols *Symbols, lut []uint32) { 57 profile.Location = make([]*googlev1.Location, len(symbols.Locations)) 58 // Copy locations referenced by nodes. 59 for _, n := range profile.Sample { 60 for _, loc := range n.LocationId { 61 if loc == truncationMark { 62 // To be replaced with a stub location. 63 continue 64 } 65 if profile.Location[loc] != nil { 66 // Already copied: it's expected that 67 // the same location is referenced by 68 // multiple nodes. 69 continue 70 } 71 src := symbols.Locations[loc] 72 // The location identifier is its index 73 // in symbols.Locations, therefore it 74 // matches the node location reference. 75 location := &googlev1.Location{ 76 Id: loc, 77 MappingId: uint64(src.MappingId), 78 Address: src.Address, 79 Line: make([]*googlev1.Line, len(src.Line)), 80 IsFolded: src.IsFolded, 81 } 82 for i, line := range src.Line { 83 location.Line[i] = &googlev1.Line{ 84 FunctionId: uint64(line.FunctionId), 85 Line: int64(line.Line), 86 } 87 } 88 profile.Location[loc] = location 89 } 90 } 91 // Now profile.Location contains copies of locations. 92 // The slice also has nil items, therefore we need to 93 // filter them out. 94 n := len(profile.Location) 95 lut = slices.GrowLen(lut, n) 96 var j int 97 for i := 0; i < len(profile.Location); i++ { 98 loc := profile.Location[i] 99 if loc == nil { 100 continue 101 } 102 oldId := loc.Id 103 loc.Id = uint64(j) + 1 104 lut[oldId] = uint32(loc.Id) 105 profile.Location[j] = loc 106 j++ 107 } 108 profile.Location = profile.Location[:j] 109 // Next we need to restore references, as the 110 // Sample.LocationId identifiers/indices are 111 // pointing to the old places. 112 for _, s := range profile.Sample { 113 for i, loc := range s.LocationId { 114 if loc != truncationMark { 115 s.LocationId[i] = uint64(lut[loc]) 116 } 117 } 118 } 119 } 120 121 func copyFunctions(profile *googlev1.Profile, symbols *Symbols, lut []uint32) { 122 profile.Function = make([]*googlev1.Function, len(symbols.Functions)) 123 for _, loc := range profile.Location { 124 for _, line := range loc.Line { 125 if profile.Function[line.FunctionId] == nil { 126 src := symbols.Functions[line.FunctionId] 127 profile.Function[line.FunctionId] = &googlev1.Function{ 128 Id: line.FunctionId, 129 Name: int64(src.Name), 130 SystemName: int64(src.SystemName), 131 Filename: int64(src.Filename), 132 StartLine: int64(src.StartLine), 133 } 134 } 135 } 136 } 137 n := len(profile.Function) 138 lut = slices.GrowLen(lut, n) 139 var j int 140 for i := 0; i < len(profile.Function); i++ { 141 fn := profile.Function[i] 142 if fn == nil { 143 continue 144 } 145 oldId := fn.Id 146 fn.Id = uint64(j) + 1 147 lut[oldId] = uint32(fn.Id) 148 profile.Function[j] = fn 149 j++ 150 } 151 profile.Function = profile.Function[:j] 152 for _, loc := range profile.Location { 153 for _, line := range loc.Line { 154 line.FunctionId = uint64(lut[line.FunctionId]) 155 } 156 } 157 } 158 159 func copyMappings(profile *googlev1.Profile, symbols *Symbols, lut []uint32) { 160 profile.Mapping = make([]*googlev1.Mapping, len(symbols.Mappings)) 161 for _, loc := range profile.Location { 162 if profile.Mapping[loc.MappingId] == nil { 163 src := symbols.Mappings[loc.MappingId] 164 profile.Mapping[loc.MappingId] = &googlev1.Mapping{ 165 Id: loc.MappingId, 166 MemoryStart: src.MemoryStart, 167 MemoryLimit: src.MemoryLimit, 168 FileOffset: src.FileOffset, 169 Filename: int64(src.Filename), 170 BuildId: int64(src.BuildId), 171 HasFunctions: src.HasFunctions, 172 HasFilenames: src.HasFilenames, 173 HasLineNumbers: src.HasLineNumbers, 174 HasInlineFrames: src.HasInlineFrames, 175 } 176 } 177 } 178 n := len(profile.Mapping) 179 lut = slices.GrowLen(lut, n) 180 var j int 181 for i := 0; i < len(profile.Mapping); i++ { 182 m := profile.Mapping[i] 183 if m == nil { 184 continue 185 } 186 oldId := m.Id 187 m.Id = uint64(j) + 1 188 lut[oldId] = uint32(m.Id) 189 profile.Mapping[j] = m 190 j++ 191 } 192 profile.Mapping = profile.Mapping[:j] 193 for _, loc := range profile.Location { 194 loc.MappingId = uint64(lut[loc.MappingId]) 195 } 196 } 197 198 func copyStrings(profile *googlev1.Profile, symbols *Symbols, lut []uint32) { 199 // symbols.Strings may not contain empty strings as it is 200 // required by the pprof format. Therefore, we create one 201 // at index 0 to ensure correctness. 202 z := -1 203 for i := 0; i < len(symbols.Strings); i++ { 204 s := symbols.Strings[i] 205 if s == "" { 206 z = i 207 break 208 } 209 } 210 // o is the offset to apply to the string table: 211 // it's 0 if the empty string is present, 1 otherwise. 212 var o int64 213 if z < 0 { 214 // There is no empty string. We need to allocate one. 215 // Otherwise, if "" is at any place other than 0, we 216 // only need to swap the strings after we gather them. 217 o = 1 218 } 219 profile.StringTable = make([]string, len(symbols.Strings)+int(o)) 220 // Gather strings referenced by the profile: profile.StringTable 221 // is a sparse array with empty slots, that will be removed later. 222 for _, m := range profile.Mapping { 223 profile.StringTable[m.Filename+o] = symbols.Strings[m.Filename] 224 profile.StringTable[m.BuildId+o] = symbols.Strings[m.BuildId] 225 } 226 for _, f := range profile.Function { 227 profile.StringTable[f.Name+o] = symbols.Strings[f.Name] 228 profile.StringTable[f.Filename+o] = symbols.Strings[f.Filename] 229 profile.StringTable[f.SystemName+o] = symbols.Strings[f.SystemName] 230 } 231 // Swap zero string, if needed. 232 if z > 0 { 233 profile.StringTable[z], profile.StringTable[0] = profile.StringTable[0], profile.StringTable[z] 234 } 235 n := len(profile.StringTable) 236 lut = slices.GrowLen(lut, n) 237 j := 1 // Skip "" as its index is deterministic. 238 for i := 1; i < len(profile.StringTable); i++ { 239 s := profile.StringTable[i] 240 if s == "" { 241 continue 242 } 243 x := i 244 if i == z { 245 // Move item at the "" index to 0. 246 x = 0 247 } 248 lut[x] = uint32(j) 249 profile.StringTable[j] = s 250 j++ 251 } 252 // Rewrite string references in the profile. 253 profile.StringTable = profile.StringTable[:j] 254 for _, m := range profile.Mapping { 255 m.Filename = int64(lut[m.Filename+o]) 256 m.BuildId = int64(lut[m.BuildId+o]) 257 } 258 for _, f := range profile.Function { 259 f.Name = int64(lut[f.Name+o]) 260 f.Filename = int64(lut[f.Filename+o]) 261 f.SystemName = int64(lut[f.SystemName+o]) 262 } 263 }