// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package profile represents a pprof profile as a directed graph. // // This package is a simplified fork of github.com/google/pprof/internal/graph.
package profile import ( ) // Options encodes the options for constructing a graph type Options struct { 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 DropNegative bool // Drop nodes with overall negative values KeptNodes NodeSet // If non-nil, only use nodes in this set } // Nodes is an ordered collection of graph nodes. type Nodes []*Node // Node is an entry on a profiling report. It represents a unique // program location. type Node struct { // Info describes the source location associated to this node. Info NodeInfo // Function represents the function that this node belongs to. On // graphs with sub-function resolution (eg line number or // addresses), two nodes in a NodeMap that are part of the same // function have the same value of Node.Function. If the Node // represents the whole function, it points back to itself. Function *Node // Values associated to this node. Flat is exclusive to this node, // Cum includes all descendents. Flat, FlatDiv, Cum, CumDiv int64 // In and out Contains the nodes immediately reaching or reached by // this node. In, Out EdgeMap } // Graph summarizes a performance profile into a format that is // suitable for visualization. type Graph struct { Nodes Nodes } // FlatValue returns the exclusive value for this node, computing the // mean if a divisor is available. func ( *Node) () int64 { if .FlatDiv == 0 { return .Flat } return .Flat / .FlatDiv } // CumValue returns the inclusive value for this node, computing the // mean if a divisor is available. func ( *Node) () int64 { if .CumDiv == 0 { return .Cum } return .Cum / .CumDiv } // AddToEdge increases the weight of an edge between two nodes. If // there isn't such an edge one is created. func ( *Node) ( *Node, int64, , bool) { .AddToEdgeDiv(, 0, , , ) } // AddToEdgeDiv increases the weight of an edge between two nodes. If // there isn't such an edge one is created. func ( *Node) ( *Node, , int64, , bool) { if := .Out.FindTo(); != nil { .WeightDiv += .Weight += if { .Residual = true } if ! { .Inline = false } return } := &Edge{Src: , Dest: , WeightDiv: , Weight: , Residual: , Inline: } .Out.Add() .In.Add() } // NodeInfo contains the attributes for a node. type NodeInfo struct { Name string Address uint64 StartLine, Lineno int } // PrintableName calls the Node's Formatter function with a single space separator. func ( *NodeInfo) () string { return strings.Join(.NameComponents(), " ") } // NameComponents returns the components of the printable name to be used for a node. func ( *NodeInfo) () []string { var []string if .Address != 0 { = append(, fmt.Sprintf("%016x", .Address)) } if := .Name; != "" { = append(, ) } switch { case .Lineno != 0: // User requested line numbers, provide what we have. = append(, fmt.Sprintf(":%d", .Lineno)) case .Name != "": // User requested function name. It was already included. default: // Do not leave it empty if there is no information at all. = append(, "<unknown>") } return } // NodeMap maps from a node info struct to a node. It is used to merge // report entries with the same info. type NodeMap map[NodeInfo]*Node // NodeSet is a collection of node info structs. type NodeSet map[NodeInfo]bool // NodePtrSet is a collection of nodes. Trimming a graph or tree requires a set // of objects which uniquely identify the nodes to keep. In a graph, NodeInfo // works as a unique identifier; however, in a tree multiple nodes may share // identical NodeInfos. A *Node does uniquely identify a node so we can use that // instead. Though a *Node also uniquely identifies a node in a graph, // currently, during trimming, graphs are rebuilt from scratch using only the // NodeSet, so there would not be the required context of the initial graph to // allow for the use of *Node. type NodePtrSet map[*Node]bool // FindOrInsertNode takes the info for a node and either returns a matching node // from the node map if one exists, or adds one to the map if one does not. // If kept is non-nil, nodes are only added if they can be located on it. func ( NodeMap) ( NodeInfo, NodeSet) *Node { if != nil { if , := []; ! { return nil } } if , := []; { return } := &Node{ Info: , } [] = if .Address == 0 && .Lineno == 0 { // This node represents the whole function, so point Function // back to itself. .Function = return } // Find a node that represents the whole function. .Address = 0 .Lineno = 0 .Function = .(, nil) return } // EdgeMap is used to represent the incoming/outgoing edges from a node. type EdgeMap []*Edge func ( EdgeMap) ( *Node) *Edge { for , := range { if .Dest == { return } } return nil } func ( *EdgeMap) ( *Edge) { * = append(*, ) } func ( *EdgeMap) ( *Edge) { for , := range * { if == { (*)[] = (*)[len(*)-1] * = (*)[:len(*)-1] return } } } // Edge contains any attributes to be represented about edges in a graph. type Edge struct { Src, Dest *Node // The summary weight of the edge Weight, WeightDiv int64 // residual edges connect nodes that were connected through a // separate node, which has been removed from the report. Residual bool // An inline edge represents a call that was inlined into the caller. Inline bool } // WeightValue returns the weight value for this edge, normalizing if a // divisor is available. func ( *Edge) () int64 { if .WeightDiv == 0 { return .Weight } return .Weight / .WeightDiv } // NewGraph computes a graph from a profile. func ( *Profile, *Options) *Graph { , := CreateNodes(, ) := make(map[*Node]bool) := make(map[nodePair]bool) for , := range .Sample { var , int64 = .SampleValue(.Value) if .SampleMeanDivisor != nil { = .SampleMeanDivisor(.Value) } if == 0 && == 0 { continue } for := range { delete(, ) } for := range { delete(, ) } var *Node // A residual edge goes over one or more nodes that were not kept. := false // Group the sample frames, based on a global map. // Count only the last two frames as a call edge. Frames higher up // the stack are unlikely to be repeated calls (e.g. runtime.main // calling main.main). So adding weights to call edges higher up // the stack may be not reflecting the actual call edge weights // in the program. Without a branch profile this is just an // approximation. := 1 if := len(.Location) - 1; < { = } for ; >= 0; -- { := .Location[] := .get(.ID) for := len() - 1; >= 0; -- { := [] if == nil { = true continue } // Add cum weight to all nodes in stack, avoiding double counting. , := [] if ! { [] = true .addSample(, , false) } // Update edge weights for all edges in stack, avoiding double counting. if (! || ![nodePair{, }]) && != nil && != { [nodePair{, }] = true .AddToEdgeDiv(, , , , != len()-1) } = = false } } if != nil && ! { // Add flat weight to leaf node. .addSample(, , true) } } return selectNodesForGraph(, .DropNegative) } func selectNodesForGraph( Nodes, bool) *Graph { // Collect nodes into a graph. := make(Nodes, 0, len()) for , := range { if == nil { continue } if .Cum == 0 && .Flat == 0 { continue } if && isNegative() { continue } = append(, ) } return &Graph{} } type nodePair struct { src, dest *Node } // isNegative returns true if the node is considered as "negative" for the // purposes of drop_negative. func isNegative( *Node) bool { switch { case .Flat < 0: return true case .Flat == 0 && .Cum < 0: return true default: return false } } type locationMap struct { s []Nodes // a slice for small sequential IDs m map[uint64]Nodes // fallback for large IDs (unlikely) } func ( *locationMap) ( uint64, Nodes) { if < uint64(len(.s)) { .s[] = } else { .m[] = } } func ( locationMap) ( uint64) Nodes { if < uint64(len(.s)) { return .s[] } else { return .m[] } } // CreateNodes creates graph nodes for all locations in a profile. It // returns set of all nodes, plus a mapping of each location to the // set of corresponding nodes (one per location.Line). func ( *Profile, *Options) (Nodes, locationMap) { := locationMap{make([]Nodes, len(.Location)+1), make(map[uint64]Nodes)} := make(NodeMap, len(.Location)) for , := range .Location { := .Line if len() == 0 { = []Line{{}} // Create empty line to include location info. } := make(Nodes, len()) for := range { [] = .findOrInsertLine(, [], ) } .add(.ID, ) } return .nodes(), } func ( NodeMap) () Nodes { := make(Nodes, 0, len()) for , := range { = append(, ) } return } func ( NodeMap) ( *Location, Line, *Options) *Node { var string if := .Mapping; != nil && .File != "" { = .File } if := nodeInfo(, , , ); != nil { return .FindOrInsertNode(*, .KeptNodes) } return nil } func nodeInfo( *Location, Line, string, *Options) *NodeInfo { if .Function == nil { return &NodeInfo{Address: .Address} } := &NodeInfo{ Address: .Address, Lineno: int(.Line), Name: .Function.Name, } .StartLine = int(.Function.StartLine) return } // Sum adds the flat and cum values of a set of nodes. func ( Nodes) () ( int64, int64) { for , := range { += .Flat += .Cum } return } func ( *Node) (, int64, bool) { // Update sample value if { .FlatDiv += .Flat += } else { .CumDiv += .Cum += } } // String returns a text representation of a graph, for debugging purposes. func ( *Graph) () string { var []string := make(map[*Node]int, len(.Nodes)) for , := range .Nodes { [] = + 1 } for , := range .Nodes { := .Info.PrintableName() var , []int for , := range .In { = append(, [.Src]) } for , := range .Out { = append(, [.Dest]) } = append(, fmt.Sprintf("%d: %s[flat=%d cum=%d] %x -> %v ", +1, , .Flat, .Cum, , )) } return strings.Join(, "\n") } // 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. func ( EdgeMap) () []*Edge { := make(edgeList, 0, len()) for , := range { = append(, ) } sort.Sort() return } // Sum returns the total weight for a set of nodes. func ( EdgeMap) () int64 { var int64 for , := range { += .Weight } return } type edgeList []*Edge func ( edgeList) () int { return len() } func ( edgeList) (, int) bool { if [].Weight != [].Weight { return abs64([].Weight) > abs64([].Weight) } := [].Src.Info.PrintableName() := [].Src.Info.PrintableName() if != { return < } := [].Dest.Info.PrintableName() := [].Dest.Info.PrintableName() return < } func ( edgeList) (, int) { [], [] = [], [] } func abs64( int64) int64 { if < 0 { return - } return }