Skip to content

Commit

Permalink
Speed-up flame graph generation by skipping unneeded work. (#881)
Browse files Browse the repository at this point in the history
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)
```
  • Loading branch information
ghemawat authored Jul 22, 2024
1 parent 7089f98 commit 304e4f0
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 14 deletions.
4 changes: 1 addition & 3 deletions internal/driver/stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"net/http"

"github.com/google/pprof/internal/measurement"
"github.com/google/pprof/internal/report"
)

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

_, legend := report.TextItems(rpt)
ui.render(w, req, "stacks", rpt, errList, legend, webArgs{
ui.render(w, req, "stacks", rpt, errList, stacks.Legend(), webArgs{
Stacks: template.JS(b),
Nodes: nodes,
UnitDefs: measurement.UnitTypes,
Expand Down
24 changes: 13 additions & 11 deletions internal/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ type TextItem struct {
func TextItems(rpt *Report) ([]TextItem, []string) {
g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
rpt.selectOutputUnit(g)
labels := reportLabels(rpt, g, origCount, droppedNodes, 0, false)
labels := reportLabels(rpt, graphTotal(g), len(g.Nodes), origCount, droppedNodes, 0, false)

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

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

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

c := &graph.DotConfig{
Title: rpt.options.Title,
Expand Down Expand Up @@ -1184,12 +1184,19 @@ func ProfileLabels(rpt *Report) []string {
return label
}

func graphTotal(g *graph.Graph) int64 {
var total int64
for _, n := range g.Nodes {
total += n.FlatValue()
}
return total
}

// reportLabels returns printable labels for a report. Includes
// profileLabels.
func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string {
func reportLabels(rpt *Report, shownTotal int64, nodeCount, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string {
nodeFraction := rpt.options.NodeFraction
edgeFraction := rpt.options.EdgeFraction
nodeCount := len(g.Nodes)

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

var flatSum int64
for _, n := range g.Nodes {
flatSum = flatSum + n.FlatValue()
}

if len(rpt.options.ActiveFilters) > 0 {
activeFilters := legendActiveFilters(rpt.options.ActiveFilters)
label = append(label, activeFilters...)
}

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)))
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)))

if rpt.total != 0 {
if droppedNodes > 0 {
Expand Down
7 changes: 7 additions & 0 deletions internal/report/stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type StackSet struct {
Unit string // One of "B", "s", "GCU", or "" (if unknown)
Stacks []Stack // List of stored stacks
Sources []StackSource // Mapping from source index to info
report *Report
}

// Stack holds a single stack instance.
Expand Down Expand Up @@ -94,6 +95,7 @@ func (rpt *Report) Stacks() StackSet {
Unit: unit,
Stacks: []Stack{}, // Ensure non-nil
Sources: []StackSource{}, // Ensure non-nil
report: rpt,
}
s.makeInitialStacks(rpt)
s.fillPlaces()
Expand Down Expand Up @@ -187,3 +189,8 @@ func (s *StackSet) assignColors() {
s.Sources[i].Color = int(index % numColors)
}
}

// Legend returns the list of lines to display as the legend.
func (s *StackSet) Legend() []string {
return reportLabels(s.report, s.report.total, len(s.Sources), len(s.Sources), 0, 0, false)
}
17 changes: 17 additions & 0 deletions internal/report/stacks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package report
import (
"fmt"
"reflect"
"strings"
"testing"

"github.com/google/pprof/profile"
Expand Down Expand Up @@ -164,6 +165,22 @@ func TestStackSources(t *testing.T) {
}
}

func TestLegend(t *testing.T) {
// See report_test.go for the functions available to use in tests.
main, foo, bar, tee := testL[0], testL[1], testL[2], testL[3]
stacks := makeTestStacks(
testSample(100, bar, foo, main),
testSample(200, tee, foo, main),
)
got := strings.Join(stacks.Legend(), "\n")
expectStrings := []string{"Type: samples", "Showing nodes", "100% of 300 total"}
for _, expect := range expectStrings {
if !strings.Contains(got, expect) {
t.Errorf("missing expected string %q in legend %q", expect, got)
}
}
}

func findSource(stacks StackSet, name string) StackSource {
for _, src := range stacks.Sources {
if src.FullName == name {
Expand Down

0 comments on commit 304e4f0

Please sign in to comment.