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