golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/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 collects a set of samples into a directed graph. 16 package graph 17 18 import ( 19 "fmt" 20 "math" 21 "path/filepath" 22 "sort" 23 "strconv" 24 "strings" 25 26 "github.com/google/pprof/profile" 27 ) 28 29 // Graph summarizes a performance profile into a format that is 30 // suitable for visualization. 31 type Graph struct { 32 Nodes Nodes 33 } 34 35 // Options encodes the options for constructing a graph 36 type Options struct { 37 SampleValue func(s []int64) int64 // Function to compute the value of a sample 38 SampleMeanDivisor func(s []int64) int64 // Function to compute the divisor for mean graphs, or nil 39 FormatTag func(int64, string) string // Function to format a sample tag value into a string 40 ObjNames bool // Always preserve obj filename 41 OrigFnNames bool // Preserve original (eg mangled) function names 42 43 CallTree bool // Build a tree instead of a graph 44 DropNegative bool // Drop nodes with overall negative values 45 46 KeptNodes NodeSet // If non-nil, only use nodes in this set 47 } 48 49 // Nodes is an ordered collection of graph nodes. 50 type Nodes []*Node 51 52 // Node is an entry on a profiling report. It represents a unique 53 // program location. 54 type Node struct { 55 // Info describes the source location associated to this node. 56 Info NodeInfo 57 58 // Function represents the function that this node belongs to. On 59 // graphs with sub-function resolution (eg line number or 60 // addresses), two nodes in a NodeMap that are part of the same 61 // function have the same value of Node.Function. If the Node 62 // represents the whole function, it points back to itself. 63 Function *Node 64 65 // Values associated to this node. Flat is exclusive to this node, 66 // Cum includes all descendents. 67 Flat, FlatDiv, Cum, CumDiv int64 68 69 // In and out Contains the nodes immediately reaching or reached by 70 // this node. 71 In, Out EdgeMap 72 73 // LabelTags provide additional information about subsets of a sample. 74 LabelTags TagMap 75 76 // NumericTags provide additional values for subsets of a sample. 77 // Numeric tags are optionally associated to a label tag. The key 78 // for NumericTags is the name of the LabelTag they are associated 79 // to, or "" for numeric tags not associated to a label tag. 80 NumericTags map[string]TagMap 81 } 82 83 // FlatValue returns the exclusive value for this node, computing the 84 // mean if a divisor is available. 85 func (n *Node) FlatValue() int64 { 86 if n.FlatDiv == 0 { 87 return n.Flat 88 } 89 return n.Flat / n.FlatDiv 90 } 91 92 // CumValue returns the inclusive value for this node, computing the 93 // mean if a divisor is available. 94 func (n *Node) CumValue() int64 { 95 if n.CumDiv == 0 { 96 return n.Cum 97 } 98 return n.Cum / n.CumDiv 99 } 100 101 // AddToEdge increases the weight of an edge between two nodes. If 102 // there isn't such an edge one is created. 103 func (n *Node) AddToEdge(to *Node, v int64, residual, inline bool) { 104 n.AddToEdgeDiv(to, 0, v, residual, inline) 105 } 106 107 // AddToEdgeDiv increases the weight of an edge between two nodes. If 108 // there isn't such an edge one is created. 109 func (n *Node) AddToEdgeDiv(to *Node, dv, v int64, residual, inline bool) { 110 if n.Out[to] != to.In[n] { 111 panic(fmt.Errorf("asymmetric edges %v %v", *n, *to)) 112 } 113 114 if e := n.Out[to]; e != nil { 115 e.WeightDiv += dv 116 e.Weight += v 117 if residual { 118 e.Residual = true 119 } 120 if !inline { 121 e.Inline = false 122 } 123 return 124 } 125 126 info := &Edge{Src: n, Dest: to, WeightDiv: dv, Weight: v, Residual: residual, Inline: inline} 127 n.Out[to] = info 128 to.In[n] = info 129 } 130 131 // NodeInfo contains the attributes for a node. 132 type NodeInfo struct { 133 Name string 134 OrigName string 135 Address uint64 136 File string 137 StartLine, Lineno int 138 Objfile string 139 } 140 141 // PrintableName calls the Node's Formatter function with a single space separator. 142 func (i *NodeInfo) PrintableName() string { 143 return strings.Join(i.NameComponents(), " ") 144 } 145 146 // NameComponents returns the components of the printable name to be used for a node. 147 func (i *NodeInfo) NameComponents() []string { 148 var name []string 149 if i.Address != 0 { 150 name = append(name, fmt.Sprintf("%016x", i.Address)) 151 } 152 if fun := i.Name; fun != "" { 153 name = append(name, fun) 154 } 155 156 switch { 157 case i.Lineno != 0: 158 // User requested line numbers, provide what we have. 159 name = append(name, fmt.Sprintf("%s:%d", i.File, i.Lineno)) 160 case i.File != "": 161 // User requested file name, provide it. 162 name = append(name, i.File) 163 case i.Name != "": 164 // User requested function name. It was already included. 165 case i.Objfile != "": 166 // Only binary name is available 167 name = append(name, "["+filepath.Base(i.Objfile)+"]") 168 default: 169 // Do not leave it empty if there is no information at all. 170 name = append(name, "<unknown>") 171 } 172 return name 173 } 174 175 // NodeMap maps from a node info struct to a node. It is used to merge 176 // report entries with the same info. 177 type NodeMap map[NodeInfo]*Node 178 179 // NodeSet is a collection of node info structs. 180 type NodeSet map[NodeInfo]bool 181 182 // NodePtrSet is a collection of nodes. Trimming a graph or tree requires a set 183 // of objects which uniquely identify the nodes to keep. In a graph, NodeInfo 184 // works as a unique identifier; however, in a tree multiple nodes may share 185 // identical NodeInfos. A *Node does uniquely identify a node so we can use that 186 // instead. Though a *Node also uniquely identifies a node in a graph, 187 // currently, during trimming, graphs are rebult from scratch using only the 188 // NodeSet, so there would not be the required context of the initial graph to 189 // allow for the use of *Node. 190 type NodePtrSet map[*Node]bool 191 192 // FindOrInsertNode takes the info for a node and either returns a matching node 193 // from the node map if one exists, or adds one to the map if one does not. 194 // If kept is non-nil, nodes are only added if they can be located on it. 195 func (nm NodeMap) FindOrInsertNode(info NodeInfo, kept NodeSet) *Node { 196 if kept != nil { 197 if _, ok := kept[info]; !ok { 198 return nil 199 } 200 } 201 202 if n, ok := nm[info]; ok { 203 return n 204 } 205 206 n := &Node{ 207 Info: info, 208 In: make(EdgeMap), 209 Out: make(EdgeMap), 210 LabelTags: make(TagMap), 211 NumericTags: make(map[string]TagMap), 212 } 213 nm[info] = n 214 if info.Address == 0 && info.Lineno == 0 { 215 // This node represents the whole function, so point Function 216 // back to itself. 217 n.Function = n 218 return n 219 } 220 // Find a node that represents the whole function. 221 info.Address = 0 222 info.Lineno = 0 223 n.Function = nm.FindOrInsertNode(info, nil) 224 return n 225 } 226 227 // EdgeMap is used to represent the incoming/outgoing edges from a node. 228 type EdgeMap map[*Node]*Edge 229 230 // Edge contains any attributes to be represented about edges in a graph. 231 type Edge struct { 232 Src, Dest *Node 233 // The summary weight of the edge 234 Weight, WeightDiv int64 235 236 // residual edges connect nodes that were connected through a 237 // separate node, which has been removed from the report. 238 Residual bool 239 // An inline edge represents a call that was inlined into the caller. 240 Inline bool 241 } 242 243 func (e *Edge) WeightValue() int64 { 244 if e.WeightDiv == 0 { 245 return e.Weight 246 } 247 return e.Weight / e.WeightDiv 248 } 249 250 // Tag represent sample annotations 251 type Tag struct { 252 Name string 253 Unit string // Describe the value, "" for non-numeric tags 254 Value int64 255 Flat, FlatDiv int64 256 Cum, CumDiv int64 257 } 258 259 // FlatValue returns the exclusive value for this tag, computing the 260 // mean if a divisor is available. 261 func (t *Tag) FlatValue() int64 { 262 if t.FlatDiv == 0 { 263 return t.Flat 264 } 265 return t.Flat / t.FlatDiv 266 } 267 268 // CumValue returns the inclusive value for this tag, computing the 269 // mean if a divisor is available. 270 func (t *Tag) CumValue() int64 { 271 if t.CumDiv == 0 { 272 return t.Cum 273 } 274 return t.Cum / t.CumDiv 275 } 276 277 // TagMap is a collection of tags, classified by their name. 278 type TagMap map[string]*Tag 279 280 // SortTags sorts a slice of tags based on their weight. 281 func SortTags(t []*Tag, flat bool) []*Tag { 282 ts := tags{t, flat} 283 sort.Sort(ts) 284 return ts.t 285 } 286 287 // New summarizes performance data from a profile into a graph. 288 func New(prof *profile.Profile, o *Options) *Graph { 289 if o.CallTree { 290 return newTree(prof, o) 291 } 292 g, _ := newGraph(prof, o) 293 return g 294 } 295 296 // newGraph computes a graph from a profile. It returns the graph, and 297 // a map from the profile location indices to the corresponding graph 298 // nodes. 299 func newGraph(prof *profile.Profile, o *Options) (*Graph, map[uint64]Nodes) { 300 nodes, locationMap := CreateNodes(prof, o) 301 for _, sample := range prof.Sample { 302 var w, dw int64 303 w = o.SampleValue(sample.Value) 304 if o.SampleMeanDivisor != nil { 305 dw = o.SampleMeanDivisor(sample.Value) 306 } 307 if dw == 0 && w == 0 { 308 continue 309 } 310 seenNode := make(map[*Node]bool, len(sample.Location)) 311 seenEdge := make(map[nodePair]bool, len(sample.Location)) 312 var parent *Node 313 // A residual edge goes over one or more nodes that were not kept. 314 residual := false 315 316 labels := joinLabels(sample) 317 // Group the sample frames, based on a global map. 318 for i := len(sample.Location) - 1; i >= 0; i-- { 319 l := sample.Location[i] 320 locNodes := locationMap[l.ID] 321 for ni := len(locNodes) - 1; ni >= 0; ni-- { 322 n := locNodes[ni] 323 if n == nil { 324 residual = true 325 continue 326 } 327 // Add cum weight to all nodes in stack, avoiding double counting. 328 if _, ok := seenNode[n]; !ok { 329 seenNode[n] = true 330 n.addSample(dw, w, labels, sample.NumLabel, o.FormatTag, false) 331 } 332 // Update edge weights for all edges in stack, avoiding double counting. 333 if _, ok := seenEdge[nodePair{n, parent}]; !ok && parent != nil && n != parent { 334 seenEdge[nodePair{n, parent}] = true 335 parent.AddToEdgeDiv(n, dw, w, residual, ni != len(locNodes)-1) 336 } 337 parent = n 338 residual = false 339 } 340 } 341 if parent != nil && !residual { 342 // Add flat weight to leaf node. 343 parent.addSample(dw, w, labels, sample.NumLabel, o.FormatTag, true) 344 } 345 } 346 347 return selectNodesForGraph(nodes, o.DropNegative), locationMap 348 } 349 350 func selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph { 351 // Collect nodes into a graph. 352 gNodes := make(Nodes, 0, len(nodes)) 353 for _, n := range nodes { 354 if n == nil { 355 continue 356 } 357 if n.Cum == 0 && n.Flat == 0 { 358 continue 359 } 360 if dropNegative && isNegative(n) { 361 continue 362 } 363 gNodes = append(gNodes, n) 364 } 365 return &Graph{gNodes} 366 } 367 368 type nodePair struct { 369 src, dest *Node 370 } 371 372 func newTree(prof *profile.Profile, o *Options) (g *Graph) { 373 parentNodeMap := make(map[*Node]NodeMap, len(prof.Sample)) 374 for _, sample := range prof.Sample { 375 var w, dw int64 376 w = o.SampleValue(sample.Value) 377 if o.SampleMeanDivisor != nil { 378 dw = o.SampleMeanDivisor(sample.Value) 379 } 380 if dw == 0 && w == 0 { 381 continue 382 } 383 var parent *Node 384 labels := joinLabels(sample) 385 // Group the sample frames, based on a per-node map. 386 for i := len(sample.Location) - 1; i >= 0; i-- { 387 l := sample.Location[i] 388 lines := l.Line 389 if len(lines) == 0 { 390 lines = []profile.Line{{}} // Create empty line to include location info. 391 } 392 for lidx := len(lines) - 1; lidx >= 0; lidx-- { 393 nodeMap := parentNodeMap[parent] 394 if nodeMap == nil { 395 nodeMap = make(NodeMap) 396 parentNodeMap[parent] = nodeMap 397 } 398 n := nodeMap.findOrInsertLine(l, lines[lidx], o) 399 if n == nil { 400 continue 401 } 402 n.addSample(dw, w, labels, sample.NumLabel, o.FormatTag, false) 403 if parent != nil { 404 parent.AddToEdgeDiv(n, dw, w, false, lidx != len(lines)-1) 405 } 406 parent = n 407 } 408 } 409 if parent != nil { 410 parent.addSample(dw, w, labels, sample.NumLabel, o.FormatTag, true) 411 } 412 } 413 414 nodes := make(Nodes, len(prof.Location)) 415 for _, nm := range parentNodeMap { 416 nodes = append(nodes, nm.nodes()...) 417 } 418 return selectNodesForGraph(nodes, o.DropNegative) 419 } 420 421 // TrimTree trims a Graph in forest form, keeping only the nodes in kept. This 422 // will not work correctly if even a single node has multiple parents. 423 func (g *Graph) TrimTree(kept NodePtrSet) { 424 // Creates a new list of nodes 425 oldNodes := g.Nodes 426 g.Nodes = make(Nodes, 0, len(kept)) 427 428 for _, cur := range oldNodes { 429 // A node may not have multiple parents 430 if len(cur.In) > 1 { 431 panic("TrimTree only works on trees") 432 } 433 434 // If a node should be kept, add it to the new list of nodes 435 if _, ok := kept[cur]; ok { 436 g.Nodes = append(g.Nodes, cur) 437 continue 438 } 439 440 // If a node has no parents, then delete all of the in edges of its 441 // children to make them each roots of their own trees. 442 if len(cur.In) == 0 { 443 for _, outEdge := range cur.Out { 444 delete(outEdge.Dest.In, cur) 445 } 446 continue 447 } 448 449 // Get the parent. This works since at this point cur.In must contain only 450 // one element. 451 if len(cur.In) != 1 { 452 panic("Get parent assertion failed. cur.In expected to be of length 1.") 453 } 454 var parent *Node 455 for _, edge := range cur.In { 456 parent = edge.Src 457 } 458 459 parentEdgeInline := parent.Out[cur].Inline 460 461 // Remove the edge from the parent to this node 462 delete(parent.Out, cur) 463 464 // Reconfigure every edge from the current node to now begin at the parent. 465 for _, outEdge := range cur.Out { 466 child := outEdge.Dest 467 468 delete(child.In, cur) 469 child.In[parent] = outEdge 470 parent.Out[child] = outEdge 471 472 outEdge.Src = parent 473 outEdge.Residual = true 474 // If the edge from the parent to the current node and the edge from the 475 // current node to the child are both inline, then this resulting residual 476 // edge should also be inline 477 outEdge.Inline = parentEdgeInline && outEdge.Inline 478 } 479 } 480 g.RemoveRedundantEdges() 481 } 482 483 func joinLabels(s *profile.Sample) string { 484 if len(s.Label) == 0 { 485 return "" 486 } 487 488 var labels []string 489 for key, vals := range s.Label { 490 for _, v := range vals { 491 labels = append(labels, key+":"+v) 492 } 493 } 494 sort.Strings(labels) 495 return strings.Join(labels, `\n`) 496 } 497 498 // isNegative returns true if the node is considered as "negative" for the 499 // purposes of drop_negative. 500 func isNegative(n *Node) bool { 501 switch { 502 case n.Flat < 0: 503 return true 504 case n.Flat == 0 && n.Cum < 0: 505 return true 506 default: 507 return false 508 } 509 } 510 511 // CreateNodes creates graph nodes for all locations in a profile. It 512 // returns set of all nodes, plus a mapping of each location to the 513 // set of corresponding nodes (one per location.Line). If kept is 514 // non-nil, only nodes in that set are included; nodes that do not 515 // match are represented as a nil. 516 func CreateNodes(prof *profile.Profile, o *Options) (Nodes, map[uint64]Nodes) { 517 locations := make(map[uint64]Nodes, len(prof.Location)) 518 nm := make(NodeMap, len(prof.Location)) 519 for _, l := range prof.Location { 520 lines := l.Line 521 if len(lines) == 0 { 522 lines = []profile.Line{{}} // Create empty line to include location info. 523 } 524 nodes := make(Nodes, len(lines)) 525 for ln := range lines { 526 nodes[ln] = nm.findOrInsertLine(l, lines[ln], o) 527 } 528 locations[l.ID] = nodes 529 } 530 return nm.nodes(), locations 531 } 532 533 func (nm NodeMap) nodes() Nodes { 534 nodes := make(Nodes, 0, len(nm)) 535 for _, n := range nm { 536 nodes = append(nodes, n) 537 } 538 return nodes 539 } 540 541 func (nm NodeMap) findOrInsertLine(l *profile.Location, li profile.Line, o *Options) *Node { 542 var objfile string 543 if m := l.Mapping; m != nil && m.File != "" { 544 objfile = m.File 545 } 546 547 if ni := nodeInfo(l, li, objfile, o); ni != nil { 548 return nm.FindOrInsertNode(*ni, o.KeptNodes) 549 } 550 return nil 551 } 552 553 func nodeInfo(l *profile.Location, line profile.Line, objfile string, o *Options) *NodeInfo { 554 if line.Function == nil { 555 return &NodeInfo{Address: l.Address, Objfile: objfile} 556 } 557 ni := &NodeInfo{ 558 Address: l.Address, 559 Lineno: int(line.Line), 560 Name: line.Function.Name, 561 } 562 if fname := line.Function.Filename; fname != "" { 563 ni.File = filepath.Clean(fname) 564 } 565 if o.ObjNames { 566 ni.Objfile = objfile 567 ni.StartLine = int(line.Function.StartLine) 568 } 569 if o.OrigFnNames { 570 ni.OrigName = line.Function.SystemName 571 } 572 return ni 573 } 574 575 type tags struct { 576 t []*Tag 577 flat bool 578 } 579 580 func (t tags) Len() int { return len(t.t) } 581 func (t tags) Swap(i, j int) { t.t[i], t.t[j] = t.t[j], t.t[i] } 582 func (t tags) Less(i, j int) bool { 583 if !t.flat { 584 if t.t[i].Cum != t.t[j].Cum { 585 return abs64(t.t[i].Cum) > abs64(t.t[j].Cum) 586 } 587 } 588 if t.t[i].Flat != t.t[j].Flat { 589 return abs64(t.t[i].Flat) > abs64(t.t[j].Flat) 590 } 591 return t.t[i].Name < t.t[j].Name 592 } 593 594 // Sum adds the flat and cum values of a set of nodes. 595 func (ns Nodes) Sum() (flat int64, cum int64) { 596 for _, n := range ns { 597 flat += n.Flat 598 cum += n.Cum 599 } 600 return 601 } 602 603 func (n *Node) addSample(dw, w int64, labels string, numLabel map[string][]int64, format func(int64, string) string, flat bool) { 604 // Update sample value 605 if flat { 606 n.FlatDiv += dw 607 n.Flat += w 608 } else { 609 n.CumDiv += dw 610 n.Cum += w 611 } 612 613 // Add string tags 614 if labels != "" { 615 t := n.LabelTags.findOrAddTag(labels, "", 0) 616 if flat { 617 t.FlatDiv += dw 618 t.Flat += w 619 } else { 620 t.CumDiv += dw 621 t.Cum += w 622 } 623 } 624 625 numericTags := n.NumericTags[labels] 626 if numericTags == nil { 627 numericTags = TagMap{} 628 n.NumericTags[labels] = numericTags 629 } 630 // Add numeric tags 631 if format == nil { 632 format = defaultLabelFormat 633 } 634 for key, nvals := range numLabel { 635 for _, v := range nvals { 636 t := numericTags.findOrAddTag(format(v, key), key, v) 637 if flat { 638 t.FlatDiv += dw 639 t.Flat += w 640 } else { 641 t.CumDiv += dw 642 t.Cum += w 643 } 644 } 645 } 646 } 647 648 func defaultLabelFormat(v int64, key string) string { 649 return strconv.FormatInt(v, 10) 650 } 651 652 func (m TagMap) findOrAddTag(label, unit string, value int64) *Tag { 653 l := m[label] 654 if l == nil { 655 l = &Tag{ 656 Name: label, 657 Unit: unit, 658 Value: value, 659 } 660 m[label] = l 661 } 662 return l 663 } 664 665 // String returns a text representation of a graph, for debugging purposes. 666 func (g *Graph) String() string { 667 var s []string 668 669 nodeIndex := make(map[*Node]int, len(g.Nodes)) 670 671 for i, n := range g.Nodes { 672 nodeIndex[n] = i + 1 673 } 674 675 for i, n := range g.Nodes { 676 name := n.Info.PrintableName() 677 var in, out []int 678 679 for _, from := range n.In { 680 in = append(in, nodeIndex[from.Src]) 681 } 682 for _, to := range n.Out { 683 out = append(out, nodeIndex[to.Dest]) 684 } 685 s = append(s, fmt.Sprintf("%d: %s[flat=%d cum=%d] %x -> %v ", i+1, name, n.Flat, n.Cum, in, out)) 686 } 687 return strings.Join(s, "\n") 688 } 689 690 // DiscardLowFrequencyNodes returns a set of the nodes at or over a 691 // specific cum value cutoff. 692 func (g *Graph) DiscardLowFrequencyNodes(nodeCutoff int64) NodeSet { 693 return makeNodeSet(g.Nodes, nodeCutoff) 694 } 695 696 // DiscardLowFrequencyNodePtrs returns a NodePtrSet of nodes at or over a 697 // specific cum value cutoff. 698 func (g *Graph) DiscardLowFrequencyNodePtrs(nodeCutoff int64) NodePtrSet { 699 cutNodes := getNodesAboveCumCutoff(g.Nodes, nodeCutoff) 700 kept := make(NodePtrSet, len(cutNodes)) 701 for _, n := range cutNodes { 702 kept[n] = true 703 } 704 return kept 705 } 706 707 func makeNodeSet(nodes Nodes, nodeCutoff int64) NodeSet { 708 cutNodes := getNodesAboveCumCutoff(nodes, nodeCutoff) 709 kept := make(NodeSet, len(cutNodes)) 710 for _, n := range cutNodes { 711 kept[n.Info] = true 712 } 713 return kept 714 } 715 716 // getNodesAboveCumCutoff returns all the nodes which have a Cum value greater 717 // than or equal to cutoff. 718 func getNodesAboveCumCutoff(nodes Nodes, nodeCutoff int64) Nodes { 719 cutoffNodes := make(Nodes, 0, len(nodes)) 720 for _, n := range nodes { 721 if abs64(n.Cum) < nodeCutoff { 722 continue 723 } 724 cutoffNodes = append(cutoffNodes, n) 725 } 726 return cutoffNodes 727 } 728 729 // TrimLowFrequencyTags removes tags that have less than 730 // the specified weight. 731 func (g *Graph) TrimLowFrequencyTags(tagCutoff int64) { 732 // Remove nodes with value <= total*nodeFraction 733 for _, n := range g.Nodes { 734 n.LabelTags = trimLowFreqTags(n.LabelTags, tagCutoff) 735 for s, nt := range n.NumericTags { 736 n.NumericTags[s] = trimLowFreqTags(nt, tagCutoff) 737 } 738 } 739 } 740 741 func trimLowFreqTags(tags TagMap, minValue int64) TagMap { 742 kept := TagMap{} 743 for s, t := range tags { 744 if abs64(t.Flat) >= minValue || abs64(t.Cum) >= minValue { 745 kept[s] = t 746 } 747 } 748 return kept 749 } 750 751 // TrimLowFrequencyEdges removes edges that have less than 752 // the specified weight. Returns the number of edges removed 753 func (g *Graph) TrimLowFrequencyEdges(edgeCutoff int64) int { 754 var droppedEdges int 755 for _, n := range g.Nodes { 756 for src, e := range n.In { 757 if abs64(e.Weight) < edgeCutoff { 758 delete(n.In, src) 759 delete(src.Out, n) 760 droppedEdges++ 761 } 762 } 763 } 764 return droppedEdges 765 } 766 767 // SortNodes sorts the nodes in a graph based on a specific heuristic. 768 func (g *Graph) SortNodes(cum bool, visualMode bool) { 769 // Sort nodes based on requested mode 770 switch { 771 case visualMode: 772 // Specialized sort to produce a more visually-interesting graph 773 g.Nodes.Sort(EntropyOrder) 774 case cum: 775 g.Nodes.Sort(CumNameOrder) 776 default: 777 g.Nodes.Sort(FlatNameOrder) 778 } 779 } 780 781 // SelectTopNodePtrs returns a set of the top maxNodes *Node in a graph. 782 func (g *Graph) SelectTopNodePtrs(maxNodes int, visualMode bool) NodePtrSet { 783 set := make(NodePtrSet) 784 for _, node := range g.selectTopNodes(maxNodes, visualMode) { 785 set[node] = true 786 } 787 return set 788 } 789 790 // SelectTopNodes returns a set of the top maxNodes nodes in a graph. 791 func (g *Graph) SelectTopNodes(maxNodes int, visualMode bool) NodeSet { 792 return makeNodeSet(g.selectTopNodes(maxNodes, visualMode), 0) 793 } 794 795 // selectTopNodes returns a slice of the top maxNodes nodes in a graph. 796 func (g *Graph) selectTopNodes(maxNodes int, visualMode bool) Nodes { 797 if maxNodes > 0 { 798 if visualMode { 799 var count int 800 // If generating a visual graph, count tags as nodes. Update 801 // maxNodes to account for them. 802 for i, n := range g.Nodes { 803 if count += countTags(n) + 1; count >= maxNodes { 804 maxNodes = i + 1 805 break 806 } 807 } 808 } 809 } 810 if maxNodes > len(g.Nodes) { 811 maxNodes = len(g.Nodes) 812 } 813 return g.Nodes[:maxNodes] 814 } 815 816 // countTags counts the tags with flat count. This underestimates the 817 // number of tags being displayed, but in practice is close enough. 818 func countTags(n *Node) int { 819 count := 0 820 for _, e := range n.LabelTags { 821 if e.Flat != 0 { 822 count++ 823 } 824 } 825 for _, t := range n.NumericTags { 826 for _, e := range t { 827 if e.Flat != 0 { 828 count++ 829 } 830 } 831 } 832 return count 833 } 834 835 // countEdges counts the number of edges below the specified cutoff. 836 func countEdges(el EdgeMap, cutoff int64) int { 837 count := 0 838 for _, e := range el { 839 if e.Weight > cutoff { 840 count++ 841 } 842 } 843 return count 844 } 845 846 // RemoveRedundantEdges removes residual edges if the destination can 847 // be reached through another path. This is done to simplify the graph 848 // while preserving connectivity. 849 func (g *Graph) RemoveRedundantEdges() { 850 // Walk the nodes and outgoing edges in reverse order to prefer 851 // removing edges with the lowest weight. 852 for i := len(g.Nodes); i > 0; i-- { 853 n := g.Nodes[i-1] 854 in := n.In.Sort() 855 for j := len(in); j > 0; j-- { 856 e := in[j-1] 857 if !e.Residual { 858 // Do not remove edges heavier than a non-residual edge, to 859 // avoid potential confusion. 860 break 861 } 862 if isRedundantEdge(e) { 863 delete(e.Src.Out, e.Dest) 864 delete(e.Dest.In, e.Src) 865 } 866 } 867 } 868 } 869 870 // isRedundantEdge determines if there is a path that allows e.Src 871 // to reach e.Dest after removing e. 872 func isRedundantEdge(e *Edge) bool { 873 src, n := e.Src, e.Dest 874 seen := map[*Node]bool{n: true} 875 queue := Nodes{n} 876 for len(queue) > 0 { 877 n := queue[0] 878 queue = queue[1:] 879 for _, ie := range n.In { 880 if e == ie || seen[ie.Src] { 881 continue 882 } 883 if ie.Src == src { 884 return true 885 } 886 seen[ie.Src] = true 887 queue = append(queue, ie.Src) 888 } 889 } 890 return false 891 } 892 893 // nodeSorter is a mechanism used to allow a report to be sorted 894 // in different ways. 895 type nodeSorter struct { 896 rs Nodes 897 less func(l, r *Node) bool 898 } 899 900 func (s nodeSorter) Len() int { return len(s.rs) } 901 func (s nodeSorter) Swap(i, j int) { s.rs[i], s.rs[j] = s.rs[j], s.rs[i] } 902 func (s nodeSorter) Less(i, j int) bool { return s.less(s.rs[i], s.rs[j]) } 903 904 // Sort reorders a slice of nodes based on the specified ordering 905 // criteria. The result is sorted in decreasing order for (absolute) 906 // numeric quantities, alphabetically for text, and increasing for 907 // addresses. 908 func (ns Nodes) Sort(o NodeOrder) error { 909 var s nodeSorter 910 911 switch o { 912 case FlatNameOrder: 913 s = nodeSorter{ns, 914 func(l, r *Node) bool { 915 if iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv { 916 return iv > jv 917 } 918 if iv, jv := l.Info.PrintableName(), r.Info.PrintableName(); iv != jv { 919 return iv < jv 920 } 921 if iv, jv := abs64(l.Cum), abs64(r.Cum); iv != jv { 922 return iv > jv 923 } 924 return compareNodes(l, r) 925 }, 926 } 927 case FlatCumNameOrder: 928 s = nodeSorter{ns, 929 func(l, r *Node) bool { 930 if iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv { 931 return iv > jv 932 } 933 if iv, jv := abs64(l.Cum), abs64(r.Cum); iv != jv { 934 return iv > jv 935 } 936 if iv, jv := l.Info.PrintableName(), r.Info.PrintableName(); iv != jv { 937 return iv < jv 938 } 939 return compareNodes(l, r) 940 }, 941 } 942 case NameOrder: 943 s = nodeSorter{ns, 944 func(l, r *Node) bool { 945 if iv, jv := l.Info.Name, r.Info.Name; iv != jv { 946 return iv < jv 947 } 948 return compareNodes(l, r) 949 }, 950 } 951 case FileOrder: 952 s = nodeSorter{ns, 953 func(l, r *Node) bool { 954 if iv, jv := l.Info.File, r.Info.File; iv != jv { 955 return iv < jv 956 } 957 if iv, jv := l.Info.StartLine, r.Info.StartLine; iv != jv { 958 return iv < jv 959 } 960 return compareNodes(l, r) 961 }, 962 } 963 case AddressOrder: 964 s = nodeSorter{ns, 965 func(l, r *Node) bool { 966 if iv, jv := l.Info.Address, r.Info.Address; iv != jv { 967 return iv < jv 968 } 969 return compareNodes(l, r) 970 }, 971 } 972 case CumNameOrder, EntropyOrder: 973 // Hold scoring for score-based ordering 974 var score map[*Node]int64 975 scoreOrder := func(l, r *Node) bool { 976 if iv, jv := abs64(score[l]), abs64(score[r]); iv != jv { 977 return iv > jv 978 } 979 if iv, jv := l.Info.PrintableName(), r.Info.PrintableName(); iv != jv { 980 return iv < jv 981 } 982 if iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv { 983 return iv > jv 984 } 985 return compareNodes(l, r) 986 } 987 988 switch o { 989 case CumNameOrder: 990 score = make(map[*Node]int64, len(ns)) 991 for _, n := range ns { 992 score[n] = n.Cum 993 } 994 s = nodeSorter{ns, scoreOrder} 995 case EntropyOrder: 996 score = make(map[*Node]int64, len(ns)) 997 for _, n := range ns { 998 score[n] = entropyScore(n) 999 } 1000 s = nodeSorter{ns, scoreOrder} 1001 } 1002 default: 1003 return fmt.Errorf("report: unrecognized sort ordering: %d", o) 1004 } 1005 sort.Sort(s) 1006 return nil 1007 } 1008 1009 // compareNodes compares two nodes to provide a deterministic ordering 1010 // between them. Two nodes cannot have the same Node.Info value. 1011 func compareNodes(l, r *Node) bool { 1012 return fmt.Sprint(l.Info) < fmt.Sprint(r.Info) 1013 } 1014 1015 // entropyScore computes a score for a node representing how important 1016 // it is to include this node on a graph visualization. It is used to 1017 // sort the nodes and select which ones to display if we have more 1018 // nodes than desired in the graph. This number is computed by looking 1019 // at the flat and cum weights of the node and the incoming/outgoing 1020 // edges. The fundamental idea is to penalize nodes that have a simple 1021 // fallthrough from their incoming to the outgoing edge. 1022 func entropyScore(n *Node) int64 { 1023 score := float64(0) 1024 1025 if len(n.In) == 0 { 1026 score++ // Favor entry nodes 1027 } else { 1028 score += edgeEntropyScore(n, n.In, 0) 1029 } 1030 1031 if len(n.Out) == 0 { 1032 score++ // Favor leaf nodes 1033 } else { 1034 score += edgeEntropyScore(n, n.Out, n.Flat) 1035 } 1036 1037 return int64(score*float64(n.Cum)) + n.Flat 1038 } 1039 1040 // edgeEntropyScore computes the entropy value for a set of edges 1041 // coming in or out of a node. Entropy (as defined in information 1042 // theory) refers to the amount of information encoded by the set of 1043 // edges. A set of edges that have a more interesting distribution of 1044 // samples gets a higher score. 1045 func edgeEntropyScore(n *Node, edges EdgeMap, self int64) float64 { 1046 score := float64(0) 1047 total := self 1048 for _, e := range edges { 1049 if e.Weight > 0 { 1050 total += abs64(e.Weight) 1051 } 1052 } 1053 if total != 0 { 1054 for _, e := range edges { 1055 frac := float64(abs64(e.Weight)) / float64(total) 1056 score += -frac * math.Log2(frac) 1057 } 1058 if self > 0 { 1059 frac := float64(abs64(self)) / float64(total) 1060 score += -frac * math.Log2(frac) 1061 } 1062 } 1063 return score 1064 } 1065 1066 // NodeOrder sets the ordering for a Sort operation 1067 type NodeOrder int 1068 1069 // Sorting options for node sort. 1070 const ( 1071 FlatNameOrder NodeOrder = iota 1072 FlatCumNameOrder 1073 CumNameOrder 1074 NameOrder 1075 FileOrder 1076 AddressOrder 1077 EntropyOrder 1078 ) 1079 1080 // Sort returns a slice of the edges in the map, in a consistent 1081 // order. The sort order is first based on the edge weight 1082 // (higher-to-lower) and then by the node names to avoid flakiness. 1083 func (e EdgeMap) Sort() []*Edge { 1084 el := make(edgeList, 0, len(e)) 1085 for _, w := range e { 1086 el = append(el, w) 1087 } 1088 1089 sort.Sort(el) 1090 return el 1091 } 1092 1093 // Sum returns the total weight for a set of nodes. 1094 func (e EdgeMap) Sum() int64 { 1095 var ret int64 1096 for _, edge := range e { 1097 ret += edge.Weight 1098 } 1099 return ret 1100 } 1101 1102 type edgeList []*Edge 1103 1104 func (el edgeList) Len() int { 1105 return len(el) 1106 } 1107 1108 func (el edgeList) Less(i, j int) bool { 1109 if el[i].Weight != el[j].Weight { 1110 return abs64(el[i].Weight) > abs64(el[j].Weight) 1111 } 1112 1113 from1 := el[i].Src.Info.PrintableName() 1114 from2 := el[j].Src.Info.PrintableName() 1115 if from1 != from2 { 1116 return from1 < from2 1117 } 1118 1119 to1 := el[i].Dest.Info.PrintableName() 1120 to2 := el[j].Dest.Info.PrintableName() 1121 1122 return to1 < to2 1123 } 1124 1125 func (el edgeList) Swap(i, j int) { 1126 el[i], el[j] = el[j], el[i] 1127 } 1128 1129 func abs64(i int64) int64 { 1130 if i < 0 { 1131 return -i 1132 } 1133 return i 1134 }