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 }