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 }