github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/helper/profile/pyroscope/pprof/pyroscope.go (about) 1 // Copyright 2023 iLogtail Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package pprof 16 17 import ( 18 "fmt" 19 20 "github.com/pyroscope-io/pyroscope/pkg/storage/metadata" 21 "github.com/pyroscope-io/pyroscope/pkg/storage/segment" 22 "github.com/pyroscope-io/pyroscope/pkg/storage/tree" 23 24 "github.com/alibaba/ilogtail/pkg/helper/profile" 25 ) 26 27 type StackFrameFormatter interface { 28 format(x *tree.Profile, fn *tree.Function, line *tree.Line) []byte 29 } 30 31 type Formatter struct { 32 } 33 34 func (Formatter) format(x *tree.Profile, fn *tree.Function, _ *tree.Line) []byte { 35 return []byte(fmt.Sprintf("%s %s", 36 x.StringTable[fn.Name], 37 x.StringTable[fn.Filename], 38 )) 39 } 40 41 func filterKnownSamples(sampleTypes map[string]*tree.SampleTypeConfig) func(string) bool { 42 return func(s string) bool { 43 _, ok := sampleTypes[s] 44 return ok 45 } 46 } 47 48 type Parser struct { 49 stackFrameFormatter StackFrameFormatter 50 sampleTypesFilter func(string) bool 51 sampleTypes map[string]*tree.SampleTypeConfig 52 cache tree.LabelsCache 53 } 54 55 func (p *Parser) getDisplayName(defaultName string) string { 56 config, ok := p.sampleTypes[defaultName] 57 if !ok || config.DisplayName == "" { 58 return defaultName 59 } 60 return config.DisplayName 61 } 62 63 func (p *Parser) getAggregationType(name string, defaultval string) string { 64 config, ok := p.sampleTypes[name] 65 if !ok { 66 return defaultval 67 } 68 switch config.Aggregation { 69 case metadata.AverageAggregationType: 70 return string(profile.AvgAggType) 71 case metadata.SumAggregationType: 72 return string(profile.SumAggType) 73 default: 74 return defaultval 75 } 76 } 77 78 func (p *Parser) iterate(x *tree.Profile, fn func(vt *tree.ValueType, l tree.Labels, t *tree.Tree) (keep bool, err error)) error { 79 c := make(tree.LabelsCache) 80 p.readTrees(x, c, tree.NewFinder(x)) 81 for sampleType, entries := range c { 82 if t, ok := x.ResolveSampleType(sampleType); ok { 83 for h, e := range entries { 84 keep, err := fn(t, e.Labels, e.Tree) 85 if err != nil { 86 return err 87 } 88 if !keep { 89 c.Remove(sampleType, h) 90 } 91 } 92 } 93 } 94 p.cache = c 95 return nil 96 } 97 98 func (p *Parser) load(sampleType int64, labels tree.Labels) (*tree.Tree, bool) { 99 e, ok := p.cache.Get(sampleType, labels.Hash()) 100 if !ok { 101 return nil, false 102 } 103 return e.Tree, true 104 } 105 106 // readTrees generates trees from the profile populating c. 107 func (p *Parser) readTrees(x *tree.Profile, c tree.LabelsCache, f tree.Finder) { 108 // SampleType value indexes. 109 indexes := make([]int, 0, len(x.SampleType)) 110 // Corresponding type IDs used as the main cache keys. 111 types := make([]int64, 0, len(x.SampleType)) 112 for i, s := range x.SampleType { 113 if p.sampleTypesFilter != nil && p.sampleTypesFilter(x.StringTable[s.Type]) { 114 indexes = append(indexes, i) 115 types = append(types, s.Type) 116 } 117 } 118 if len(indexes) == 0 { 119 return 120 } 121 stack := make([][]byte, 0, 16) 122 for _, s := range x.Sample { 123 for i := len(s.LocationId) - 1; i >= 0; i-- { 124 // Resolve stack. 125 loc, ok := f.FindLocation(s.LocationId[i]) 126 if !ok { 127 continue 128 } 129 // Multiple line indicates this location has inlined functions, 130 // where the last entry represents the caller into which the 131 // preceding entries were inlined. 132 // 133 // E.g., if memcpy() is inlined into printf: 134 // line[0].function_name == "memcpy" 135 // line[1].function_name == "printf" 136 // 137 // Therefore iteration goes in reverse order. 138 for j := len(loc.Line) - 1; j >= 0; j-- { 139 fn, ok := f.FindFunction(loc.Line[j].FunctionId) 140 if !ok || x.StringTable[fn.Name] == "" { 141 continue 142 } 143 sf := p.stackFrameFormatter.format(x, fn, loc.Line[j]) 144 stack = append(stack, sf) 145 } 146 } 147 // Insert tree nodes. 148 for i, vi := range indexes { 149 v := uint64(s.Value[vi]) 150 if v == 0 { 151 continue 152 } 153 // If the sample has ProfileID label, it belongs to an exemplar. 154 if j := labelIndex(x, s.Label, segment.ProfileIDLabelName); j >= 0 { 155 // Regardless of whether we should skip exemplars or not, the value 156 // should be appended to the exemplar baseline profile (w/o ProfileID label). 157 c.GetOrCreateTree(types[i], tree.CutLabel(s.Label, j)).InsertStack(stack, v) 158 } 159 c.GetOrCreateTree(types[i], s.Label).InsertStack(stack, v) 160 } 161 stack = stack[:0] 162 } 163 } 164 165 func labelIndex(p *tree.Profile, labels tree.Labels, key string) int { 166 for i, label := range labels { 167 if n, ok := p.ResolveLabelName(label); ok && n == key { 168 return i 169 } 170 } 171 return -1 172 }