diff --git a/CHANGELOG.md b/CHANGELOG.md
index 38051d2126..60b44b6169 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## Unreleased
+
+### Fixes
+
+- Unknown stack frames in profiles on .NET 8+ ([#3967](https://github.com/getsentry/sentry-dotnet/pull/3967))
+- Deduplicate profiling stack frames ([#3969](https://github.com/getsentry/sentry-dotnet/pull/3969))
+
## 5.1.1
### Fixes
@@ -10,7 +17,6 @@
- Fixed envelopes with oversized attachments getting stuck in __processing ([#3938](https://github.com/getsentry/sentry-dotnet/pull/3938))
- 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/SampleProfilerSession.cs b/src/Sentry.Profiling/SampleProfilerSession.cs
index e54cf62020..9b5e2756b1 100644
--- a/src/Sentry.Profiling/SampleProfilerSession.cs
+++ b/src/Sentry.Profiling/SampleProfilerSession.cs
@@ -12,7 +12,6 @@ namespace Sentry.Profiling;
internal class SampleProfilerSession : IDisposable
{
private readonly EventPipeSession _session;
- private readonly TraceLogEventSource _eventSource;
private readonly SampleProfilerTraceEventParser _sampleEventParser;
private readonly IDiagnosticLogger? _logger;
private readonly SentryStopwatch _stopwatch;
@@ -23,8 +22,8 @@ private SampleProfilerSession(SentryStopwatch stopwatch, EventPipeSession sessio
{
_session = session;
_logger = logger;
- _eventSource = eventSource;
- _sampleEventParser = new SampleProfilerTraceEventParser(_eventSource);
+ EventSource = eventSource;
+ _sampleEventParser = new SampleProfilerTraceEventParser(EventSource);
_stopwatch = stopwatch;
_processing = processing;
}
@@ -38,7 +37,7 @@ private SampleProfilerSession(SentryStopwatch stopwatch, EventPipeSession sessio
// Default = GC | Type | GCHeapSurvivalAndMovement | Binder | Loader | Jit | NGen | SupressNGen
// | StopEnumeration | Security | AppDomainResourceManagement | Exception | Threading | Contention | Stack | JittedMethodILToNativeMap
// | ThreadTransfer | GCHeapAndTypeNames | Codesymbols | Compilation,
- new EventPipeProvider(ClrTraceEventParser.ProviderName, EventLevel.Informational, (long) ClrTraceEventParser.Keywords.Default),
+ new EventPipeProvider(ClrTraceEventParser.ProviderName, EventLevel.Verbose, (long) ClrTraceEventParser.Keywords.Default),
new EventPipeProvider(SampleProfilerTraceEventParser.ProviderName, EventLevel.Informational),
// new EventPipeProvider(TplEtwProviderTraceEventParser.ProviderName, EventLevel.Informational, (long) TplEtwProviderTraceEventParser.Keywords.Default)
};
@@ -48,11 +47,14 @@ private SampleProfilerSession(SentryStopwatch stopwatch, EventPipeSession sessio
// need a large buffer if we're connecting righ away. Leaving it too large increases app memory usage.
internal static int CircularBufferMB = 16;
+ // Exposed for tests
+ internal TraceLogEventSource EventSource { get; }
+
public SampleProfilerTraceEventParser SampleEventParser => _sampleEventParser;
public TimeSpan Elapsed => _stopwatch.Elapsed;
- public TraceLog TraceLog => _eventSource.TraceLog;
+ public TraceLog TraceLog => EventSource.TraceLog;
// default is false, set 1 for true.
private static int _throwOnNextStartupForTests = 0;
@@ -110,7 +112,7 @@ public async Task WaitForFirstEventAsync(CancellationToken cancellationToken = d
{
var tcs = new TaskCompletionSource();
var cb = (TraceEvent _) => { tcs.TrySetResult(); };
- _eventSource.AllEvents += cb;
+ EventSource.AllEvents += cb;
try
{
// Wait for the first event to be processed.
@@ -118,7 +120,7 @@ public async Task WaitForFirstEventAsync(CancellationToken cancellationToken = d
}
finally
{
- _eventSource.AllEvents -= cb;
+ EventSource.AllEvents -= cb;
}
}
@@ -132,7 +134,7 @@ public void Stop()
_session.Stop();
_processing.Wait();
_session.Dispose();
- _eventSource.Dispose();
+ EventSource.Dispose();
}
catch (Exception ex)
{
diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs
index 12bdcc5995..af5914011d 100644
--- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs
+++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs
@@ -1,4 +1,5 @@
-using System.IO.Abstractions.TestingHelpers;
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Sentry.Internal.Http;
namespace Sentry.Profiling.Tests;
@@ -180,6 +181,46 @@ public async Task Profiler_AfterTimeout_Stops()
}
}
+
+ ///
+ /// Guards regression of https://github.com/microsoft/perfview/issues/2155
+ ///
+ [SkippableFact]
+ public async Task EventPipeSession_ReceivesExpectedCLREvents()
+ {
+ SampleProfilerSession? session = null;
+ SkipIfFailsInCI(() => session = SampleProfilerSession.StartNew(_testOutputLogger));
+ using (session)
+ {
+ var eventsReceived = new HashSet();
+ session!.EventSource.Clr.MethodLoadVerbose += (_) => eventsReceived.Add("Method/LoadVerbose");
+ session!.EventSource.Clr.MethodILToNativeMap += (_) => eventsReceived.Add("Method/ILToNativeMap");
+
+ await session.WaitForFirstEventAsync(CancellationToken.None);
+ var tries = 0;
+ while (eventsReceived.Count < 2 && tries++ < 10)
+ {
+ if (tries > 1)
+ {
+ await Task.Delay(100);
+ }
+ var limitMs = 50;
+ var sut = new SamplingTransactionProfiler(_testSentryOptions, session, limitMs, CancellationToken.None);
+ MethodToBeLoaded(100);
+ RunForMs(limitMs);
+ sut.Finish();
+ }
+
+ Assert.Contains("Method/LoadVerbose", eventsReceived);
+ Assert.Contains("Method/ILToNativeMap", eventsReceived);
+ }
+ }
+
+ private static long MethodToBeLoaded(int n)
+ {
+ return -n;
+ }
+
[SkippableTheory]
[InlineData(true)]
[InlineData(false)]