Skip to content

Commit 9ab24e4

Browse files
authored
fix: skip already-symbolized locations to preserve existing symbols (#4259)
1 parent 5d502a1 commit 9ab24e4

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

pkg/experiment/symbolizer/symbolizer.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ func (s *Symbolizer) groupLocationsByMapping(profile *googlev1.Profile, mappings
139139
continue
140140
}
141141

142+
// Skip locations that already have symbols
143+
if len(loc.Line) > 0 {
144+
continue
145+
}
146+
142147
locsByMapping[loc.MappingId] = append(locsByMapping[loc.MappingId], loc)
143148
}
144149

pkg/experiment/symbolizer/symbolizer_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"os"
10+
"strings"
1011
"testing"
1112

1213
googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
@@ -144,6 +145,78 @@ func TestSymbolizePprof(t *testing.T) {
144145
assertLocationHasFunction(t, p, p.Location[2], "main", "main")
145146
},
146147
},
148+
{
149+
name: "preserve existing symbols when HasFunctions=false",
150+
// This tests a defensive check against data inconsistency where a mapping has
151+
// HasFunctions=false but contains locations with existing symbols.
152+
// This scenario should be rare, but we maintain the check for robustness.
153+
profile: &googlev1.Profile{
154+
Mapping: []*googlev1.Mapping{{
155+
Id: 1,
156+
BuildId: 1,
157+
Filename: 2,
158+
MemoryStart: 0x0,
159+
MemoryLimit: 0x1000000,
160+
FileOffset: 0x0,
161+
HasFunctions: false,
162+
}},
163+
Location: []*googlev1.Location{
164+
{
165+
Id: 1,
166+
MappingId: 1,
167+
Address: 0x1000,
168+
Line: []*googlev1.Line{{
169+
FunctionId: 1,
170+
Line: 42,
171+
}},
172+
},
173+
{
174+
Id: 2,
175+
MappingId: 1,
176+
Address: 0x1500,
177+
Line: nil,
178+
},
179+
},
180+
Function: []*googlev1.Function{{
181+
Id: 1,
182+
Name: 3,
183+
}},
184+
StringTable: []string{"", "build-id", "alloy", "existing_function"},
185+
},
186+
setupMock: func(mockClient *mocksymbolizer.MockDebuginfodClient, mockBucket *mockobjstore.MockBucket) {
187+
mockClient.On("FetchDebuginfo", mock.Anything, "build-id").Return(openTestFile(t), nil).Once()
188+
mockBucket.On("Get", mock.Anything, "build-id").Return(nil, fmt.Errorf("not found")).Once()
189+
mockBucket.On("Upload", mock.Anything, "build-id", mock.Anything).Return(nil).Once()
190+
},
191+
validate: func(t *testing.T, p *googlev1.Profile) {
192+
require.True(t, p.Mapping[0].HasFunctions)
193+
194+
require.Len(t, p.Location[0].Line, 1)
195+
require.Equal(t, uint64(1), p.Location[0].Line[0].FunctionId)
196+
require.Equal(t, "existing_function", p.StringTable[p.Function[0].Name])
197+
198+
require.Len(t, p.Location[1].Line, 1)
199+
assertLocationHasFunction(t, p, p.Location[1], "main", "main")
200+
201+
existingFuncStillExists := false
202+
for _, str := range p.StringTable {
203+
if str == "existing_function" {
204+
existingFuncStillExists = true
205+
break
206+
}
207+
}
208+
require.True(t, existingFuncStillExists)
209+
210+
placeholderFound := false
211+
for _, str := range p.StringTable {
212+
if strings.Contains(str, "!0x") {
213+
placeholderFound = true
214+
break
215+
}
216+
}
217+
require.False(t, placeholderFound)
218+
},
219+
},
147220
}
148221

149222
for _, tt := range tests {

0 commit comments

Comments
 (0)