import (
"fmt"
"internal/profile"
- "math"
"sort"
"strings"
)
SampleValue func(s []int64) int64 // Function to compute the value of a sample
SampleMeanDivisor func(s []int64) int64 // Function to compute the divisor for mean graphs, or nil
- CallTree bool // Build a tree instead of a graph
DropNegative bool // Drop nodes with overall negative values
KeptNodes NodeSet // If non-nil, only use nodes in this set
Name string
Address uint64
StartLine, Lineno int
- //File string
- //OrigName string
- //Objfile string
}
// PrintableName calls the Node's Formatter function with a single space separator.
src, dest *Node
}
-func newTree(prof *profile.Profile, o *Options) (g *Graph) {
- parentNodeMap := make(map[*Node]NodeMap, len(prof.Sample))
- for _, sample := range prof.Sample {
- var w, dw int64
- w = o.SampleValue(sample.Value)
- if o.SampleMeanDivisor != nil {
- dw = o.SampleMeanDivisor(sample.Value)
- }
- if dw == 0 && w == 0 {
- continue
- }
- var parent *Node
- // Group the sample frames, based on a per-node map.
- for i := len(sample.Location) - 1; i >= 0; i-- {
- l := sample.Location[i]
- lines := l.Line
- if len(lines) == 0 {
- lines = []profile.Line{{}} // Create empty line to include location info.
- }
- for lidx := len(lines) - 1; lidx >= 0; lidx-- {
- nodeMap := parentNodeMap[parent]
- if nodeMap == nil {
- nodeMap = make(NodeMap)
- parentNodeMap[parent] = nodeMap
- }
- n := nodeMap.findOrInsertLine(l, lines[lidx], o)
- if n == nil {
- continue
- }
- n.addSample(dw, w, false)
- if parent != nil {
- parent.AddToEdgeDiv(n, dw, w, false, lidx != len(lines)-1)
- }
- parent = n
- }
- }
- if parent != nil {
- parent.addSample(dw, w, true)
- }
- }
-
- nodes := make(Nodes, len(prof.Location))
- for _, nm := range parentNodeMap {
- nodes = append(nodes, nm.nodes()...)
- }
- return selectNodesForGraph(nodes, o.DropNegative)
-}
-
// isNegative returns true if the node is considered as "negative" for the
// purposes of drop_negative.
func isNegative(n *Node) bool {
return strings.Join(s, "\n")
}
-// DiscardLowFrequencyNodes returns a set of the nodes at or over a
-// specific cum value cutoff.
-func (g *Graph) DiscardLowFrequencyNodes(nodeCutoff int64) NodeSet {
- return makeNodeSet(g.Nodes, nodeCutoff)
-}
-
-// DiscardLowFrequencyNodePtrs returns a NodePtrSet of nodes at or over a
-// specific cum value cutoff.
-func (g *Graph) DiscardLowFrequencyNodePtrs(nodeCutoff int64) NodePtrSet {
- cutNodes := getNodesAboveCumCutoff(g.Nodes, nodeCutoff)
- kept := make(NodePtrSet, len(cutNodes))
- for _, n := range cutNodes {
- kept[n] = true
- }
- return kept
-}
-
-func makeNodeSet(nodes Nodes, nodeCutoff int64) NodeSet {
- cutNodes := getNodesAboveCumCutoff(nodes, nodeCutoff)
- kept := make(NodeSet, len(cutNodes))
- for _, n := range cutNodes {
- kept[n.Info] = true
- }
- return kept
-}
-
-// getNodesAboveCumCutoff returns all the nodes which have a Cum value greater
-// than or equal to cutoff.
-func getNodesAboveCumCutoff(nodes Nodes, nodeCutoff int64) Nodes {
- cutoffNodes := make(Nodes, 0, len(nodes))
- for _, n := range nodes {
- if abs64(n.Cum) < nodeCutoff {
- continue
- }
- cutoffNodes = append(cutoffNodes, n)
- }
- return cutoffNodes
-}
-
-// TrimLowFrequencyEdges removes edges that have less than
-// the specified weight. Returns the number of edges removed
-func (g *Graph) TrimLowFrequencyEdges(edgeCutoff int64) int {
- var droppedEdges int
- for _, n := range g.Nodes {
- for _, e := range n.In {
- if abs64(e.Weight) < edgeCutoff {
- n.In.Delete(e)
- e.Src.Out.Delete(e)
- droppedEdges++
- }
- }
- }
- return droppedEdges
-}
-
-// SortNodes sorts the nodes in a graph based on a specific heuristic.
-func (g *Graph) SortNodes(cum bool, visualMode bool) {
- // Sort nodes based on requested mode
- switch {
- case visualMode:
- // Specialized sort to produce a more visually-interesting graph
- g.Nodes.Sort(EntropyOrder)
- case cum:
- g.Nodes.Sort(CumNameOrder)
- default:
- g.Nodes.Sort(FlatNameOrder)
- }
-}
-
-// SelectTopNodePtrs returns a set of the top maxNodes *Node in a graph.
-func (g *Graph) SelectTopNodePtrs(maxNodes int, visualMode bool) NodePtrSet {
- set := make(NodePtrSet)
- for _, node := range g.selectTopNodes(maxNodes, visualMode) {
- set[node] = true
- }
- return set
-}
-
-// SelectTopNodes returns a set of the top maxNodes nodes in a graph.
-func (g *Graph) SelectTopNodes(maxNodes int, visualMode bool) NodeSet {
- return makeNodeSet(g.selectTopNodes(maxNodes, visualMode), 0)
-}
-
-// selectTopNodes returns a slice of the top maxNodes nodes in a graph.
-func (g *Graph) selectTopNodes(maxNodes int, visualMode bool) Nodes {
- if maxNodes > len(g.Nodes) {
- maxNodes = len(g.Nodes)
- }
- return g.Nodes[:maxNodes]
-}
-
-// nodeSorter is a mechanism used to allow a report to be sorted
-// in different ways.
-type nodeSorter struct {
- rs Nodes
- less func(l, r *Node) bool
-}
-
-func (s nodeSorter) Len() int { return len(s.rs) }
-func (s nodeSorter) Swap(i, j int) { s.rs[i], s.rs[j] = s.rs[j], s.rs[i] }
-func (s nodeSorter) Less(i, j int) bool { return s.less(s.rs[i], s.rs[j]) }
-
-// Sort reorders a slice of nodes based on the specified ordering
-// criteria. The result is sorted in decreasing order for (absolute)
-// numeric quantities, alphabetically for text, and increasing for
-// addresses.
-func (ns Nodes) Sort(o NodeOrder) error {
- var s nodeSorter
-
- switch o {
- case FlatNameOrder:
- s = nodeSorter{ns,
- func(l, r *Node) bool {
- if iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv {
- return iv > jv
- }
- if iv, jv := l.Info.PrintableName(), r.Info.PrintableName(); iv != jv {
- return iv < jv
- }
- if iv, jv := abs64(l.Cum), abs64(r.Cum); iv != jv {
- return iv > jv
- }
- return compareNodes(l, r)
- },
- }
- case FlatCumNameOrder:
- s = nodeSorter{ns,
- func(l, r *Node) bool {
- if iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv {
- return iv > jv
- }
- if iv, jv := abs64(l.Cum), abs64(r.Cum); iv != jv {
- return iv > jv
- }
- if iv, jv := l.Info.PrintableName(), r.Info.PrintableName(); iv != jv {
- return iv < jv
- }
- return compareNodes(l, r)
- },
- }
- case NameOrder:
- s = nodeSorter{ns,
- func(l, r *Node) bool {
- if iv, jv := l.Info.Name, r.Info.Name; iv != jv {
- return iv < jv
- }
- return compareNodes(l, r)
- },
- }
- case FileOrder:
- s = nodeSorter{ns,
- func(l, r *Node) bool {
- if iv, jv := l.Info.StartLine, r.Info.StartLine; iv != jv {
- return iv < jv
- }
- return compareNodes(l, r)
- },
- }
- case AddressOrder:
- s = nodeSorter{ns,
- func(l, r *Node) bool {
- if iv, jv := l.Info.Address, r.Info.Address; iv != jv {
- return iv < jv
- }
- return compareNodes(l, r)
- },
- }
- case CumNameOrder, EntropyOrder:
- // Hold scoring for score-based ordering
- var score map[*Node]int64
- scoreOrder := func(l, r *Node) bool {
- if iv, jv := abs64(score[l]), abs64(score[r]); iv != jv {
- return iv > jv
- }
- if iv, jv := l.Info.PrintableName(), r.Info.PrintableName(); iv != jv {
- return iv < jv
- }
- if iv, jv := abs64(l.Flat), abs64(r.Flat); iv != jv {
- return iv > jv
- }
- return compareNodes(l, r)
- }
-
- switch o {
- case CumNameOrder:
- score = make(map[*Node]int64, len(ns))
- for _, n := range ns {
- score[n] = n.Cum
- }
- s = nodeSorter{ns, scoreOrder}
- case EntropyOrder:
- score = make(map[*Node]int64, len(ns))
- for _, n := range ns {
- score[n] = entropyScore(n)
- }
- s = nodeSorter{ns, scoreOrder}
- }
- default:
- return fmt.Errorf("report: unrecognized sort ordering: %d", o)
- }
- sort.Sort(s)
- return nil
-}
-
-// compareNodes compares two nodes to provide a deterministic ordering
-// between them. Two nodes cannot have the same Node.Info value.
-func compareNodes(l, r *Node) bool {
- return fmt.Sprint(l.Info) < fmt.Sprint(r.Info)
-}
-
-// entropyScore computes a score for a node representing how important
-// it is to include this node on a graph visualization. It is used to
-// sort the nodes and select which ones to display if we have more
-// nodes than desired in the graph. This number is computed by looking
-// at the flat and cum weights of the node and the incoming/outgoing
-// edges. The fundamental idea is to penalize nodes that have a simple
-// fallthrough from their incoming to the outgoing edge.
-func entropyScore(n *Node) int64 {
- score := float64(0)
-
- if len(n.In) == 0 {
- score++ // Favor entry nodes
- } else {
- score += edgeEntropyScore(n, n.In, 0)
- }
-
- if len(n.Out) == 0 {
- score++ // Favor leaf nodes
- } else {
- score += edgeEntropyScore(n, n.Out, n.Flat)
- }
-
- return int64(score*float64(n.Cum)) + n.Flat
-}
-
-// edgeEntropyScore computes the entropy value for a set of edges
-// coming in or out of a node. Entropy (as defined in information
-// theory) refers to the amount of information encoded by the set of
-// edges. A set of edges that have a more interesting distribution of
-// samples gets a higher score.
-func edgeEntropyScore(n *Node, edges EdgeMap, self int64) float64 {
- score := float64(0)
- total := self
- for _, e := range edges {
- if e.Weight > 0 {
- total += abs64(e.Weight)
- }
- }
- if total != 0 {
- for _, e := range edges {
- frac := float64(abs64(e.Weight)) / float64(total)
- score += -frac * math.Log2(frac)
- }
- if self > 0 {
- frac := float64(abs64(self)) / float64(total)
- score += -frac * math.Log2(frac)
- }
- }
- return score
-}
-
-// NodeOrder sets the ordering for a Sort operation
-type NodeOrder int
-
-// Sorting options for node sort.
-const (
- FlatNameOrder NodeOrder = iota
- FlatCumNameOrder
- CumNameOrder
- NameOrder
- FileOrder
- AddressOrder
- EntropyOrder
-)
-
// Sort returns a slice of the edges in the map, in a consistent
// order. The sort order is first based on the edge weight
// (higher-to-lower) and then by the node names to avoid flakiness.