Skip to content

Commit 304e4f0

Browse files
authored
Speed-up flame graph generation by skipping unneeded work. (#881)
Previously, we used to call report.TextItems() during flame graph generation just so we could get a hand on the legend to print in the profile details box. All the work done by TextItems() to produce a trimmed graph was discarded. This change separates out the legend generation into a separate routine so that we can avoid doing the unnecessary work. Benchmark result: ``` name old time/op new time/op delta Flame-12 6.10s ± 3% 0.39s ± 4% -93.59% (p=0.000 n=10+10) ```
1 parent 7089f98 commit 304e4f0

4 files changed

Lines changed: 38 additions & 14 deletions

File tree

internal/driver/stacks.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"net/http"
2121

2222
"github.com/google/pprof/internal/measurement"
23-
"github.com/google/pprof/internal/report"
2423
)
2524

2625
// stackView generates the flamegraph view.
@@ -51,8 +50,7 @@ func (ui *webInterface) stackView(w http.ResponseWriter, req *http.Request) {
5150
}
5251
nodes[0] = "" // root is not a real node
5352

54-
_, legend := report.TextItems(rpt)
55-
ui.render(w, req, "stacks", rpt, errList, legend, webArgs{
53+
ui.render(w, req, "stacks", rpt, errList, stacks.Legend(), webArgs{
5654
Stacks: template.JS(b),
5755
Nodes: nodes,
5856
UnitDefs: measurement.UnitTypes,

internal/report/report.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ type TextItem struct {
781781
func TextItems(rpt *Report) ([]TextItem, []string) {
782782
g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
783783
rpt.selectOutputUnit(g)
784-
labels := reportLabels(rpt, g, origCount, droppedNodes, 0, false)
784+
labels := reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, 0, false)
785785

786786
var items []TextItem
787787
var flatSum int64
@@ -1064,7 +1064,7 @@ func printTree(w io.Writer, rpt *Report) error {
10641064
g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
10651065
rpt.selectOutputUnit(g)
10661066

1067-
fmt.Fprintln(w, strings.Join(reportLabels(rpt, g, origCount, droppedNodes, 0, false), "\n"))
1067+
fmt.Fprintln(w, strings.Join(reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, 0, false), "\n"))
10681068

10691069
fmt.Fprintln(w, separator)
10701070
fmt.Fprintln(w, legend)
@@ -1128,7 +1128,7 @@ func printTree(w io.Writer, rpt *Report) error {
11281128
func GetDOT(rpt *Report) (*graph.Graph, *graph.DotConfig) {
11291129
g, origCount, droppedNodes, droppedEdges := rpt.newTrimmedGraph()
11301130
rpt.selectOutputUnit(g)
1131-
labels := reportLabels(rpt, g, origCount, droppedNodes, droppedEdges, true)
1131+
labels := reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, droppedEdges, true)
11321132

11331133
c := &graph.DotConfig{
11341134
Title: rpt.options.Title,
@@ -1184,12 +1184,19 @@ func ProfileLabels(rpt *Report) []string {
11841184
return label
11851185
}
11861186

1187+
func graphTotal(g *graph.Graph) int64 {
1188+
var total int64
1189+
for _, n := range g.Nodes {
1190+
total += n.FlatValue()
1191+
}
1192+
return total
1193+
}
1194+
11871195
// reportLabels returns printable labels for a report. Includes
11881196
// profileLabels.
1189-
func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string {
1197+
func reportLabels(rpt *Report, shownTotal int64, nodeCount, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string {
11901198
nodeFraction := rpt.options.NodeFraction
11911199
edgeFraction := rpt.options.EdgeFraction
1192-
nodeCount := len(g.Nodes)
11931200

11941201
var label []string
11951202
if len(rpt.options.ProfileLabels) > 0 {
@@ -1198,17 +1205,12 @@ func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedE
11981205
label = ProfileLabels(rpt)
11991206
}
12001207

1201-
var flatSum int64
1202-
for _, n := range g.Nodes {
1203-
flatSum = flatSum + n.FlatValue()
1204-
}
1205-
12061208
if len(rpt.options.ActiveFilters) > 0 {
12071209
activeFilters := legendActiveFilters(rpt.options.ActiveFilters)
12081210
label = append(label, activeFilters...)
12091211
}
12101212

1211-
label = append(label, fmt.Sprintf("Showing nodes accounting for %s, %s of %s total", rpt.formatValue(flatSum), strings.TrimSpace(measurement.Percentage(flatSum, rpt.total)), rpt.formatValue(rpt.total)))
1213+
label = append(label, fmt.Sprintf("Showing nodes accounting for %s, %s of %s total", rpt.formatValue(shownTotal), strings.TrimSpace(measurement.Percentage(shownTotal, rpt.total)), rpt.formatValue(rpt.total)))
12121214

12131215
if rpt.total != 0 {
12141216
if droppedNodes > 0 {

internal/report/stacks.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type StackSet struct {
3434
Unit string // One of "B", "s", "GCU", or "" (if unknown)
3535
Stacks []Stack // List of stored stacks
3636
Sources []StackSource // Mapping from source index to info
37+
report *Report
3738
}
3839

3940
// Stack holds a single stack instance.
@@ -94,6 +95,7 @@ func (rpt *Report) Stacks() StackSet {
9495
Unit: unit,
9596
Stacks: []Stack{}, // Ensure non-nil
9697
Sources: []StackSource{}, // Ensure non-nil
98+
report: rpt,
9799
}
98100
s.makeInitialStacks(rpt)
99101
s.fillPlaces()
@@ -187,3 +189,8 @@ func (s *StackSet) assignColors() {
187189
s.Sources[i].Color = int(index % numColors)
188190
}
189191
}
192+
193+
// Legend returns the list of lines to display as the legend.
194+
func (s *StackSet) Legend() []string {
195+
return reportLabels(s.report, s.report.total, len(s.Sources), len(s.Sources), 0, 0, false)
196+
}

internal/report/stacks_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package report
33
import (
44
"fmt"
55
"reflect"
6+
"strings"
67
"testing"
78

89
"github.com/google/pprof/profile"
@@ -164,6 +165,22 @@ func TestStackSources(t *testing.T) {
164165
}
165166
}
166167

168+
func TestLegend(t *testing.T) {
169+
// See report_test.go for the functions available to use in tests.
170+
main, foo, bar, tee := testL[0], testL[1], testL[2], testL[3]
171+
stacks := makeTestStacks(
172+
testSample(100, bar, foo, main),
173+
testSample(200, tee, foo, main),
174+
)
175+
got := strings.Join(stacks.Legend(), "\n")
176+
expectStrings := []string{"Type: samples", "Showing nodes", "100% of 300 total"}
177+
for _, expect := range expectStrings {
178+
if !strings.Contains(got, expect) {
179+
t.Errorf("missing expected string %q in legend %q", expect, got)
180+
}
181+
}
182+
}
183+
167184
func findSource(stacks StackSet, name string) StackSource {
168185
for _, src := range stacks.Sources {
169186
if src.FullName == name {

0 commit comments

Comments
 (0)