Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: optimize pprof parsing in pull mode. #628

Merged
merged 4 commits into from Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
103 changes: 103 additions & 0 deletions pkg/convert/profile_extra_bench_test.go
Expand Up @@ -3,10 +3,12 @@ package convert
import (
"bytes"
"compress/gzip"
"fmt"
"os"
"testing"

"github.com/pyroscope-io/pyroscope/pkg/agent/spy"
"github.com/pyroscope-io/pyroscope/pkg/storage/tree"
)

func BenchmarkProfile_Get(b *testing.B) {
Expand All @@ -22,3 +24,104 @@ func BenchmarkProfile_Get(b *testing.B) {
}
})
}

// parse emulates the parsing work needed to write profiles, without the writing part.
func parse(p *tree.Profile) int {
var b bytes.Buffer
for _, s := range p.Sample {
for i := len(s.LocationId) - 1; i >= 0; i-- {
loc, ok := tree.FindLocation(p, s.LocationId[i])
if !ok {
continue
}
for j := len(loc.Line) - 1; j >= 0; j-- {
fn, found := tree.FindFunction(p, loc.Line[j].FunctionId)
if !found {
continue
}
if b.Len() > 0 {
_ = b.WriteByte(';')
}
_, _ = b.WriteString(p.StringTable[fn.Name])
}
}
}
return len(b.Bytes())
}

// parseWithCache is like parse, but locations and functions are tabled first.
func parseWithCache(p *tree.Profile) int {
finder := tree.NewFinder(p)
var b bytes.Buffer
for _, s := range p.Sample {
for i := len(s.LocationId) - 1; i >= 0; i-- {
loc, ok := finder.FindLocation(s.LocationId[i])
if !ok {
continue
}
for j := len(loc.Line) - 1; j >= 0; j-- {
fn, ok := finder.FindFunction(loc.Line[j].FunctionId)
if !ok {
continue
}
if b.Len() > 0 {
_ = b.WriteByte(';')
}
_, _ = b.WriteString(p.StringTable[fn.Name])
}
}
}
return len(b.Bytes())
}

func BenchmarkProfile_ParseNoCache(b *testing.B) {
buf, _ := os.ReadFile("testdata/cpu.pprof")
p, _ := ParsePprof(bytes.NewReader(buf))

b.ResetTimer()

b.Run(fmt.Sprintf("Locations: %d, functions %d", len(p.Location), len(p.Function)), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = parse(p)
}
})
}

func BenchmarkProfile_ParseWithCache(b *testing.B) {
buf, _ := os.ReadFile("testdata/cpu.pprof")
p, _ := ParsePprof(bytes.NewReader(buf))

b.ResetTimer()

b.Run(fmt.Sprintf("Locations: %d, functions %d", len(p.Location), len(p.Function)), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = parseWithCache(p)
}
})
}

func BenchmarkProfile_ParseNoCache_Big(b *testing.B) {
buf, _ := os.ReadFile("testdata/cpu-big.pprof")
p, _ := ParsePprof(bytes.NewReader(buf))

b.ResetTimer()

b.Run(fmt.Sprintf("Locations: %d, functions %d", len(p.Location), len(p.Function)), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = parse(p)
}
})
}

func BenchmarkProfile_ParseWithCache_Big(b *testing.B) {
buf, _ := os.ReadFile("testdata/cpu-big.pprof")
p, _ := ParsePprof(bytes.NewReader(buf))

b.ResetTimer()

b.Run(fmt.Sprintf("Locations %d, functions %d", len(p.Location), len(p.Function)), func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = parseWithCache(p)
}
})
}