github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/profile/flamegraph.go (about) 1 // Copyright 2020 WHTCORPS INC, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package profile 15 16 import ( 17 "fmt" 18 "math" 19 "sort" 20 21 "github.com/google/pprof/profile" 22 "github.com/whtcorpsinc/milevadb/types" 23 "github.com/whtcorpsinc/milevadb/soliton/texttree" 24 ) 25 26 type flamegraphNode struct { 27 cumValue int64 28 children map[uint64]*flamegraphNode 29 name string 30 } 31 32 func newFlamegraphNode() *flamegraphNode { 33 return &flamegraphNode{ 34 cumValue: 0, 35 children: make(map[uint64]*flamegraphNode), 36 name: "", 37 } 38 } 39 40 // add the value from a sample into the flamegraph PosetDag. 41 // This method should only be called on the root node. 42 func (n *flamegraphNode) add(sample *profile.Sample) { 43 // FIXME: we take the last sample value by default, but some profiles have multiple samples. 44 // - allocs: alloc_objects, alloc_space, inuse_objects, inuse_space 45 // - causet: contentions, delay 46 // - cpu: samples, cpu 47 // - heap: alloc_objects, alloc_space, inuse_objects, inuse_space 48 // - mutex: contentions, delay 49 50 value := sample.Value[len(sample.Value)-1] 51 if value == 0 { 52 return 53 } 54 55 locs := sample.Location 56 57 for { 58 n.cumValue += value 59 if len(locs) == 0 { 60 return 61 } 62 63 // The previous implementation in MilevaDB identify nodes using location ID, 64 // but `go tool pprof` identify nodes using function ID. Should we follow? 65 loc := locs[len(locs)-1] 66 locID := loc.ID 67 child, ok := n.children[locID] 68 if !ok { 69 child = newFlamegraphNode() 70 n.children[locID] = child 71 if len(loc.Line) > 0 && loc.Line[0].Function != nil { 72 child.name = locs[len(locs)-1].Line[0].Function.Name 73 } 74 } 75 locs = locs[:len(locs)-1] 76 n = child 77 } 78 } 79 80 // defCauslectFuncUsage defCauslect the value by given function name 81 func (n *flamegraphNode) defCauslectFuncUsage(name string) int64 { 82 if n.name == name { 83 return n.cumValue 84 } 85 if len(n.children) == 0 { 86 return 0 87 } 88 var usage int64 = 0 89 for _, child := range n.children { 90 usage = child.defCauslectFuncUsage(name) + usage 91 } 92 return usage 93 } 94 95 type flamegraphNodeWithLocation struct { 96 *flamegraphNode 97 locID uint64 98 } 99 100 // sortedChildren returns a list of children of this node, sorted by each 101 // child's cumulative value. 102 func (n *flamegraphNode) sortedChildren() []flamegraphNodeWithLocation { 103 children := make([]flamegraphNodeWithLocation, 0, len(n.children)) 104 for locID, child := range n.children { 105 children = append(children, flamegraphNodeWithLocation{ 106 flamegraphNode: child, 107 locID: locID, 108 }) 109 } 110 sort.Slice(children, func(i, j int) bool { 111 a, b := children[i], children[j] 112 if a.cumValue != b.cumValue { 113 return a.cumValue > b.cumValue 114 } 115 return a.locID < b.locID 116 }) 117 118 return children 119 } 120 121 type flamegraphDefCauslector struct { 122 rows [][]types.Causet 123 locations map[uint64]*profile.Location 124 total int64 125 rootChild int 126 } 127 128 func newFlamegraphDefCauslector(p *profile.Profile) *flamegraphDefCauslector { 129 locations := make(map[uint64]*profile.Location, len(p.Location)) 130 for _, loc := range p.Location { 131 locations[loc.ID] = loc 132 } 133 return &flamegraphDefCauslector{locations: locations} 134 } 135 136 func (c *flamegraphDefCauslector) locationName(locID uint64) (funcName, fileLine string) { 137 loc := c.locations[locID] 138 if len(loc.Line) == 0 { 139 return "<unknown>", "<unknown>" 140 } 141 line := loc.Line[0] 142 funcName = line.Function.Name 143 fileLine = fmt.Sprintf("%s:%d", line.Function.Filename, line.Line) 144 return 145 } 146 147 func (c *flamegraphDefCauslector) defCauslectChild( 148 node flamegraphNodeWithLocation, 149 depth int64, 150 indent string, 151 parentCumValue int64, 152 isLastChild bool, 153 ) { 154 funcName, fileLine := c.locationName(node.locID) 155 c.rows = append(c.rows, types.MakeCausets( 156 texttree.PrettyIdentifier(funcName, indent, isLastChild), 157 percentage(node.cumValue, c.total), 158 percentage(node.cumValue, parentCumValue), 159 c.rootChild, 160 depth, 161 fileLine, 162 )) 163 164 if len(node.children) == 0 { 165 return 166 } 167 168 indent4Child := texttree.Indent4Child(indent, isLastChild) 169 children := node.sortedChildren() 170 for i, child := range children { 171 c.defCauslectChild(child, depth+1, indent4Child, node.cumValue, i == len(children)-1) 172 } 173 } 174 175 func (c *flamegraphDefCauslector) defCauslect(root *flamegraphNode) { 176 c.rows = append(c.rows, types.MakeCausets("root", "100%", "100%", 0, 0, "root")) 177 if len(root.children) == 0 { 178 return 179 } 180 181 c.total = root.cumValue 182 indent4Child := texttree.Indent4Child("", false) 183 children := root.sortedChildren() 184 for i, child := range children { 185 c.rootChild = i + 1 186 c.defCauslectChild(child, 1, indent4Child, root.cumValue, i == len(children)-1) 187 } 188 } 189 190 func percentage(value, total int64) string { 191 var ratio float64 192 if total != 0 { 193 ratio = math.Abs(float64(value)/float64(total)) * 100 194 } 195 switch { 196 case ratio >= 99.95 && ratio <= 100.05: 197 return "100%" 198 case ratio >= 1.0: 199 return fmt.Sprintf("%.2f%%", ratio) 200 default: 201 return fmt.Sprintf("%.2g%%", ratio) 202 } 203 }