Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
162ea2a
prepare traceevent integration
vaind Apr 23, 2023
68bafaa
wip: in-memory tracelog
vaind May 4, 2023
f6657f5
in-memory tracelog support
vaind May 5, 2023
9f8727f
profiler - remove temp dir
vaind May 8, 2023
3833b5f
tracelog streaming
vaind May 8, 2023
ad5e21f
fix sample collection
vaind May 9, 2023
1510734
migrate downsampling & fix tests
vaind May 9, 2023
886a2e9
fix: SparseArray
vaind May 10, 2023
2b70a79
update benchmark & profile builder
vaind May 10, 2023
ba3b4e1
minor changes rundown issue investigation
vaind May 11, 2023
079a72c
Merge branch 'main' into feat/streaming-tracelog
vaind May 18, 2023
25592ba
separate rundown session during tracelog init
vaind May 19, 2023
aa3ff4d
fixes
vaind May 20, 2023
3f7b063
Merge branch 'main' into feat/streaming-tracelog
vaind May 22, 2023
9a780eb
update profiling benchmark results
vaind May 22, 2023
61d2718
Merge branch 'main' into feat/streaming-tracelog
vaind May 22, 2023
cc6119f
update tests
vaind May 23, 2023
9005697
Merge branch 'main' into feat/streaming-tracelog
vaind May 23, 2023
f88a20d
update tests
vaind May 23, 2023
f864e7c
fixes
vaind May 23, 2023
04d38a5
update tests to support streaming tracelog
vaind May 30, 2023
8b3691d
Merge branch 'main' into feat/streaming-tracelog
vaind May 30, 2023
9b51611
Merge branch 'main' into feat/streaming-tracelog
vaind Jun 4, 2023
d171989
Merge branch 'main' into feat/streaming-tracelog
vaind Aug 4, 2023
c7f6929
update to latest perfview
vaind Aug 4, 2023
4ffb45a
submodule perfview sentry fork
bruno-garcia Aug 14, 2023
3a7a10f
Merge branch 'main' into chore/integrate-traceevent
bitsandfoxes Aug 16, 2023
5b159e1
fix test code
vaind Aug 16, 2023
f457e3a
Merge branch 'main' into feat/streaming-tracelog
vaind Aug 16, 2023
abc81db
Merge branch 'feat/streaming-tracelog' into chore/integrate-traceevent
vaind Aug 16, 2023
b4f9485
script & project updates
vaind Aug 16, 2023
5ff8f1a
profiling in core sln
bruno-garcia Aug 19, 2023
0bbb0bd
Merge branch 'chore/integrate-traceevent' into feat/profiling-low-ove…
bruno-garcia Aug 19, 2023
aee692b
perfview sampling
bruno-garcia Aug 20, 2023
3376bea
perfview under profiling
bruno-garcia Aug 20, 2023
6e2ae7c
remove profiling from sln/slnf
bruno-garcia Aug 20, 2023
790d774
internal perfview
bruno-garcia Aug 20, 2023
b346567
ignore build files from submodule perfview
bruno-garcia Aug 20, 2023
6cb6cd1
fix structure
bruno-garcia Aug 20, 2023
19e24a2
resolve errors
bruno-garcia Aug 20, 2023
1c98746
builds!
bruno-garcia Aug 20, 2023
40b6add
in SentryCore sln
bruno-garcia Aug 20, 2023
43f78a0
gitignore on module
bruno-garcia Aug 20, 2023
53715f4
set correct platform to profile
bruno-garcia Aug 20, 2023
67ef890
log on collection failure
bruno-garcia Aug 20, 2023
915a0f2
sample
bruno-garcia Aug 20, 2023
791cdc6
fixed sln file with all but mobile
bruno-garcia Aug 20, 2023
bec4641
ignore obj
bruno-garcia Aug 20, 2023
3a05994
fix benchmarks. Package it
bruno-garcia Aug 20, 2023
f1ea1ec
fix tfm in script
bruno-garcia Aug 20, 2023
7c7404d
verify
bruno-garcia Aug 20, 2023
20286f4
new full sln file
bruno-garcia Aug 20, 2023
aa01035
pack profiling
bruno-garcia Aug 20, 2023
b5cccee
add profile category
bruno-garcia Aug 20, 2023
1a593bc
fuck this slnf mess
bruno-garcia Aug 21, 2023
eaa320b
debugging on sample
bruno-garcia Aug 21, 2023
833a319
Merge branch 'main' into feat/profiling-low-overhead
vaind Aug 21, 2023
6a1df09
timeout update
vaind Aug 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix sample collection
  • Loading branch information
