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  }