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  }