github.com/grafana/pyroscope@v1.18.0/pkg/model/flamegraph_diff.go (about) 1 package model 2 3 import ( 4 "bytes" 5 "fmt" 6 7 "github.com/grafana/pyroscope/pkg/util/minheap" 8 9 querierv1 "github.com/grafana/pyroscope/api/gen/proto/go/querier/v1" 10 ) 11 12 // NewFlamegraphDiff generates a FlameGraphDiff from 2 trees. 13 // It also prunes the final tree based on the maxNodes parameter 14 // Notice that the resulting FlameGraph can't be used interchangeably with a 'single' Flamegraph 15 // Due to many differences: 16 // * Nodes 17 // * It's structure is different 18 // 19 // i+0 = x offset, left tree 20 // i+1 = total , left tree 21 // i+2 = self , left tree 22 // i+3 = x offset, right tree 23 // i+4 = total , right tree 24 // i+5 = self , right tree 25 // i+6 = index in the names array 26 func NewFlamegraphDiff(left, right *Tree, maxNodes int64) (*querierv1.FlameGraphDiff, error) { 27 // The algorithm doesn't work properly with negative nodes 28 // Although it's possible to silently drop these nodes 29 // Let's fail early and analyze properly with real data when the issue happens 30 err := assertPositiveTrees(left, right) 31 if err != nil { 32 return nil, err 33 } 34 leftTree, rightTree := combineTree(left, right) 35 36 totalLeft := leftTree.root[0].total 37 totalRight := rightTree.root[0].total 38 39 res := &querierv1.FlameGraphDiff{ 40 Names: []string{}, 41 Levels: []*querierv1.Level{}, 42 Total: totalLeft + totalRight, 43 MaxSelf: 0, 44 LeftTicks: totalLeft, 45 RightTicks: totalRight, 46 } 47 48 leftNodes, xLeftOffsets := leftTree.root, []int64{0} 49 rghtNodes, xRghtOffsets := rightTree.root, []int64{0} 50 levels := []int{0} 51 var minVal int64 52 if maxNodes > 0 { 53 minVal = int64(combineMinValues(leftTree, rightTree, int(maxNodes))) 54 } 55 nameLocationCache := map[string]int{} 56 57 for len(leftNodes) > 0 { 58 left, rght := leftNodes[0], rghtNodes[0] 59 leftNodes, rghtNodes = leftNodes[1:], rghtNodes[1:] 60 xLeftOffset, xRghtOffset := xLeftOffsets[0], xRghtOffsets[0] 61 xLeftOffsets, xRghtOffsets = xLeftOffsets[1:], xRghtOffsets[1:] 62 63 level := levels[0] 64 levels = levels[1:] 65 66 name := left.name 67 if left.total >= minVal || rght.total >= minVal || name == "other" { 68 i, ok := nameLocationCache[name] 69 if !ok { 70 i = len(res.Names) 71 nameLocationCache[name] = i 72 if i == 0 { 73 name = "total" 74 } 75 76 res.Names = append(res.Names, name) 77 } 78 79 if level == len(res.Levels) { 80 res.Levels = append(res.Levels, &querierv1.Level{}) 81 } 82 res.MaxSelf = max(res.MaxSelf, left.self) 83 res.MaxSelf = max(res.MaxSelf, rght.self) 84 85 // i+0 = x offset, left tree 86 // i+1 = total , left tree 87 // i+2 = self , left tree 88 // i+3 = x offset, right tree 89 // i+4 = total , right tree 90 // i+5 = self , right tree 91 // i+6 = index in the names array 92 values := []int64{ 93 xLeftOffset, left.total, left.self, 94 xRghtOffset, rght.total, rght.self, 95 int64(i), 96 } 97 98 // We need to prepend values here, but this is expensive. We'll reverse the order later. 99 res.Levels[level].Values = append(res.Levels[level].Values, values...) 100 xLeftOffset += left.self 101 xRghtOffset += rght.self 102 otherLeftTotal, otherRghtTotal := int64(0), int64(0) 103 104 // both left and right must have the same number of children nodes 105 for ni := range left.children { 106 leftNode, rghtNode := left.children[ni], rght.children[ni] 107 if leftNode.total >= minVal || rghtNode.total >= minVal { 108 levels = prependInt(levels, level+1) 109 xLeftOffsets = prependInt64(xLeftOffsets, xLeftOffset) 110 xRghtOffsets = prependInt64(xRghtOffsets, xRghtOffset) 111 leftNodes = prependTreeNode(leftNodes, leftNode) 112 rghtNodes = prependTreeNode(rghtNodes, rghtNode) 113 xLeftOffset += leftNode.total 114 xRghtOffset += rghtNode.total 115 } else { 116 otherLeftTotal += leftNode.total 117 otherRghtTotal += rghtNode.total 118 } 119 } 120 if otherLeftTotal != 0 || otherRghtTotal != 0 { 121 levels = prependInt(levels, level+1) 122 { 123 leftNode := &node{ 124 name: "other", 125 total: otherLeftTotal, 126 self: otherLeftTotal, 127 } 128 xLeftOffsets = prependInt64(xLeftOffsets, xLeftOffset) 129 leftNodes = prependTreeNode(leftNodes, leftNode) 130 } 131 { 132 rghtNode := &node{ 133 name: "other", 134 total: otherRghtTotal, 135 self: otherRghtTotal, 136 } 137 xRghtOffsets = prependInt64(xRghtOffsets, xRghtOffset) 138 rghtNodes = prependTreeNode(rghtNodes, rghtNode) 139 } 140 } 141 } 142 } 143 144 // reverse each level's values since we appended values instead of prepending them 145 for _, level := range res.Levels { 146 reverseSliceInChunks(level.Values, 7) 147 } 148 149 deltaEncoding(res.Levels, 0, 7) 150 deltaEncoding(res.Levels, 3, 7) 151 152 return res, nil 153 } 154 155 func NewFlamegraphDiffFromBytes(left, right []byte, maxNodes int64) (*querierv1.FlameGraphDiff, error) { 156 l, err := UnmarshalTree(left) 157 if err != nil { 158 return nil, err 159 } 160 r, err := UnmarshalTree(right) 161 if err != nil { 162 return nil, err 163 } 164 return NewFlamegraphDiff(l, r, maxNodes) 165 } 166 167 // combineTree aligns 2 trees by making them having the same structure with the 168 // same number of nodes 169 // It also makes the tree have a single root 170 func combineTree(leftTree, rightTree *Tree) (*Tree, *Tree) { 171 leftTotal := int64(0) 172 for _, l := range leftTree.root { 173 leftTotal = leftTotal + l.total 174 } 175 176 rightTotal := int64(0) 177 for _, l := range rightTree.root { 178 rightTotal = rightTotal + l.total 179 } 180 181 // differently from pyroscope, there could be multiple roots 182 // so we add a fake root as expected 183 leftTree = &Tree{ 184 root: []*node{{ 185 children: leftTree.root, 186 total: leftTotal, 187 self: 0, 188 }}, 189 } 190 191 rightTree = &Tree{ 192 root: []*node{{ 193 children: rightTree.root, 194 total: rightTotal, 195 self: 0, 196 }}, 197 } 198 199 leftNodes := leftTree.root 200 rghtNodes := rightTree.root 201 202 for len(leftNodes) > 0 { 203 left, rght := leftNodes[0], rghtNodes[0] 204 205 leftNodes, rghtNodes = leftNodes[1:], rghtNodes[1:] 206 207 newLeftChildren, newRightChildren := combineNodes(left.children, rght.children) 208 209 left.children, rght.children = newLeftChildren, newRightChildren 210 leftNodes = append(leftNodes, left.children...) 211 rghtNodes = append(rghtNodes, rght.children...) 212 } 213 return leftTree, rightTree 214 } 215 216 // combineNodes makes 2 slices of nodes equal 217 // by filling with non existing nodes 218 // and sorting lexicographically 219 func combineNodes(leftNodes, rghtNodes []*node) ([]*node, []*node) { 220 size := nextPow2(max(len(leftNodes), len(rghtNodes))) 221 leftResult := make([]*node, 0, size) 222 rghtResult := make([]*node, 0, size) 223 224 for len(leftNodes) != 0 && len(rghtNodes) != 0 { 225 left, rght := leftNodes[0], rghtNodes[0] 226 switch bytes.Compare([]byte(left.name), []byte(rght.name)) { 227 case 0: 228 leftResult = append(leftResult, left) 229 rghtResult = append(rghtResult, rght) 230 leftNodes, rghtNodes = leftNodes[1:], rghtNodes[1:] 231 case -1: 232 leftResult = append(leftResult, left) 233 rghtResult = append(rghtResult, &node{name: left.name}) 234 235 leftNodes = leftNodes[1:] 236 case 1: 237 leftResult = append(leftResult, &node{name: rght.name}) 238 rghtResult = append(rghtResult, rght) 239 rghtNodes = rghtNodes[1:] 240 } 241 } 242 leftResult = append(leftResult, leftNodes...) 243 rghtResult = append(rghtResult, rghtNodes...) 244 for _, left := range leftNodes { 245 rghtResult = append(rghtResult, &node{name: left.name}) 246 } 247 for _, rght := range rghtNodes { 248 leftResult = append(leftResult, &node{name: rght.name}) 249 } 250 return leftResult, rghtResult 251 } 252 253 func nextPow2(a int) int { 254 a-- 255 a |= a >> 1 256 a |= a >> 2 257 a |= a >> 4 258 a |= a >> 8 259 a |= a >> 16 260 a++ 261 return a 262 } 263 264 func combineMinValues(leftTree, rightTree *Tree, maxNodes int) uint64 { 265 if maxNodes < 1 { 266 return 0 267 } 268 treeSize := leftTree.size(make([]*node, 0, defaultDFSSize)) 269 if treeSize <= int64(maxNodes) { 270 return 0 271 } 272 273 h := make([]int64, 0, maxNodes) 274 combineIterateWithTotal(leftTree, rightTree, func(left uint64, right uint64) bool { 275 maxVal := int64(max(left, right)) 276 if len(h) >= maxNodes { 277 if maxVal > h[0] { 278 h = minheap.Pop(h) 279 h = minheap.Push(h, maxVal) 280 } 281 } else { 282 h = minheap.Push(h, maxVal) 283 } 284 return true 285 }) 286 287 if len(h) < maxNodes { 288 return 0 289 } 290 return uint64(h[0]) 291 } 292 293 // iterate both trees, both trees must be returned from combineTree 294 func combineIterateWithTotal(leftTree, rightTree *Tree, cb func(uint64, uint64) bool) { 295 leftNodes, rghtNodes := leftTree.root, rightTree.root 296 i := 0 297 for len(leftNodes) > 0 { 298 leftNode, rghtNode := leftNodes[0], rghtNodes[0] 299 leftNodes, rghtNodes = leftNodes[1:], rghtNodes[1:] 300 i++ 301 302 // TODO: dangerous conversion 303 if cb(uint64(leftNode.total), uint64(rghtNode.total)) { 304 leftNodes = append(leftNode.children, leftNodes...) 305 rghtNodes = append(rghtNode.children, rghtNodes...) 306 } 307 } 308 } 309 310 // isPositiveTree returns whether a tree only contain positive values 311 func isPositiveTree(t *Tree) bool { 312 stack := Stack[*node]{} 313 for _, node := range t.root { 314 stack.Push(node) 315 } 316 317 for { 318 current, hasMoreNodes := stack.Pop() 319 if !hasMoreNodes { 320 break 321 } 322 323 if current.self < 0 { 324 return false 325 } 326 327 for _, child := range current.children { 328 stack.Push(child) 329 } 330 } 331 332 return true 333 } 334 335 func assertPositiveTrees(left *Tree, right *Tree) error { 336 leftRes := isPositiveTree(left) 337 rightRes := isPositiveTree(right) 338 339 if !leftRes && !rightRes { 340 return fmt.Errorf("both trees require only positive values") 341 } 342 343 if !leftRes { 344 return fmt.Errorf("left tree require only positive values") 345 } 346 347 if !rightRes { 348 return fmt.Errorf("left tree require only positive values") 349 } 350 351 return nil 352 } 353 354 func deltaEncoding(levels []*querierv1.Level, start, step int) { 355 for _, l := range levels { 356 prev := int64(0) 357 for i := start; i < len(l.Values); i += step { 358 l.Values[i] -= prev 359 prev += l.Values[i] + l.Values[i+1] 360 } 361 } 362 } 363 364 func prependInt(s []int, x int) []int { 365 s = append(s, 0) 366 copy(s[1:], s) 367 s[0] = x 368 return s 369 } 370 371 func prependInt64(s []int64, x int64) []int64 { 372 s = append(s, 0) 373 copy(s[1:], s) 374 s[0] = x 375 return s 376 } 377 378 func prependTreeNode(s []*node, x *node) []*node { 379 s = append(s, nil) 380 copy(s[1:], s) 381 s[0] = x 382 return s 383 } 384 385 // reverseSliceInChunks reverses a slice in chunks of the given size 386 func reverseSliceInChunks(slice []int64, chunkSize int) { 387 if len(slice) < chunkSize { 388 return 389 } 390 391 for i, j := 0, len(slice)-chunkSize; i < j; i, j = i+chunkSize, j-chunkSize { 392 for k := 0; k < chunkSize; k++ { 393 slice[i+k], slice[j+k] = slice[j+k], slice[i+k] 394 } 395 } 396 }