github.com/grafana/pyroscope@v1.18.0/pkg/model/stacktraces.go (about) 1 package model 2 3 import ( 4 "bytes" 5 "io" 6 "sync" 7 "unsafe" 8 9 ingestv1 "github.com/grafana/pyroscope/api/gen/proto/go/ingester/v1" 10 "github.com/grafana/pyroscope/pkg/og/util/varint" 11 "github.com/grafana/pyroscope/pkg/util/minheap" 12 ) 13 14 // TODO(kolesnikovae): Remove support for StacktracesMergeFormat_MERGE_FORMAT_STACKTRACES. 15 16 type StacktraceMerger struct { 17 mu sync.Mutex 18 s *StacktraceTree 19 r *functionsRewriter 20 } 21 22 // NewStackTraceMerger merges collections of StacktraceSamples. 23 // The result is a byte tree representation of the merged samples. 24 func NewStackTraceMerger() *StacktraceMerger { 25 return new(StacktraceMerger) 26 } 27 28 // MergeStackTraces adds the stack traces to the resulting tree. 29 // The call is thread-safe, but the resulting tree bytes should 30 // be only build after all the samples are merged. 31 // Note that the function may reuse the capacity of the names slice. 32 func (m *StacktraceMerger) MergeStackTraces(stacks []*ingestv1.StacktraceSample, names []string) { 33 m.mu.Lock() 34 if m.s == nil { 35 // Estimate resulting tree size: it's likely that the first 36 // batch is small, therefore we can safely assume the tree 37 // will grow (factor of 2 is quite conservative). 38 // 4 here is the branching factor: how many new nodes per 39 // a stack on average we expect. 40 m.s = NewStacktraceTree(len(stacks) * 4 * 2) 41 // We can use function IDs as is for the first batch. 42 m.r = newFunctionsRewriter(names) 43 for _, s := range stacks { 44 m.s.Insert(s.FunctionIds, s.Value) 45 } 46 m.mu.Unlock() 47 return 48 } 49 m.r.union(names) 50 for _, s := range stacks { 51 m.r.rewrite(s.FunctionIds) 52 m.s.Insert(s.FunctionIds, s.Value) 53 } 54 m.mu.Unlock() 55 } 56 57 func (m *StacktraceMerger) TreeBytes(maxNodes int64) []byte { 58 if m.s == nil || len(m.s.Nodes) == 0 { 59 return nil 60 } 61 // Reuse of the slice (or the whole StacktraceMerger) is possible, 62 // but it is unlikely that the performance will be impacted. 63 size := len(m.s.Nodes) 64 if mn := int(maxNodes); maxNodes > 0 && mn < size { 65 size = mn 66 } 67 buf := make([]byte, 0, size*estimateBytesPerNode) 68 b := bytes.NewBuffer(buf) 69 m.s.Bytes(b, maxNodes, m.r.names) 70 return b.Bytes() 71 } 72 73 func (m *StacktraceMerger) Size() int { 74 if m.s != nil { 75 return len(m.s.Nodes) 76 } 77 return 0 78 } 79 80 func newFunctionsRewriter(names []string) *functionsRewriter { 81 p := make(map[string]int, 2*len(names)) 82 for i, v := range names { 83 p[v] = i 84 } 85 return &functionsRewriter{ 86 positions: p, 87 names: names, 88 } 89 } 90 91 type functionsRewriter struct { 92 positions map[string]int 93 names []string 94 tmp []int32 95 } 96 97 func (r *functionsRewriter) union(names []string) { 98 if cap(r.tmp) > len(names) { 99 r.tmp = r.tmp[:len(names)] 100 } else { 101 r.tmp = make([]int32, len(names)) 102 } 103 for i, name := range names { 104 position, found := r.positions[name] 105 if !found { 106 position = len(r.names) 107 r.names = append(r.names, name) 108 r.positions[name] = position 109 } 110 r.tmp[i] = int32(position) 111 } 112 } 113 114 func (r *functionsRewriter) rewrite(stack []int32) { 115 for i := range stack { 116 stack[i] = r.tmp[stack[i]] 117 } 118 } 119 120 type StacktraceTree struct{ Nodes []StacktraceNode } 121 122 type StacktraceNode struct { 123 FirstChild int32 124 NextSibling int32 125 Parent int32 126 Location int32 127 Value int64 128 Total int64 129 } 130 131 func NewStacktraceTree(size int) *StacktraceTree { 132 if size < 1 { 133 size = 1 134 } 135 t := StacktraceTree{Nodes: make([]StacktraceNode, 1, size)} 136 t.Nodes[0] = StacktraceNode{ 137 FirstChild: sentinel, 138 NextSibling: sentinel, 139 } 140 return &t 141 } 142 143 func (t *StacktraceTree) Reset() { 144 if cap(t.Nodes) < 1 { 145 *t = *(NewStacktraceTree(0)) 146 return 147 } 148 t.Nodes = t.Nodes[:1] 149 t.Nodes[0] = StacktraceNode{ 150 FirstChild: sentinel, 151 NextSibling: sentinel, 152 } 153 } 154 155 const sentinel = -1 156 157 func (t *StacktraceTree) Insert(locations []int32, value int64) int32 { 158 var ( 159 n = &t.Nodes[0] 160 next = n.FirstChild 161 cur int32 162 ) 163 164 for j := len(locations) - 1; j >= 0; { 165 r := locations[j] 166 if next == sentinel { 167 ni := int32(len(t.Nodes)) 168 n.FirstChild = ni 169 t.Nodes = append(t.Nodes, StacktraceNode{ 170 Parent: cur, 171 FirstChild: sentinel, 172 NextSibling: sentinel, 173 Location: r, 174 }) 175 cur = ni 176 n = &t.Nodes[ni] 177 } else { 178 cur = next 179 n = &t.Nodes[next] 180 } 181 if n.Location == r { 182 n.Total += value 183 next = n.FirstChild 184 j-- 185 continue 186 } 187 if n.NextSibling < 0 { 188 n.NextSibling = int32(len(t.Nodes)) 189 t.Nodes = append(t.Nodes, StacktraceNode{ 190 Parent: n.Parent, 191 FirstChild: sentinel, 192 NextSibling: sentinel, 193 Location: r, 194 }) 195 } 196 next = n.NextSibling 197 } 198 199 t.Nodes[cur].Value += value 200 return cur 201 } 202 203 func (t *StacktraceTree) LookupLocations(dst []uint64, idx int32) []uint64 { 204 dst = dst[:0] 205 if idx >= int32(len(t.Nodes)) { 206 return dst 207 } 208 for i := idx; i > 0; i = t.Nodes[i].Parent { 209 dst = append(dst, uint64(t.Nodes[i].Location)) 210 } 211 return dst 212 } 213 214 // MinValue returns the minimum "total" value a node in a tree has to have. 215 func (t *StacktraceTree) MinValue(maxNodes int64) int64 { 216 if maxNodes < 1 || maxNodes >= int64(len(t.Nodes)) { 217 return 0 218 } 219 h := make([]int64, 0, maxNodes) 220 for _, n := range t.Nodes { 221 if len(h) >= int(maxNodes) { 222 if n.Total > h[0] { 223 h = minheap.Pop(h) 224 } else { 225 continue 226 } 227 } 228 h = minheap.Push(h, n.Total) 229 } 230 if len(h) < int(maxNodes) { 231 return 0 232 } 233 return h[0] 234 } 235 236 type StacktraceTreeTraverseFn = func(index int32, children []int32) error 237 238 func (t *StacktraceTree) Traverse(maxNodes int64, fn StacktraceTreeTraverseFn) error { 239 minValue := t.MinValue(maxNodes) 240 children := make([]int32, 0, 128) // Children per node. 241 nodesSize := maxNodes // Depth search buffer. 242 if nodesSize < 1 || nodesSize > 10<<10 { 243 nodesSize = 1 << 10 // Sane default. 244 } 245 nodes := make([]int32, 1, nodesSize) 246 var current int32 247 for len(nodes) > 0 { 248 current, nodes, children = nodes[len(nodes)-1], nodes[:len(nodes)-1], children[:0] 249 var truncated int64 250 n := &t.Nodes[current] 251 if n.Location == sentinel { 252 goto call 253 } 254 255 for x := n.FirstChild; x > 0; { 256 child := &t.Nodes[x] 257 if child.Total >= minValue && child.Location != sentinel { 258 children = append(children, x) 259 } else { 260 truncated += child.Total 261 } 262 x = child.NextSibling 263 } 264 265 if truncated > 0 { 266 // Create a stub for removed nodes. 267 i := len(t.Nodes) 268 t.Nodes = append(t.Nodes, StacktraceNode{ 269 Location: sentinel, 270 Value: truncated, 271 }) 272 children = append(children, int32(i)) 273 } 274 275 if len(children) > 0 { 276 nodes = append(nodes, children...) 277 } 278 279 call: 280 if err := fn(current, children); err != nil { 281 return err 282 } 283 } 284 285 return nil 286 } 287 288 func (t *StacktraceTree) Bytes(dst io.Writer, maxNodes int64, funcs []string) { 289 if len(t.Nodes) == 0 || len(funcs) == 0 { 290 return 291 } 292 vw := varint.NewWriter() 293 _ = t.Traverse(maxNodes, func(index int32, children []int32) error { 294 n := t.Nodes[index] 295 var name []byte 296 switch n.Location { 297 default: 298 // It is guaranteed that funcs slice and its contents are immutable, 299 // and the byte slice backing capacity is managed by GC. 300 name = unsafeStringBytes(funcs[n.Location]) 301 case sentinel: 302 name = truncatedNodeNameBytes 303 } 304 _, _ = vw.Write(dst, uint64(len(name))) 305 _, _ = dst.Write(name) 306 _, _ = vw.Write(dst, uint64(n.Value)) 307 _, _ = vw.Write(dst, uint64(len(children))) 308 return nil 309 }) 310 } 311 312 func unsafeStringBytes(s string) []byte { 313 return unsafe.Slice(unsafe.StringData(s), len(s)) 314 } 315 316 func (t *StacktraceTree) Tree(maxNodes int64, names []string) *Tree { 317 if len(t.Nodes) < 2 || len(names) == 0 { 318 // stack trace tree has root at 0: trees with less 319 // than 2 nodes are considered empty. 320 return new(Tree) 321 } 322 323 nodesSize := maxNodes 324 if nodesSize < 1 || nodesSize > 10<<10 { 325 nodesSize = 1 << 10 // Sane default. 326 } 327 root := new(node) // Virtual root node. 328 nodes := make([]*node, 1, nodesSize) 329 nodes[0] = root 330 var current *node 331 332 _ = t.Traverse(maxNodes, func(index int32, children []int32) error { 333 current, nodes = nodes[len(nodes)-1], nodes[:len(nodes)-1] 334 sn := &t.Nodes[index] 335 var name string 336 if sn.Location < 0 { 337 name = truncatedNodeName 338 sn.Total = sn.Value 339 } else { 340 name = names[sn.Location] 341 } 342 n := current.insert(name) 343 n.self = sn.Value 344 n.total = sn.Total 345 n.children = make([]*node, 0, len(children)) 346 for i := 0; i < len(children); i++ { 347 nodes = append(nodes, n) 348 } 349 return nil 350 }) 351 352 // Roots should not have parents. 353 s := root.children[0].children 354 for _, n := range s { 355 n.parent.parent = nil 356 } 357 358 return &Tree{root: s} 359 }