diff --git a/CHANGELOG.md b/CHANGELOG.md index 49ac701258..dba7707f62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Deduplicate profiling stack frames ([#3941](https://github.com/getsentry/sentry-dotnet/pull/3941)) - 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)) - Ignore null value on CocoaScopeObserver.SetTag ([#3948](https://github.com/getsentry/sentry-dotnet/pull/3948)) +- Deduplicate profiling stack frames ([#3969](https://github.com/getsentry/sentry-dotnet/pull/3969)) ## 5.1.0 diff --git a/src/Sentry.Profiling/SampleProfileBuilder.cs b/src/Sentry.Profiling/SampleProfileBuilder.cs index bdef768d85..56927be4a9 100644 --- a/src/Sentry.Profiling/SampleProfileBuilder.cs +++ b/src/Sentry.Profiling/SampleProfileBuilder.cs @@ -16,8 +16,12 @@ internal class SampleProfileBuilder // Output profile being built. public readonly SampleProfile Profile = new(); - // A sparse array that maps from StackSourceFrameIndex to an index in the output Profile.frames. - private readonly Dictionary _frameIndexes = new(); + // A sparse array that maps from CodeAddressIndex to an index in the output Profile.frames. + private readonly Dictionary _frameIndexesByCodeAddressIndex = new(); + + // A sparse array that maps from MethodIndex to an index in the output Profile.frames. + // This deduplicates frames that map to the same method but have a different CodeAddressIndex. + private readonly Dictionary _frameIndexesByMethodIndex = new(); // A dictionary from a CallStackIndex to an index in the output Profile.stacks. private readonly Dictionary _stackIndexes = new(); @@ -85,13 +89,14 @@ private int AddStackTrace(CallStackIndex callstackIndex) { var key = (int)callstackIndex; - if (!_stackIndexes.ContainsKey(key)) + if (!_stackIndexes.TryGetValue(key, out var value)) { Profile.Stacks.Add(CreateStackTrace(callstackIndex)); - _stackIndexes[key] = Profile.Stacks.Count - 1; + value = Profile.Stacks.Count - 1; + _stackIndexes[key] = value; } - return _stackIndexes[key]; + return value; } private Internal.GrowableArray CreateStackTrace(CallStackIndex callstackIndex) @@ -116,21 +121,71 @@ private Internal.GrowableArray CreateStackTrace(CallStackIndex callstackInd return stackTrace; } + private int PushNewFrame(SentryStackFrame frame) + { + Profile.Frames.Add(frame); + return Profile.Frames.Count - 1; + } + /// /// Check if the frame is already stored in the output Profile, or adds it. /// /// The index to the output Profile frames array. private int AddStackFrame(CodeAddressIndex codeAddressIndex) { - var key = (int)codeAddressIndex; + if (_frameIndexesByCodeAddressIndex.TryGetValue((int)codeAddressIndex, out var value)) + { + return value; + } - if (!_frameIndexes.ContainsKey(key)) + var methodIndex = _traceLog.CodeAddresses.MethodIndex(codeAddressIndex); + if (methodIndex != MethodIndex.Invalid) + { + value = AddStackFrame(methodIndex); + _frameIndexesByCodeAddressIndex[(int)codeAddressIndex] = value; + return value; + } + + // Fall back if the method info is unknown, see more info on Symbol resolution in + // https://github.com/getsentry/perfview/blob/031250ffb4f9fcadb9263525d6c9f274be19ca51/src/PerfView/SupportFiles/UsersGuide.htm#L7745-L7784 + if (_traceLog.CodeAddresses[codeAddressIndex] is { } codeAddressInfo) { - Profile.Frames.Add(CreateStackFrame(codeAddressIndex)); - _frameIndexes[key] = Profile.Frames.Count - 1; + var frame = new SentryStackFrame + { + InstructionAddress = (long?)codeAddressInfo.Address, + Module = codeAddressInfo.ModuleFile?.Name, + }; + frame.ConfigureAppFrame(_options); + + return _frameIndexesByCodeAddressIndex[(int)codeAddressIndex] = PushNewFrame(frame); } - return _frameIndexes[key]; + // If all else fails, it's a completely unknown frame. + // TODO check this - maybe we would be able to resolve it later in the future? + return PushNewFrame(new SentryStackFrame { InApp = false }); + } + + /// + /// Check if the frame is already stored in the output Profile, or adds it. + /// + /// The index to the output Profile frames array. + private int AddStackFrame(MethodIndex methodIndex) + { + if (_frameIndexesByMethodIndex.TryGetValue((int)methodIndex, out var value)) + { + return value; + } + + var method = _traceLog.CodeAddresses.Methods[methodIndex]; + + var frame = new SentryStackFrame + { + Function = method.FullMethodName, + Module = method.MethodModuleFile?.Name + }; + frame.ConfigureAppFrame(_options); + + return _frameIndexesByMethodIndex[(int)methodIndex] = PushNewFrame(frame); } /// @@ -141,52 +196,17 @@ private int AddThread(TraceThread thread) { var key = (int)thread.ThreadIndex; - if (!_threadIndexes.ContainsKey(key)) + if (!_threadIndexes.TryGetValue(key, out var value)) { Profile.Threads.Add(new() { Name = thread.ThreadInfo ?? $"Thread {thread.ThreadID}", }); - _threadIndexes[key] = Profile.Threads.Count - 1; + value = Profile.Threads.Count - 1; + _threadIndexes[key] = value; _downsampler.NewThreadAdded(_threadIndexes[key]); } - return _threadIndexes[key]; - } - - private SentryStackFrame CreateStackFrame(CodeAddressIndex codeAddressIndex) - { - var frame = new SentryStackFrame(); - - var methodIndex = _traceLog.CodeAddresses.MethodIndex(codeAddressIndex); - if (_traceLog.CodeAddresses.Methods[methodIndex] is { } method) - { - frame.Function = method.FullMethodName; - - if (method.MethodModuleFile is { } moduleFile) - { - frame.Module = moduleFile.Name; - } - - frame.ConfigureAppFrame(_options); - } - else - { - // Fall back if the method info is unknown, see more info on Symbol resolution in - // https://github.com/getsentry/perfview/blob/031250ffb4f9fcadb9263525d6c9f274be19ca51/src/PerfView/SupportFiles/UsersGuide.htm#L7745-L7784 - frame.InstructionAddress = (long?)_traceLog.CodeAddresses.Address(codeAddressIndex); - - if (_traceLog.CodeAddresses.ModuleFile(codeAddressIndex) is { } moduleFile) - { - frame.Module = moduleFile.Name; - frame.ConfigureAppFrame(_options); - } - else - { - frame.InApp = false; - } - } - - return frame; + return value; } } diff --git a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.ProfileInfo_Serialization_Works.verified.txt b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.ProfileInfo_Serialization_Works.verified.txt index 714859c86d..481fcf2e42 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.ProfileInfo_Serialization_Works.verified.txt +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.ProfileInfo_Serialization_Works.verified.txt @@ -107,11 +107,12 @@ 36 ], [ - 37 + 17 ], [ 0, 1, + 37, 38, 39, 40, @@ -119,35 +120,37 @@ 42, 43, 44, - 45, - 46, + 21, 22 ], [ + 45, + 46, 47, 48, 49, 50, - 51, - 52, - 37 + 17 ], [ + 51, + 52, 53, 54, 55, - 56, - 57, - 37 + 17 ], [ + 56, + 57, 58, 59, - 60, - 61, - 62 + 17 ], [ + 60, + 61, + 62, 63, 64, 65, @@ -155,48 +158,55 @@ 67, 68, 69, + 56, + 57, + 58, + 59, + 17 + ], + [ 70, 71, 72, + 73, + 74, + 69, + 56, + 57, 58, 59, - 60, - 61, - 62 + 17 ], [ - 73, - 74, 75, - 76, - 77, - 78, + 74, + 69, + 56, + 57, 58, 59, - 60, - 61, - 62 + 17 ], [ - 79, - 80, - 78, + 56, + 57, 58, 59, - 60, - 61, - 62 + 17 ], [ + 76, + 77, + 78, + 79, + 80, 81, + 82, + 83, 59, - 60, - 61, - 62 + 17 ], [ - 82, - 83, 84, 85, 86, @@ -204,31 +214,22 @@ 88, 89, 90, - 62 - ], - [ 91, 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - 100, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 62 + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 59, + 17 ], [ 0, 1, + 37, 38, 39, 40, @@ -236,15 +237,14 @@ 42, 43, 44, - 45, - 46, + 21, 22 ], [ 18, 19, 20, - 101, + 21, 22 ] ], @@ -340,7 +340,6 @@ in_app: true }, { - in_app: false, instruction_addr: 0x7ff947fd8378 }, { @@ -433,11 +432,6 @@ module: System.Private.CoreLib.il, in_app: false }, - { - function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), - module: Aura.UI.Gallery.NetCore, - in_app: true - }, { function: System.IO.Stream+<>c.b__40_0(class System.Object), module: System.Private.CoreLib.il, @@ -478,11 +472,6 @@ module: System.Private.CoreLib.il, in_app: false }, - { - function: System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart(), - module: System.Private.CoreLib.il, - in_app: false - }, { function: System.Collections.Concurrent.ConcurrentDictionary`2[System.__Canon,System.IntPtr].TryAddInternal(!0,value class System.Nullable`1,!1,bool,bool,!1&), module: System.Collections.Concurrent.il, @@ -509,7 +498,6 @@ in_app: true }, { - in_app: false, instruction_addr: 0x7ff94be7aed3 }, { @@ -557,11 +545,6 @@ module: Avalonia.Controls, in_app: true }, - { - function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), - module: Aura.UI.Gallery.NetCore, - in_app: true - }, { function: Avalonia.Animation.Animation..cctor(), module: Avalonia.Animation, @@ -637,26 +620,11 @@ module: Avalonia.Controls, in_app: true }, - { - function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), - module: Avalonia.Controls, - in_app: true - }, { function: System.Reactive.Linq.Observable.Merge(class System.IObservable`1[]), module: system.reactive, in_app: false }, - { - function: Avalonia.Controls.TextBlock..cctor(), - module: Avalonia.Controls, - in_app: true - }, - { - function: Avalonia.Controls.Window..cctor(), - module: Avalonia.Controls, - in_app: true - }, { function: Avalonia.Win32.Win32Platform.SetDpiAwareness(), module: avalonia.win32, @@ -697,11 +665,6 @@ module: Avalonia.Controls, in_app: true }, - { - function: Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(!!0,class System.String[],value class Avalonia.Controls.ShutdownMode), - module: Avalonia.Controls, - in_app: true - }, { function: System.Drawing.SafeNativeMethods+Gdip..cctor(), module: system.drawing.common, @@ -738,23 +701,12 @@ in_app: true }, { - in_app: false, instruction_addr: 0x7ffa0d468281 }, { function: Avalonia.Win32.Win32Platform.CreateMessageWindow(), module: avalonia.win32, in_app: true - }, - { - function: Avalonia.Win32.Win32Platform..ctor(), - module: avalonia.win32, - in_app: true - }, - { - function: System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart(), - module: System.Private.CoreLib.il, - in_app: false } ], samples: [ @@ -1070,4 +1022,4 @@ } ] } -} \ No newline at end of file +} diff --git a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.Profile_Serialization_Works.verified.txt b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.Profile_Serialization_Works.verified.txt index 6eb35f3c22..7720ab6755 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.Profile_Serialization_Works.verified.txt +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.Profile_Serialization_Works.verified.txt @@ -84,11 +84,12 @@ 36 ], [ - 37 + 17 ], [ 0, 1, + 37, 38, 39, 40, @@ -96,35 +97,37 @@ 42, 43, 44, - 45, - 46, + 21, 22 ], [ + 45, + 46, 47, 48, 49, 50, - 51, - 52, - 37 + 17 ], [ + 51, + 52, 53, 54, 55, - 56, - 57, - 37 + 17 ], [ + 56, + 57, 58, 59, - 60, - 61, - 62 + 17 ], [ + 60, + 61, + 62, 63, 64, 65, @@ -132,48 +135,55 @@ 67, 68, 69, + 56, + 57, + 58, + 59, + 17 + ], + [ 70, 71, 72, + 73, + 74, + 69, + 56, + 57, 58, 59, - 60, - 61, - 62 + 17 ], [ - 73, - 74, 75, - 76, - 77, - 78, + 74, + 69, + 56, + 57, 58, 59, - 60, - 61, - 62 + 17 ], [ - 79, - 80, - 78, + 56, + 57, 58, 59, - 60, - 61, - 62 + 17 ], [ + 76, + 77, + 78, + 79, + 80, 81, + 82, + 83, 59, - 60, - 61, - 62 + 17 ], [ - 82, - 83, 84, 85, 86, @@ -181,31 +191,22 @@ 88, 89, 90, - 62 - ], - [ 91, 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - 100, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 62 + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 59, + 17 ], [ 0, 1, + 37, 38, 39, 40, @@ -213,15 +214,14 @@ 42, 43, 44, - 45, - 46, + 21, 22 ], [ 18, 19, 20, - 101, + 21, 22 ] ], @@ -317,7 +317,6 @@ in_app: true }, { - in_app: false, instruction_addr: 0x7ff947fd8378 }, { @@ -410,11 +409,6 @@ module: System.Private.CoreLib.il, in_app: false }, - { - function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), - module: Aura.UI.Gallery.NetCore, - in_app: true - }, { function: System.IO.Stream+<>c.b__40_0(class System.Object), module: System.Private.CoreLib.il, @@ -455,11 +449,6 @@ module: System.Private.CoreLib.il, in_app: false }, - { - function: System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart(), - module: System.Private.CoreLib.il, - in_app: false - }, { function: System.Collections.Concurrent.ConcurrentDictionary`2[System.__Canon,System.IntPtr].TryAddInternal(!0,value class System.Nullable`1,!1,bool,bool,!1&), module: System.Collections.Concurrent.il, @@ -486,7 +475,6 @@ in_app: true }, { - in_app: false, instruction_addr: 0x7ff94be7aed3 }, { @@ -534,11 +522,6 @@ module: Avalonia.Controls, in_app: true }, - { - function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), - module: Aura.UI.Gallery.NetCore, - in_app: true - }, { function: Avalonia.Animation.Animation..cctor(), module: Avalonia.Animation, @@ -614,26 +597,11 @@ module: Avalonia.Controls, in_app: true }, - { - function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), - module: Avalonia.Controls, - in_app: true - }, { function: System.Reactive.Linq.Observable.Merge(class System.IObservable`1[]), module: system.reactive, in_app: false }, - { - function: Avalonia.Controls.TextBlock..cctor(), - module: Avalonia.Controls, - in_app: true - }, - { - function: Avalonia.Controls.Window..cctor(), - module: Avalonia.Controls, - in_app: true - }, { function: Avalonia.Win32.Win32Platform.SetDpiAwareness(), module: avalonia.win32, @@ -674,11 +642,6 @@ module: Avalonia.Controls, in_app: true }, - { - function: Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(!!0,class System.String[],value class Avalonia.Controls.ShutdownMode), - module: Avalonia.Controls, - in_app: true - }, { function: System.Drawing.SafeNativeMethods+Gdip..cctor(), module: system.drawing.common, @@ -715,23 +678,12 @@ in_app: true }, { - in_app: false, instruction_addr: 0x7ffa0d468281 }, { function: Avalonia.Win32.Win32Platform.CreateMessageWindow(), module: avalonia.win32, in_app: true - }, - { - function: Avalonia.Win32.Win32Platform..ctor(), - module: avalonia.win32, - in_app: true - }, - { - function: System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart(), - module: System.Private.CoreLib.il, - in_app: false } ], samples: [ @@ -1046,4 +998,4 @@ stack_id: 18 } ] -} \ No newline at end of file +}