github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/tree/flamebearer.go (about)

     1  package tree
     2  
     3  import "bytes"
     4  
     5  type Format string
     6  
     7  const (
     8  	FormatSingle Format = "single"
     9  	FormatDouble Format = "double"
    10  )
    11  
    12  type Flamebearer struct {
    13  	Names    []string `json:"names"`
    14  	Levels   [][]int  `json:"levels"`
    15  	NumTicks int      `json:"numTicks"`
    16  	MaxSelf  int      `json:"maxSelf"`
    17  	// TODO: see note in render.go
    18  	SpyName    string `json:"spyName"`
    19  	SampleRate uint32 `json:"sampleRate"`
    20  	Units      string `json:"units"`
    21  	Format     Format `json:"format"`
    22  }
    23  
    24  var lostDuringRenderingName = jsonableSlice("other")
    25  
    26  func (t *Tree) FlamebearerStruct(maxNodes int) *Flamebearer {
    27  	t.RLock()
    28  	defer t.RUnlock()
    29  
    30  	res := Flamebearer{
    31  		Names:    []string{},
    32  		Levels:   [][]int{},
    33  		NumTicks: int(t.Samples()),
    34  		MaxSelf:  int(0),
    35  		Format:   FormatSingle,
    36  	}
    37  
    38  	nodes := []*treeNode{t.root}
    39  	xOffsets := []int{0}
    40  	levels := []int{0}
    41  	minVal := t.minValue(maxNodes)
    42  	nameLocationCache := map[string]int{}
    43  
    44  	for len(nodes) > 0 {
    45  		tn := nodes[0]
    46  		nodes = nodes[1:]
    47  
    48  		xOffset := xOffsets[0]
    49  		xOffsets = xOffsets[1:]
    50  
    51  		level := levels[0]
    52  		levels = levels[1:]
    53  
    54  		name := string(tn.Name)
    55  		if tn.Total >= minVal || name == "other" {
    56  			var i int
    57  			var ok bool
    58  			if i, ok = nameLocationCache[name]; !ok {
    59  				i = len(res.Names)
    60  				if i == 0 {
    61  					name = "total"
    62  				}
    63  				nameLocationCache[name] = i
    64  				res.Names = append(res.Names, name)
    65  			}
    66  
    67  			if level == len(res.Levels) {
    68  				res.Levels = append(res.Levels, []int{})
    69  			}
    70  			if res.MaxSelf < int(tn.Self) {
    71  				res.MaxSelf = int(tn.Self)
    72  			}
    73  
    74  			// i+0 = x offset
    75  			// i+1 = total
    76  			// i+2 = self
    77  			// i+3 = index in names array
    78  			res.Levels[level] = append([]int{xOffset, int(tn.Total), int(tn.Self), i}, res.Levels[level]...)
    79  
    80  			xOffset += int(tn.Self)
    81  			otherTotal := uint64(0)
    82  			var otherNode *treeNode
    83  			for _, n := range tn.ChildrenNodes {
    84  				if bytes.Equal(n.Name, lostDuringRenderingName) {
    85  					otherTotal += n.Total
    86  					continue
    87  				}
    88  				if n.Total >= minVal {
    89  					xOffsets = append([]int{xOffset}, xOffsets...)
    90  					levels = append([]int{level + 1}, levels...)
    91  					nodes = append([]*treeNode{n}, nodes...)
    92  					xOffset += int(n.Total)
    93  				} else {
    94  					otherTotal += n.Total
    95  				}
    96  			}
    97  			if otherTotal != 0 {
    98  				otherNode = &treeNode{
    99  					Name:  lostDuringRenderingName,
   100  					Total: otherTotal,
   101  					Self:  otherTotal,
   102  				}
   103  				xOffsets = append([]int{xOffset}, xOffsets...)
   104  				levels = append([]int{level + 1}, levels...)
   105  				nodes = append([]*treeNode{otherNode}, nodes...)
   106  			}
   107  		}
   108  	}
   109  
   110  	// delta encoding
   111  	deltaEncoding(res.Levels, 0, 4)
   112  
   113  	// TODO: we used to drop the first level, because it's always an empty node
   114  	//   but that didn't work because flamebearer doesn't work with more
   115  	//   than one root element. Long term we should fix it on flamebearer side
   116  	// if len(res.Levels) > 0 {
   117  	// 	res.Levels = res.Levels[1:]
   118  	// }
   119  	return &res
   120  }
   121  
   122  func deltaEncoding(levels [][]int, start, step int) {
   123  	for _, l := range levels {
   124  		prev := 0
   125  		for i := start; i < len(l); i += step {
   126  			l[i] -= prev
   127  			prev += l[i] + l[i+1]
   128  		}
   129  	}
   130  }