github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/tree/profile_finder.go (about)

     1  package tree
     2  
     3  import (
     4  	"sort"
     5  )
     6  
     7  // NewFinder creates an efficient finder for functions or locations in a profile.
     8  //
     9  // It exists to abstract the details of how functions and locations exist in a pprof profile,
    10  // and make it easy to provide an efficient implementation depending on the actual profile format,
    11  // as location and function finding is a recurrent operation while processing pprof profiles.
    12  //
    13  // The [pprof format description](https://github.com/google/pprof/tree/master/proto#general-structure-of-a-profile)
    14  // describes that both locations and functions have unique nonzero ids.
    15  // A [comment in the proto file](https://github.com/google/pprof/blob/master/proto/profile.proto#L164-L166)
    16  // goes further: _A profile could use instruction addresses or any integer sequence as ids_.
    17  //
    18  // Based on this, any uint64 value (except 0) can appear as ids, and a map based cache can be used in that case.
    19  // In practice, [go runtime](https://github.com/golang/go/blob/master/src/runtime/pprof/proto.go#L537)
    20  // generates profiles where locations and functions use consecutive IDs starting from 1,
    21  // making optimized access possible.
    22  //
    23  // Taking advantage of this, the finder will try to:
    24  // - Use direct access to functions and locations indexed by IDs when possible
    25  //   (sorting location and function sequences if needed).
    26  // - Use a map based cache otherwise.
    27  func NewFinder(p *Profile) Finder {
    28  	return &finder{p: p, lf: nil, ff: nil}
    29  }
    30  
    31  type Finder interface {
    32  	FunctionFinder
    33  	LocationFinder
    34  }
    35  
    36  // Find location in a profile based on its ID
    37  type LocationFinder interface {
    38  	FindLocation(id uint64) (*Location, bool)
    39  }
    40  
    41  // Find function in a profile based on its ID
    42  type FunctionFinder interface {
    43  	FindFunction(id uint64) (*Function, bool)
    44  }
    45  
    46  type sliceLocationFinder []*Location
    47  
    48  func (f sliceLocationFinder) FindLocation(id uint64) (*Location, bool) {
    49  	if id == 0 || id > uint64(len(f)) {
    50  		return nil, false
    51  	}
    52  	return f[id-1], true
    53  }
    54  
    55  type mapLocationFinder map[uint64]*Location
    56  
    57  func (f mapLocationFinder) FindLocation(id uint64) (*Location, bool) {
    58  	loc, ok := f[id]
    59  	return loc, ok
    60  }
    61  
    62  type sliceFunctionFinder []*Function
    63  
    64  func (f sliceFunctionFinder) FindFunction(id uint64) (*Function, bool) {
    65  	if id == 0 || id > uint64(len(f)) {
    66  		return nil, false
    67  	}
    68  	return f[id-1], true
    69  }
    70  
    71  type mapFunctionFinder map[uint64]*Function
    72  
    73  func (f mapFunctionFinder) FindFunction(id uint64) (*Function, bool) {
    74  	fun, ok := f[id]
    75  	return fun, ok
    76  }
    77  
    78  // finder is a lazy implementation of Finder, that will be using the most efficient function and location finder.
    79  type finder struct {
    80  	p  *Profile
    81  	lf LocationFinder
    82  	ff FunctionFinder
    83  }
    84  
    85  func (f *finder) FindLocation(id uint64) (*Location, bool) {
    86  	if f.lf == nil {
    87  		var ok bool
    88  		f.lf, ok = locationSlice(f.p)
    89  		if !ok {
    90  			f.lf = locationMap(f.p)
    91  		}
    92  	}
    93  	return f.lf.FindLocation(id)
    94  }
    95  
    96  func (f *finder) FindFunction(id uint64) (*Function, bool) {
    97  	if f.ff == nil {
    98  		var ok bool
    99  		f.ff, ok = functionSlice(f.p)
   100  		if !ok {
   101  			f.ff = functionMap(f.p)
   102  		}
   103  	}
   104  	return f.ff.FindFunction(id)
   105  }
   106  
   107  func locationSlice(p *Profile) (sliceLocationFinder, bool) {
   108  	// Check if it's already sorted first
   109  	max := uint64(0)
   110  	sorted := true
   111  	for i, l := range p.Location {
   112  		if l.Id != uint64(i+1) {
   113  			sorted = false
   114  			if l.Id > max {
   115  				max = l.Id
   116  			}
   117  		}
   118  	}
   119  	if max > uint64(len(p.Location)) {
   120  		// IDs are not consecutive numbers starting at 1, a slice is not good enough
   121  		return nil, false
   122  	}
   123  	if !sorted {
   124  		sort.Slice(p.Location, func(i, j int) bool {
   125  			return p.Location[i].Id < p.Location[j].Id
   126  		})
   127  	}
   128  	return sliceLocationFinder(p.Location), true
   129  }
   130  
   131  func locationMap(p *Profile) mapLocationFinder {
   132  	m := make(map[uint64]*Location, len(p.Location))
   133  	for _, l := range p.Location {
   134  		m[l.Id] = l
   135  	}
   136  	return mapLocationFinder(m)
   137  }
   138  
   139  func functionSlice(p *Profile) (sliceFunctionFinder, bool) {
   140  	// Check if it's already sorted first
   141  	max := uint64(0)
   142  	sorted := true
   143  	for i, f := range p.Function {
   144  		if f.Id != uint64(i+1) {
   145  			sorted = false
   146  			if f.Id > max {
   147  				max = f.Id
   148  			}
   149  		}
   150  	}
   151  	if max > uint64(len(p.Function)) {
   152  		// IDs are not consecutive numbers starting at one, this won't work
   153  		return nil, false
   154  	}
   155  	if !sorted {
   156  		sort.Slice(p.Function, func(i, j int) bool {
   157  			return p.Function[i].Id < p.Function[j].Id
   158  		})
   159  	}
   160  	return sliceFunctionFinder(p.Function), true
   161  }
   162  
   163  func functionMap(p *Profile) mapFunctionFinder {
   164  	m := make(map[uint64]*Function, len(p.Function))
   165  	for _, f := range p.Function {
   166  		m[f.Id] = f
   167  	}
   168  	return m
   169  }