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  }