From e9f91f2129a4f555d1ae1c47ba1161fb8e54a7c3 Mon Sep 17 00:00:00 2001 From: Anton Kolesnikov Date: Sun, 25 May 2025 14:25:52 +0800 Subject: [PATCH 1/2] perf: pprof truncation without symbols --- pkg/phlaredb/symdb/resolver.go | 8 -- pkg/phlaredb/symdb/resolver_pprof.go | 32 ++++- pkg/phlaredb/symdb/resolver_pprof_go_pgo.go | 5 - ...f_tree.go => resolver_pprof_tree_funcs.go} | 52 +++---- .../symdb/resolver_pprof_tree_locs.go | 134 ++++++++++++++++++ .../testdata/profile.loc.truncated.pb.gz | Bin 0 -> 6427 bytes 6 files changed, 180 insertions(+), 51 deletions(-) rename pkg/phlaredb/symdb/{resolver_pprof_tree.go => resolver_pprof_tree_funcs.go} (85%) create mode 100644 pkg/phlaredb/symdb/resolver_pprof_tree_locs.go create mode 100644 pkg/phlaredb/symdb/testdata/profile.loc.truncated.pb.gz diff --git a/pkg/phlaredb/symdb/resolver.go b/pkg/phlaredb/symdb/resolver.go index aa5438577a..8405047ba8 100644 --- a/pkg/phlaredb/symdb/resolver.go +++ b/pkg/phlaredb/symdb/resolver.go @@ -41,14 +41,6 @@ type Resolver struct { type ResolverOption func(*Resolver) -// WithResolverMaxConcurrent specifies how many partitions -// can be resolved concurrently. -func WithResolverMaxConcurrent(n int) ResolverOption { - return func(r *Resolver) { - r.c = n - } -} - // WithResolverMaxNodes specifies the desired maximum number // of nodes the resulting profile should include. func WithResolverMaxNodes(n int64) ResolverOption { diff --git a/pkg/phlaredb/symdb/resolver_pprof.go b/pkg/phlaredb/symdb/resolver_pprof.go index ba1dd4639e..9b8a549f5d 100644 --- a/pkg/phlaredb/symdb/resolver_pprof.go +++ b/pkg/phlaredb/symdb/resolver_pprof.go @@ -2,6 +2,7 @@ package symdb import ( "context" + "unsafe" googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" @@ -35,11 +36,18 @@ func buildPprof( // profile can exist. Otherwise, build an empty profile. case !selection.HasValidCallSite(): return b.buildPprof(), nil - // Truncation is applicable when there is an explicit - // limit on the number of the nodes in the profile, or - // if stack traces should be filtered by the call site. - case maxNodes > 0 || len(selection.callSite) > 0: - b = &pprofTree{maxNodes: maxNodes, selection: selection} + // Stack trace filtering is only possible when the profile + // has functions (symbolized); for that, we first build a + // function call tree and then trim nodes according to the + // max nodes limit. + case len(selection.callSite) > 0: + b = &pprofFuncTree{maxNodes: maxNodes, selection: selection} + // Otherwise, if the max nodes limit is provided, we rely on + // the location tree, and ignore symbols altogether. Note that + // the result of truncation may be slightly different compared + // to the function tree. + case maxNodes > 0: + b = &pprofLocTree{maxNodes: maxNodes} } b.init(symbols, samples) if err := symbols.Stacktraces.ResolveStacktraceLocations(ctx, b, samples.StacktraceIDs); err != nil { @@ -261,3 +269,17 @@ func copyStrings(profile *googlev1.Profile, symbols *Symbols, lut []uint32) { f.SystemName = int64(lut[f.SystemName+o]) } } + +func uint64sliceString(u []uint64) string { + if len(u) == 0 { + return "" + } + return unsafe.String((*byte)(unsafe.Pointer(&u[0])), len(u)*8) +} + +func int32sliceString(u []int32) string { + if len(u) == 0 { + return "" + } + return unsafe.String((*byte)(unsafe.Pointer(&u[0])), len(u)*4) +} diff --git a/pkg/phlaredb/symdb/resolver_pprof_go_pgo.go b/pkg/phlaredb/symdb/resolver_pprof_go_pgo.go index 0ce338d7da..9cb15b111c 100644 --- a/pkg/phlaredb/symdb/resolver_pprof_go_pgo.go +++ b/pkg/phlaredb/symdb/resolver_pprof_go_pgo.go @@ -2,7 +2,6 @@ package symdb import ( "strings" - "unsafe" googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" @@ -57,10 +56,6 @@ func (r *pprofGoPGO) InsertStacktrace(_ uint32, locations []int32) { r.cur++ } -func int32sliceString(u []int32) string { - return unsafe.String((*byte)(unsafe.Pointer(&u[0])), len(u)*4) -} - func (r *pprofGoPGO) buildPprof() *googlev1.Profile { createSampleTypeStub(&r.profile) r.appendSamples() diff --git a/pkg/phlaredb/symdb/resolver_pprof_tree.go b/pkg/phlaredb/symdb/resolver_pprof_tree_funcs.go similarity index 85% rename from pkg/phlaredb/symdb/resolver_pprof_tree.go rename to pkg/phlaredb/symdb/resolver_pprof_tree_funcs.go index da7de651a4..6695e62734 100644 --- a/pkg/phlaredb/symdb/resolver_pprof_tree.go +++ b/pkg/phlaredb/symdb/resolver_pprof_tree_funcs.go @@ -1,8 +1,6 @@ package symdb import ( - "unsafe" - googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" "github.com/grafana/pyroscope/pkg/model" schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" @@ -14,7 +12,7 @@ const ( truncatedNodeName = "other" ) -type pprofTree struct { +type pprofFuncTree struct { symbols *Symbols samples *schemav1.Samples profile googlev1.Profile @@ -44,12 +42,12 @@ type pprofTree struct { } type truncatedStacktraceSample struct { - stacktraceID uint32 - functionNodeIdx int32 - value int64 + stacktraceID uint32 + nodeIdx int32 + value int64 } -func (r *pprofTree) init(symbols *Symbols, samples schemav1.Samples) { +func (r *pprofFuncTree) init(symbols *Symbols, samples schemav1.Samples) { r.symbols = symbols r.samples = &samples // We optimistically assume that each stacktrace has only @@ -64,21 +62,21 @@ func (r *pprofTree) init(symbols *Symbols, samples schemav1.Samples) { } } -func (r *pprofTree) InsertStacktrace(stacktraceID uint32, locations []int32) { +func (r *pprofFuncTree) InsertStacktrace(stacktraceID uint32, locations []int32) { value := int64(r.samples.Values[r.cur]) r.cur++ functions, ok := r.fnNames(locations) if ok { functionNodeIdx := r.functionTree.Insert(functions, value) r.stacktraces = append(r.stacktraces, truncatedStacktraceSample{ - stacktraceID: stacktraceID, - functionNodeIdx: functionNodeIdx, - value: value, + stacktraceID: stacktraceID, + nodeIdx: functionNodeIdx, + value: value, }) } } -func (r *pprofTree) locFunctions(locations []int32) ([]int32, bool) { +func (r *pprofFuncTree) locFunctions(locations []int32) ([]int32, bool) { r.functionsBuf = r.functionsBuf[:0] for i := 0; i < len(locations); i++ { lines := r.symbols.Locations[locations[i]].Line @@ -89,7 +87,7 @@ func (r *pprofTree) locFunctions(locations []int32) ([]int32, bool) { return r.functionsBuf, true } -func (r *pprofTree) locFunctionsFiltered(locations []int32) ([]int32, bool) { +func (r *pprofFuncTree) locFunctionsFiltered(locations []int32) ([]int32, bool) { r.functionsBuf = r.functionsBuf[:0] var pos int pathLen := int(r.selection.depth) @@ -115,7 +113,7 @@ func (r *pprofTree) locFunctionsFiltered(locations []int32) ([]int32, bool) { return r.functionsBuf, true } -func (r *pprofTree) buildPprof() *googlev1.Profile { +func (r *pprofFuncTree) buildPprof() *googlev1.Profile { r.markNodesForTruncation() for _, n := range r.stacktraces { r.addSample(n) @@ -132,7 +130,7 @@ func (r *pprofTree) buildPprof() *googlev1.Profile { return &r.profile } -func (r *pprofTree) markNodesForTruncation() { +func (r *pprofFuncTree) markNodesForTruncation() { minValue := r.functionTree.MinValue(r.maxNodes) if minValue == 0 { return @@ -145,11 +143,11 @@ func (r *pprofTree) markNodesForTruncation() { } } -func (r *pprofTree) addSample(n truncatedStacktraceSample) { +func (r *pprofFuncTree) addSample(n truncatedStacktraceSample) { // Find the original stack trace and remove truncated // locations based on the truncated functions. var off int - r.functionsBuf, off = r.buildFunctionsStack(r.functionsBuf, n.functionNodeIdx) + r.functionsBuf, off = r.buildFunctionsStack(r.functionsBuf, n.nodeIdx) if off < 0 { // The stack has no functions without the truncation mark. r.fullyTruncated += n.value @@ -182,7 +180,7 @@ func (r *pprofTree) addSample(n truncatedStacktraceSample) { r.sampleMap[uint64sliceString(locationsCopy)] = s } -func (r *pprofTree) buildFunctionsStack(funcs []int32, idx int32) ([]int32, int) { +func (r *pprofFuncTree) buildFunctionsStack(funcs []int32, idx int32) ([]int32, int) { offset := -1 funcs = funcs[:0] for i := idx; i > 0; i = r.functionTree.Nodes[i].Parent { @@ -196,7 +194,7 @@ func (r *pprofTree) buildFunctionsStack(funcs []int32, idx int32) ([]int32, int) return funcs, offset } -func (r *pprofTree) createSamples() { +func (r *pprofFuncTree) createSamples() { samples := len(r.sampleMap) r.profile.Sample = make([]*googlev1.Sample, samples, samples+1) var i int @@ -216,10 +214,7 @@ func truncateLocations(locations []uint64, functions []int32, offset int, symbol f := len(functions) l := len(locations) for ; l > 0 && f >= offset; l-- { - location := symbols.Locations[locations[l-1]] - for j := len(location.Line) - 1; j >= 0; j-- { - f-- - } + f -= len(symbols.Locations[locations[l-1]].Line) } if l > 0 { locations[0] = truncationMark @@ -228,15 +223,7 @@ func truncateLocations(locations []uint64, functions []int32, offset int, symbol return locations[l:] } -func uint64sliceString(u []uint64) string { - if len(u) == 0 { - return "" - } - p := (*byte)(unsafe.Pointer(&u[0])) - return unsafe.String(p, len(u)*8) -} - -func (r *pprofTree) createStubSample() { +func (r *pprofFuncTree) createStubSample() { r.profile.Sample = append(r.profile.Sample, &googlev1.Sample{ LocationId: []uint64{truncationMark}, Value: []int64{r.fullyTruncated}, @@ -261,7 +248,6 @@ func createLocationStub(profile *googlev1.Profile) { SystemName: stubNodeNameIdx, } profile.Function = append(profile.Function, stubFn) - // in the case there is no mapping, we need to create one if len(profile.Mapping) == 0 { profile.Mapping = append(profile.Mapping, &googlev1.Mapping{Id: 1}) } diff --git a/pkg/phlaredb/symdb/resolver_pprof_tree_locs.go b/pkg/phlaredb/symdb/resolver_pprof_tree_locs.go new file mode 100644 index 0000000000..65b984b43d --- /dev/null +++ b/pkg/phlaredb/symdb/resolver_pprof_tree_locs.go @@ -0,0 +1,134 @@ +package symdb + +import ( + googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" + "github.com/grafana/pyroscope/pkg/model" + schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" +) + +type pprofLocTree struct { + symbols *Symbols + samples *schemav1.Samples + profile googlev1.Profile + lut []uint32 + cur int + + maxNodes int64 + truncated int + // Sum of fully truncated samples. + fullyTruncated int64 + + locTree *model.StacktraceTree + stacktraces []truncatedStacktraceSample + locationsBuf []int32 + sampleMap map[string]*googlev1.Sample +} + +func (r *pprofLocTree) init(symbols *Symbols, samples schemav1.Samples) { + r.symbols = symbols + r.samples = &samples + // We optimistically assume that each stacktrace has only + // 2 unique nodes. For pathological cases it may exceed 10. + r.locTree = model.NewStacktraceTree(samples.Len() * 2) + r.stacktraces = make([]truncatedStacktraceSample, 0, samples.Len()) + r.sampleMap = make(map[string]*googlev1.Sample, samples.Len()) +} + +func (r *pprofLocTree) InsertStacktrace(_ uint32, locations []int32) { + value := int64(r.samples.Values[r.cur]) + r.cur++ + locNodeIdx := r.locTree.Insert(locations, value) + r.stacktraces = append(r.stacktraces, truncatedStacktraceSample{ + nodeIdx: locNodeIdx, + value: value, + }) +} + +func (r *pprofLocTree) buildPprof() *googlev1.Profile { + r.markNodesForTruncation() + for _, n := range r.stacktraces { + r.addSample(n) + } + r.createSamples() + createSampleTypeStub(&r.profile) + copyLocations(&r.profile, r.symbols, r.lut) + copyFunctions(&r.profile, r.symbols, r.lut) + copyMappings(&r.profile, r.symbols, r.lut) + copyStrings(&r.profile, r.symbols, r.lut) + if r.truncated > 0 || r.fullyTruncated > 0 { + createLocationStub(&r.profile) + } + return &r.profile +} + +func (r *pprofLocTree) markNodesForTruncation() { + // We preserve more nodes than requested to preserve more + // locations with inlined functions. The multiplier is + // chosen empirically; it should be roughly equal to the + // ratio of nodes in the location tree to the nodes in the + // function tree (after truncation). + minValue := r.locTree.MinValue(r.maxNodes * 4) + if minValue == 0 { + return + } + for i := range r.locTree.Nodes { + if r.locTree.Nodes[i].Total < minValue { + r.locTree.Nodes[i].Location |= truncationMark + r.truncated++ + } + } +} + +func (r *pprofLocTree) addSample(n truncatedStacktraceSample) { + r.locationsBuf = r.buildLocationsStack(r.locationsBuf, n.nodeIdx) + if len(r.locationsBuf) == 0 { + // The stack has no functions without the truncation mark. + r.fullyTruncated += n.value + return + } + if s, ok := r.sampleMap[int32sliceString(r.locationsBuf)]; ok { + s.Value[0] += n.value + return + } + + locationsCopy := make([]uint64, len(r.locationsBuf)) + for i := 0; i < len(r.locationsBuf); i++ { + locationsCopy[i] = uint64(r.locationsBuf[i]) + } + + s := &googlev1.Sample{LocationId: locationsCopy, Value: []int64{n.value}} + r.profile.Sample = append(r.profile.Sample, s) + + k := make([]int32, len(r.locationsBuf)) + copy(k, r.locationsBuf) + r.sampleMap[int32sliceString(k)] = s +} + +func (r *pprofLocTree) buildLocationsStack(dst []int32, idx int32) []int32 { + dst = dst[:0] + for i := idx; i > 0; i = r.locTree.Nodes[i].Parent { + if r.locTree.Nodes[i].Location&truncationMark == 0 { + dst = append(dst, r.locTree.Nodes[i].Location&^truncationMark) + } else if len(dst) == 0 { + dst = append(dst, truncationMark) + } + } + if len(dst) == 1 && dst[0] == truncationMark { + return dst[:0] + } + return dst +} + +func (r *pprofLocTree) createSamples() { + samples := len(r.sampleMap) + r.profile.Sample = make([]*googlev1.Sample, 0, samples+1) + for _, s := range r.sampleMap { + r.profile.Sample = append(r.profile.Sample, s) + } + if r.fullyTruncated > 0 { + r.profile.Sample = append(r.profile.Sample, &googlev1.Sample{ + LocationId: []uint64{truncationMark}, + Value: []int64{r.fullyTruncated}, + }) + } +} diff --git a/pkg/phlaredb/symdb/testdata/profile.loc.truncated.pb.gz b/pkg/phlaredb/symdb/testdata/profile.loc.truncated.pb.gz new file mode 100644 index 0000000000000000000000000000000000000000..5cb3eb39c5667be775875b4406d56f894c88bc33 GIT binary patch literal 6427 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 Date: Thu, 5 Jun 2025 16:55:03 +0800 Subject: [PATCH 2/2] fix(v2): add example test for manual testing (to be removed) --- pkg/phlaredb/symdb/resolver_pprof_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/phlaredb/symdb/resolver_pprof_test.go b/pkg/phlaredb/symdb/resolver_pprof_test.go index f67707e5a0..95d7ef0a84 100644 --- a/pkg/phlaredb/symdb/resolver_pprof_test.go +++ b/pkg/phlaredb/symdb/resolver_pprof_test.go @@ -2,6 +2,7 @@ package symdb import ( "context" + "os" "slices" "sort" "testing" @@ -12,6 +13,7 @@ import ( googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" v1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" + "github.com/grafana/pyroscope/pkg/pprof" ) func Test_memory_Resolver_ResolvePprof(t *testing.T) { @@ -25,6 +27,19 @@ func Test_memory_Resolver_ResolvePprof(t *testing.T) { require.Equal(t, expectedFingerprint, pprofFingerprint(resolved, 0)) } +func Test_memory_Resolver_ResolvePprof_REMOVE_ME(t *testing.T) { + s := newMemSuite(t, [][]string{{"testdata/profile.pb.gz"}}) + + r := NewResolver(context.Background(), s.db, WithResolverMaxNodes(64)) + defer r.Release() + r.AddSamples(0, s.indexed[0][0].Samples) + resolved, err := r.Pprof() + require.NoError(t, err) + + b, err := pprof.Marshal(resolved, true) + assert.NoError(t, os.WriteFile("testdata/profile.pb.truncated", b, 0644)) +} + func Test_block_Resolver_ResolvePprof_multiple_partitions(t *testing.T) { s := newBlockSuite(t, [][]string{ {"testdata/profile.pb.gz"},