Skip to content

Commit bea672a

Browse files
authored
chore: profiling improvements (#3941)
* feat: deduplicate profile frames * lint: prefer TryGetValue * chore: update Microsoft.Diagnostics.NETCore.Client * chore: changelog * chore: fix changelog
1 parent 04ba5e3 commit bea672a

6 files changed

+197
-264
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- OTel activities that are marked as not recorded are no longer sent to Sentry ([#3890](https://github.com/getsentry/sentry-dotnet/pull/3890))
8+
- Deduplicate profiling stack frames ([#3941](https://github.com/getsentry/sentry-dotnet/pull/3941))
89

910
## 5.1.0
1011

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
}

src/Sentry.Profiling/Sentry.Profiling.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
<ItemGroup>
1616
<ProjectReference Include="..\..\src\Sentry\Sentry.csproj" />
17-
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.510501" />
17+
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.553101" />
1818
<!-- This triggers the build of this project and its dependencies. We don't need all of them but this is the easiest way -->
1919
<!-- to make sure the project builds/cleans etc in tandem with this. Packaging copies the 2 DLLs we need below -->
2020
<ProjectReference Include="../../modules/perfview/src/TraceEvent/TraceEvent.csproj" PrivateAssets="all" />

0 commit comments

Comments
 (0)