Skip to content

Commit d197cb2

Browse files
authored
fix: net8 unknown stack trace methods for JIT methods (#3942)
* fix: net8 unknown stack trace methods for JIT methods * chore: changelog * chore: fix changelog * remove problematic assertion * fix: dispose before processing is done
1 parent bea672a commit d197cb2

File tree

3 files changed

+59
-18
lines changed

3 files changed

+59
-18
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+
- Unknown stack frames in profiles on .NET 8+ ([#3942](https://github.com/getsentry/sentry-dotnet/pull/3942))
89
- Deduplicate profiling stack frames ([#3941](https://github.com/getsentry/sentry-dotnet/pull/3941))
910

1011
## 5.1.0

src/Sentry.Profiling/SampleProfilerSession.cs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,32 @@ namespace Sentry.Profiling;
1212
internal class SampleProfilerSession : IDisposable
1313
{
1414
private readonly EventPipeSession _session;
15-
private readonly TraceLogEventSource _eventSource;
1615
private readonly SampleProfilerTraceEventParser _sampleEventParser;
1716
private readonly IDiagnosticLogger? _logger;
1817
private readonly SentryStopwatch _stopwatch;
1918
private bool _stopped = false;
19+
private Task _processing;
2020

21-
private SampleProfilerSession(SentryStopwatch stopwatch, EventPipeSession session, TraceLogEventSource eventSource, IDiagnosticLogger? logger)
21+
private SampleProfilerSession(SentryStopwatch stopwatch, EventPipeSession session, TraceLogEventSource eventSource, Task processing, IDiagnosticLogger? logger)
2222
{
2323
_session = session;
2424
_logger = logger;
25-
_eventSource = eventSource;
26-
_sampleEventParser = new SampleProfilerTraceEventParser(_eventSource);
25+
EventSource = eventSource;
26+
_sampleEventParser = new SampleProfilerTraceEventParser(EventSource);
2727
_stopwatch = stopwatch;
28+
_processing = processing;
2829
}
2930

3031
// Exposed only for benchmarks.
3132
internal static EventPipeProvider[] Providers = new[]
3233
{
33-
// Note: all events we need issued by "DotNETRuntime" provider are at "EventLevel.Informational"
34-
// see https://learn.microsoft.com/en-us/dotnet/fundamentals/diagnostics/runtime-events
35-
// TODO replace Keywords.Default with a subset. Currently it is:
36-
// Default = GC | Type | GCHeapSurvivalAndMovement | Binder | Loader | Jit | NGen | SupressNGen
37-
// | StopEnumeration | Security | AppDomainResourceManagement | Exception | Threading | Contention | Stack | JittedMethodILToNativeMap
38-
// | ThreadTransfer | GCHeapAndTypeNames | Codesymbols | Compilation,
39-
new EventPipeProvider(ClrTraceEventParser.ProviderName, EventLevel.Informational, (long) ClrTraceEventParser.Keywords.Default),
34+
new EventPipeProvider(ClrTraceEventParser.ProviderName, EventLevel.Verbose, (long) (
35+
ClrTraceEventParser.Keywords.Jit
36+
| ClrTraceEventParser.Keywords.NGen
37+
| ClrTraceEventParser.Keywords.Loader
38+
| ClrTraceEventParser.Keywords.Binder
39+
| ClrTraceEventParser.Keywords.JittedMethodILToNativeMap
40+
)),
4041
new EventPipeProvider(SampleProfilerTraceEventParser.ProviderName, EventLevel.Informational),
4142
// new EventPipeProvider(TplEtwProviderTraceEventParser.ProviderName, EventLevel.Informational, (long) TplEtwProviderTraceEventParser.Keywords.Default)
4243
};
@@ -46,11 +47,14 @@ private SampleProfilerSession(SentryStopwatch stopwatch, EventPipeSession sessio
4647
// need a large buffer if we're connecting righ away. Leaving it too large increases app memory usage.
4748
internal static int CircularBufferMB = 16;
4849

50+
// Exposed for tests
51+
internal TraceLogEventSource EventSource { get; }
52+
4953
public SampleProfilerTraceEventParser SampleEventParser => _sampleEventParser;
5054

5155
public TimeSpan Elapsed => _stopwatch.Elapsed;
5256

53-
public TraceLog TraceLog => _eventSource.TraceLog;
57+
public TraceLog TraceLog => EventSource.TraceLog;
5458

5559
// default is false, set 1 for true.
5660
private static int _throwOnNextStartupForTests = 0;
@@ -86,7 +90,7 @@ public static SampleProfilerSession StartNew(IDiagnosticLogger? logger = null)
8690
var eventSource = TraceLog.CreateFromEventPipeSession(session, TraceLog.EventPipeRundownConfiguration.Enable(client));
8791

8892
// Process() blocks until the session is stopped so we need to run it on a separate thread.
89-
Task.Factory.StartNew(eventSource.Process, TaskCreationOptions.LongRunning)
93+
var processing = Task.Factory.StartNew(eventSource.Process, TaskCreationOptions.LongRunning)
9094
.ContinueWith(_ =>
9195
{
9296
if (_.Exception?.InnerException is { } e)
@@ -95,7 +99,7 @@ public static SampleProfilerSession StartNew(IDiagnosticLogger? logger = null)
9599
}
96100
}, TaskContinuationOptions.OnlyOnFaulted);
97101

98-
return new SampleProfilerSession(stopWatch, session, eventSource, logger);
102+
return new SampleProfilerSession(stopWatch, session, eventSource, processing, logger);
99103
}
100104
catch (Exception ex)
101105
{
@@ -108,15 +112,15 @@ public async Task WaitForFirstEventAsync(CancellationToken cancellationToken = d
108112
{
109113
var tcs = new TaskCompletionSource();
110114
var cb = (TraceEvent _) => { tcs.TrySetResult(); };
111-
_eventSource.AllEvents += cb;
115+
EventSource.AllEvents += cb;
112116
try
113117
{
114118
// Wait for the first event to be processed.
115119
await tcs.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
116120
}
117121
finally
118122
{
119-
_eventSource.AllEvents -= cb;
123+
EventSource.AllEvents -= cb;
120124
}
121125
}
122126

@@ -128,8 +132,9 @@ public void Stop()
128132
{
129133
_stopped = true;
130134
_session.Stop();
135+
_processing.Wait();
131136
_session.Dispose();
132-
_eventSource.Dispose();
137+
EventSource.Dispose();
133138
}
134139
catch (Exception ex)
135140
{

test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.IO.Abstractions.TestingHelpers;
1+
using Microsoft.Diagnostics.Tracing;
2+
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
23
using Sentry.Internal.Http;
34

45
namespace Sentry.Profiling.Tests;
@@ -180,6 +181,40 @@ public async Task Profiler_AfterTimeout_Stops()
180181
}
181182
}
182183

184+
[SkippableFact]
185+
public async Task EventPipeSession_ReceivesExpectedCLREvents()
186+
{
187+
SampleProfilerSession? session = null;
188+
SkipIfFailsInCI(() => session = SampleProfilerSession.StartNew(_testOutputLogger));
189+
using (session)
190+
{
191+
var eventsReceived = new HashSet<string>();
192+
session!.EventSource.Clr.All += (TraceEvent ev) => eventsReceived.Add(ev.EventName);
193+
194+
var loadedMethods = new HashSet<string>();
195+
session!.EventSource.Clr.MethodLoadVerbose += (MethodLoadUnloadVerboseTraceData ev) => loadedMethods.Add(ev.MethodName);
196+
197+
198+
await session.WaitForFirstEventAsync(CancellationToken.None);
199+
var limitMs = 50;
200+
var sut = new SamplingTransactionProfiler(_testSentryOptions, session, limitMs, CancellationToken.None);
201+
RunForMs(limitMs * 5);
202+
MethodToBeLoaded(100);
203+
RunForMs(limitMs * 5);
204+
sut.Finish();
205+
206+
Assert.Contains("Method/LoadVerbose", eventsReceived);
207+
Assert.Contains("Method/ILToNativeMap", eventsReceived);
208+
209+
Assert.Contains("MethodToBeLoaded", loadedMethods);
210+
}
211+
}
212+
213+
private static long MethodToBeLoaded(int n)
214+
{
215+
return -n;
216+
}
217+
183218
[SkippableTheory]
184219
[InlineData(true)]
185220
[InlineData(false)]

0 commit comments

Comments
 (0)