github.com/grafana/pyroscope@v1.18.0/pkg/phlaredb/symdb/stacktrace_selection.go (about)

     1  package symdb
     2  
     3  import (
     4  	"github.com/parquet-go/parquet-go"
     5  
     6  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
     7  	schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1"
     8  )
     9  
    10  // CallSiteValues represents statistics associated with a call tree node.
    11  type CallSiteValues struct {
    12  	// Flat is the sum of sample values directly attributed to the node.
    13  	Flat uint64
    14  	// Total is the total sum of sample values attributed to the node and
    15  	// its descendants.
    16  	Total uint64
    17  	// LocationFlat is the sum of sample values directly attributed to the
    18  	// node location, irrespectively of the call chain.
    19  	LocationFlat uint64
    20  	// LocationTotal is the total sum of sample values attributed to the
    21  	// node location and its descendants, irrespectively of the call chain.
    22  	LocationTotal uint64
    23  }
    24  
    25  // stackTraceLocationRelation represents the relation between a stack trace
    26  // and a location, according to the stack trace selector parameters.
    27  type stackTraceLocationRelation uint8
    28  
    29  const (
    30  	// relationSubtree indicates whether the stack trace belongs
    31  	// to the callSite specified by the stack trace selector.
    32  	relationSubtree stackTraceLocationRelation = 1 << iota
    33  	// relationLeaf specifies that the stack trace leaf is the
    34  	// location specified by the stack trace selector,
    35  	// irrespectively of the call chain.
    36  	relationLeaf
    37  	// relationNode specifies that the stack trace includes
    38  	// the location specified by the stack trace selector,
    39  	// irrespectively of the call chain.
    40  	relationNode
    41  )
    42  
    43  type SelectedStackTraces struct {
    44  	symbols *Symbols
    45  	// Go PGO filter.
    46  	gopgo *typesv1.GoPGO
    47  	// Call site filter
    48  	relations        map[uint32]stackTraceLocationRelation
    49  	callSiteSelector []*typesv1.Location
    50  	callSite         []string // call site strings in the original order.
    51  	location         string   // stack trace leaf function.
    52  	depth            uint32
    53  	buf              []uint64
    54  	// Function ID => name. The lookup table is used to
    55  	// avoid unnecessary indirect accesses through the
    56  	// strings[functions[id].Name] path. Instead, the
    57  	// name can be resolved directly funcNames[id].
    58  	funcNames []string
    59  }
    60  
    61  func SelectStackTraces(symbols *Symbols, selector *typesv1.StackTraceSelector) *SelectedStackTraces {
    62  	x := &SelectedStackTraces{
    63  		symbols:          symbols,
    64  		callSiteSelector: selector.GetCallSite(),
    65  		gopgo:            selector.GetGoPgo(),
    66  	}
    67  	x.callSite = callSiteFunctions(x.callSiteSelector)
    68  	if x.depth = uint32(len(x.callSite)); x.depth > 0 {
    69  		x.location = x.callSite[x.depth-1]
    70  	}
    71  	x.funcNames = make([]string, len(symbols.Functions))
    72  	for i, f := range symbols.Functions {
    73  		x.funcNames[i] = symbols.Strings[f.Name]
    74  	}
    75  	return x
    76  }
    77  
    78  // HasValidCallSite reports whether any stack traces match the selector.
    79  // An empty selector results in a valid empty selection.
    80  func (x *SelectedStackTraces) HasValidCallSite() bool {
    81  	return len(x.callSiteSelector) == 0 || len(x.callSiteSelector) != 0 && len(x.callSite) != 0
    82  }
    83  
    84  // CallSiteValues writes the call site statistics for
    85  // the selected stack traces and the given set of samples.
    86  func (x *SelectedStackTraces) CallSiteValues(values *CallSiteValues, samples schemav1.Samples) {
    87  	*values = CallSiteValues{}
    88  	if x.depth == 0 {
    89  		return
    90  	}
    91  	if x.relations == nil {
    92  		// relations will grow to the size of the number of stack traces:
    93  		// if a stack trace does not belong to the selection, we still
    94  		// have to memoize the negative result.
    95  		x.relations = make(map[uint32]stackTraceLocationRelation, len(x.symbols.Locations))
    96  	}
    97  	for i, sid := range samples.StacktraceIDs {
    98  		v := samples.Values[i]
    99  		r, ok := x.relations[sid]
   100  		if !ok {
   101  			x.buf = x.symbols.Stacktraces.LookupLocations(x.buf, sid)
   102  			r = x.appendStackTrace(x.buf)
   103  			x.relations[sid] = r
   104  		}
   105  		x.write(values, v, r)
   106  	}
   107  }
   108  
   109  // CallSiteValuesParquet is identical to CallSiteValues
   110  // but accepts raw parquet values instead of samples.
   111  func (x *SelectedStackTraces) CallSiteValuesParquet(values *CallSiteValues, stacktraceID, value []parquet.Value) {
   112  	*values = CallSiteValues{}
   113  	if x.depth == 0 {
   114  		return
   115  	}
   116  	if x.relations == nil {
   117  		// relations will grow to the size of the number of stack traces:
   118  		// if a stack trace does not belong to the selection, we still
   119  		// have to memoize the negative result.
   120  		x.relations = make(map[uint32]stackTraceLocationRelation, len(x.symbols.Locations))
   121  	}
   122  	for i, pv := range stacktraceID {
   123  		sid := pv.Uint32()
   124  		v := value[i].Uint64()
   125  		r, ok := x.relations[sid]
   126  		if !ok {
   127  			x.buf = x.symbols.Stacktraces.LookupLocations(x.buf, sid)
   128  			r = x.appendStackTrace(x.buf)
   129  			x.relations[sid] = r
   130  		}
   131  		x.write(values, v, r)
   132  	}
   133  }
   134  
   135  func (x *SelectedStackTraces) write(m *CallSiteValues, v uint64, r stackTraceLocationRelation) {
   136  	s := uint64(r & relationSubtree)
   137  	l := uint64(r&relationLeaf) >> 1
   138  	n := uint64(r&relationNode) >> 2
   139  	m.LocationTotal += v * (n | l)
   140  	m.LocationFlat += v * l
   141  	m.Total += v * s
   142  	m.Flat += v * s * l
   143  }
   144  
   145  func (x *SelectedStackTraces) appendStackTrace(locations []uint64) stackTraceLocationRelation {
   146  	if len(locations) == 0 {
   147  		return 0
   148  	}
   149  	var n uint32 // Number of times callSite root function seen.
   150  	var pos uint32
   151  	var l uint32
   152  	for i := len(locations) - 1; i >= 0; i-- {
   153  		lines := x.symbols.Locations[locations[i]].Line
   154  		for j := len(lines) - 1; j >= 0; j-- {
   155  			f := lines[j].FunctionId
   156  			if x.location == x.funcNames[f] {
   157  				n++
   158  			}
   159  			if pos < x.depth && pos == l && x.callSite[pos] == x.funcNames[f] {
   160  				pos++
   161  			}
   162  			l++
   163  		}
   164  	}
   165  	if n == 0 {
   166  		return 0
   167  	}
   168  	var isLeaf uint32
   169  	leaf := x.symbols.Locations[locations[0]].Line[0]
   170  	if x.location == x.funcNames[leaf.FunctionId] {
   171  		isLeaf = 1
   172  	}
   173  	var inSubtree uint32
   174  	if pos >= x.depth {
   175  		inSubtree = 1
   176  	}
   177  	return stackTraceLocationRelation(inSubtree | isLeaf<<1 | (1-isLeaf)<<2)
   178  }
   179  
   180  func callSiteFunctions(locations []*typesv1.Location) []string {
   181  	callSite := make([]string, len(locations))
   182  	for i, loc := range locations {
   183  		callSite[i] = loc.Name
   184  	}
   185  	return callSite
   186  }