// 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 profileimport ()// Options encodes the options for constructing a graphtypeOptionsstruct { 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.typeNodes []*Node// Node is an entry on a profiling report. It represents a unique// program location.typeNodestruct {// 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.typeGraphstruct { 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.typeNodeInfostruct { Name string Address uint64 StartLine, Lineno int}// PrintableName calls the Node's Formatter function with a single space separator.func ( *NodeInfo) () string {returnstrings.Join(.NameComponents(), " ")}// NameComponents returns the components of the printable name to be used for a node.func ( *NodeInfo) () []string {var []stringif .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.typeNodeMapmap[NodeInfo]*Node// NodeSet is a collection of node info structs.typeNodeSetmap[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.typeNodePtrSetmap[*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 , := []; ! {returnnil } }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.typeEdgeMap []*Edgefunc ( EdgeMap) ( *Node) *Edge {for , := range {if .Dest == {return } }returnnil}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.typeEdgestruct { 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. := 1if := len(.Location) - 1; < { = }for ; >= 0; -- { := .Location[] := .get(.ID)for := len() - 1; >= 0; -- { := []if == nil { = truecontinue }// 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) } }returnselectNodesForGraph(, .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:returntruecase .Flat == 0 && .Cum < 0:returntruedefault:returnfalse }}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 { := .Lineiflen() == 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 {varstringif := .Mapping; != nil && .File != "" { = .File }if := nodeInfo(, , , ); != nil {return .FindOrInsertNode(*, .KeptNodes) }returnnil}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 valueif { .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 , []intfor , := range .In { = append(, [.Src]) }for , := range .Out { = append(, [.Dest]) } = append(, fmt.Sprintf("%d: %s[flat=%d cum=%d] %x -> %v ", +1, , .Flat, .Cum, , )) }returnstrings.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 {varint64for , := range { += .Weight }return}type edgeList []*Edgefunc ( edgeList) () int {returnlen()}func ( edgeList) (, int) bool {if [].Weight != [].Weight {returnabs64([].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}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.