github.com/grafana/pyroscope@v1.18.0/pkg/model/pprofsplit/pprof_split.go (about) 1 package pprofsplit 2 3 import ( 4 "unsafe" 5 6 profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" 7 typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" 8 phlaremodel "github.com/grafana/pyroscope/pkg/model" 9 "github.com/grafana/pyroscope/pkg/model/relabel" 10 "github.com/grafana/pyroscope/pkg/pprof" 11 ) 12 13 type SampleSeriesVisitor interface { 14 // VisitProfile is called when no sample labels are present in 15 // the profile, or if all the sample labels are identical. 16 // Provided labels are the series labels processed with relabeling rules. 17 VisitProfile(phlaremodel.Labels) 18 VisitSampleSeries(phlaremodel.Labels, []*profilev1.Sample) 19 // ValidateLabels is called to validate the labels before 20 // they are passed to the visitor. Returns the potentially modified 21 // labels slice (e.g., after sanitization) and any validation error. 22 ValidateLabels(phlaremodel.Labels) (phlaremodel.Labels, error) 23 Discarded(profiles, bytes int) 24 } 25 26 func VisitSampleSeries( 27 profile *profilev1.Profile, 28 labels []*typesv1.LabelPair, 29 rules []*relabel.Config, 30 visitor SampleSeriesVisitor, 31 ) error { 32 var profilesDiscarded, bytesDiscarded int 33 defer func() { 34 visitor.Discarded(profilesDiscarded, bytesDiscarded) 35 }() 36 37 pprof.RenameLabel(profile, pprof.ProfileIDLabelName, pprof.SpanIDLabelName) 38 groups := pprof.GroupSamplesWithoutLabels(profile, pprof.SpanIDLabelName) 39 builder := phlaremodel.NewLabelsBuilder(nil) 40 41 if len(groups) == 0 || (len(groups) == 1 && len(groups[0].Labels) == 0) { 42 // No sample labels in the profile. 43 // Relabel the series labels. 44 builder.Reset(labels) 45 if len(rules) > 0 { 46 keep := relabel.ProcessBuilder(builder, rules...) 47 if !keep { 48 // We drop the profile. 49 profilesDiscarded++ 50 bytesDiscarded += profile.SizeVT() 51 return nil 52 } 53 } 54 if len(profile.Sample) > 0 { 55 labels = builder.Labels() 56 var err error 57 labels, err = visitor.ValidateLabels(labels) 58 if err != nil { 59 return err 60 } 61 visitor.VisitProfile(labels) 62 } 63 return nil 64 } 65 66 // iterate through groups relabel them and find relevant overlapping label sets. 67 groupsKept := newGroupsWithFingerprints() 68 for _, group := range groups { 69 builder.Reset(labels) 70 addSampleLabelsToLabelsBuilder(builder, profile, group.Labels) 71 if len(rules) > 0 { 72 keep := relabel.ProcessBuilder(builder, rules...) 73 if !keep { 74 bytesDiscarded += sampleSize(group.Samples) 75 continue 76 } 77 } 78 // add the group to the list. 79 groupsKept.add(profile.StringTable, builder.Labels(), group) 80 } 81 82 if len(groupsKept.m) == 0 { 83 // no groups kept, count the whole profile as dropped 84 profilesDiscarded++ 85 return nil 86 } 87 88 for _, idx := range groupsKept.order { 89 for _, group := range groupsKept.m[idx] { 90 if len(group.sampleGroup.Samples) > 0 { 91 var err error 92 group.labels, err = visitor.ValidateLabels(group.labels) 93 if err != nil { 94 return err 95 } 96 visitor.VisitSampleSeries(group.labels, group.sampleGroup.Samples) 97 } 98 } 99 } 100 101 return nil 102 } 103 104 // addSampleLabelsToLabelsBuilder: adds sample label that don't exists yet on the profile builder. So the existing labels take precedence. 105 func addSampleLabelsToLabelsBuilder(b *phlaremodel.LabelsBuilder, p *profilev1.Profile, pl []*profilev1.Label) { 106 var name string 107 for _, l := range pl { 108 name = p.StringTable[l.Key] 109 if l.Str <= 0 { 110 // skip if label value is not a string 111 continue 112 } 113 if b.Get(name) != "" { 114 // do nothing if label name already exists 115 continue 116 } 117 b.Set(name, p.StringTable[l.Str]) 118 } 119 } 120 121 type sampleKey struct { 122 stacktrace string 123 // note this is an index into the string table, rather than span ID 124 spanIDIdx int64 125 } 126 127 func sampleKeyFromSample(stringTable []string, s *profilev1.Sample) sampleKey { 128 var k sampleKey 129 // populate spanID if present 130 for _, l := range s.Label { 131 if stringTable[int(l.Key)] == pprof.SpanIDLabelName { 132 k.spanIDIdx = l.Str 133 } 134 } 135 if len(s.LocationId) > 0 { 136 k.stacktrace = unsafe.String( 137 (*byte)(unsafe.Pointer(&s.LocationId[0])), 138 len(s.LocationId)*8, 139 ) 140 } 141 return k 142 } 143 144 type lazyGroup struct { 145 sampleGroup pprof.SampleGroup 146 // The map is only initialized when the group is being modified. Key is the 147 // string representation (unsafe) of the sample stack trace and its potential 148 // span ID. 149 sampleMap map[sampleKey]*profilev1.Sample 150 labels phlaremodel.Labels 151 } 152 153 func (g *lazyGroup) addSampleGroup(stringTable []string, sg pprof.SampleGroup) { 154 if len(g.sampleGroup.Samples) == 0 { 155 g.sampleGroup.Labels = sg.Labels 156 g.sampleGroup.Samples = append(g.sampleGroup.Samples, sg.Samples...) 157 return 158 } 159 160 // If the group is already initialized, we need to merge the samples. 161 if g.sampleMap == nil { 162 g.sampleMap = make(map[sampleKey]*profilev1.Sample) 163 for _, s := range g.sampleGroup.Samples { 164 g.sampleMap[sampleKeyFromSample(stringTable, s)] = s 165 } 166 } 167 168 for _, s := range sg.Samples { 169 k := sampleKeyFromSample(stringTable, s) 170 if _, ok := g.sampleMap[k]; !ok { 171 g.sampleGroup.Samples = append(g.sampleGroup.Samples, s) 172 g.sampleMap[k] = s 173 } else { 174 // merge the samples 175 for idx := range s.Value { 176 g.sampleMap[k].Value[idx] += s.Value[idx] 177 } 178 } 179 } 180 } 181 182 type groupsWithFingerprints struct { 183 m map[uint64][]*lazyGroup 184 order []uint64 185 } 186 187 func newGroupsWithFingerprints() *groupsWithFingerprints { 188 return &groupsWithFingerprints{ 189 m: make(map[uint64][]*lazyGroup), 190 } 191 } 192 193 func (g *groupsWithFingerprints) add(stringTable []string, lbls phlaremodel.Labels, group pprof.SampleGroup) { 194 fp := lbls.Hash() 195 idxs, ok := g.m[fp] 196 if ok { 197 // fingerprint matches, check if the labels are the same 198 for _, idx := range idxs { 199 if phlaremodel.CompareLabelPairs(idx.labels, lbls) == 0 { 200 // append samples to the group 201 idx.addSampleGroup(stringTable, group) 202 return 203 } 204 } 205 } else { 206 g.order = append(g.order, fp) 207 } 208 209 // add the labels to the list 210 g.m[fp] = append(g.m[fp], &lazyGroup{ 211 sampleGroup: pprof.SampleGroup{ 212 Labels: group.Labels, 213 // defensive copy, we don't want to modify the original slice 214 Samples: append([]*profilev1.Sample(nil), group.Samples...), 215 }, 216 labels: lbls, 217 }) 218 } 219 220 // sampleSize returns the size of a samples in bytes. 221 func sampleSize(samples []*profilev1.Sample) int { 222 var size int 223 for _, s := range samples { 224 size += s.SizeVT() 225 } 226 return size 227 }