Skip to content

Commit 81e0e5f

Browse files
authored
Revert "fix: correct HasFunctions flags for mixed symbolization profiles (#4413)" (#4457)
This reverts commit 7ee3759.
1 parent fac6ecc commit 81e0e5f

File tree

2 files changed

+49
-192
lines changed

2 files changed

+49
-192
lines changed

pkg/symbolizer/symbolizer.go

Lines changed: 35 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -70,67 +70,53 @@ func (s *Symbolizer) SymbolizePprof(ctx context.Context, profile *googlev1.Profi
7070
}
7171
mappingsToSymbolize[uint64(i+1)] = true
7272
}
73-
var allSymbolizedLocs []symbolizedLocation
73+
if len(mappingsToSymbolize) == 0 {
74+
return nil
75+
}
76+
77+
locationsByMapping, err := s.groupLocationsByMapping(profile, mappingsToSymbolize)
78+
if err != nil {
79+
return fmt.Errorf("grouping locations by mapping: %w", err)
80+
}
81+
7482
stringMap := make(map[string]int64, len(profile.StringTable))
7583
for i, str := range profile.StringTable {
7684
stringMap[str] = int64(i)
7785
}
7886

79-
// Only do actual symbolization if there are mappings that need it
80-
if len(mappingsToSymbolize) > 0 {
81-
locationsByMapping, err := s.groupLocationsByMapping(profile, mappingsToSymbolize)
82-
if err != nil {
83-
return fmt.Errorf("grouping locations by mapping: %w", err)
84-
}
87+
var allSymbolizedLocs []symbolizedLocation
8588

86-
for mappingID, locations := range locationsByMapping {
87-
mapping := profile.Mapping[mappingID-1]
89+
for mappingID, locations := range locationsByMapping {
90+
mapping := profile.Mapping[mappingID-1]
8891

89-
binaryName, err := s.extractBinaryName(profile, mapping)
90-
if err != nil {
91-
return fmt.Errorf("extract binary name: %w", err)
92-
}
92+
binaryName, err := s.extractBinaryName(profile, mapping)
93+
if err != nil {
94+
return fmt.Errorf("extract binary name: %w", err)
95+
}
9396

94-
buildID, err := s.extractBuildID(profile, mapping)
95-
if err != nil {
96-
return fmt.Errorf("extract build ID: %w", err)
97-
}
97+
buildID, err := s.extractBuildID(profile, mapping)
98+
if err != nil {
99+
return fmt.Errorf("extract build ID: %w", err)
100+
}
98101

99-
if buildID == "" {
100-
continue
101-
}
102+
if buildID == "" {
103+
continue
104+
}
102105

103-
req := s.createSymbolizationRequest(binaryName, buildID, locations)
106+
req := s.createSymbolizationRequest(binaryName, buildID, locations)
104107

105-
s.symbolize(ctx, &req)
108+
s.symbolize(ctx, &req)
106109

107-
for i, loc := range locations {
108-
allSymbolizedLocs = append(allSymbolizedLocs, symbolizedLocation{
109-
loc: loc,
110-
symLoc: req.locations[i],
111-
mapping: mapping,
112-
})
113-
}
110+
for i, loc := range locations {
111+
allSymbolizedLocs = append(allSymbolizedLocs, symbolizedLocation{
112+
loc: loc,
113+
symLoc: req.locations[i],
114+
mapping: mapping,
115+
})
114116
}
115117
}
116118

117-
mappingStats := s.updateAllSymbolsInProfile(profile, allSymbolizedLocs, stringMap)
118-
119-
// Correct HasFunctions flags for ALL mappings based on final symbolization state
120-
for i, mapping := range profile.Mapping {
121-
mappingID := uint64(i + 1)
122-
stats := mappingStats[mappingID]
123-
124-
// Always correct HasFunctions based on actual symbolization state
125-
if stats.total > 0 {
126-
// Has locations: set based on symbolization ratio
127-
shouldHaveFunctions := stats.symbolized == stats.total
128-
mapping.HasFunctions = shouldHaveFunctions
129-
} else {
130-
// No locations in this profile: assume not symbolized
131-
mapping.HasFunctions = false
132-
}
133-
}
119+
s.updateAllSymbolsInProfile(profile, allSymbolizedLocs, stringMap)
134120

135121
return nil
136122
}
@@ -204,22 +190,18 @@ func (s *Symbolizer) createSymbolizationRequest(binaryName, buildID string, locs
204190
return req
205191
}
206192

207-
type mappingStats struct {
208-
total int
209-
symbolized int
210-
}
211-
212193
func (s *Symbolizer) updateAllSymbolsInProfile(
213194
profile *googlev1.Profile,
214195
symbolizedLocs []symbolizedLocation,
215196
stringMap map[string]int64,
216-
) map[uint64]mappingStats {
197+
) {
217198
funcMap := make(map[funcKey]uint64)
218199
maxFuncID := uint64(len(profile.Function))
219200

220201
for _, item := range symbolizedLocs {
221202
loc := item.loc
222203
symLoc := item.symLoc
204+
mapping := item.mapping
223205

224206
locIdx := loc.Id - 1
225207
if loc.Id <= 0 || locIdx >= uint64(len(profile.Location)) {
@@ -259,23 +241,9 @@ func (s *Symbolizer) updateAllSymbolsInProfile(
259241
FunctionId: funcID,
260242
}
261243
}
262-
}
263244

264-
// Collect final symbolization stats in single pass through locations
265-
stats := make(map[uint64]mappingStats)
266-
for _, loc := range profile.Location {
267-
if loc.MappingId == 0 {
268-
continue
269-
}
270-
s := stats[loc.MappingId]
271-
s.total++
272-
if len(loc.Line) > 0 {
273-
s.symbolized++
274-
}
275-
stats[loc.MappingId] = s
245+
mapping.HasFunctions = true
276246
}
277-
278-
return stats
279247
}
280248

281249
func (s *Symbolizer) symbolize(ctx context.Context, req *request) {

pkg/symbolizer/symbolizer_test.go

Lines changed: 14 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212

1313
googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
1414
"github.com/grafana/pyroscope/pkg/model"
15-
"github.com/grafana/pyroscope/pkg/pprof"
1615
"github.com/grafana/pyroscope/pkg/test/mocks/mockobjstore"
1716
"github.com/grafana/pyroscope/pkg/test/mocks/mocksymbolizer"
1817

@@ -247,19 +246,31 @@ func TestSymbolizePprof(t *testing.T) {
247246
}
248247

249248
func TestSymbolizationKeepsSequentialFunctionIDs(t *testing.T) {
250-
s := createSymbolizerForBuildID(t, "build-id")
249+
mockClient := mocksymbolizer.NewMockDebuginfodClient(t)
250+
mockBucket := mockobjstore.NewMockBucket(t)
251251

252252
profile := &googlev1.Profile{
253253
Mapping: []*googlev1.Mapping{{BuildId: 1}},
254254
Location: []*googlev1.Location{{Id: 1, MappingId: 1, Address: 0x1500}},
255-
Function: []*googlev1.Function{{Id: 1, Name: 2}},
255+
Function: []*googlev1.Function{{Id: 1, Name: 1}},
256256
StringTable: []string{"", "build-id", "existing_func"},
257257
Sample: []*googlev1.Sample{{
258258
LocationId: []uint64{1},
259259
Value: []int64{100},
260260
}},
261261
}
262262

263+
mockBucket.On("Get", mock.Anything, "build-id").Return(nil, fmt.Errorf("not found"))
264+
mockClient.On("FetchDebuginfo", mock.Anything, "build-id").Return(openTestFile(t), nil)
265+
mockBucket.On("Upload", mock.Anything, "build-id", mock.Anything).Return(nil)
266+
267+
s := &Symbolizer{
268+
logger: log.NewNopLogger(),
269+
client: mockClient,
270+
bucket: mockBucket,
271+
metrics: newMetrics(nil),
272+
}
273+
263274
err := s.SymbolizePprof(context.Background(), profile)
264275
require.NoError(t, err)
265276

@@ -520,56 +531,6 @@ func TestSymbolizerMetrics(t *testing.T) {
520531
}
521532
}
522533

523-
// TestMixedSymbolizationCorrectsFlagsForAllMappings tests that the symbolizer correctly sets HasFunctions=false for
524-
// mappings with partial symbolization, fixing incorrect flags from upstream sources.
525-
func TestMixedSymbolizationCorrectsFlagsForAllMappings(t *testing.T) {
526-
// Create a profile with incorrect HasFunctions=true on a mixed mapping
527-
profile := &googlev1.Profile{
528-
Mapping: []*googlev1.Mapping{
529-
{Id: 1, HasFunctions: true, BuildId: 1, Filename: 1}, // incorrect: has mixed symbolization
530-
},
531-
Location: []*googlev1.Location{
532-
{Id: 1, MappingId: 1, Address: 0x1000, Line: []*googlev1.Line{{FunctionId: 1}}}, // symbolized
533-
{Id: 2, MappingId: 1, Address: 0x2000, Line: nil}, // unsymbolized
534-
},
535-
Function: []*googlev1.Function{{Id: 1, Name: 2}},
536-
StringTable: []string{"", "test.so", "existing_func"},
537-
}
538-
539-
profile.StringTable[profile.Mapping[0].BuildId] = "" // Make buildID empty so symbolizer skips it
540-
541-
s := &Symbolizer{
542-
logger: log.NewNopLogger(),
543-
client: nil,
544-
bucket: nil,
545-
metrics: newMetrics(nil),
546-
}
547-
548-
err := s.SymbolizePprof(context.Background(), profile)
549-
require.NoError(t, err)
550-
551-
// Verify that HasFunctions was corrected to false for mixed symbolization
552-
require.False(t, profile.Mapping[0].HasFunctions, "Mapping with mixed symbolization should have HasFunctions=false")
553-
}
554-
555-
// createSymbolizerForBuildID creates a symbolizer with mocks for a specific buildID
556-
func createSymbolizerForBuildID(t *testing.T, buildID string) *Symbolizer {
557-
t.Helper()
558-
mockClient := mocksymbolizer.NewMockDebuginfodClient(t)
559-
mockBucket := mockobjstore.NewMockBucket(t)
560-
561-
mockBucket.On("Get", mock.Anything, buildID).Return(nil, fmt.Errorf("not found"))
562-
mockClient.On("FetchDebuginfo", mock.Anything, buildID).Return(openTestFile(t), nil)
563-
mockBucket.On("Upload", mock.Anything, buildID, mock.Anything).Return(nil)
564-
565-
return &Symbolizer{
566-
logger: log.NewNopLogger(),
567-
client: mockClient,
568-
bucket: mockBucket,
569-
metrics: newMetrics(nil),
570-
}
571-
}
572-
573534
func assertLocationHasFunction(t *testing.T, profile *googlev1.Profile, loc *googlev1.Location,
574535
functionName, fileName string) {
575536
t.Helper()
@@ -645,75 +606,3 @@ func createRequest(t *testing.T, buildID string, address uint64) *request {
645606
},
646607
}
647608
}
648-
649-
// TestFlamegraphTruncationIntegration is an integration test for the flamegraph truncation fix.
650-
// TODO: Move this to an integration test package when one exists.
651-
//
652-
// Context: Mixed symbolization profiles with incorrect HasFunctions=true flags caused flamegraph truncation.
653-
// When clearAddresses() cleared ALL addresses during normalize(), unsymbolized locations became
654-
// indistinguishable (identical LocationKeys) and merged incorrectly, losing flamegraph blocks.
655-
//
656-
// Symbolizer now corrects HasFunctions flags based on actual symbolization state.
657-
// Mixed mappings get HasFunctions=false, preserving addresses for proper deduplication.
658-
func TestFlamegraphTruncationIntegration(t *testing.T) {
659-
symbolizer := &Symbolizer{
660-
logger: log.NewNopLogger(),
661-
metrics: newMetrics(prometheus.NewRegistry()),
662-
}
663-
664-
// Create profile with mixed symbolization - some locations already symbolized
665-
profile := &googlev1.Profile{
666-
SampleType: []*googlev1.ValueType{{Type: 1, Unit: 2}},
667-
PeriodType: &googlev1.ValueType{Type: 1, Unit: 2},
668-
Period: 1000000,
669-
Sample: []*googlev1.Sample{
670-
{LocationId: []uint64{1, 2}, Value: []int64{100}},
671-
{LocationId: []uint64{3, 2}, Value: []int64{200}},
672-
},
673-
Mapping: []*googlev1.Mapping{{
674-
Id: 1, HasFunctions: true, BuildId: 3, Filename: 4, // Incorrectly set to true for mixed mapping
675-
MemoryStart: 0x400000, MemoryLimit: 0x500000,
676-
}},
677-
Location: []*googlev1.Location{
678-
{Id: 1, MappingId: 1, Address: 0x1500, Line: []*googlev1.Line{{FunctionId: 1}}}, // symbolized location
679-
{Id: 2, MappingId: 1, Address: 0x999999, Line: nil},
680-
{Id: 3, MappingId: 1, Address: 0x888888, Line: nil},
681-
},
682-
Function: []*googlev1.Function{
683-
{Id: 1, Name: 5}, // Function for the symbolized location
684-
},
685-
StringTable: []string{"", "samples", "count", "build-id", "test.so", "symbolized_function"},
686-
}
687-
688-
// Symbolizer corrects HasFunctions flag for mixed mapping
689-
err := symbolizer.SymbolizePprof(context.Background(), profile)
690-
require.NoError(t, err)
691-
require.False(t, profile.Mapping[0].HasFunctions, "Mixed mapping should have HasFunctions=false")
692-
693-
// Profile normalization preserves addresses when HasFunctions=false
694-
pprofProfile := &pprof.Profile{Profile: profile}
695-
pprofProfile.Normalize() // calls clearAddresses()
696-
697-
require.NotZero(t, profile.Location[0].Address, "Address should be preserved when HasFunctions=false")
698-
require.NotZero(t, profile.Location[1].Address, "Address should be preserved when HasFunctions=false")
699-
require.NotZero(t, profile.Location[2].Address, "Address should be preserved when HasFunctions=false")
700-
701-
// Profile merge preserves all distinct locations
702-
var merge pprof.ProfileMerge
703-
err = merge.Merge(profile, true)
704-
require.NoError(t, err)
705-
706-
mergedProfile := merge.Profile()
707-
require.Equal(t, 3, len(mergedProfile.Location), "All locations should be preserved with distinct addresses")
708-
709-
// Verify unsymbolized locations maintain their distinct addresses
710-
var unsymbolizedAddrs []uint64
711-
for _, loc := range mergedProfile.Location {
712-
if len(loc.Line) == 0 {
713-
unsymbolizedAddrs = append(unsymbolizedAddrs, loc.Address)
714-
}
715-
}
716-
require.Len(t, unsymbolizedAddrs, 2, "Both unsymbolized locations should be preserved")
717-
require.Contains(t, unsymbolizedAddrs, uint64(0x999999))
718-
require.Contains(t, unsymbolizedAddrs, uint64(0x888888))
719-
}

0 commit comments

Comments
 (0)