github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/internal/profile/graph.go (about) 1 // Copyright 2014 Google Inc. All Rights Reserved. 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package profile represents a pprof profile as a directed graph. 16 // 17 // This package is a simplified fork of github.com/google/pprof/internal/graph. 18 package profile 19 20 import ( 21 "fmt" 22 "sort" 23 "strings" 24 ) 25 26 // Options encodes the options for constructing a graph 27 type Options struct { 28 SampleValue func(s []int64) int64 // Function to compute the value of a sample 29 SampleMeanDivisor func(s []int64) int64 // Function to compute the divisor for mean graphs, or nil 30 31 DropNegative bool // Drop nodes with overall negative values 32 33 KeptNodes NodeSet // If non-nil, only use nodes in this set 34 } 35 36 // Nodes is an ordered collection of graph nodes. 37 type Nodes []*Node 38 39 // Node is an entry on a profiling report. It represents a unique 40 // program location. 41 type Node struct { 42 // Info describes the source location associated to this node. 43 Info NodeInfo 44 45 // Function represents the function that this node belongs to. On 46 // graphs with sub-function resolution (eg line number or 47 // addresses), two nodes in a NodeMap that are part of the same 48 // function have the same value of Node.Function. If the Node 49 // represents the whole function, it points back to itself. 50 Function *Node 51 52 // Values associated to this node. Flat is exclusive to this node, 53 // Cum includes all descendents. 54 Flat, FlatDiv, Cum, CumDiv int64 55 56 // In and out Contains the nodes immediately reaching or reached by 57 // this node. 58 In, Out EdgeMap 59 } 60 61 // Graph summarizes a performance profile into a format that is 62 // suitable for visualization. 63 type Graph struct { 64 Nodes Nodes 65 } 66 67 // FlatValue returns the exclusive value for this node, computing the 68 // mean if a divisor is available. 69 func (n *Node) FlatValue() int64 { 70 if n.FlatDiv == 0 { 71 return n.Flat 72 } 73 return n.Flat / n.FlatDiv 74 } 75 76 // CumValue returns the inclusive value for this node, computing the 77 // mean if a divisor is available. 78 func (n *Node) CumValue() int64 { 79 if n.CumDiv == 0 { 80 return n.Cum 81 } 82 return n.Cum / n.CumDiv 83 } 84 85 // AddToEdge increases the weight of an edge between two nodes. If 86 // there isn't such an edge one is created. 87 func (n *Node) AddToEdge(to *Node, v int64, residual, inline bool) { 88 n.AddToEdgeDiv(to, 0, v, residual, inline) 89 } 90 91 // AddToEdgeDiv increases the weight of an edge between two nodes. If 92 // there isn't such an edge one is created. 93 func (n *Node) AddToEdgeDiv(to *Node, dv, v int64, residual, inline bool) { 94 if e := n.Out.FindTo(to); e != nil { 95 e.WeightDiv += dv 96 e.Weight += v 97 if residual { 98 e.Residual = true 99 } 100 if !inline { 101 e.Inline = false 102 } 103 return 104 } 105 106 info := &Edge{Src: n, Dest: to, WeightDiv: dv, Weight: v, Residual: residual, Inline: inline} 107 n.Out.Add(info) 108 to.In.Add(info) 109 } 110 111 // NodeInfo contains the attributes for a node. 112 type NodeInfo struct { 113 Name string 114 Address uint64 115 StartLine, Lineno int 116 } 117 118 // PrintableName calls the Node's Formatter function with a single space separator. 119 func (i *NodeInfo) PrintableName() string { 120 return strings.Join(i.NameComponents(), " ") 121 } 122 123 // NameComponents returns the components of the printable name to be used for a node. 124 func (i *NodeInfo) NameComponents() []string { 125 var name []string 126 if i.Address != 0 { 127 name = append(name, fmt.Sprintf("%016x", i.Address)) 128 } 129 if fun := i.Name; fun != "" { 130 name = append(name, fun) 131 } 132 133 switch { 134 case i.Lineno != 0: 135 // User requested line numbers, provide what we have. 136 name = append(name, fmt.Sprintf(":%d", i.Lineno)) 137 case i.Name != "": 138 // User requested function name. It was already included. 139 default: 140 // Do not leave it empty if there is no information at all. 141 name = append(name, "<unknown>") 142 } 143 return name 144 } 145 146 // NodeMap maps from a node info struct to a node. It is used to merge 147 // report entries with the same info. 148 type NodeMap map[NodeInfo]*Node 149 150 // NodeSet is a collection of node info structs. 151 type NodeSet map[NodeInfo]bool 152 153 // NodePtrSet is a collection of nodes. Trimming a graph or tree requires a set 154 // of objects which uniquely identify the nodes to keep. In a graph, NodeInfo 155 // works as a unique identifier; however, in a tree multiple nodes may share 156 // identical NodeInfos. A *Node does uniquely identify a node so we can use that 157 // instead. Though a *Node also uniquely identifies a node in a graph, 158 // currently, during trimming, graphs are rebuilt from scratch using only the 159 // NodeSet, so there would not be the required context of the initial graph to 160 // allow for the use of *Node. 161 type NodePtrSet map[*Node]bool 162 163 // FindOrInsertNode takes the info for a node and either returns a matching node 164 // from the node map if one exists, or adds one to the map if one does not. 165 // If kept is non-nil, nodes are only added if they can be located on it. 166 func (nm NodeMap) FindOrInsertNode(info NodeInfo, kept NodeSet) *Node { 167 if kept != nil { 168 if _, ok := kept[info]; !ok { 169 return nil 170 } 171 } 172 173 if n, ok := nm[info]; ok { 174 return n 175 } 176 177 n := &Node{ 178 Info: info, 179 } 180 nm[info] = n 181 if info.Address == 0 && info.Lineno == 0 { 182 // This node represents the whole function, so point Function 183 // back to itself. 184 n.Function = n 185 return n 186 } 187 // Find a node that represents the whole function. 188 info.Address = 0 189 info.Lineno = 0 190 n.Function = nm.FindOrInsertNode(info, nil) 191 return n 192 } 193 194 // EdgeMap is used to represent the incoming/outgoing edges from a node. 195 type EdgeMap []*Edge 196 197 func (em EdgeMap) FindTo(n *Node) *Edge { 198 for _, e := range em { 199 if e.Dest == n { 200 return e 201 } 202 } 203 return nil 204 } 205 206 func (em *EdgeMap) Add(e *Edge) { 207 *em = append(*em, e) 208 } 209 210 func (em *EdgeMap) Delete(e *Edge) { 211 for i, edge := range *em { 212 if edge == e { 213 (*em)[i] = (*em)[len(*em)-1] 214 *em = (*em)[:len(*em)-1] 215 return 216 } 217 } 218 } 219 220 // Edge contains any attributes to be represented about edges in a graph. 221 type Edge struct { 222 Src, Dest *Node 223 // The summary weight of the edge 224 Weight, WeightDiv int64 225 226 // residual edges connect nodes that were connected through a 227 // separate node, which has been removed from the report. 228 Residual bool 229 // An inline edge represents a call that was inlined into the caller. 230 Inline bool 231 } 232 233 // WeightValue returns the weight value for this edge, normalizing if a 234 // divisor is available. 235 func (e *Edge) WeightValue() int64 { 236 if e.WeightDiv == 0 { 237 return e.Weight 238 } 239 return e.Weight / e.WeightDiv 240 } 241 242 // NewGraph computes a graph from a profile. 243 func NewGraph(prof *Profile, o *Options) *Graph { 244 nodes, locationMap := CreateNodes(prof, o) 245 seenNode := make(map[*Node]bool) 246 seenEdge := make(map[nodePair]bool) 247 for _, sample := range prof.Sample { 248 var w, dw int64 249 w = o.SampleValue(sample.Value) 250 if o.SampleMeanDivisor != nil { 251 dw = o.SampleMeanDivisor(sample.Value) 252 } 253 if dw == 0 && w == 0 { 254 continue 255 } 256 for k := range seenNode { 257 delete(seenNode, k) 258 } 259 for k := range seenEdge { 260 delete(seenEdge, k) 261 } 262 var parent *Node 263 // A residual edge goes over one or more nodes that were not kept. 264 residual := false 265 266 // Group the sample frames, based on a global map. 267 // Count only the last two frames as a call edge. Frames higher up 268 // the stack are unlikely to be repeated calls (e.g. runtime.main 269 // calling main.main). So adding weights to call edges higher up 270 // the stack may be not reflecting the actual call edge weights 271 // in the program. Without a branch profile this is just an 272 // approximation. 273 i := 1 274 if last := len(sample.Location) - 1; last < i { 275 i = last 276 } 277 for ; i >= 0; i-- { 278 l := sample.Location[i] 279 locNodes := locationMap.get(l.ID) 280 for ni := len(locNodes) - 1; ni >= 0; ni-- { 281 n := locNodes[ni] 282 if n == nil { 283 residual = true 284 continue 285 } 286 // Add cum weight to all nodes in stack, avoiding double counting. 287 _, sawNode := seenNode[n] 288 if !sawNode { 289 seenNode[n] = true 290 n.addSample(dw, w, false) 291 } 292 // Update edge weights for all edges in stack, avoiding double counting. 293 if (!sawNode || !seenEdge[nodePair{n, parent}]) && parent != nil && n != parent { 294 seenEdge[nodePair{n, parent}] = true 295 parent.AddToEdgeDiv(n, dw, w, residual, ni != len(locNodes)-1) 296 } 297 298 parent = n 299 residual = false 300 } 301 } 302 if parent != nil && !residual { 303 // Add flat weight to leaf node. 304 parent.addSample(dw, w, true) 305 } 306 } 307 308 return selectNodesForGraph(nodes, o.DropNegative) 309 } 310 311 func selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph { 312 // Collect nodes into a graph. 313 gNodes := make(Nodes, 0, len(nodes)) 314 for _, n := range nodes { 315 if n == nil { 316 continue 317 } 318 if n.Cum == 0 && n.Flat == 0 { 319 continue 320 } 321 if dropNegative && isNegative(n) { 322 continue 323 } 324 gNodes = append(gNodes, n) 325 } 326 return &Graph{gNodes} 327 } 328 329 type nodePair struct { 330 src, dest *Node 331 } 332 333 // isNegative returns true if the node is considered as "negative" for the 334 // purposes of drop_negative. 335 func isNegative(n *Node) bool { 336 switch { 337 case n.Flat < 0: 338 return true 339 case n.Flat == 0 && n.Cum < 0: 340 return true 341 default: 342 return false 343 } 344 } 345 346 type locationMap struct { 347 s []Nodes // a slice for small sequential IDs 348 m map[uint64]Nodes // fallback for large IDs (unlikely) 349 } 350 351 func (l *locationMap) add(id uint64, n Nodes) { 352 if id < uint64(len(l.s)) { 353 l.s[id] = n 354 } else { 355 l.m[id] = n 356 } 357 } 358 359 func (l locationMap) get(id uint64) Nodes { 360 if id < uint64(len(l.s)) { 361 return l.s[id] 362 } else { 363 return l.m[id] 364 } 365 } 366 367 // CreateNodes creates graph nodes for all locations in a profile. It 368 // returns set of all nodes, plus a mapping of each location to the 369 // set of corresponding nodes (one per location.Line). 370 func CreateNodes(prof *Profile, o *Options) (Nodes, locationMap) { 371 locations := locationMap{make([]Nodes, len(prof.Location)+1), make(map[uint64]Nodes)} 372 nm := make(NodeMap, len(prof.Location)) 373 for _, l := range prof.Location { 374 lines := l.Line 375 if len(lines) == 0 { 376 lines = []Line{{}} // Create empty line to include location info. 377 } 378 nodes := make(Nodes, len(lines)) 379 for ln := range lines { 380 nodes[ln] = nm.findOrInsertLine(l, lines[ln], o) 381 } 382 locations.add(l.ID, nodes) 383 } 384 return nm.nodes(), locations 385 } 386 387 func (nm NodeMap) nodes() Nodes { 388 nodes := make(Nodes, 0, len(nm)) 389 for _, n := range nm { 390 nodes = append(nodes, n) 391 } 392 return nodes 393 } 394 395 func (nm NodeMap) findOrInsertLine(l *Location, li Line, o *Options) *Node { 396 var objfile string 397 if m := l.Mapping; m != nil && m.File != "" { 398 objfile = m.File 399 } 400 401 if ni := nodeInfo(l, li, objfile, o); ni != nil { 402 return nm.FindOrInsertNode(*ni, o.KeptNodes) 403 } 404 return nil 405 } 406 407 func nodeInfo(l *Location, line Line, objfile string, o *Options) *NodeInfo { 408 if line.Function == nil { 409 return &NodeInfo{Address: l.Address} 410 } 411 ni := &NodeInfo{ 412 Address: l.Address, 413 Lineno: int(line.Line), 414 Name: line.Function.Name, 415 } 416 ni.StartLine = int(line.Function.StartLine) 417 return ni 418 } 419 420 // Sum adds the flat and cum values of a set of nodes. 421 func (ns Nodes) Sum() (flat int64, cum int64) { 422 for _, n := range ns { 423 flat += n.Flat 424 cum += n.Cum 425 } 426 return 427 } 428 429 func (n *Node) addSample(dw, w int64, flat bool) { 430 // Update sample value 431 if flat { 432 n.FlatDiv += dw 433 n.Flat += w 434 } else { 435 n.CumDiv += dw 436 n.Cum += w 437 } 438 } 439 440 // String returns a text representation of a graph, for debugging purposes. 441 func (g *Graph) String() string { 442 var s []string 443 444 nodeIndex := make(map[*Node]int, len(g.Nodes)) 445 446 for i, n := range g.Nodes { 447 nodeIndex[n] = i + 1 448 } 449 450 for i, n := range g.Nodes { 451 name := n.Info.PrintableName() 452 var in, out []int 453 454 for _, from := range n.In { 455 in = append(in, nodeIndex[from.Src]) 456 } 457 for _, to := range n.Out { 458 out = append(out, nodeIndex[to.Dest]) 459 } 460 s = append(s, fmt.Sprintf("%d: %s[flat=%d cum=%d] %x -> %v ", i+1, name, n.Flat, n.Cum, in, out)) 461 } 462 return strings.Join(s, "\n") 463 } 464 465 // Sort returns a slice of the edges in the map, in a consistent 466 // order. The sort order is first based on the edge weight 467 // (higher-to-lower) and then by the node names to avoid flakiness. 468 func (em EdgeMap) Sort() []*Edge { 469 el := make(edgeList, 0, len(em)) 470 for _, w := range em { 471 el = append(el, w) 472 } 473 474 sort.Sort(el) 475 return el 476 } 477 478 // Sum returns the total weight for a set of nodes. 479 func (em EdgeMap) Sum() int64 { 480 var ret int64 481 for _, edge := range em { 482 ret += edge.Weight 483 } 484 return ret 485 } 486 487 type edgeList []*Edge 488 489 func (el edgeList) Len() int { 490 return len(el) 491 } 492 493 func (el edgeList) Less(i, j int) bool { 494 if el[i].Weight != el[j].Weight { 495 return abs64(el[i].Weight) > abs64(el[j].Weight) 496 } 497 498 from1 := el[i].Src.Info.PrintableName() 499 from2 := el[j].Src.Info.PrintableName() 500 if from1 != from2 { 501 return from1 < from2 502 } 503 504 to1 := el[i].Dest.Info.PrintableName() 505 to2 := el[j].Dest.Info.PrintableName() 506 507 return to1 < to2 508 } 509 510 func (el edgeList) Swap(i, j int) { 511 el[i], el[j] = el[j], el[i] 512 } 513 514 func abs64(i int64) int64 { 515 if i < 0 { 516 return -i 517 } 518 return i 519 }