github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/pgo/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 collects a set of samples into a directed graph. 16 17 // Original file location: https://github.com/google/pprof/tree/main/internal/graph/graph.go 18 package pgo 19 20 import ( 21 "fmt" 22 "github.com/bir3/gocompiler/src/internal/profile" 23 "math" 24 "sort" 25 "strings" 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 CallTree bool // Build a tree instead of a graph 34 DropNegative bool // Drop nodes with overall negative values 35 36 KeptNodes NodeSet // If non-nil, only use nodes in this set 37 } 38 39 // Nodes is an ordered collection of graph nodes. 40 type Nodes []*Node 41 42 // Node is an entry on a profiling report. It represents a unique 43 // program location. 44 type Node struct { 45 // Info describes the source location associated to this node. 46 Info NodeInfo 47 48 // Function represents the function that this node belongs to. On 49 // graphs with sub-function resolution (eg line number or 50 // addresses), two nodes in a NodeMap that are part of the same 51 // function have the same value of Node.Function. If the Node 52 // represents the whole function, it points back to itself. 53 Function *Node 54 55 // Values associated to this node. Flat is exclusive to this node, 56 // Cum includes all descendents. 57 Flat, FlatDiv, Cum, CumDiv int64 58 59 // In and out Contains the nodes immediately reaching or reached by 60 // this node. 61 In, Out EdgeMap 62 } 63 64 // Graph summarizes a performance profile into a format that is 65 // suitable for visualization. 66 type Graph struct { 67 Nodes Nodes 68 } 69 70 // FlatValue returns the exclusive value for this node, computing the 71 // mean if a divisor is available. 72 func (n *Node) FlatValue() int64 { 73 if n.FlatDiv == 0 { 74 return n.Flat 75 } 76 return n.Flat / n.FlatDiv 77 } 78 79 // CumValue returns the inclusive value for this node, computing the 80 // mean if a divisor is available. 81 func (n *Node) CumValue() int64 { 82 if n.CumDiv == 0 { 83 return n.Cum 84 } 85 return n.Cum / n.CumDiv 86 } 87 88 // AddToEdge increases the weight of an edge between two nodes. If 89 // there isn't such an edge one is created. 90 func (n *Node) AddToEdge(to *Node, v int64, residual, inline bool) { 91 n.AddToEdgeDiv(to, 0, v, residual, inline) 92 } 93 94 // AddToEdgeDiv increases the weight of an edge between two nodes. If 95 // there isn't such an edge one is created. 96 func (n *Node) AddToEdgeDiv(to *Node, dv, v int64, residual, inline bool) { 97 if e := n.Out.FindTo(to); e != nil { 98 e.WeightDiv += dv 99 e.Weight += v 100 if residual { 101 e.Residual = true 102 } 103 if !inline { 104 e.Inline = false 105 } 106 return 107 } 108 109 info := &Edge{Src: n, Dest: to, WeightDiv: dv, Weight: v, Residual: residual, Inline: inline} 110 n.Out.Add(info) 111 to.In.Add(info) 112 } 113 114 // NodeInfo contains the attributes for a node. 115 type NodeInfo struct { 116 Name string 117 Address uint64 118 StartLine, Lineno int 119 //File string 120 //OrigName string 121 //Objfile string 122 } 123 124 // PrintableName calls the Node's Formatter function with a single space separator. 125 func (i *NodeInfo) PrintableName() string { 126 return strings.Join(i.NameComponents(), " ") 127 } 128 129 // NameComponents returns the components of the printable name to be used for a node. 130 func (i *NodeInfo) NameComponents() []string { 131 var name []string 132 if i.Address != 0 { 133 name = append(name, fmt.Sprintf("%016x", i.Address)) 134 } 135 if fun := i.Name; fun != "" { 136 name = append(name, fun) 137 } 138 139 switch { 140 case i.Lineno != 0: 141 // User requested line numbers, provide what we have. 142 name = append(name, fmt.Sprintf(":%d", i.Lineno)) 143 case i.Name != "": 144 // User requested function name. It was already included. 145 default: 146 // Do not leave it empty if there is no information at all. 147 name = append(name, "<unknown>") 148 } 149 return name 150 } 151 152 // NodeMap maps from a node info struct to a node. It is used to merge 153 // report entries with the same info. 154 type NodeMap map[NodeInfo]*Node 155 156 // NodeSet is a collection of node info structs. 157 type NodeSet map[NodeInfo]bool 158 159 // NodePtrSet is a collection of nodes. Trimming a graph or tree requires a set 160 // of objects which uniquely identify the nodes to keep. In a graph, NodeInfo 161 // works as a unique identifier; however, in a tree multiple nodes may share 162 // identical NodeInfos. A *Node does uniquely identify a node so we can use that 163 // instead. Though a *Node also uniquely identifies a node in a graph, 164 // currently, during trimming, graphs are rebuilt from scratch using only the 165 // NodeSet, so there would not be the required context of the initial graph to 166 // allow for the use of *Node. 167 type NodePtrSet map[*Node]bool 168 169 // FindOrInsertNode takes the info for a node and either returns a matching node 170 // from the node map if one exists, or adds one to the map if one does not. 171 // If kept is non-nil, nodes are only added if they can be located on it. 172 func (nm NodeMap) FindOrInsertNode(info NodeInfo, kept NodeSet) *Node { 173 if kept != nil { 174 if _, ok := kept[info]; !ok { 175 return nil 176 } 177 } 178 179 if n, ok := nm[info]; ok { 180 return n 181 } 182 183 n := &Node{ 184 Info: info, 185 } 186 nm[info] = n 187 if info.Address == 0 && info.Lineno == 0 { 188 // This node represents the whole function, so point Function 189 // back to itself. 190 n.Function = n 191 return n 192 } 193 // Find a node that represents the whole function. 194 info.Address = 0 195 info.Lineno = 0 196 n.Function = nm.FindOrInsertNode(info, nil) 197 return n 198 } 199 200 // EdgeMap is used to represent the incoming/outgoing edges from a node. 201 type EdgeMap []*Edge 202 203 func (em EdgeMap) FindTo(n *Node) *Edge { 204 for _, e := range em { 205 if e.Dest == n { 206 return e 207 } 208 } 209 return nil 210 } 211 212 func (em *EdgeMap) Add(e *Edge) { 213 *em = append(*em, e) 214 } 215 216 func (em *EdgeMap) Delete(e *Edge) { 217 for i, edge := range *em { 218 if edge == e { 219 (*em)[i] = (*em)[len(*em)-1] 220 *em = (*em)[:len(*em)-1] 221 return 222 } 223 } 224 } 225 226 // Edge contains any attributes to be represented about edges in a graph. 227 type Edge struct { 228 Src, Dest *Node 229 // The summary weight of the edge 230 Weight, WeightDiv int64 231 232 // residual edges connect nodes that were connected through a 233 // separate node, which has been removed from the report. 234 Residual bool 235 // An inline edge represents a call that was inlined into the caller. 236 Inline bool 237 } 238 239 // WeightValue returns the weight value for this edge, normalizing if a 240 // divisor is available. 241 func (e *Edge) WeightValue() int64 { 242 if e.WeightDiv == 0 { 243 return e.Weight 244 } 245 return e.Weight / e.WeightDiv 246 } 247 248 // newGraph computes a graph from a profile. 249 func newGraph(prof *profile.Profile, o *Options) *Graph { 250 nodes, locationMap := CreateNodes(prof, o) 251 seenNode := make(map[*Node]bool) 252 seenEdge := make(map[nodePair]bool) 253 for _, sample := range prof.Sample { 254 var w, dw int64 255 w = o.SampleValue(sample.Value) 256 if o.SampleMeanDivisor != nil { 257 dw = o.SampleMeanDivisor(sample.Value) 258 } 259 if dw == 0 && w == 0 { 260 continue 261 } 262 for k := range seenNode { 263 delete(seenNode, k) 264 } 265 for k := range seenEdge { 266 delete(seenEdge, k) 267 } 268 var parent *Node 269 // A residual edge goes over one or more nodes that were not kept. 270 residual := false 271 272 // Group the sample frames, based on a global map. 273 // Count only the last two frames as a call edge. Frames higher up 274 // the stack are unlikely to be repeated calls (e.g. runtime.main 275 // calling main.main). So adding weights to call edges higher up 276 // the stack may be not reflecting the actual call edge weights 277 // in the program. Without a branch profile this is just an 278 // approximation. 279 i := 1 280 if last := len(sample.Location) - 1; last < i { 281 i = last 282 } 283 for ; i >= 0; i-- { 284 l := sample.Location[i] 285 locNodes := locationMap.get(l.ID) 286 for ni := len(locNodes) - 1; ni >= 0; ni-- { 287 n := locNodes[ni] 288 if n == nil { 289 residual = true 290 continue 291 } 292 // Add cum weight to all nodes in stack, avoiding double counting. 293 _, sawNode := seenNode[n] 294 if !sawNode { 295 seenNode[n] = true 296 n.addSample(dw, w, false) 297 } 298 // Update edge weights for all edges in stack, avoiding double counting. 299 if (!sawNode || !seenEdge[nodePair{n, parent}]) && parent != nil && n != parent { 300 seenEdge[nodePair{n, parent}] = true 301 parent.AddToEdgeDiv(n, dw, w, residual, ni != len(locNodes)-1) 302 } 303 304 parent = n 305 residual = false 306 } 307 } 308 if parent != nil && !residual { 309 // Add flat weight to leaf node. 310 parent.addSample(dw, w, true) 311 } 312 } 313 314 return selectNodesForGraph(nodes, o.DropNegative) 315 } 316 317 func selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph { 318 // Collect nodes into a graph. 319 gNodes := make(Nodes, 0, len(nodes)) 320 for _, n := range nodes { 321 if n == nil { 322 continue 323 } 324 if n.Cum == 0 && n.Flat == 0 { 325 continue 326 } 327 if dropNegative && isNegative(n) { 328 continue 329 } 330 gNodes = append(gNodes, n) 331 } 332 return &Graph{gNodes} 333 } 334 335 type nodePair struct { 336 src, dest *Node 337 } 338 339 func newTree(prof *profile.Profile, o *Options) (g *Graph) { 340 parentNodeMap := make(map[*Node]NodeMap, len(prof.Sample)) 341 for _, sample := range prof.Sample { 342 var w, dw int64 343 w = o.SampleValue(sample.Value) 344 if o.SampleMeanDivisor != nil { 345 dw = o.SampleMeanDivisor(sample.Value) 346 } 347 if dw == 0 && w == 0 { 348 continue 349 } 350 var parent *Node 351 // Group the sample frames, based on a per-node map. 352 for i := len(sample.Location) - 1; i >= 0; i-- { 353 l := sample.Location[i] 354 lines := l.Line 355 if len(lines) == 0 { 356 lines = []profile.Line{{}} // Create empty line to include location info. 357 } 358 for lidx := len(lines) - 1; lidx >= 0; lidx-- { 359 nodeMap := parentNodeMap[parent] 360 if nodeMap == nil { 361 nodeMap = make(NodeMap) 362 parentNodeMap[parent] = nodeMap 363 } 364 n := nodeMap.findOrInsertLine(l, lines[lidx], o) 365 if n == nil { 366 continue 367 } 368 n.addSample(dw, w, false) 369 if parent != nil { 370 parent.AddToEdgeDiv(n, dw, w, false, lidx != len(lines)-1) 371 } 372 parent = n 373 } 374 } 375 if parent != nil { 376 parent.addSample(dw, w, true) 377 } 378 } 379 380 nodes := make(Nodes, len(prof.Location)) 381 for _, nm := range parentNodeMap { 382 nodes = append(nodes, nm.nodes()...) 383 } 384 return selectNodesForGraph(nodes, o.DropNegative) 385 } 386 387 // isNegative returns true if the node is considered as "negative" for the 388 // purposes of drop_negative. 389 func isNegative(n *Node) bool { 390 switch { 391 case n.Flat < 0: 392 return true 393 case n.Flat == 0 && n.Cum < 0: 394 return true 395 default: 396 return false 397 } 398 } 399 400 type locationMap struct { 401 s []Nodes // a slice for small sequential IDs 402 m map[uint64]Nodes // fallback for large IDs (unlikely) 403 } 404 405 func (l *locationMap) add(id uint64, n Nodes) { 406 if id < uint64(len(l.s)) { 407 l.s[id] = n 408 } else { 409 l.m[id] = n 410 } 411 } 412 413 func (l locationMap) get(id uint64) Nodes { 414 if id < uint64(len(l.s)) { 415 return l.s[id] 416 } else { 417 return l.m[id] 418 } 419 } 420 421 // CreateNodes creates graph nodes for all locations in a profile. It 422 // returns set of all nodes, plus a mapping of each location to the 423 // set of corresponding nodes (one per location.Line). 424 func CreateNodes(prof *profile.Profile, o *Options) (Nodes, locationMap) { 425 locations := locationMap{make([]Nodes, len(prof.Location)+1), make(map[uint64]Nodes)} 426 nm := make(NodeMap, len(prof.Location)) 427 for _, l := range prof.Location { 428 lines := l.Line 429 if len(lines) == 0 { 430 lines = []profile.Line{{}} // Create empty line to include location info. 431 } 432 nodes := make(Nodes, len(lines)) 433 for ln := range lines { 434 nodes[ln] = nm.findOrInsertLine(l, lines[ln], o) 435 } 436 locations.add(l.ID, nodes) 437 } 438 return nm.nodes(), locations 439 } 440 441 func (nm NodeMap) nodes() Nodes { 442 nodes := make(Nodes, 0, len(nm)) 443 for _, n := range nm { 444 nodes = append(nodes, n) 445 } 446 return nodes 447 } 448 449 func (nm NodeMap) findOrInsertLine(l *profile.Location, li profile.Line, o *Options) *Node { 450 var objfile string 451 if m := l.Mapping; m != nil && m.File != "" { 452 objfile = m.File 453 } 454 455 if ni := nodeInfo(l, li, objfile, o); ni != nil { 456 return nm.FindOrInsertNode(*ni, o.KeptNodes) 457 } 458 return nil 459 } 460 461 func nodeInfo(l *profile.Location, line profile.Line, objfile string, o *Options) *NodeInfo { 462 if line.Function == nil { 463 return &NodeInfo{Address: l.Address} 464 } 465 ni := &NodeInfo{ 466 Address: l.Address, 467 Lineno: int(line.Line), 468 Name: line.Function.Name, 469 } 470 ni.StartLine = int(line.Function.StartLine) 471 return ni 472 } 473 474 // Sum adds the flat and cum values of a set of nodes. 475 func (ns Nodes) Sum() (flat int64, cum int64) { 476 for _, n := range ns { 477 flat += n.Flat 478 cum += n.Cum 479 } 480 return 481 } 482 483 func (n *Node) addSample(dw, w int64, flat bool) { 484 // Update sample value 485 if flat { 486 n.FlatDiv += dw 487 n.Flat += w 488 } else { 489 n.CumDiv += dw 490 n.Cum += w 491 } 492 } 493 494 // String returns a text representation of a graph, for debugging purposes. 495 func (g *Graph) String() string { 496 var s []string 497 498 nodeIndex := make(map[*Node]int, len(g.Nodes)) 499 500 for i, n := range g.Nodes { 501 nodeIndex[n] = i + 1 502 } 503 504 for i, n := range g.Nodes { 505 name := n.Info.PrintableName() 506 var in, out []int 507 508 for _, from := range n.In { 509 in = append(in, nodeIndex[from.Src]) 510 } 511 for _, to := range n.Out { 512 out = append(out, nodeIndex[to.Dest]) 513 } 514 s = append(s, fmt.Sprintf("%d: %s[flat=%d cum=%d] %x -> %v ", i+1, name, n.Flat, n.Cum, in, out)) 515 } 516 return strings.Join(s, "\n") 517 } 518 519 // DiscardLowFrequencyNodes returns a set of the nodes at or over a 520 // specific cum value cutoff. 521 func (g *Graph) DiscardLowFrequencyNodes(nodeCutoff int64) NodeSet { 522 return makeNodeSet(g.Nodes, nodeCutoff) 523 } 524 525 // DiscardLowFrequencyNodePtrs returns a NodePtrSet of nodes at or over a 526 // specific cum value cutoff. 527 func (g *Graph) DiscardLowFrequencyNodePtrs(nodeCutoff int64) NodePtrSet { 528 cutNodes := getNodesAboveCumCutoff(g.Nodes, nodeCutoff) 529 kept := make(NodePtrSet, len(cutNodes)) 530 for _, n := range cutNodes { 531 kept[n] = true 532 } 533 return kept 534 } 535 536 func makeNodeSet(nodes Nodes, nodeCutoff int64) NodeSet { 537 cutNodes := getNodesAboveCumCutoff(nodes, nodeCutoff) 538 kept := make(NodeSet, len(cutNodes)) 539 for _, n := range cutNodes { 540 kept[n.Info] = true 541 } 542 return kept 543 } 544 545 // getNodesAboveCumCutoff returns all the nodes which have a Cum value greater 546 // than or equal to cutoff. 547 func getNodesAboveCumCutoff(nodes Nodes, nodeCutoff int64) Nodes { 548 cutoffNodes := make(Nodes, 0, len(nodes)) 549 for _, n := range nodes { 550 if abs64(n.Cum) < nodeCutoff { 551 continue 552 } 553 cutoffNodes = append(cutoffNodes, n) 554 } 555 return cutoffNodes 556 } 557 558 // TrimLowFrequencyEdges removes edges that have less than 559 // the specified weight. Returns the number of edges removed 560 func (g *Graph) TrimLowFrequencyEdges(edgeCutoff int64) int { 561 var droppedEdges int 562 for _, n := range g.Nodes { 563 for _, e := range n.In { 564 if abs64(e.Weight) < edgeCutoff { 565 n.In.Delete(e) 566 e.Src.Out.Delete(e) 567 droppedEdges++ 568 } 569 } 570 } 571 return droppedEdges 572 } 573 574 // SortNodes sorts the nodes in a graph based on a specific heuristic. 575 func (g *Graph) SortNodes(cum bool, visualMode bool) { 576 // Sort nodes based on requested mode 577 switch { 578 case visualMode: 579 // Specialized sort to produce a more visually-interesting graph 580 g.Nodes.Sort(EntropyOrder) 581 case cum: 582 g.Nodes.Sort(CumNameOrder) 583 default: 584 g.Nodes.Sort(FlatNameOrder) 585 } 586 } 587 588 // SelectTopNodePtrs returns a set of the top maxNodes *Node in a graph. 589 func (g *Graph) SelectTopNodePtrs(maxNodes int, visualMode bool) NodePtrSet { 590 set := make(NodePtrSet) 591 for _, node := range g.selectTopNodes(maxNodes, visualMode) { 592 set[node] = true 593 } 594 return set 595 } 596 597 // SelectTopNodes returns a set of the top maxNodes nodes in a graph. 598 func (g *Graph) SelectTopNodes(maxNodes int, visualMode bool) NodeSet { 599 return makeNodeSet(g.selectTopNodes(maxNodes, visualMode), 0) 600 } 601 602 // selectTopNodes returns a slice of the top maxNodes nodes in a graph. 603 func (g *Graph) selectTopNodes(maxNodes int, visualMode bool) Nodes { 604 if maxNodes > len(g.Nodes) { 605 maxNodes = len(g.Nodes) 606 } 607 return g.Nodes[:maxNodes] 608 } 609 610 // nodeSorter is a mechanism used to allow a report to be sorted 611 // in different ways. 612 type nodeSorter struct { 613 rs Nodes 614 less func(l, r *Node) bool 615 } 616 617 func (s nodeSorter) Len() int { return len(s.rs) } 618 func (s nodeSorter) Swap(i, j int) { s.rs[i], s.rs[j] = s.rs[j], s.rs[i] } 619 func (s nodeSorter) Less(i, j int) bool { return s.less(s.rs[i], s.rs[j]) } 620 621 // Sort reorders a slice of nodes based on the specified ordering 622 // criteria. The result is sorted in decreasing order for (absolute) 623 // numeric quantities, alphabetically for text, and increasing for 624 // addresses. 625 func (ns Nodes) Sort(o NodeOrder) error { 626 var s nodeSorter 627 628 switch o { 629 case FlatNameOrder: 630 s = nodeSorter{ns, 631 func(l, r *Node) bool { 632 if iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv { 633 return iv > jv 634 } 635 if iv, jv := l.Info.PrintableName(), r.Info.PrintableName(); iv != jv { 636 return iv < jv 637 } 638 if iv, jv := abs64(l.Cum), abs64(r.Cum); iv != jv { 639 return iv > jv 640 } 641 return compareNodes(l, r) 642 }, 643 } 644 case FlatCumNameOrder: 645 s = nodeSorter{ns, 646 func(l, r *Node) bool { 647 if iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv { 648 return iv > jv 649 } 650 if iv, jv := abs64(l.Cum), abs64(r.Cum); iv != jv { 651 return iv > jv 652 } 653 if iv, jv := l.Info.PrintableName(), r.Info.PrintableName(); iv != jv { 654 return iv < jv 655 } 656 return compareNodes(l, r) 657 }, 658 } 659 case NameOrder: 660 s = nodeSorter{ns, 661 func(l, r *Node) bool { 662 if iv, jv := l.Info.Name, r.Info.Name; iv != jv { 663 return iv < jv 664 } 665 return compareNodes(l, r) 666 }, 667 } 668 case FileOrder: 669 s = nodeSorter{ns, 670 func(l, r *Node) bool { 671 if iv, jv := l.Info.StartLine, r.Info.StartLine; iv != jv { 672 return iv < jv 673 } 674 return compareNodes(l, r) 675 }, 676 } 677 case AddressOrder: 678 s = nodeSorter{ns, 679 func(l, r *Node) bool { 680 if iv, jv := l.Info.Address, r.Info.Address; iv != jv { 681 return iv < jv 682 } 683 return compareNodes(l, r) 684 }, 685 } 686 case CumNameOrder, EntropyOrder: 687 // Hold scoring for score-based ordering 688 var score map[*Node]int64 689 scoreOrder := func(l, r *Node) bool { 690 if iv, jv := abs64(score[l]), abs64(score[r]); iv != jv { 691 return iv > jv 692 } 693 if iv, jv := l.Info.PrintableName(), r.Info.PrintableName(); iv != jv { 694 return iv < jv 695 } 696 if iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv { 697 return iv > jv 698 } 699 return compareNodes(l, r) 700 } 701 702 switch o { 703 case CumNameOrder: 704 score = make(map[*Node]int64, len(ns)) 705 for _, n := range ns { 706 score[n] = n.Cum 707 } 708 s = nodeSorter{ns, scoreOrder} 709 case EntropyOrder: 710 score = make(map[*Node]int64, len(ns)) 711 for _, n := range ns { 712 score[n] = entropyScore(n) 713 } 714 s = nodeSorter{ns, scoreOrder} 715 } 716 default: 717 return fmt.Errorf("report: unrecognized sort ordering: %d", o) 718 } 719 sort.Sort(s) 720 return nil 721 } 722 723 // compareNodes compares two nodes to provide a deterministic ordering 724 // between them. Two nodes cannot have the same Node.Info value. 725 func compareNodes(l, r *Node) bool { 726 return fmt.Sprint(l.Info) < fmt.Sprint(r.Info) 727 } 728 729 // entropyScore computes a score for a node representing how important 730 // it is to include this node on a graph visualization. It is used to 731 // sort the nodes and select which ones to display if we have more 732 // nodes than desired in the graph. This number is computed by looking 733 // at the flat and cum weights of the node and the incoming/outgoing 734 // edges. The fundamental idea is to penalize nodes that have a simple 735 // fallthrough from their incoming to the outgoing edge. 736 func entropyScore(n *Node) int64 { 737 score := float64(0) 738 739 if len(n.In) == 0 { 740 score++ // Favor entry nodes 741 } else { 742 score += edgeEntropyScore(n, n.In, 0) 743 } 744 745 if len(n.Out) == 0 { 746 score++ // Favor leaf nodes 747 } else { 748 score += edgeEntropyScore(n, n.Out, n.Flat) 749 } 750 751 return int64(score*float64(n.Cum)) + n.Flat 752 } 753 754 // edgeEntropyScore computes the entropy value for a set of edges 755 // coming in or out of a node. Entropy (as defined in information 756 // theory) refers to the amount of information encoded by the set of 757 // edges. A set of edges that have a more interesting distribution of 758 // samples gets a higher score. 759 func edgeEntropyScore(n *Node, edges EdgeMap, self int64) float64 { 760 score := float64(0) 761 total := self 762 for _, e := range edges { 763 if e.Weight > 0 { 764 total += abs64(e.Weight) 765 } 766 } 767 if total != 0 { 768 for _, e := range edges { 769 frac := float64(abs64(e.Weight)) / float64(total) 770 score += -frac * math.Log2(frac) 771 } 772 if self > 0 { 773 frac := float64(abs64(self)) / float64(total) 774 score += -frac * math.Log2(frac) 775 } 776 } 777 return score 778 } 779 780 // NodeOrder sets the ordering for a Sort operation 781 type NodeOrder int 782 783 // Sorting options for node sort. 784 const ( 785 FlatNameOrder NodeOrder = iota 786 FlatCumNameOrder 787 CumNameOrder 788 NameOrder 789 FileOrder 790 AddressOrder 791 EntropyOrder 792 ) 793 794 // Sort returns a slice of the edges in the map, in a consistent 795 // order. The sort order is first based on the edge weight 796 // (higher-to-lower) and then by the node names to avoid flakiness. 797 func (e EdgeMap) Sort() []*Edge { 798 el := make(edgeList, 0, len(e)) 799 for _, w := range e { 800 el = append(el, w) 801 } 802 803 sort.Sort(el) 804 return el 805 } 806 807 // Sum returns the total weight for a set of nodes. 808 func (e EdgeMap) Sum() int64 { 809 var ret int64 810 for _, edge := range e { 811 ret += edge.Weight 812 } 813 return ret 814 } 815 816 type edgeList []*Edge 817 818 func (el edgeList) Len() int { 819 return len(el) 820 } 821 822 func (el edgeList) Less(i, j int) bool { 823 if el[i].Weight != el[j].Weight { 824 return abs64(el[i].Weight) > abs64(el[j].Weight) 825 } 826 827 from1 := el[i].Src.Info.PrintableName() 828 from2 := el[j].Src.Info.PrintableName() 829 if from1 != from2 { 830 return from1 < from2 831 } 832 833 to1 := el[i].Dest.Info.PrintableName() 834 to2 := el[j].Dest.Info.PrintableName() 835 836 return to1 < to2 837 } 838 839 func (el edgeList) Swap(i, j int) { 840 el[i], el[j] = el[j], el[i] 841 } 842 843 func abs64(i int64) int64 { 844 if i < 0 { 845 return -i 846 } 847 return i 848 }