Skip to content

Commit 3974e41

Browse files
authored
fix: Deduplicate profiling stack frames (#3969)
1 parent 8efb6e5 commit 3974e41

File tree

4 files changed

+184
-259
lines changed

4 files changed

+184
-259
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Fixed envelopes with oversized attachments getting stuck in __processing ([#3938](https://github.com/getsentry/sentry-dotnet/pull/3938))
1111
- OperatingSystem will now return macOS as OS name instead of 'Darwin' as well as the proper version. ([#2710](https://github.com/getsentry/sentry-dotnet/pull/3956))
1212
- Ignore null value on CocoaScopeObserver.SetTag ([#3948](https://github.com/getsentry/sentry-dotnet/pull/3948))
13+
- Deduplicate profiling stack frames ([#3969](https://github.com/getsentry/sentry-dotnet/pull/3969))
1314

1415
## 5.1.0
1516

src/Sentry.Profiling/SampleProfileBuilder.cs

Lines changed: 69 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ internal class SampleProfileBuilder
1616
// Output profile being built.
1717
public readonly SampleProfile Profile = new();
1818

19-
// A sparse array that maps from StackSourceFrameIndex to an index in the output Profile.frames.
20-
private readonly Dictionary<int, int> _frameIndexes = new();
19+
// A sparse array that maps from CodeAddressIndex to an index in the output Profile.frames.
20+
private readonly Dictionary<int, int> _frameIndexesByCodeAddressIndex = new();
21+
22+
// A sparse array that maps from MethodIndex to an index in the output Profile.frames.
23+
// This deduplicates frames that map to the same method but have a different CodeAddressIndex.
24+
private readonly Dictionary<int, int> _frameIndexesByMethodIndex = new();
2125

2226
// A dictionary from a CallStackIndex to an index in the output Profile.stacks.
2327
private readonly Dictionary<int, int> _stackIndexes = new();
@@ -85,13 +89,14 @@ private int AddStackTrace(CallStackIndex callstackIndex)
8589
{
8690
var key = (int)callstackIndex;
8791

88-
if (!_stackIndexes.ContainsKey(key))
92+
if (!_stackIndexes.TryGetValue(key, out var value))
8993
{
9094
Profile.Stacks.Add(CreateStackTrace(callstackIndex));
91-
_stackIndexes[key] = Profile.Stacks.Count - 1;
95+
value = Profile.Stacks.Count - 1;
96+
_stackIndexes[key] = value;
9297
}
9398

94-
return _stackIndexes[key];
99+
return value;
95100
}
96101

97102
private Internal.GrowableArray<int> CreateStackTrace(CallStackIndex callstackIndex)
@@ -116,21 +121,71 @@ private Internal.GrowableArray<int> CreateStackTrace(CallStackIndex callstackInd
116121
return stackTrace;
117122
}
118123

124+
private int PushNewFrame(SentryStackFrame frame)
125+
{
126+
Profile.Frames.Add(frame);
127+
return Profile.Frames.Count - 1;
128+
}
129+
119130
/// <summary>
120131
/// Check if the frame is already stored in the output Profile, or adds it.
121132
/// </summary>
122133
/// <returns>The index to the output Profile frames array.</returns>
123134
private int AddStackFrame(CodeAddressIndex codeAddressIndex)
124135
{
125-
var key = (int)codeAddressIndex;
136+
if (_frameIndexesByCodeAddressIndex.TryGetValue((int)codeAddressIndex, out var value))
137+
{
138+
return value;
139+
}
126140

127-
if (!_frameIndexes.ContainsKey(key))
141+
var methodIndex = _traceLog.CodeAddresses.MethodIndex(codeAddressIndex);
142+
if (methodIndex != MethodIndex.Invalid)
143+
{
144+
value = AddStackFrame(methodIndex);
145+
_frameIndexesByCodeAddressIndex[(int)codeAddressIndex] = value;
146+
return value;
147+
}
148+
149+
// Fall back if the method info is unknown, see more info on Symbol resolution in
150+
// https://github.com/getsentry/perfview/blob/031250ffb4f9fcadb9263525d6c9f274be19ca51/src/PerfView/SupportFiles/UsersGuide.htm#L7745-L7784
151+
if (_traceLog.CodeAddresses[codeAddressIndex] is { } codeAddressInfo)
128152
{
129-
Profile.Frames.Add(CreateStackFrame(codeAddressIndex));
130-
_frameIndexes[key] = Profile.Frames.Count - 1;
153+
var frame = new SentryStackFrame
154+
{
155+
InstructionAddress = (long?)codeAddressInfo.Address,
156+
Module = codeAddressInfo.ModuleFile?.Name,
157+
};
158+
frame.ConfigureAppFrame(_options);
159+
160+
return _frameIndexesByCodeAddressIndex[(int)codeAddressIndex] = PushNewFrame(frame);
131161
}
132162

133-
return _frameIndexes[key];
163+
// If all else fails, it's a completely unknown frame.
164+
// TODO check this - maybe we would be able to resolve it later in the future?
165+
return PushNewFrame(new SentryStackFrame { InApp = false });
166+
}
167+
168+
/// <summary>
169+
/// Check if the frame is already stored in the output Profile, or adds it.
170+
/// </summary>
171+
/// <returns>The index to the output Profile frames array.</returns>
172+
private int AddStackFrame(MethodIndex methodIndex)
173+
{
174+
if (_frameIndexesByMethodIndex.TryGetValue((int)methodIndex, out var value))
175+
{
176+
return value;
177+
}
178+
179+
var method = _traceLog.CodeAddresses.Methods[methodIndex];
180+
181+
var frame = new SentryStackFrame
182+
{
183+
Function = method.FullMethodName,
184+
Module = method.MethodModuleFile?.Name
185+
};
186+
frame.ConfigureAppFrame(_options);
187+
188+
return _frameIndexesByMethodIndex[(int)methodIndex] = PushNewFrame(frame);
134189
}
135190

136191
/// <summary>
@@ -141,52 +196,17 @@ private int AddThread(TraceThread thread)
141196
{
142197
var key = (int)thread.ThreadIndex;
143198

144-
if (!_threadIndexes.ContainsKey(key))
199+
if (!_threadIndexes.TryGetValue(key, out var value))
145200
{
146201
Profile.Threads.Add(new()
147202
{
148203
Name = thread.ThreadInfo ?? $"Thread {thread.ThreadID}",
149204
});
150-
_threadIndexes[key] = Profile.Threads.Count - 1;
205+
value = Profile.Threads.Count - 1;
206+
_threadIndexes[key] = value;
151207
_downsampler.NewThreadAdded(_threadIndexes[key]);
152208
}
153209

154-
return _threadIndexes[key];
155-
}
156-
157-
private SentryStackFrame CreateStackFrame(CodeAddressIndex codeAddressIndex)
158-
{
159-
var frame = new SentryStackFrame();
160-
161-
var methodIndex = _traceLog.CodeAddresses.MethodIndex(codeAddressIndex);
162-
if (_traceLog.CodeAddresses.Methods[methodIndex] is { } method)
163-
{
164-
frame.Function = method.FullMethodName;
165-
166-
if (method.MethodModuleFile is { } moduleFile)
167-
{
168-
frame.Module = moduleFile.Name;
169-
}
170-
171-
frame.ConfigureAppFrame(_options);
172-
}
173-
else
174-
{
175-
// Fall back if the method info is unknown, see more info on Symbol resolution in
176-
// https://github.com/getsentry/perfview/blob/031250ffb4f9fcadb9263525d6c9f274be19ca51/src/PerfView/SupportFiles/UsersGuide.htm#L7745-L7784
177-
frame.InstructionAddress = (long?)_traceLog.CodeAddresses.Address(codeAddressIndex);
178-
179-
if (_traceLog.CodeAddresses.ModuleFile(codeAddressIndex) is { } moduleFile)
180-
{
181-
frame.Module = moduleFile.Name;
182-
frame.ConfigureAppFrame(_options);
183-
}
184-
else
185-
{
186-
frame.InApp = false;
187-
}
188-
}
189-
190-
return frame;
210+
return value;
191211
}
192212
}

0 commit comments

Comments
 (0)