vaind committed May 9, 2023
commit ad5e21f68c05b42b507489c58889a40128d53bc6
2 changes: 1 addition & 1 deletion benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public void DiagnosticsSessionStartCopyStop(bool rundown, string provider)
[Benchmark]
public void SampleProfilerSessionStartStop()
{
var session = SampleProfilerSession.StartNew();
using var session = SampleProfilerSession.StartNew();
session.Stop();
}
#endregion
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Diagnostics.Tracing.EventPipe;
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Protocol;
Expand All @@ -13,7 +12,7 @@ namespace Sentry.Profiling;
/// <summary>
/// Processes TraceLog to produce a SampleProfile.
/// </summary>
internal class TraceLogProcessor
internal class SampleProfileBuilder
{
private readonly SentryOptions _options;
private readonly TraceLog _traceLog;
Expand All @@ -30,16 +29,13 @@ internal class TraceLogProcessor
// A sparse array mapping from a ThreadIndex to an index in Profile.Threads.
private readonly SparseScalarArray<int> _threadIndexes = new(-1, 10);

private readonly double _timeShiftMs;

public TraceLogProcessor(SentryOptions options, TraceLog traceLog, double TimeShiftMs)
public SampleProfileBuilder(SentryOptions options, TraceLog traceLog)
{
_options = options;
_traceLog = traceLog;
_timeShiftMs = TimeShiftMs;
}

internal void AddSample(TraceEvent data)
internal void AddSample(TraceEvent data, double timestampMs)
{
var thread = data.Thread();
if (thread.ThreadIndex == ThreadIndex.Invalid)
Expand All @@ -55,8 +51,6 @@ internal void AddSample(TraceEvent data)
return;
}

var timestampMs = data.TimeStampRelativeMSec + _timeShiftMs;

var stackIndex = AddStackTrace(callStackIndex);
if (stackIndex < 0)
{
Expand Down
33 changes: 22 additions & 11 deletions src/Sentry.Profiling/SampleProfilerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.Diagnostics.Tracing.Etlx;
using Microsoft.Diagnostics.Tracing.EventPipe;
using Microsoft.Diagnostics.Tracing.Parsers;
using Sentry.Extensibility;
using Sentry.Internal;

namespace Sentry.Profiling;
Expand All @@ -12,25 +13,28 @@ internal class SampleProfilerSession : IDisposable
private readonly EventPipeSession _session;
private readonly TraceLogEventSource _eventSource;
private readonly SampleProfilerTraceEventParser _sampleEventParser;
private readonly IDiagnosticLogger? _logger;
private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew();
private bool _stopped;

private SampleProfilerSession(EventPipeSession session)
private SampleProfilerSession(EventPipeSession session, IDiagnosticLogger? logger)
{
_session = session;
_logger = logger;
_eventSource = TraceLog.CreateFromEventPipeSession(_session);
_sampleEventParser = new SampleProfilerTraceEventParser(_eventSource);
_eventSource.Process();
// Process() blocks until the session is stopped so we need to run it on a separate thread.
Task.Factory.StartNew(_eventSource.Process, TaskCreationOptions.LongRunning);
}

// Exposed only for benchmarks.
internal static EventPipeProvider[] Providers = new[]
{
// Note: all events we need issued by "DotNETRuntime" provider are at "EventLevel.Informational"
// see https://learn.microsoft.com/en-us/dotnet/fundamentals/diagnostics/runtime-events
new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Informational, (long) ClrTraceEventParser.Keywords.Default),
new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational),
// new EventPipeProvider("System.Threading.Tasks.TplEventSource", EventLevel.Informational, (long) TplEtwProviderTraceEventParser.Keywords.Default)
new EventPipeProvider(ClrTraceEventParser.ProviderName, EventLevel.Informational, (long) ClrTraceEventParser.Keywords.Default),
new EventPipeProvider(SampleProfilerTraceEventParser.ProviderName, EventLevel.Informational),
// new EventPipeProvider(TplEtwProviderTraceEventParser.ProviderName, EventLevel.Informational, (long) TplEtwProviderTraceEventParser.Keywords.Default)
};

