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  }