diff options
| author | Michael Pratt <mpratt@google.com> | 2024-01-22 22:35:04 +0000 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2024-01-22 23:07:48 +0000 |
| commit | a807295d438f9575c32a5d4fa0bbaf6b8827f344 (patch) | |
| tree | f6165e24558760960a9f051985d384b6dd2acf07 /src/cmd | |
| parent | c46966653f6144e20f8b9bccb96e7a7f1d32aeb9 (diff) | |
| download | go-a807295d438f9575c32a5d4fa0bbaf6b8827f344.tar.xz | |
Revert "cmd/preprofile: Add preprocess tool to pre-parse the profile file."
This reverts CL 529738.
Reason for revert: Breaking longtest builders
For #58102.
Fixes #65220.
Change-Id: Id295e3249da9d82f6a9e4fc571760302a1362def
Reviewed-on: https://go-review.googlesource.com/c/go/+/557460
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/cmd')
| -rw-r--r-- | src/cmd/compile/internal/base/flag.go | 2 | ||||
| -rw-r--r-- | src/cmd/compile/internal/pgo/internal/graph/graph.go | 520 | ||||
| -rw-r--r-- | src/cmd/compile/internal/pgo/irgraph.go | 207 | ||||
| -rw-r--r-- | src/cmd/compile/internal/test/pgo_inl_test.go | 35 | ||||
| -rw-r--r-- | src/cmd/compile/internal/test/testdata/pgo/inline/inline_hot.pprof.node_map | 13 | ||||
| -rw-r--r-- | src/cmd/preprofile/main.go | 224 |
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) - } -} |
