aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/compile/internal/base/flag.go2
-rw-r--r--src/cmd/compile/internal/pgo/internal/graph/graph.go520
-rw-r--r--src/cmd/compile/internal/pgo/irgraph.go207
-rw-r--r--src/cmd/compile/internal/test/pgo_inl_test.go35
-rw-r--r--src/cmd/compile/internal/test/testdata/pgo/inline/inline_hot.pprof.node_map13
-rw-r--r--src/cmd/preprofile/main.go224
6 files changed, 557 insertions, 444 deletions
diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go
index 5b3c3ad8c6..a3144f8fb4 100644
--- a/src/cmd/compile/internal/base/flag.go
+++ b/src/cmd/compile/internal/base/flag.go
@@ -124,7 +124,7 @@ type CmdFlags struct {
TraceProfile string "help:\"write an execution trace to `file`\""
TrimPath string "help:\"remove `prefix` from recorded source file paths\""
WB bool "help:\"enable write barrier\"" // TODO: remove
- PgoProfile string "help:\"read profile or pre-process profile from `file`\""
+ PgoProfile string "help:\"read profile from `file`\""
ErrorURL bool "help:\"print explanatory URL with error message if applicable\""
// Configuration derived from flags; not a flag itself.
diff --git a/src/cmd/compile/internal/pgo/internal/graph/graph.go b/src/cmd/compile/internal/pgo/internal/graph/graph.go
new file mode 100644
index 0000000000..4d89b1ba63
--- /dev/null
+++ b/src/cmd/compile/internal/pgo/internal/graph/graph.go
@@ -0,0 +1,520 @@
+// 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 graph represents a pprof profile as a directed graph.
+//
+// This package is a simplified fork of github.com/google/pprof/internal/graph.
+package graph
+
+import (
+ "fmt"
+ "internal/profile"
+ "sort"
+ "strings"
+)
+
+// 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 (n *Node) FlatValue() int64 {
+ if n.FlatDiv == 0 {
+ return n.Flat
+ }
+ return n.Flat / n.FlatDiv
+}
+
+// CumValue returns the inclusive value for this node, computing the
+// mean if a divisor is available.
+func (n *Node) CumValue() int64 {
+ if n.CumDiv == 0 {
+ return n.Cum
+ }
+ return n.Cum / n.CumDiv
+}
+
+// AddToEdge increases the weight of an edge between two nodes. If
+// there isn't such an edge one is created.
+func (n *Node) AddToEdge(to *Node, v int64, residual, inline bool) {
+ n.AddToEdgeDiv(to, 0, v, residual, inline)
+}
+
+// AddToEdgeDiv increases the weight of an edge between two nodes. If
+// there isn't such an edge one is created.
+func (n *Node) AddToEdgeDiv(to *Node, dv, v int64, residual, inline bool) {
+ if e := n.Out.FindTo(to); e != nil {
+ e.WeightDiv += dv
+ e.Weight += v
+ if residual {
+ e.Residual = true
+ }
+ if !inline {
+ e.Inline = false
+ }
+ return
+ }
+
+ info := &Edge{Src: n, Dest: to, WeightDiv: dv, Weight: v, Residual: residual, Inline: inline}
+ n.Out.Add(info)
+ to.In.Add(info)
+}
+
+// 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 (i *NodeInfo) PrintableName() string {
+ return strings.Join(i.NameComponents(), " ")
+}
+
+// NameComponents returns the components of the printable name to be used for a node.
+func (i *NodeInfo) NameComponents() []string {
+ var name []string
+ if i.Address != 0 {
+ name = append(name, fmt.Sprintf("%016x", i.Address))
+ }
+ if fun := i.Name; fun != "" {
+ name = append(name, fun)
+ }
+
+ switch {
+ case i.Lineno != 0:
+ // User requested line numbers, provide what we have.
+ name = append(name, fmt.Sprintf(":%d", i.Lineno))
+ case i.Name != "":
+ // User requested function name. It was already included.
+ default:
+ // Do not leave it empty if there is no information at all.
+ name = append(name, "<unknown>")
+ }
+ return name
+}
+
+// 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 (nm NodeMap) FindOrInsertNode(info NodeInfo, kept NodeSet) *Node {
+ if kept != nil {
+ if _, ok := kept[info]; !ok {
+ return nil
+ }
+ }
+
+ if n, ok := nm[info]; ok {
+ return n
+ }
+
+ n := &Node{
+ Info: info,
+ }
+ nm[info] = n
+ if info.Address == 0 && info.Lineno == 0 {
+ // This node represents the whole function, so point Function
+ // back to itself.
+ n.Function = n
+ return n
+ }
+ // Find a node that represents the whole function.
+ info.Address = 0
+ info.Lineno = 0
+ n.Function = nm.FindOrInsertNode(info, nil)
+ return n
+}
+
+// EdgeMap is used to represent the incoming/outgoing edges from a node.
+type EdgeMap []*Edge
+
+func (em EdgeMap) FindTo(n *Node) *Edge {
+ for _, e := range em {
+ if e.Dest == n {
+ return e
+ }
+ }
+ return nil
+}
+
+func (em *EdgeMap) Add(e *Edge) {
+ *em = append(*em, e)
+}
+
+func (em *EdgeMap) Delete(e *Edge) {
+ for i, edge := range *em {
+ if edge == e {
+ (*em)[i] = (*em)[len(*em)-1]
+ *em = (*em)[:len(*em)-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 (e *Edge) WeightValue() int64 {
+ if e.WeightDiv == 0 {
+ return e.Weight
+ }
+ return e.Weight / e.WeightDiv
+}
+
+// NewGraph computes a graph from a profile.
+func NewGraph(prof *profile.Profile, o *Options) *Graph {
+ nodes, locationMap := CreateNodes(prof, o)
+ seenNode := make(map[*Node]bool)
+ seenEdge := make(map[nodePair]bool)
+ 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
+ }
+ for k := range seenNode {
+ delete(seenNode, k)
+ }
+ for k := range seenEdge {
+ delete(seenEdge, k)
+ }
+ var parent *Node
+ // A residual edge goes over one or more nodes that were not kept.
+ residual := 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.
+ i := 1
+ if last := len(sample.Location) - 1; last < i {
+ i = last
+ }
+ for ; i >= 0; i-- {
+ l := sample.Location[i]
+ locNodes := locationMap.get(l.ID)
+ for ni := len(locNodes) - 1; ni >= 0; ni-- {
+ n := locNodes[ni]
+ if n == nil {
+ residual = true
+ continue
+ }
+ // Add cum weight to all nodes in stack, avoiding double counting.
+ _, sawNode := seenNode[n]
+ if !sawNode {
+ seenNode[n] = true
+ n.addSample(dw, w, false)
+ }
+ // Update edge weights for all edges in stack, avoiding double counting.
+ if (!sawNode || !seenEdge[nodePair{n, parent}]) && parent != nil && n != parent {
+ seenEdge[nodePair{n, parent}] = true
+ parent.AddToEdgeDiv(n, dw, w, residual, ni != len(locNodes)-1)
+ }
+
+ parent = n
+ residual = false
+ }
+ }
+ if parent != nil && !residual {
+ // Add flat weight to leaf node.
+ parent.addSample(dw, w, true)
+ }
+ }
+
+ return selectNodesForGraph(nodes, o.DropNegative)
+}
+
+func selectNodesForGraph(nodes Nodes, dropNegative bool) *Graph {
+ // Collect nodes into a graph.
+ gNodes := make(Nodes, 0, len(nodes))
+ for _, n := range nodes {
+ if n == nil {
+ continue
+ }
+ if n.Cum == 0 && n.Flat == 0 {
+ continue
+ }
+ if dropNegative && isNegative(n) {
+ continue
+ }
+ gNodes = append(gNodes, n)
+ }
+ return &Graph{gNodes}
+}
+
+type nodePair struct {
+ src, dest *Node
+}
+
+// isNegative returns true if the node is considered as "negative" for the
+// purposes of drop_negative.
+func isNegative(n *Node) bool {
+ switch {
+ case n.Flat < 0:
+ return true
+ case n.Flat == 0 && n.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 (l *locationMap) add(id uint64, n Nodes) {
+ if id < uint64(len(l.s)) {
+ l.s[id] = n
+ } else {
+ l.m[id] = n
+ }
+}
+
+func (l locationMap) get(id uint64) Nodes {
+ if id < uint64(len(l.s)) {
+ return l.s[id]
+ } else {
+ return l.m[id]
+ }
+}
+
+// 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 CreateNodes(prof *profile.Profile, o *Options) (Nodes, locationMap) {
+ locations := locationMap{make([]Nodes, len(prof.Location)+1), make(map[uint64]Nodes)}
+ nm := make(NodeMap, len(prof.Location))
+ for _, l := range prof.Location {
+ lines := l.Line
+ if len(lines) == 0 {
+ lines = []profile.Line{{}} // Create empty line to include location info.
+ }
+ nodes := make(Nodes, len(lines))
+ for ln := range lines {
+ nodes[ln] = nm.findOrInsertLine(l, lines[ln], o)
+ }
+ locations.add(l.ID, nodes)
+ }
+ return nm.nodes(), locations
+}
+
+func (nm NodeMap) nodes() Nodes {
+ nodes := make(Nodes, 0, len(nm))
+ for _, n := range nm {
+ nodes = append(nodes, n)
+ }
+ return nodes
+}
+
+func (nm NodeMap) findOrInsertLine(l *profile.Location, li profile.Line, o *Options) *Node {
+ var objfile string
+ if m := l.Mapping; m != nil && m.File != "" {
+ objfile = m.File
+ }
+
+ if ni := nodeInfo(l, li, objfile, o); ni != nil {
+ return nm.FindOrInsertNode(*ni, o.KeptNodes)
+ }
+ return nil
+}
+
+func nodeInfo(l *profile.Location, line profile.Line, objfile string, o *Options) *NodeInfo {
+ if line.Function == nil {
+ return &NodeInfo{Address: l.Address}
+ }
+ ni := &NodeInfo{
+ Address: l.Address,
+ Lineno: int(line.Line),
+ Name: line.Function.Name,
+ }
+ ni.StartLine = int(line.Function.StartLine)
+ return ni
+}
+
+// Sum adds the flat and cum values of a set of nodes.
+func (ns Nodes) Sum() (flat int64, cum int64) {
+ for _, n := range ns {
+ flat += n.Flat
+ cum += n.Cum
+ }
+ return
+}
+
+func (n *Node) addSample(dw, w int64, flat bool) {
+ // Update sample value
+ if flat {
+ n.FlatDiv += dw
+ n.Flat += w
+ } else {
+ n.CumDiv += dw
+ n.Cum += w
+ }
+}
+
+// String returns a text representation of a graph, for debugging purposes.
+func (g *Graph) String() string {
+ var s []string
+
+ nodeIndex := make(map[*Node]int, len(g.Nodes))
+
+ for i, n := range g.Nodes {
+ nodeIndex[n] = i + 1
+ }
+
+ for i, n := range g.Nodes {
+ name := n.Info.PrintableName()
+ var in, out []int
+
+ for _, from := range n.In {
+ in = append(in, nodeIndex[from.Src])
+ }
+ for _, to := range n.Out {
+ out = append(out, nodeIndex[to.Dest])
+ }
+ s = append(s, fmt.Sprintf("%d: %s[flat=%d cum=%d] %x -> %v ", i+1, name, n.Flat, n.Cum, in, out))
+ }
+ return strings.Join(s, "\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 (em EdgeMap) Sort() []*Edge {
+ el := make(edgeList, 0, len(em))
+ for _, w := range em {
+ el = append(el, w)
+ }
+
+ sort.Sort(el)
+ return el
+}
+
+// Sum returns the total weight for a set of nodes.
+func (em EdgeMap) Sum() int64 {
+ var ret int64
+ for _, edge := range em {
+ ret += edge.Weight
+ }
+ return ret
+}
+
+type edgeList []*Edge
+
+func (el edgeList) Len() int {
+ return len(el)
+}
+
+func (el edgeList) Less(i, j int) bool {
+ if el[i].Weight != el[j].Weight {
+ return abs64(el[i].Weight) > abs64(el[j].Weight)
+ }
+
+ from1 := el[i].Src.Info.PrintableName()
+ from2 := el[j].Src.Info.PrintableName()
+ if from1 != from2 {
+ return from1 < from2
+ }
+
+ to1 := el[i].Dest.Info.PrintableName()
+ to2 := el[j].Dest.Info.PrintableName()
+
+ return to1 < to2
+}
+
+func (el edgeList) Swap(i, j int) {
+ el[i], el[j] = el[j], el[i]
+}
+
+func abs64(i int64) int64 {
+ if i < 0 {
+ return -i
+ }
+ return i
+}
diff --git a/src/cmd/compile/internal/pgo/irgraph.go b/src/cmd/compile/internal/pgo/irgraph.go
index 9a7dadfe25..96485e33ab 100644
--- a/src/cmd/compile/internal/pgo/irgraph.go
+++ b/src/cmd/compile/internal/pgo/irgraph.go
@@ -41,20 +41,16 @@
package pgo
import (
- "bufio"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
+ "cmd/compile/internal/pgo/internal/graph"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
- "cmd/internal/bio"
"errors"
"fmt"
"internal/profile"
- "log"
"os"
"sort"
- "strconv"
- "strings"
)
// IRGraph is a call graph with nodes pointing to IRs of functions and edges
@@ -109,7 +105,6 @@ type NamedCallEdge struct {
CallerName string
CalleeName string
CallSiteOffset int // Line offset from function start line.
- CallStartLine int // Start line of the function. Can be 0 which means missing.
}
// NamedEdgeMap contains all unique call edges in the profile and their
@@ -144,52 +139,8 @@ type Profile struct {
WeightedCG *IRGraph
}
-var wantHdr = "GO PREPROFILE V1\n"
-
-func isPreProfileFile(filename string) (bool, error) {
- file, err := bio.Open(filename)
- if err != nil {
- return false, err
- }
- defer file.Close()
-
- /* check the header */
- line, err := file.ReadString('\n')
- if err != nil {
- return false, err
- }
-
- if wantHdr == line {
- return true, nil
- }
- return false, nil
-}
-
-// New generates a profile-graph from the profile or pre-processed profile.
+// New generates a profile-graph from the profile.
func New(profileFile string) (*Profile, error) {
- var profile *Profile
- var err error
- isPreProf, err := isPreProfileFile(profileFile)
- if err != nil {
- return nil, fmt.Errorf("error opening profile: %w", err)
- }
- if !isPreProf {
- profile, err = processProto(profileFile)
- if err != nil {
- log.Fatalf("%s: PGO error: %v", profileFile, err)
- }
- } else {
- profile, err = processPreprof(profileFile)
- if err != nil {
- log.Fatalf("%s: Preprocessed PGO error: %v", profileFile, err)
- }
- }
- return profile, nil
-
-}
-
-// processProto generates a profile-graph from the profile.
-func processProto(profileFile string) (*Profile, error) {
f, err := os.Open(profileFile)
if err != nil {
return nil, fmt.Errorf("error opening profile: %w", err)
@@ -224,7 +175,7 @@ func processProto(profileFile string) (*Profile, error) {
return nil, fmt.Errorf(`profile does not contain a sample index with value/type "samples/count" or cpu/nanoseconds"`)
}
- g := profile.NewGraph(p, &profile.Options{
+ g := graph.NewGraph(p, &graph.Options{
SampleValue: func(v []int64) int64 { return v[valueIndex] },
})
@@ -247,130 +198,11 @@ func processProto(profileFile string) (*Profile, error) {
}, nil
}
-// processPreprof generates a profile-graph from the pre-procesed profile.
-func processPreprof(preprofileFile string) (*Profile, error) {
- namedEdgeMap, totalWeight, err := createNamedEdgeMapFromPreprocess(preprofileFile)
- if err != nil {
- return nil, err
- }
-
- if totalWeight == 0 {
- return nil, nil // accept but ignore profile with no samples.
- }
-
- // Create package-level call graph with weights from profile and IR.
- wg := createIRGraph(namedEdgeMap)
-
- return &Profile{
- TotalWeight: totalWeight,
- NamedEdgeMap: namedEdgeMap,
- WeightedCG: wg,
- }, nil
-}
-
-func postProcessNamedEdgeMap(weight map[NamedCallEdge]int64, weightVal int64) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
- if weightVal == 0 {
- return NamedEdgeMap{}, 0, nil // accept but ignore profile with no samples.
- }
- byWeight := make([]NamedCallEdge, 0, len(weight))
- for namedEdge := range weight {
- byWeight = append(byWeight, namedEdge)
- }
- sort.Slice(byWeight, func(i, j int) bool {
- ei, ej := byWeight[i], byWeight[j]
- if wi, wj := weight[ei], weight[ej]; wi != wj {
- return wi > wj // want larger weight first
- }
- // same weight, order by name/line number
- if ei.CallerName != ej.CallerName {
- return ei.CallerName < ej.CallerName
- }
- if ei.CalleeName != ej.CalleeName {
- return ei.CalleeName < ej.CalleeName
- }
- return ei.CallSiteOffset < ej.CallSiteOffset
- })
-
- edgeMap = NamedEdgeMap{
- Weight: weight,
- ByWeight: byWeight,
- }
-
- totalWeight = weightVal
-
- return edgeMap, totalWeight, nil
-}
-
-// restore NodeMap information from a preprocessed profile.
-// The reader can refer to the format of preprocessed profile in cmd/preprofile/main.go.
-func createNamedEdgeMapFromPreprocess(preprofileFile string) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
- readFile, err := os.Open(preprofileFile)
- if err != nil {
- log.Fatal("preprofile: failed to open file " + preprofileFile)
- return
- }
- defer readFile.Close()
-
- fileScanner := bufio.NewScanner(readFile)
- fileScanner.Split(bufio.ScanLines)
- weight := make(map[NamedCallEdge]int64)
-
- if !fileScanner.Scan() {
- log.Fatal("fail to parse preprocessed profile: missing header")
- return
- }
- if fileScanner.Text()+"\n" != wantHdr {
- log.Fatal("fail to parse preprocessed profile: mismatched header")
- return
- }
-
- for fileScanner.Scan() {
- readStr := fileScanner.Text()
-
- callerName := readStr
-
- if !fileScanner.Scan() {
- log.Fatal("fail to parse preprocessed profile: missing callee")
- return
- }
- calleeName := fileScanner.Text()
-
- if !fileScanner.Scan() {
- log.Fatal("fail to parse preprocessed profile: missing weight")
- return
- }
- readStr = fileScanner.Text()
-
- split := strings.Split(readStr, " ")
-
- if len(split) == 5 {
- co, _ := strconv.Atoi(split[0])
- cs, _ := strconv.Atoi(split[1])
-
- namedEdge := NamedCallEdge{
- CallerName: callerName,
- CallSiteOffset: co - cs,
- }
-
- namedEdge.CalleeName = calleeName
- EWeight, _ := strconv.ParseInt(split[4], 10, 64)
-
- weight[namedEdge] += EWeight
- totalWeight += EWeight
- } else {
- log.Fatal("fail to parse preprocessed profile: mismatched fields.\n")
- }
- }
-
- return postProcessNamedEdgeMap(weight, totalWeight)
-
-}
-
// createNamedEdgeMap builds a map of callsite-callee edge weights from the
// profile-graph.
//
// Caller should ignore the profile if totalWeight == 0.
-func createNamedEdgeMap(g *profile.Graph) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
+func createNamedEdgeMap(g *graph.Graph) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
seenStartLine := false
// Process graph and build various node and edge maps which will
@@ -394,13 +226,42 @@ func createNamedEdgeMap(g *profile.Graph) (edgeMap NamedEdgeMap, totalWeight int
}
}
+ if totalWeight == 0 {
+ return NamedEdgeMap{}, 0, nil // accept but ignore profile with no samples.
+ }
+
if !seenStartLine {
// TODO(prattmic): If Function.start_line is missing we could
// fall back to using absolute line numbers, which is better
// than nothing.
return NamedEdgeMap{}, 0, fmt.Errorf("profile missing Function.start_line data (Go version of profiled application too old? Go 1.20+ automatically adds this to profiles)")
}
- return postProcessNamedEdgeMap(weight, totalWeight)
+
+ byWeight := make([]NamedCallEdge, 0, len(weight))
+ for namedEdge := range weight {
+ byWeight = append(byWeight, namedEdge)
+ }
+ sort.Slice(byWeight, func(i, j int) bool {
+ ei, ej := byWeight[i], byWeight[j]
+ if wi, wj := weight[ei], weight[ej]; wi != wj {
+ return wi > wj // want larger weight first
+ }
+ // same weight, order by name/line number
+ if ei.CallerName != ej.CallerName {
+ return ei.CallerName < ej.CallerName
+ }
+ if ei.CalleeName != ej.CalleeName {
+ return ei.CalleeName < ej.CalleeName
+ }
+ return ei.CallSiteOffset < ej.CallSiteOffset
+ })
+
+ edgeMap = NamedEdgeMap{
+ Weight: weight,
+ ByWeight: byWeight,
+ }
+
+ return edgeMap, totalWeight, nil
}
// initializeIRGraph builds the IRGraph by visiting all the ir.Func in decl list
diff --git a/src/cmd/compile/internal/test/pgo_inl_test.go b/src/cmd/compile/internal/test/pgo_inl_test.go
index 3aafaee197..da6c4a53d3 100644
--- a/src/cmd/compile/internal/test/pgo_inl_test.go
+++ b/src/cmd/compile/internal/test/pgo_inl_test.go
@@ -43,12 +43,7 @@ go 1.19
}
// testPGOIntendedInlining tests that specific functions are inlined.
-func testPGOIntendedInlining(t *testing.T, dir string, preprocessed ...bool) {
- defaultPGOPackValue := false
- if len(preprocessed) > 0 {
- defaultPGOPackValue = preprocessed[0]
- }
-
+func testPGOIntendedInlining(t *testing.T, dir string) {
testenv.MustHaveGoRun(t)
t.Parallel()
@@ -91,12 +86,7 @@ func testPGOIntendedInlining(t *testing.T, dir string, preprocessed ...bool) {
// Build the test with the profile. Use a smaller threshold to test.
// TODO: maybe adjust the test to work with default threshold.
- var pprof string
- if defaultPGOPackValue == false {
- pprof = filepath.Join(dir, "inline_hot.pprof")
- } else {
- pprof = filepath.Join(dir, "inline_hot.pprof.node_map")
- }
+ pprof := filepath.Join(dir, "inline_hot.pprof")
gcflag := fmt.Sprintf("-m -m -pgoprofile=%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90", pprof)
out := buildPGOInliningTest(t, dir, gcflag)
@@ -175,27 +165,6 @@ func TestPGOIntendedInlining(t *testing.T) {
}
// TestPGOIntendedInlining tests that specific functions are inlined when PGO
-// is applied to the exact source that was profiled.
-func TestPGOPreprocessInlining(t *testing.T) {
- wd, err := os.Getwd()
- if err != nil {
- t.Fatalf("error getting wd: %v", err)
- }
- srcDir := filepath.Join(wd, "testdata/pgo/inline")
-
- // Copy the module to a scratch location so we can add a go.mod.
- dir := t.TempDir()
-
- for _, file := range []string{"inline_hot.go", "inline_hot_test.go", "inline_hot.pprof.node_map"} {
- if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
- t.Fatalf("error copying %s: %v", file, err)
- }
- }
-
- testPGOIntendedInlining(t, dir, true)
-}
-
-// TestPGOIntendedInlining tests that specific functions are inlined when PGO
// is applied to the modified source.
func TestPGOIntendedInliningShiftedLines(t *testing.T) {
wd, err := os.Getwd()
diff --git a/src/cmd/compile/internal/test/testdata/pgo/inline/inline_hot.pprof.node_map b/src/cmd/compile/internal/test/testdata/pgo/inline/inline_hot.pprof.node_map
deleted file mode 100644
index bc5bc66b61..0000000000
--- a/src/cmd/compile/internal/test/testdata/pgo/inline/inline_hot.pprof.node_map
+++ /dev/null
@@ -1,13 +0,0 @@
-GO PREPROFILE V1
-example.com/pgo/inline.benchmarkB
-example.com/pgo/inline.A
-18 17 0 1 1
-example.com/pgo/inline.(*BS).NS
-example.com/pgo/inline.T
-13 53 124 129 2
-example.com/pgo/inline.(*BS).NS
-example.com/pgo/inline.T
-8 53 124 129 3
-example.com/pgo/inline.A
-example.com/pgo/inline.(*BS).NS
-7 74 1 130 129
diff --git a/src/cmd/preprofile/main.go b/src/cmd/preprofile/main.go
deleted file mode 100644
index cf747266ca..0000000000
--- a/src/cmd/preprofile/main.go
+++ /dev/null
@@ -1,224 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Preprofile handles pprof files.
-//
-// Usage:
-//
-// go tool preprofile [-v] [-o output] [-i (pprof)input]
-//
-//
-
-package main
-
-import (
- "bufio"
- "flag"
- "fmt"
- "internal/profile"
- "log"
- "os"
- "path/filepath"
- "strconv"
-)
-
-// The current Go Compiler consumes significantly long compilation time when the PGO
-// is enabled. To optimize the existing flow and reduce build time of multiple Go
-// services, we create a standalone tool, PGO preprocessor, to extract information
-// from collected profiling files and to cache the WeightedCallGraph in one time
-// fashion. By adding the new tool to the Go compiler, it will reduce the time
-// of repeated profiling file parsing and avoid WeightedCallGraph reconstruction
-// in current Go Compiler.
-// The format of the pre-processed output is as follows.
-//
-// Header
-// caller_name
-// callee_name
-// "call site offset" "caller's start line number" "flat" "cum" "call edge weight"
-// ...
-// caller_name
-// callee_name
-// "call site offset" "caller's start line number" "flat" "cum" "call edge weight"
-
-func usage() {
- fmt.Fprintf(os.Stderr, "MUST have (pprof) input file \n")
- fmt.Fprintf(os.Stderr, "usage: go tool preprofile [-v] [-o output] [-i (pprof)input] \n\n")
- flag.PrintDefaults()
- os.Exit(2)
-}
-
-type NodeMapKey struct {
- CallerName string
- CalleeName string
- CallSiteOffset int // Line offset from function start line.
- CallStartLine int // Start line of the function. Can be 0 which means missing.
-}
-
-type Weights struct {
- NFlat int64
- NCum int64
- EWeight int64
-}
-
-func readPprofFile(profileFile string, outputFile string, verbose bool) bool {
- // open the pprof profile file
- f, err := os.Open(profileFile)
- if err != nil {
- log.Fatal("failed to open file " + profileFile)
- return false
- }
- defer f.Close()
- p, err := profile.Parse(f)
- if err != nil {
- log.Fatal("failed to Parse profile file.")
- return false
- }
-
- if len(p.Sample) == 0 {
- // We accept empty profiles, but there is nothing to do.
- return false
- }
-
- valueIndex := -1
- for i, s := range p.SampleType {
- // Samples count is the raw data collected, and CPU nanoseconds is just
- // a scaled version of it, so either one we can find is fine.
- if (s.Type == "samples" && s.Unit == "count") ||
- (s.Type == "cpu" && s.Unit == "nanoseconds") {
- valueIndex = i
- break
- }
- }
-
- if valueIndex == -1 {
- log.Fatal("failed to find CPU samples count or CPU nanoseconds value-types in profile.")
- return false
- }
-
- // The processing here is equivalent to cmd/compile/internal/pgo.createNamedEdgeMap.
- g := profile.NewGraph(p, &profile.Options{
- SampleValue: func(v []int64) int64 { return v[valueIndex] },
- })
-
- nFlat := make(map[string]int64)
- nCum := make(map[string]int64)
-
- // Accummulate weights for the same node.
- for _, n := range g.Nodes {
- canonicalName := n.Info.Name
- nFlat[canonicalName] += n.FlatValue()
- nCum[canonicalName] += n.CumValue()
- }
-
- TotalNodeWeight := int64(0)
- TotalEdgeWeight := int64(0)
-
- NodeMap := make(map[NodeMapKey]*Weights)
- NodeWeightMap := make(map[string]int64)
-
- for _, n := range g.Nodes {
- TotalNodeWeight += n.FlatValue()
- canonicalName := n.Info.Name
- // Create the key to the nodeMapKey.
- nodeinfo := NodeMapKey{
- CallerName: canonicalName,
- CallSiteOffset: n.Info.Lineno - n.Info.StartLine,
- CallStartLine: n.Info.StartLine,
- }
-
- if nodeinfo.CallStartLine == 0 {
- if verbose {
- log.Println("[PGO] warning: " + canonicalName + " relative line number is missing from the profile")
- }
- }
-
- for _, e := range n.Out {
- TotalEdgeWeight += e.WeightValue()
- nodeinfo.CalleeName = e.Dest.Info.Name
- if w, ok := NodeMap[nodeinfo]; ok {
- w.EWeight += e.WeightValue()
- } else {
- weights := new(Weights)
- weights.NFlat = nFlat[canonicalName]
- weights.NCum = nCum[canonicalName]
- weights.EWeight = e.WeightValue()
- NodeMap[nodeinfo] = weights
- }
- }
- }
-
- for _, n := range g.Nodes {
- lineno := fmt.Sprintf("%v", n.Info.Lineno)
- canonicalName := n.Info.Name + "-" + lineno
- if _, ok := (NodeWeightMap)[canonicalName]; ok {
- (NodeWeightMap)[canonicalName] += n.CumValue()
- } else {
- (NodeWeightMap)[canonicalName] = n.CumValue()
- }
- }
-
- var fNodeMap *os.File
- if outputFile == "" {
- fNodeMap = os.Stdout
- } else {
- dirPath := filepath.Dir(outputFile)
- _, err := os.Stat(dirPath)
- if err != nil {
- log.Fatal("Directory does not exist: ", dirPath)
- }
- base := filepath.Base(outputFile)
- outputFile = filepath.Join(dirPath, base)
-
- // write out NodeMap to a file
- fNodeMap, err = os.Create(outputFile)
- if err != nil {
- log.Fatal("Error creating output file:", err)
- return false
- }
-
- defer fNodeMap.Close() // Close the file when done writing
- }
-
- w := bufio.NewWriter(fNodeMap)
- w.WriteString("GO PREPROFILE V1\n")
- count := 1
- separator := " "
- for key, element := range NodeMap {
- line := key.CallerName + "\n"
- w.WriteString(line)
- line = key.CalleeName + "\n"
- w.WriteString(line)
- line = strconv.Itoa(key.CallSiteOffset)
- line = line + separator + strconv.Itoa(key.CallStartLine)
- line = line + separator + strconv.FormatInt(element.NFlat, 10)
- line = line + separator + strconv.FormatInt(element.NCum, 10)
- line = line + separator + strconv.FormatInt(element.EWeight, 10) + "\n"
- w.WriteString(line)
- w.Flush()
- count += 1
- }
-
- if TotalNodeWeight == 0 || TotalEdgeWeight == 0 {
- return false
- }
-
- return true
-}
-
-var dumpCode = flag.String("o", "", "dump output file ")
-var input = flag.String("i", "", "input pprof file ")
-var verbose = flag.Bool("v", false, "verbose log")
-
-func main() {
- log.SetFlags(0)
- log.SetPrefix("preprofile: ")
-
- flag.Usage = usage
- flag.Parse()
- if *input == "" {
- usage()
- } else {
- readPprofFile(*input, *dumpCode, *verbose)
- }
-}