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  }