// Exposed only for benchmarks.
Expand All @@ -46,21 +50,28 @@ private SampleProfilerSession(EventPipeSession session)

public TraceLog TraceLog => _eventSource.TraceLog;

public static SampleProfilerSession StartNew()
public static SampleProfilerSession StartNew(IDiagnosticLogger? logger = null)
{
var client = new DiagnosticsClient(Process.GetCurrentProcess().Id);
var session = client.StartEventPipeSession(Providers, RequestRundown, CircularBufferMB);
return new SampleProfilerSession(session);
return new SampleProfilerSession(session, logger);
}

public void Stop()
{
if (!_stopped)
{
_stopped = true;
_session.Stop();
_session.Dispose();
_eventSource.Dispose();
try
{
_stopped = true;
_session.Stop();
_session.Dispose();
_eventSource.Dispose();
}
catch (Exception ex)
{
_logger?.LogWarning("Error during sampler profiler session shutdown.", ex);
}
}
}

Expand Down
56 changes: 42 additions & 14 deletions src/Sentry.Profiling/SamplingTransactionProfiler.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.Diagnostics.Tracing;
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Protocol;
Expand All @@ -8,18 +9,23 @@ internal class SamplingTransactionProfiler : ITransactionProfiler
{
public Action? OnFinish;
private readonly CancellationToken _cancellationToken;
private TimeSpan? _duration;
private bool _stopped = false;
private readonly SentryOptions _options;
private TraceLogProcessor _processor;
private SampleProfileBuilder _processor;
private SampleProfilerSession _session;
private readonly double _startTimeMs;
private double _endTimeMs;
private TaskCompletionSource _completionSource = new();

public SamplingTransactionProfiler(SentryOptions options, SampleProfilerSession session, int timeoutMs, CancellationToken cancellationToken)
{
_options = options;
_session = session;
_cancellationToken = cancellationToken;
_processor = new TraceLogProcessor(options, session.TraceLog, -session.Elapsed.TotalMilliseconds);
session.SampleEventParser.ThreadSample += _processor.AddSample;
_startTimeMs = session.Elapsed.TotalMilliseconds;
_endTimeMs = Double.MaxValue;
_processor = new SampleProfileBuilder(options, session.TraceLog);
session.SampleEventParser.ThreadSample += OnThreadSample;
cancellationToken.Register(() =>
{
if (Stop())
Expand All @@ -29,30 +35,49 @@ public SamplingTransactionProfiler(SentryOptions options, SampleProfilerSession
});
Task.Delay(timeoutMs, cancellationToken).ContinueWith(_ =>
{
if (Stop(TimeSpan.FromMilliseconds(timeoutMs)))
if (Stop(_startTimeMs + timeoutMs))
{
options.LogDebug("Profiling is being cut-of after {0} ms because the transaction takes longer than that.", timeoutMs);
}
}, CancellationToken.None);
}

private bool Stop(TimeSpan? duration = null)
private bool Stop(double? endTimeMs = null)
{
if (_duration is null)
endTimeMs ??= _session.Elapsed.TotalMilliseconds;
if (!_stopped)
{
lock (_session)
{
if (_duration is null)
if (!_stopped)
{
_duration = duration ?? _session.Elapsed;
_session.SampleEventParser.ThreadSample -= _processor.AddSample;
_stopped = true;
_endTimeMs = endTimeMs.Value;
return true;
}
}
}
return false;
}

// We need custom sampling because the TraceLog dispatches events from a queue with a delay of about 2 seconds.
private void OnThreadSample(TraceEvent data)
{
var timestampMs = data.TimeStampRelativeMSec;
if (timestampMs >= _startTimeMs)
{
if (timestampMs <= _endTimeMs)
{
_processor.AddSample(data, timestampMs - _startTimeMs);
}
else
{
_session.SampleEventParser.ThreadSample -= OnThreadSample;
_completionSource.TrySetResult();
}
}
}

/// <inheritdoc />
public void Finish()
{
Expand All @@ -62,16 +87,19 @@ public void Finish()
}
}

// TODO doesn't need to be async anymore...
/// <inheritdoc />
public async Task<ProfileInfo> CollectAsync(Transaction transaction)
{
if (_duration is null)
if (!_stopped)
{
throw new InvalidOperationException("Profiler.CollectAsync() called before Finish()");
}
_cancellationToken.ThrowIfCancellationRequested();
return await Task.FromResult(CreateProfileInfo(transaction, _processor.Profile)).ConfigureAwait(false);

// Wait for the last sample (<= _endTimeMs), or at most 10 seconds. The timeout shouldn't happen because
// TraceLog.realTimeQueue should dispatch events after ~2 seconds, but if it does, send what we have.
await Task.WhenAny(_completionSource.Task, Task.Delay(10_000, _cancellationToken)).ConfigureAwait(false);

return CreateProfileInfo(transaction, _processor.Profile);
}

internal static ProfileInfo CreateProfileInfo(Transaction transaction, SampleProfile profile)
Expand Down
7 changes: 4 additions & 3 deletions src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ internal class SamplingTransactionProfilerFactory : IDisposable, ITransactionPro

public static SamplingTransactionProfilerFactory Create(SentryOptions options)
{
return new SamplingTransactionProfilerFactory(options, SampleProfilerSession.StartNew());
var session = SampleProfilerSession.StartNew(options.DiagnosticLogger);
return new SamplingTransactionProfilerFactory(options, session);
}

public static async Task<SamplingTransactionProfilerFactory> CreateAsync(SentryOptions options)
{
var session = await Task.Run(() => SampleProfilerSession.StartNew()).ConfigureAwait(false);
var session = await Task.Run(() => SampleProfilerSession.StartNew(options.DiagnosticLogger)).ConfigureAwait(false);
return new SamplingTransactionProfilerFactory(options, session);
}

Expand Down Expand Up @@ -59,6 +60,6 @@ private SamplingTransactionProfilerFactory(SentryOptions options, SampleProfiler

public void Dispose()
{
_session.Stop();
_session.Dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ private void ValidateProfile(SampleProfile profile, ulong maxTimestampNs)

private void RunForMs(int milliseconds)
{
for (int i = 0; i < milliseconds / 20; i++)
var clock = Stopwatch.StartNew();
while (clock.ElapsedMilliseconds < milliseconds)
{
_testOutputLogger.LogDebug("sleeping...");
Thread.Sleep(20);
_testOutputLogger.LogDebug("Sleeping... time remaining: {0} ms", milliseconds - clock.ElapsedMilliseconds);
Thread.Sleep((int)Math.Min(milliseconds / 5, milliseconds - clock.ElapsedMilliseconds));
}
}

Expand Down Expand Up @@ -83,7 +84,7 @@ public void Profiler_AfterTimeout_Stops()
{
var hub = Substitute.For<IHub>();
var options = new SentryOptions { DiagnosticLogger = _testOutputLogger };
using var session = SampleProfilerSession.StartNew();
using var session = SampleProfilerSession.StartNew(_testOutputLogger);
var limitMs = 50;
var sut = new SamplingTransactionProfiler(options, session, limitMs, CancellationToken.None);
RunForMs(limitMs * 4);
Expand Down