From 162ea2a5c5aa4b36f1cc5ba8f16beb7608ae841a Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sun, 23 Apr 2023 23:04:20 +0200 Subject: [PATCH 01/21] prepare traceevent integration --- .gitmodules | 3 ++ before.Sentry.sln.targets | 7 ++-- modules/perfview | 1 + src/Sentry/Protocol/SampleProfile.cs | 6 +-- src/Sentry/Sentry.csproj | 60 ++++++++++++++++------------ 5 files changed, 44 insertions(+), 33 deletions(-) create mode 160000 modules/perfview diff --git a/.gitmodules b/.gitmodules index e065467a10..1ea998c72d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,3 +8,6 @@ path = modules/Carthage url = https://github.com/Carthage/Carthage branch = xcframework-catalyst +[submodule "modules/perfview"] + path = modules/perfview + url = https://github.com/vaind/perfview.git diff --git a/before.Sentry.sln.targets b/before.Sentry.sln.targets index a574a849ba..6bfd70d883 100644 --- a/before.Sentry.sln.targets +++ b/before.Sentry.sln.targets @@ -1,8 +1,7 @@ - - - + + + diff --git a/modules/perfview b/modules/perfview new file mode 160000 index 0000000000..97221adca5 --- /dev/null +++ b/modules/perfview @@ -0,0 +1 @@ +Subproject commit 97221adca5331725b59515d521a5f0ee8bd35cfb diff --git a/src/Sentry/Protocol/SampleProfile.cs b/src/Sentry/Protocol/SampleProfile.cs index 8d70e22415..45a3f6144a 100644 --- a/src/Sentry/Protocol/SampleProfile.cs +++ b/src/Sentry/Protocol/SampleProfile.cs @@ -17,9 +17,9 @@ namespace Sentry.Protocol; internal sealed class SampleProfile : IJsonSerializable { // Note: changing these to properties would break because GrowableArray is a struct. - internal GrowableArray Samples = new(10000); - internal GrowableArray Frames = new(100); - internal GrowableArray Stacks = new(100); + internal Internal.GrowableArray Samples = new(10000); + internal Internal.GrowableArray Frames = new(100); + internal Internal.GrowableArray Stacks = new(100); internal List Threads = new(10); public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger) diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index 2a822bb47b..4d73a9bd7f 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -45,6 +45,32 @@ + + + <_ProfilingSupported>true + true + + + + + + %(RecursiveDir)\%(Filename)%(Extension) + + + %(RecursiveDir)\%(Filename)%(Extension) + + + %(RecursiveDir)\%(Filename)%(Extension) + + + + + + + + + + - + From 68bafaabbfc57a91486f552b382a5aa215aa0c0b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 4 May 2023 22:21:17 +0200 Subject: [PATCH 02/21] wip: in-memory tracelog --- .../SamplingTransactionProfiler.cs | 100 +++--------------- src/Sentry.Profiling/Sentry.Profiling.csproj | 2 +- .../TraceEvent/TraceLogProcessor.cs | 9 +- .../TraceLogProcessorTests.verify.cs | 6 +- 4 files changed, 21 insertions(+), 96 deletions(-) diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index b340916ca3..3eb3aa59f1 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -92,31 +92,21 @@ public async Task CollectAsync(Transaction transaction) _options.LogDebug("Starting profile processing."); _transaction = transaction; - using var traceLog = await CreateTraceLogAsync().ConfigureAwait(false); + using var nettraceStream = await _data.ConfigureAwait(false); + using var eventPipeEventSource = new EventPipeEventSource(nettraceStream); + using var traceLogEventSource = TraceLog.CreateFromEventPipeEventSource(eventPipeEventSource); - try - { - _options.LogDebug("Converting profile to Sentry format."); + _options.LogDebug("Converting profile to Sentry format."); - _cancellationToken.ThrowIfCancellationRequested(); - var processor = new TraceLogProcessor(_options, traceLog) - { - MaxTimestampMs = _duration.Value.TotalMilliseconds - }; - - var profile = processor.Process(_cancellationToken); - _options.LogDebug("Profiling finished successfully."); - return CreateProfileInfo(transaction, profile); - } - finally + _cancellationToken.ThrowIfCancellationRequested(); + var processor = new TraceLogProcessor(_options, traceLogEventSource) { - traceLog.Dispose(); - if (File.Exists(traceLog.FilePath)) - { - _options.LogDebug("Removing temporarily file '{0}'.", traceLog.FilePath); - File.Delete(traceLog.FilePath); - } - } + MaxTimestampMs = _duration.Value.TotalMilliseconds + }; + + var profile = processor.Process(_cancellationToken); + _options.LogDebug("Profiling finished successfully."); + return CreateProfileInfo(transaction, profile); } internal static ProfileInfo CreateProfileInfo(Transaction transaction, SampleProfile profile) @@ -134,70 +124,4 @@ internal static ProfileInfo CreateProfileInfo(Transaction transaction, SamplePro Profile = profile }; } - - // We need the TraceLog for all the stack processing it does. - private async Task CreateTraceLogAsync() - { - _cancellationToken.ThrowIfCancellationRequested(); - Debug.Assert(_data is not null); - using var nettraceStream = await _data.ConfigureAwait(false); - using var eventSource = CreateEventPipeEventSource(nettraceStream); - return ConvertToETLX(eventSource); - } - - // EventPipeEventSource(Stream stream) sets isStreaming = true even though the stream is pre-collected. This - // causes read issues when converting to ETLX. It works fine if we use the private constructor, setting false. - // TODO make a PR to change this - private EventPipeEventSource CreateEventPipeEventSource(MemoryStream nettraceStream) - { - var privateNewEventPipeEventSource = typeof(EventPipeEventSource).GetConstructor( - _commonBindingFlags, - new Type[] { typeof(PinnedStreamReader), typeof(string), typeof(bool) }); - - var eventSource = privateNewEventPipeEventSource?.Invoke(new object[] { - new PinnedStreamReader(nettraceStream, 16384, new SerializationConfiguration{ StreamLabelWidth = StreamLabelWidth.FourBytes }, StreamReaderAlignment.OneByte), - "stream", - false - }) as EventPipeEventSource; - - if (eventSource is null) - { - throw new InvalidOperationException("Couldn't initialize EventPipeEventSource"); - } - - // TODO make downsampling conditional once this is available: https://github.com/dotnet/runtime/issues/82939 - // _options.LogDebug("Profile will be downsampled before processing."); - new Downsampler().AttachTo(eventSource); - - return eventSource; - } - - private TraceLog ConvertToETLX(EventPipeEventSource source) - { - Debug.Assert(_transaction is not null); - var etlxPath = Path.Combine(_tempDirectoryPath, $"{_transaction.EventId}.etlx"); - _options.LogDebug("Writing profile temporarily to '{0}'.", etlxPath); - if (File.Exists(etlxPath)) - { - _options.LogDebug("Temporary file '{0}' already exists, deleting first.", etlxPath); - File.Delete(etlxPath); - } - - _cancellationToken.ThrowIfCancellationRequested(); - typeof(TraceLog) - .GetMethod( - "CreateFromEventPipeEventSources", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, - new Type[] { typeof(TraceEventDispatcher), typeof(string), typeof(TraceLogOptions) })? - .Invoke(null, new object[] { source, etlxPath, new TraceLogOptions() { ContinueOnError = true } }); - - if (!File.Exists(etlxPath)) - { - throw new FileNotFoundException($"Profiler failed at CreateFromEventPipeEventSources() - temporary file doesn't exist", etlxPath); - } - - return new TraceLog(etlxPath); - } - - private readonly BindingFlags _commonBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; } diff --git a/src/Sentry.Profiling/Sentry.Profiling.csproj b/src/Sentry.Profiling/Sentry.Profiling.csproj index f85e4abd9d..a57200da6b 100644 --- a/src/Sentry.Profiling/Sentry.Profiling.csproj +++ b/src/Sentry.Profiling/Sentry.Profiling.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Sentry.Profiling/TraceEvent/TraceLogProcessor.cs b/src/Sentry.Profiling/TraceEvent/TraceLogProcessor.cs index 3b0b5ff6b4..027e1b4c6f 100644 --- a/src/Sentry.Profiling/TraceEvent/TraceLogProcessor.cs +++ b/src/Sentry.Profiling/TraceEvent/TraceLogProcessor.cs @@ -51,7 +51,7 @@ internal class TraceLogProcessor private readonly Dictionary _stackIndexes = new(100); // A sparse array mapping from a ThreadIndex to an index in Profile.Threads. - private readonly SparseScalarArray _threadIndexes; + private readonly SparseScalarArray _threadIndexes = new(-1, 10); // A sparse array mapping from an ActivityIndex to an index in Profile.Threads. private readonly SparseScalarArray _activityIndexes = new(-1, 100); @@ -95,12 +95,11 @@ internal class TraceLogProcessor public double MaxTimestampMs { get; set; } = double.MaxValue; - public TraceLogProcessor(SentryOptions options, TraceLog traceLog) + public TraceLogProcessor(SentryOptions options, TraceLogEventSource eventSource) { - _threadIndexes = new(-1, traceLog.Threads.Count); _options = options; - _traceLog = traceLog; - _eventSource = _traceLog.Events.GetSource(); + _traceLog = eventSource.TraceLog; + _eventSource = eventSource; _stackSource = new MutableTraceEventStackSource(_traceLog) { //_stackSource = new TraceEventStackSource(_eventLog.Events) { diff --git a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs index 20c2946153..a9659b32ec 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs @@ -50,8 +50,10 @@ private SampleProfile GetProfile() } using var eventLog = new TraceLog(etlxFilePath); - var processor = new TraceLogProcessor(new(), eventLog); - return processor.Process(CancellationToken.None); + // FIXME + // var processor = new TraceLogProcessor(new(), eventLog); + // return processor.Process(CancellationToken.None); + return new(); } [Fact] From f6657f558e49213fcb9b901e1bfc4b0e24990bbb Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 5 May 2023 20:58:21 +0200 Subject: [PATCH 03/21] in-memory tracelog support --- src/Sentry.Profiling/Downsampler.cs | 3 +- .../SamplingTransactionProfiler.cs | 3 + .../TraceEvent/ATTRIBUTION.txt | 29 - .../TraceEvent/TraceLogProcessor.cs | 678 ------------------ src/Sentry.Profiling/TraceLogProcessor.cs | 222 ++++++ 5 files changed, 227 insertions(+), 708 deletions(-) delete mode 100644 src/Sentry.Profiling/TraceEvent/ATTRIBUTION.txt delete mode 100644 src/Sentry.Profiling/TraceEvent/TraceLogProcessor.cs create mode 100644 src/Sentry.Profiling/TraceLogProcessor.cs diff --git a/src/Sentry.Profiling/Downsampler.cs b/src/Sentry.Profiling/Downsampler.cs index 1b75d6d959..de8d62d69c 100644 --- a/src/Sentry.Profiling/Downsampler.cs +++ b/src/Sentry.Profiling/Downsampler.cs @@ -1,6 +1,7 @@ using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.EventPipe; +// TODO remove /// /// Reduce sampling rate from 1 Hz that is the default for the provider to the configured SamplingRateMs. /// @@ -11,7 +12,7 @@ internal class Downsampler private double NextSampleLow = 0; private double NextSampleHigh = -1; - public void AttachTo(EventPipeEventSource source) + public void AttachTo(TraceEventDispatcher source) { source.AddDispatchHook(DispatchHook); } diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index 3eb3aa59f1..7f68202290 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -96,6 +96,9 @@ public async Task CollectAsync(Transaction transaction) using var eventPipeEventSource = new EventPipeEventSource(nettraceStream); using var traceLogEventSource = TraceLog.CreateFromEventPipeEventSource(eventPipeEventSource); + // TODO make downsampling conditional once this is available: https://github.com/dotnet/runtime/issues/82939 + new Downsampler().AttachTo(traceLogEventSource); + _options.LogDebug("Converting profile to Sentry format."); _cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/Sentry.Profiling/TraceEvent/ATTRIBUTION.txt b/src/Sentry.Profiling/TraceEvent/ATTRIBUTION.txt deleted file mode 100644 index 46afb105ec..0000000000 --- a/src/Sentry.Profiling/TraceEvent/ATTRIBUTION.txt +++ /dev/null @@ -1,29 +0,0 @@ -Parts of the code in this subdirectory have been adapted from -https://github.com/microsoft/perfview/blob/ff6e8e6c4118a26521515f41ae77f15981d68c53/src/TraceEvent/Computers/SampleProfilerThreadTimeComputer.cs - -The original license is as follows: - - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/src/Sentry.Profiling/TraceEvent/TraceLogProcessor.cs b/src/Sentry.Profiling/TraceEvent/TraceLogProcessor.cs deleted file mode 100644 index 027e1b4c6f..0000000000 --- a/src/Sentry.Profiling/TraceEvent/TraceLogProcessor.cs +++ /dev/null @@ -1,678 +0,0 @@ -using Microsoft.Diagnostics.Symbols; -using Microsoft.Diagnostics.Tracing; -using Microsoft.Diagnostics.Tracing.Etlx; -using Microsoft.Diagnostics.Tracing.EventPipe; -using Microsoft.Diagnostics.Tracing.Parsers; -using Microsoft.Diagnostics.Tracing.Stacks; -using Sentry.Internal; -using Sentry.Protocol; - -namespace Sentry.Profiling; - -// A list of frame indexes. -using SentryProfileStackTrace = HashableGrowableArray; - -/// -/// Processes TraceLog to produce a SampleProfile. -/// Based on https://github.com/microsoft/perfview/blob/ff6e8e6c4118a26521515f41ae77f15981d68c53/src/TraceEvent/Computers/SampleProfilerThreadTimeComputer.cs -/// Note that several parts of the code below are commented out as we intend to use this in follow up PRs -/// -internal class TraceLogProcessor -{ - private readonly SentryOptions _options; - // /// - // /// If set we compute thread time using Tasks - // /// - // private bool UseTasks = true; - - ///// - ///// Use start-stop activities as the grouping construct. - ///// - //private bool GroupByStartStopActivity = true; - - // /// - // /// Reduce nested application insights requests by using related activity id. - // /// - // /// - // private bool IgnoreApplicationInsightsRequestsWithRelatedActivityId { get; set; } = true; - - private readonly TraceLog _traceLog; - private readonly TraceLogEventSource _eventSource; - - /// - /// Output profile being built. - /// - private readonly SampleProfile _profile = new(); - - // A sparse array that maps from StackSourceFrameIndex to an index in the output Profile.frames. - private readonly SparseScalarArray _frameIndexes = new(-1, 1000); - - // A dictionary from a StackTrace sealed array to an index in the output Profile.stacks. - private readonly Dictionary _stackIndexes = new(100); - - // A sparse array mapping from a ThreadIndex to an index in Profile.Threads. - private readonly SparseScalarArray _threadIndexes = new(-1, 10); - - // A sparse array mapping from an ActivityIndex to an index in Profile.Threads. - private readonly SparseScalarArray _activityIndexes = new(-1, 100); - - // private readonly StartStopActivityComputer _startStopActivities; // Tracks start-stop activities so we can add them to the top above thread in the stack. - - // // UNKNOWN_ASYNC support - // /// - // /// Used to create UNKNOWN frames for start-stop activities. This is indexed by StartStopActivityIndex. - // /// and for each start-stop activity indicates when unknown time starts. However if that activity still - // /// has known activities associated with it then the number will be negative, and its value is the - // /// ref-count of known activities (thus when it falls to 0, it we set it to the start of unknown time. - // /// This is indexed by the TOP-MOST start-stop activity. - // /// - // private Internal.GrowableArray _unknownTimeStartMsec; - - // /// - // /// maps thread ID to the current TOP-MOST start-stop activity running on that thread. Used to updated _unknownTimeStartMsec - // /// to figure out when to put in UNKNOWN_ASYNC nodes. - // /// - // private readonly StartStopActivity[] _threadToStartStopActivity; - - // /// - // /// Sadly, with AWAIT nodes might come into existence AFTER we would have normally identified - // /// a region as having no thread/await working on it. Thus you have to be able to 'undo' ASYNC_UNKONWN - // /// nodes. We solve this by remembering all of our ASYNC_UNKNOWN nodes on a list (basically provisional) - // /// and only add them when the start-stop activity dies (when we know there can't be another AWAIT. - // /// Note that we only care about TOP-MOST activities. - // /// - // private Internal.GrowableArray> _startStopActivityToAsyncUnknownSamples; - - // End UNKNOWN_ASYNC support - - //private ThreadState[] _threadState; // This maps thread (indexes) to what we know about the thread - - //private StackSourceSample _sample; // Reusable scratch space - private readonly MutableTraceEventStackSource _stackSource; // The output source we are generating. - // private TraceEventStackSource _stackSource; - - private readonly ActivityComputer _activityComputer; // Used to compute stacks for Tasks - - public double MaxTimestampMs { get; set; } = double.MaxValue; - - public TraceLogProcessor(SentryOptions options, TraceLogEventSource eventSource) - { - _options = options; - _traceLog = eventSource.TraceLog; - _eventSource = eventSource; - _stackSource = new MutableTraceEventStackSource(_traceLog) - { - //_stackSource = new TraceEventStackSource(_eventLog.Events) { - OnlyManagedCodeStacks = true // EventPipe currently only has managed code stacks. - }; - // var _sample = new StackSourceSample(_stackSource); - - //if (GroupByStartStopActivity) { - // UseTasks = true; - //} - - if (true)//UseTasks) - { - // TODO replace SymbolReader's http handler so that it doesn't do any actual requests. - _activityComputer = new ActivityComputer(_eventSource, new SymbolReader(TextWriter.Null)) - { - NoCache = true, // Safer option, see its docs. Evaluate later if we can enable caching. - }; - // _activityComputer.AwaitUnblocks += delegate (TraceActivity activity, TraceEvent data) - // { - // var sample = _sample; - // sample.Metric = (float)(activity.StartTimeRelativeMSec - activity.CreationTimeRelativeMSec); - // sample.TimeRelativeMSec = activity.CreationTimeRelativeMSec; - - // // The stack at the Unblock, is the stack at the time the task was created (when blocking started). - // sample.StackIndex = _activityComputer.GetCallStackForActivity(_stackSource, activity, GetTopFramesForActivityComputerCase(data, data.Thread(), true)); - - // StackSourceFrameIndex awaitFrame = _stackSource.Interner.FrameIntern("AWAIT_TIME"); - // sample.StackIndex = _stackSource.Interner.CallStackIntern(awaitFrame, sample.StackIndex); - - // _stackSource.AddSample(sample); - - // // if (_threadToStartStopActivity != null) - // // { - // // UpdateStartStopActivityOnAwaitComplete(activity, data); - // // } - // }; - - // We can provide a bit of extra value (and it is useful for debugging) if we immediately log a CPU - // sample when we schedule or start a task. That we we get the very instant it starts. - // TODO does this make sense without "System.Threading.Tasks.TplEventSource" being enabled? - // see https://learn.microsoft.com/en-us/dotnet/core/diagnostics/well-known-event-providers#systemthreadingtaskstpleventsource-provider - // var tplProvider = new TplEtwProviderTraceEventParser(_eventSource); - // tplProvider.AwaitTaskContinuationScheduledSend += OnSampledProfile; - // tplProvider.TaskScheduledSend += OnSampledProfile; - // tplProvider.TaskExecuteStart += OnSampledProfile; - // tplProvider.TaskWaitSend += OnSampledProfile; - // tplProvider.TaskWaitStop += OnSampledProfile; // Log the activity stack even if you don't have a stack. - } - - // if (true) // GroupByStartStopActivity - // { - // _startStopActivities = new StartStopActivityComputer(_eventSource, _activityComputer, IgnoreApplicationInsightsRequestsWithRelatedActivityId); - - // // Maps thread Indexes to the start-stop activity that they are executing. - // _threadToStartStopActivity = new StartStopActivity[_traceLog.Threads.Count]; - - // /********* Start Unknown Async State machine for StartStop activities ******/ - // // The delegates below along with the AddUnkownAsyncDurationIfNeeded have one purpose: - // // To inject UNKNOWN_ASYNC stacks when there is an active start-stop activity that is - // // 'missing' time. It has the effect of ensuring that Start-Stop tasks always have - // // a metric that is not unrealistically small. - // _activityComputer.Start += delegate (TraceActivity activity, TraceEvent data) - // { - // StartStopActivity newStartStopActivityForThread = _startStopActivities.GetCurrentStartStopActivity(activity.Thread, data); - // UpdateThreadToWorkOnStartStopActivity(activity.Thread, newStartStopActivityForThread, data); - // }; - - // _activityComputer.AfterStop += delegate (TraceActivity activity, TraceEvent data, TraceThread thread) - // { - // StartStopActivity newStartStopActivityForThread = _startStopActivities.GetCurrentStartStopActivity(thread, data); - // UpdateThreadToWorkOnStartStopActivity(thread, newStartStopActivityForThread, data); - // }; - - // _startStopActivities.Start += delegate (StartStopActivity startStopActivity, TraceEvent data) - // { - // // We only care about the top-most activities since unknown async time is defined as time - // // where a top most activity is running but no thread (or await time) is associated with it - // // fast out otherwise (we just ensure that we mark the thread as doing this activity) - // if (startStopActivity.Creator != null) - // { - // UpdateThreadToWorkOnStartStopActivity(data.Thread(), startStopActivity, data); - // return; - // } - - // // Then we have a refcount of exactly one - // Debug.Assert(_unknownTimeStartMsec.Get((int)startStopActivity.Index) >= 0); // There was nothing running before. - - // _unknownTimeStartMsec.Set((int)startStopActivity.Index, -1); // Set it so just we are running. - // _threadToStartStopActivity[(int)data.Thread().ThreadIndex] = startStopActivity; - // }; - - // _startStopActivities.Stop += delegate (StartStopActivity startStopActivity, TraceEvent data) - // { - // // We only care about the top-most activities since unknown async time is defined as time - // // where a top most activity is running but no thread (or await time) is associated with it - // // fast out otherwise - // if (startStopActivity.Creator != null) - // { - // return; - // } - - // double unknownStartTime = _unknownTimeStartMsec.Get((int)startStopActivity.Index); - // if (0 < unknownStartTime) - // { - // AddUnkownAsyncDurationIfNeeded(startStopActivity, unknownStartTime, data); - // } - - // // Actually emit all the async unknown events. - // List samples = _startStopActivityToAsyncUnknownSamples.Get((int)startStopActivity.Index); - // if (samples != null) - // { - // foreach (var sample in samples) - // { - // _stackSource.AddSample(sample); // Adding Unknown ASync - // } - - // _startStopActivityToAsyncUnknownSamples.Set((int)startStopActivity.Index, null); - // } - - // _unknownTimeStartMsec.Set((int)startStopActivity.Index, 0); - // Debug.Assert(_threadToStartStopActivity[(int)data.Thread().ThreadIndex] == startStopActivity || - // _threadToStartStopActivity[(int)data.Thread().ThreadIndex] == null); - // _threadToStartStopActivity[(int)data.Thread().ThreadIndex] = null; - // }; - // } - - // _eventSource.Clr.GCAllocationTick += OnSampledProfile; - // _eventSource.Clr.GCSampledObjectAllocation += OnSampledProfile; - - var sampleEventParser = new SampleProfilerTraceEventParser(_eventSource); - sampleEventParser.ThreadSample += OnSampledProfile; - } - - public SampleProfile Process(CancellationToken cancellationToken) - { - var registration = cancellationToken.Register(_eventSource.StopProcessing); - _eventSource.Process(); - registration.Unregister(); - return _profile; - } - - //private void UpdateStartStopActivityOnAwaitComplete(TraceActivity activity, TraceEvent data) { - // // If we are createing 'UNKNOWN_ASYNC nodes, make sure that AWAIT_TIME does not overlap with UNKNOWN_ASYNC time - - // var startStopActivity = _startStopActivities.GetStartStopActivityForActivity(activity); - // if (startStopActivity == null) { - // return; - // } - - // while (startStopActivity.Creator != null) { - // startStopActivity = startStopActivity.Creator; - // } - - // // If the await finishes before the ASYNC_UNKNOWN, simply adust the time. - // if (0 <= _unknownTimeStartMsec.Get((int)startStopActivity.Index)) { - // _unknownTimeStartMsec.Set((int)startStopActivity.Index, data.TimeStampRelativeMSec); - // } - - // // It is possible that the ASYNC_UNKOWN has already completed. In that case, remove overlapping ones - // List async_unknownSamples = _startStopActivityToAsyncUnknownSamples.Get((int)startStopActivity.Index); - // if (async_unknownSamples != null) { - // int removeStart = async_unknownSamples.Count; - // while (0 < removeStart) { - // int probe = removeStart - 1; - // var sample = async_unknownSamples[probe]; - // if (activity.CreationTimeRelativeMSec <= sample.TimeRelativeMSec + sample.Metric) // There is overlap - // { - // removeStart = probe; - // } - // else { - // break; - // } - // } - // int removeCount = async_unknownSamples.Count - removeStart; - // if (removeCount > 0) { - // async_unknownSamples.RemoveRange(removeStart, removeCount); - // } - // } - //} - - ///// - ///// Updates it so that 'thread' is now working on newStartStop, which can be null which means that it is not working on any - ///// start-stop task. - ///// - //private void UpdateThreadToWorkOnStartStopActivity(TraceThread thread, StartStopActivity newStartStop, TraceEvent data) { - // // Make the new-start stop activity be the top most one. This is all we need and is more robust in the case - // // of unusual state transitions (e.g. lost events non-nested start-stops ...). Ref-counting is very fragile - // // after all... - // if (newStartStop != null) { - // while (newStartStop.Creator != null) { - // newStartStop = newStartStop.Creator; - // } - // } - - // StartStopActivity oldStartStop = _threadToStartStopActivity[(int)thread.ThreadIndex]; - // Debug.Assert(oldStartStop == null || oldStartStop.Creator == null); - // if (oldStartStop == newStartStop) // No change, nothing to do, quick exit. - // { - // return; - // } - - // // Decrement the start-stop which lost its thread. - // if (oldStartStop != null) { - // double unknownStartTimeMSec = _unknownTimeStartMsec.Get((int)oldStartStop.Index); - // Debug.Assert(unknownStartTimeMSec < 0); - // if (unknownStartTimeMSec < 0) { - // unknownStartTimeMSec++; //We represent the ref count as a negative number, here we are decrementing the ref count - // if (unknownStartTimeMSec == 0) { - // unknownStartTimeMSec = data.TimeStampRelativeMSec; // Remember when we dropped to zero. - // } - - // _unknownTimeStartMsec.Set((int)oldStartStop.Index, unknownStartTimeMSec); - // } - // } - // _threadToStartStopActivity[(int)thread.ThreadIndex] = newStartStop; - - // // Increment refcount on the new startStop activity - // if (newStartStop != null) { - // double unknownStartTimeMSec = _unknownTimeStartMsec.Get((int)newStartStop.Index); - // // If we were off before (a positive number) then log the unknown time. - // if (0 < unknownStartTimeMSec) { - // AddUnkownAsyncDurationIfNeeded(newStartStop, unknownStartTimeMSec, data); - // unknownStartTimeMSec = 0; - // } - // --unknownStartTimeMSec; //We represent the ref count as a negative number, here we are incrementing the ref count - // _unknownTimeStartMsec.Set((int)newStartStop.Index, unknownStartTimeMSec); - // } - //} - - //private void AddUnkownAsyncDurationIfNeeded(StartStopActivity startStopActivity, double unknownStartTimeMSec, TraceEvent data) { - // Debug.Assert(0 < unknownStartTimeMSec); - // Debug.Assert(unknownStartTimeMSec <= data.TimeStampRelativeMSec); - - // if (startStopActivity.IsStopped) { - // return; - // } - - // // We dont bother with times that are too small, we consider 1msec the threshold - // double delta = data.TimeStampRelativeMSec - unknownStartTimeMSec; - // if (delta < 1) { - // return; - // } - - // // Add a sample with the amount of unknown duration. - // var sample = new StackSourceSample(_stackSource); - // sample.Metric = (float)delta; - // sample.TimeRelativeMSec = unknownStartTimeMSec; - - // StackSourceCallStackIndex stackIndex = _startStopActivities.GetStartStopActivityStack(_stackSource, startStopActivity, data.Process()); - // StackSourceFrameIndex unknownAsyncFrame = _stackSource.Interner.FrameIntern("UNKNOWN_ASYNC"); - // stackIndex = _stackSource.Interner.CallStackIntern(unknownAsyncFrame, stackIndex); - // sample.StackIndex = stackIndex; - - // // We can't add the samples right now because AWAIT nodes might overlap and we have to take these back. - // // The add the to this list so that they can be trimmed at that time if needed. - - // List list = _startStopActivityToAsyncUnknownSamples.Get((int)startStopActivity.Index); - // if (list == null) { - // list = new List(); - // _startStopActivityToAsyncUnknownSamples.Set((int)startStopActivity.Index, list); - // } - // list.Add(sample); - //} - - private void AddSample(TraceThread thread, TraceActivity activity, StackSourceCallStackIndex callstackIndex, double timestampMs) - { - if (thread.ThreadIndex == ThreadIndex.Invalid || callstackIndex == StackSourceCallStackIndex.Invalid) - { - return; - } - - // Trim samples coming after the profiling has been stopped (i.e. after the Stop() IPC request has been sent). - if (timestampMs > MaxTimestampMs) - { - // We can completely stop processing after the first sample that is after the timeout. Samples are - // ordered (I've checked this manually so I hope that assumption holds...) so no need to go through the rest. - _eventSource.StopProcessing(); - return; - } - - var stackIndex = AddStackTrace(callstackIndex); - if (stackIndex < 0) - { - return; - } - - var threadIndex = AddThreadOrActivity(thread, activity); - if (threadIndex < 0) - { - return; - } - - _profile.Samples.Add(new() - { - Timestamp = (ulong)(timestampMs * 1_000_000), - StackId = stackIndex, - ThreadId = threadIndex - }); - } - - /// - /// Adds stack trace and frames, if missing. - /// - /// The index into the Profile's stacks list - private int AddStackTrace(StackSourceCallStackIndex callstackIndex) - { - SentryProfileStackTrace stackTrace = new(10); - StackSourceFrameIndex tlFrameIndex; - while (callstackIndex != StackSourceCallStackIndex.Invalid) - { - tlFrameIndex = _stackSource.GetFrameIndex(callstackIndex); - - if (tlFrameIndex == StackSourceFrameIndex.Invalid) - { - break; - } - - // "tlFrameIndex" may point to "CodeAddresses" or "Threads" or "Processes" - // See TraceEventStackSource.GetFrameName() code for details. - // We only care about the CodeAddresses bit because we don't want to show Threads and Processes in the stack trace. - CodeAddressIndex codeAddressIndex = _stackSource.GetFrameCodeAddress(tlFrameIndex); - if (codeAddressIndex != CodeAddressIndex.Invalid) - { - stackTrace.Add(AddStackFrame(codeAddressIndex)); - callstackIndex = _stackSource.GetCallerIndex(callstackIndex); - } - else - { - // No need to traverse further up the stack when we're on the thread/process. - break; - } - } - - int result = -1; - if (stackTrace.Count > 0) - { - stackTrace.Seal(); - if (!_stackIndexes.TryGetValue(stackTrace, out result)) - { - stackTrace.Trim(10); - _profile.Stacks.Add(stackTrace); - result = _profile.Stacks.Count - 1; - _stackIndexes[stackTrace] = result; - } - } - - return result; - } - - /// - /// 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 (!_frameIndexes.ContainsKey(key)) - { - _profile.Frames.Add(CreateStackFrame(codeAddressIndex)); - _frameIndexes[key] = _profile.Frames.Count - 1; - } - - return _frameIndexes[key]; - } - - /// - /// Check if the thread is already stored in the output Profile, or adds it. - /// - /// The index to the output Profile frames array. - private int AddThreadOrActivity(TraceThread thread, TraceActivity activity) - { - if (activity.IsThreadActivity) - { - var key = (int)thread.ThreadIndex; - - if (!_threadIndexes.ContainsKey(key)) - { - _profile.Threads.Add(new() - { - Name = thread.ThreadInfo ?? $"Thread {thread.ThreadID}", - }); - _threadIndexes[key] = _profile.Threads.Count - 1; - } - - return _threadIndexes[key]; - } - else - { - var key = (int)activity.Index; - - if (!_activityIndexes.ContainsKey(key)) - { - _profile.Threads.Add(new() - { - Name = $"Activity {ActivityPath(activity)}", - }); - _activityIndexes[key] = _profile.Threads.Count - 1; - } - - return _activityIndexes[key]; - } - } - - private static string ActivityPath(TraceActivity activity) - { - var creator = activity.Creator; - if (creator is null || creator.IsThreadActivity) - { - return activity.Index.ToString(); - } - else - { - return $"{ActivityPath(creator)}/{activity.Index.ToString()}"; - } - } - - 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; - - TraceModuleFile moduleFile = method.MethodModuleFile; - if (moduleFile is not null) - { - frame.Module = moduleFile.Name; - } - - // Displays the optimization tier of each code version executed for the method. E.g. "QuickJitted" - // Doesn't seem very useful (not much users can do with this information) so disabling for now. - // if (frame.Function is not null) - // { - // var optimizationTier = _traceLog.CodeAddresses.OptimizationTier(codeAddressIndex); - // if (optimizationTier != Microsoft.Diagnostics.Tracing.Parsers.Clr.OptimizationTier.Unknown) - // { - // frame.Function = $"{frame.Function} {{{optimizationTier}}}"; - // } - // } - - frame.ConfigureAppFrame(_options); - } - else - { - // native frame - frame.InApp = false; - } - - // TODO enable this once we implement symbolication (we will need to send debug_meta too), see StackTraceFactory. - // if (_traceLog.CodeAddresses.ILOffset(codeAddressIndex) is { } ilOffset && ilOffset >= 0) - // { - // frame.InstructionOffset = ilOffset; - // } - // else if (_traceLog.CodeAddresses.Address(codeAddressIndex) is { } address) - // { - // frame.InstructionAddress = $"0x{address:x}"; - // } - - return frame; - } - - private void OnSampledProfile(TraceEvent data) - { - TraceThread thread = data.Thread(); - if (thread != null) - { - TraceActivity activity = _activityComputer.GetCurrentActivity(thread); - // TODO expose ActivityComputer.GetCallStackWithActivityFrames() and use it - it's a bit faster because we also need to fetch thread & activity for AddSample(). - StackSourceCallStackIndex stackFrameIndex = _activityComputer.GetCallStack(_stackSource, data, GetTopFramesForActivityComputerCase(data, thread)); - AddSample(thread, activity, stackFrameIndex, data.TimeStampRelativeMSec); - } - else - { - Debug.WriteLine("Warning, no thread at " + data.TimeStampRelativeMSec.ToString("f3")); - } - } - - /// - /// Returns a function that figures out the top (closest to stack root) frames for an event. Often - /// this returns null which means 'use the normal thread-process frames'. - /// Normally this stack is for the current time, but if 'getAtCreationTime' is true, it will compute the - /// stack at the time that the current activity was CREATED rather than the current time. This works - /// better for await time. - /// - private Func? GetTopFramesForActivityComputerCase(TraceEvent data, TraceThread thread, bool getAtCreationTime = false) - { - return null; - // return (topThread => _startStopActivities.GetCurrentStartStopActivityStack(_stackSource, thread, topThread, getAtCreationTime)); - } - - ///// - ///// Represents all the information that we need to track for each thread. - ///// - //private struct ThreadState { - // public void LogThreadStack(double timeRelativeMSec, StackSourceCallStackIndex stackIndex, TraceThread thread, SampleProfiler computer, bool onCPU) { - // if (onCPU) { - // if (ThreadUninitialized) // First event is onCPU - // { - // AddCPUSample(timeRelativeMSec, thread, computer); - // LastBlockStackRelativeMSec = -1; // make ThreadRunning true - // } - // else if (ThreadRunning) // continue running - // { - // AddCPUSample(timeRelativeMSec, thread, computer); - // } - // else if (ThreadBlocked) // unblocked - // { - // AddBlockTimeSample(timeRelativeMSec, thread, computer); - // LastBlockStackRelativeMSec = -timeRelativeMSec; - // } - - // LastCPUStackRelativeMSec = timeRelativeMSec; - // LastCPUCallStack = stackIndex; - // } - // else { - // if (ThreadBlocked || ThreadUninitialized) // continue blocking or assume we started blocked - // { - // AddBlockTimeSample(timeRelativeMSec, thread, computer); - // } - // else if (ThreadRunning) // blocked - // { - // AddCPUSample(timeRelativeMSec, thread, computer); - // } - - // LastBlockStackRelativeMSec = timeRelativeMSec; - // LastBlockCallStack = stackIndex; - // } - // } - - // public void AddCPUSample(double timeRelativeMSec, TraceThread thread, SampleProfiler computer) { - // // Log the last sample if it was present - // if (LastCPUStackRelativeMSec > 0) { - // var sample = computer._sample; - // sample.Metric = (float)(timeRelativeMSec - LastCPUStackRelativeMSec); - // sample.TimeRelativeMSec = LastCPUStackRelativeMSec; - - // var nodeIndex = computer._cpuFrameIndex; - // sample.StackIndex = LastCPUCallStack; - - // sample.StackIndex = computer._stackSource.Interner.CallStackIntern(nodeIndex, sample.StackIndex); - // computer._stackSource.AddSample(sample); // CPU - // } - // } - - // public void AddBlockTimeSample(double timeRelativeMSec, TraceThread thread, SampleProfiler computer) { - // // Log the last sample if it was present - // if (LastBlockStackRelativeMSec > 0) { - // var sample = computer._sample; - // sample.Metric = (float)(timeRelativeMSec - LastBlockStackRelativeMSec); - // sample.TimeRelativeMSec = LastBlockStackRelativeMSec; - - // var nodeIndex = computer._ExternalFrameIndex; // BLOCKED_TIME - // sample.StackIndex = LastBlockCallStack; - - // sample.StackIndex = computer._stackSource.Interner.CallStackIntern(nodeIndex, sample.StackIndex); - // computer._stackSource.AddSample(sample); - // } - // } - - // public bool ThreadDead { get { return double.IsNegativeInfinity(LastBlockStackRelativeMSec); } } - // public bool ThreadRunning { get { return LastBlockStackRelativeMSec < 0 && !ThreadDead; } } - // public bool ThreadBlocked { get { return 0 < LastBlockStackRelativeMSec; } } - // public bool ThreadUninitialized { get { return LastBlockStackRelativeMSec == 0; } } - - // /* State */ - // internal double LastBlockStackRelativeMSec; // Negative means not blocked, NegativeInfinity means dead. 0 means uninitialized. - // internal StackSourceCallStackIndex LastBlockCallStack; - - // internal double LastCPUStackRelativeMSec; - // private StackSourceCallStackIndex LastCPUCallStack; - //} -} diff --git a/src/Sentry.Profiling/TraceLogProcessor.cs b/src/Sentry.Profiling/TraceLogProcessor.cs new file mode 100644 index 0000000000..2a3241a8e6 --- /dev/null +++ b/src/Sentry.Profiling/TraceLogProcessor.cs @@ -0,0 +1,222 @@ +using Microsoft.Diagnostics.Tracing; +using Microsoft.Diagnostics.Tracing.Etlx; +using Microsoft.Diagnostics.Tracing.EventPipe; +using Sentry.Extensibility; +using Sentry.Internal; +using Sentry.Protocol; + +namespace Sentry.Profiling; + +// A list of frame indexes. +using SentryProfileStackTrace = HashableGrowableArray; + +/// +/// Processes TraceLog to produce a SampleProfile. +/// +internal class TraceLogProcessor +{ + private readonly SentryOptions _options; + private readonly TraceLog _traceLog; + private readonly TraceLogEventSource _eventSource; + + // Output profile being built. + private readonly SampleProfile _profile = new(); + + // A sparse array that maps from StackSourceFrameIndex to an index in the output Profile.frames. + private readonly SparseScalarArray _frameIndexes = new(-1, 1000); + + // A dictionary from a StackTrace sealed array to an index in the output Profile.stacks. + private readonly Dictionary _stackIndexes = new(100); + + // A sparse array mapping from a ThreadIndex to an index in Profile.Threads. + private readonly SparseScalarArray _threadIndexes = new(-1, 10); + + public double MaxTimestampMs { get; set; } = double.MaxValue; + + public TraceLogProcessor(SentryOptions options, TraceLogEventSource eventSource) + { + _options = options; + _traceLog = eventSource.TraceLog; + _eventSource = eventSource; + var sampleEventParser = new SampleProfilerTraceEventParser(_eventSource); + sampleEventParser.ThreadSample += AddSample; + } + + public SampleProfile Process(CancellationToken cancellationToken) + { + var registration = cancellationToken.Register(_eventSource.StopProcessing); + _eventSource.Process(); + registration.Unregister(); + return _profile; + } + + private void AddSample(TraceEvent data) + { + var thread = data.Thread(); + if (thread.ThreadIndex == ThreadIndex.Invalid) + { + _options.DiagnosticLogger?.LogDebug("Encountered a Profiler Sample without a correct thread. Skipping."); + return; + } + + var callStackIndex = data.CallStackIndex(); + if (callStackIndex == CallStackIndex.Invalid) + { + _options.DiagnosticLogger?.LogDebug("Encountered a Profiler Sample without an associated call stack. Skipping."); + return; + } + + // Trim samples coming after the profiling has been stopped (i.e. after the Stop() IPC request has been sent). + var timestampMs = data.TimeStampRelativeMSec; + if (timestampMs > MaxTimestampMs) + { + // We can completely stop processing after the first sample that is after the timeout. Samples are + // ordered (I've checked this manually so I hope that assumption holds...) so no need to go through the rest. + _eventSource.StopProcessing(); + return; + } + + var stackIndex = AddStackTrace(callStackIndex); + if (stackIndex < 0) + { + return; + } + + var threadIndex = AddThread(thread); + if (threadIndex < 0) + { + return; + } + + _profile.Samples.Add(new() + { + Timestamp = (ulong)(timestampMs * 1_000_000), + StackId = stackIndex, + ThreadId = threadIndex + }); + } + + /// + /// Adds stack trace and frames, if missing. + /// + /// The index into the Profile's stacks list + private int AddStackTrace(CallStackIndex callstackIndex) + { + SentryProfileStackTrace stackTrace = new(10); + while (callstackIndex != CallStackIndex.Invalid) + { + var codeAddressIndex = _traceLog.CallStacks.CodeAddressIndex(callstackIndex); + if (codeAddressIndex != CodeAddressIndex.Invalid) + { + stackTrace.Add(AddStackFrame(codeAddressIndex)); + callstackIndex = _traceLog.CallStacks.Caller(callstackIndex); + } + else + { + // No need to traverse further up the stack when we're on the thread/process. + break; + } + } + + int result = -1; + if (stackTrace.Count > 0) + { + stackTrace.Seal(); + if (!_stackIndexes.TryGetValue(stackTrace, out result)) + { + stackTrace.Trim(10); + _profile.Stacks.Add(stackTrace); + result = _profile.Stacks.Count - 1; + _stackIndexes[stackTrace] = result; + } + } + + return result; + } + + /// + /// 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 (!_frameIndexes.ContainsKey(key)) + { + _profile.Frames.Add(CreateStackFrame(codeAddressIndex)); + _frameIndexes[key] = _profile.Frames.Count - 1; + } + + return _frameIndexes[key]; + } + + /// + /// Check if the thread is already stored in the output Profile, or adds it. + /// + /// The index to the output Profile frames array. + private int AddThread(TraceThread thread) + { + var key = (int)thread.ThreadIndex; + + if (!_threadIndexes.ContainsKey(key)) + { + _profile.Threads.Add(new() + { + Name = thread.ThreadInfo ?? $"Thread {thread.ThreadID}", + }); + _threadIndexes[key] = _profile.Threads.Count - 1; + } + + return _threadIndexes[key]; + } + + private static string ActivityPath(TraceActivity activity) + { + var creator = activity.Creator; + if (creator is null || creator.IsThreadActivity) + { + return activity.Index.ToString(); + } + else + { + return $"{ActivityPath(creator)}/{activity.Index.ToString()}"; + } + } + + 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; + + TraceModuleFile moduleFile = method.MethodModuleFile; + if (moduleFile is not null) + { + frame.Module = moduleFile.Name; + } + + frame.ConfigureAppFrame(_options); + } + else + { + // native frame + frame.InApp = false; + } + + // TODO enable this once we implement symbolication (we will need to send debug_meta too), see StackTraceFactory. + // if (_traceLog.CodeAddresses.ILOffset(codeAddressIndex) is { } ilOffset && ilOffset >= 0) + // { + // frame.InstructionOffset = ilOffset; + // } + // else if (_traceLog.CodeAddresses.Address(codeAddressIndex) is { } address) + // { + // frame.InstructionAddress = $"0x{address:x}"; + // } + + return frame; + } +} From 9f8727f5005c20193d5e7f0c2077c6b880726c35 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 8 May 2023 13:30:52 +0200 Subject: [PATCH 04/21] profiler - remove temp dir --- benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs | 2 +- src/Sentry.Profiling/SamplingTransactionProfiler.cs | 4 +--- src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs | 7 ++----- src/Sentry.Profiling/SentryProfiling.cs | 2 +- .../SamplingTransactionProfilerTests.cs | 4 ++-- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs index 60ae5b22d7..b1a82f70dc 100644 --- a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs @@ -11,7 +11,7 @@ namespace Sentry.Benchmarks; public class ProfilingBenchmarks { private IHub _hub = Substitute.For(); - private ITransactionProfilerFactory _factory = new SamplingTransactionProfilerFactory(Path.GetTempPath(), new()); + private ITransactionProfilerFactory _factory = new SamplingTransactionProfilerFactory(new()); #region full transaction profiling public IEnumerable ProfilerArguments() diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index 7f68202290..229693edf3 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -15,14 +15,12 @@ internal class SamplingTransactionProfiler : ITransactionProfiler private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); private TimeSpan? _duration; private Task? _data; - private readonly string _tempDirectoryPath; private Transaction? _transaction; private readonly SentryOptions _options; - public SamplingTransactionProfiler(string tempDirectoryPath, SentryOptions options, CancellationToken cancellationToken) + public SamplingTransactionProfiler(SentryOptions options, CancellationToken cancellationToken) { _options = options; - _tempDirectoryPath = tempDirectoryPath; _cancellationToken = cancellationToken; } diff --git a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs index 91dac242cb..72337be066 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs @@ -14,13 +14,10 @@ internal class SamplingTransactionProfilerFactory : ITransactionProfilerFactory // Stop profiling after the given number of milliseconds. private const int TIME_LIMIT_MS = 30_000; - private readonly string _tempDirectoryPath; - private readonly SentryOptions _options; - public SamplingTransactionProfilerFactory(string tempDirectoryPath, SentryOptions options) + public SamplingTransactionProfilerFactory(SentryOptions options) { - _tempDirectoryPath = tempDirectoryPath; _options = options; } @@ -33,7 +30,7 @@ public SamplingTransactionProfilerFactory(string tempDirectoryPath, SentryOption _options.LogDebug("Starting a sampling profiler session."); try { - var profiler = new SamplingTransactionProfiler(_tempDirectoryPath, _options, cancellationToken) + var profiler = new SamplingTransactionProfiler(_options, cancellationToken) { OnFinish = () => _inProgress = FALSE }; diff --git a/src/Sentry.Profiling/SentryProfiling.cs b/src/Sentry.Profiling/SentryProfiling.cs index 717f746903..13e8a3a980 100644 --- a/src/Sentry.Profiling/SentryProfiling.cs +++ b/src/Sentry.Profiling/SentryProfiling.cs @@ -21,6 +21,6 @@ public ProfilingIntegration(string tempDirectoryPath) /// public void Register(IHub hub, SentryOptions options) { - options.TransactionProfilerFactory = new SamplingTransactionProfilerFactory(_tempDirectoryPath, options); + options.TransactionProfilerFactory = new SamplingTransactionProfilerFactory(options); } } diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 40d7e6e9cc..5a22f3cf5c 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -62,7 +62,7 @@ public void Profiler_StartedNormally_Works() var hub = Substitute.For(); var transactionTracer = new TransactionTracer(hub, "test", ""); - var factory = new SamplingTransactionProfilerFactory(Path.GetTempPath(), new SentryOptions { DiagnosticLogger = _testOutputLogger }); + var factory = new SamplingTransactionProfilerFactory(new SentryOptions { DiagnosticLogger = _testOutputLogger }); var clock = SentryStopwatch.StartNew(); var sut = factory.Start(transactionTracer, CancellationToken.None); transactionTracer.TransactionProfiler = sut; @@ -83,7 +83,7 @@ public void Profiler_AfterTimeout_Stops() { var hub = Substitute.For(); var options = new SentryOptions { DiagnosticLogger = _testOutputLogger }; - var sut = new SamplingTransactionProfiler(Path.GetTempPath(), options, CancellationToken.None); + var sut = new SamplingTransactionProfiler(options, CancellationToken.None); var limitMs = 50; sut.Start(limitMs); RunForMs(limitMs * 4); From 3833b5f1ec35cc41d3cdc87024de06c680a9ce38 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 8 May 2023 20:11:27 +0200 Subject: [PATCH 05/21] tracelog streaming --- .../Sentry.Benchmarks/ProfilingBenchmarks.cs | 13 +--- src/Sentry.Profiling/SampleProfilerSession.cs | 50 +++++++------- .../SamplingTransactionProfiler.cs | 68 +++++-------------- .../SamplingTransactionProfilerFactory.cs | 28 ++++++-- src/Sentry.Profiling/SentryProfiling.cs | 3 +- src/Sentry.Profiling/TraceLogProcessor.cs | 47 ++++--------- .../SamplingTransactionProfilerTests.cs | 6 +- 7 files changed, 82 insertions(+), 133 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs index b1a82f70dc..1fa676a9f2 100644 --- a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs @@ -11,7 +11,7 @@ namespace Sentry.Benchmarks; public class ProfilingBenchmarks { private IHub _hub = Substitute.For(); - private ITransactionProfilerFactory _factory = new SamplingTransactionProfilerFactory(new()); + private ITransactionProfilerFactory _factory = SamplingTransactionProfilerFactory.Create(new()); #region full transaction profiling public IEnumerable ProfilerArguments() @@ -139,19 +139,10 @@ public void DiagnosticsSessionStartCopyStop(bool rundown, string provider) session.Dispose(); } - // Same as DiagnosticsSessionStartCopyStop(rundown: true, provider: 'all') - [Benchmark] - public void SampleProfilerSessionStartStopFinishWait() - { - var session = SampleProfilerSession.StartNew(CancellationToken.None); - session.Stop(); - session.FinishAsync().Wait(); - } - [Benchmark] public void SampleProfilerSessionStartStop() { - var session = SampleProfilerSession.StartNew(CancellationToken.None); + var session = SampleProfilerSession.StartNew(); session.Stop(); } #endregion diff --git a/src/Sentry.Profiling/SampleProfilerSession.cs b/src/Sentry.Profiling/SampleProfilerSession.cs index f8811de3fd..ed41bb2704 100644 --- a/src/Sentry.Profiling/SampleProfilerSession.cs +++ b/src/Sentry.Profiling/SampleProfilerSession.cs @@ -1,24 +1,26 @@ using System.Diagnostics.Tracing; using Microsoft.Diagnostics.NETCore.Client; +using Microsoft.Diagnostics.Tracing.Etlx; +using Microsoft.Diagnostics.Tracing.EventPipe; using Microsoft.Diagnostics.Tracing.Parsers; +using Sentry.Internal; namespace Sentry.Profiling; -internal class SampleProfilerSession +internal class SampleProfilerSession : IDisposable { private readonly EventPipeSession _session; - private readonly MemoryStream _stream; - private readonly Task _copyTask; - - private readonly CancellationTokenRegistration _stopRegistration; + private readonly TraceLogEventSource _eventSource; + private readonly SampleProfilerTraceEventParser _sampleEventParser; + private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); private bool _stopped; - private SampleProfilerSession(EventPipeSession session, MemoryStream stream, Task copyTask, CancellationTokenRegistration stopRegistration) + private SampleProfilerSession(EventPipeSession session) { _session = session; - _stream = stream; - _copyTask = copyTask; - _stopRegistration = stopRegistration; + _eventSource = TraceLog.CreateFromEventPipeSession(_session); + _sampleEventParser = new SampleProfilerTraceEventParser(_eventSource); + _eventSource.Process(); } // Exposed only for benchmarks. @@ -28,7 +30,7 @@ private SampleProfilerSession(EventPipeSession session, MemoryStream stream, Tas // 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("System.Threading.Tasks.TplEventSource", EventLevel.Informational, (long) TplEtwProviderTraceEventParser.Keywords.Default) }; // Exposed only for benchmarks. @@ -38,33 +40,29 @@ private SampleProfilerSession(EventPipeSession session, MemoryStream stream, Tas // The size of the runtime's buffer for collecting events in MB, same as the current default in StartEventPipeSession(). internal static int CircularBufferMB = 256; - public static SampleProfilerSession StartNew(CancellationToken cancellationToken) + public SampleProfilerTraceEventParser SampleEventParser => _sampleEventParser; + + public TimeSpan Elapsed => _stopwatch.Elapsed; + + public TraceLog TraceLog => _eventSource.TraceLog; + + public static SampleProfilerSession StartNew() { var client = new DiagnosticsClient(Process.GetCurrentProcess().Id); var session = client.StartEventPipeSession(Providers, RequestRundown, CircularBufferMB); - var stopRegistration = cancellationToken.Register(() => session.Stop(), false); - var stream = new MemoryStream(); - var copyTask = session.EventStream.CopyToAsync(stream, cancellationToken); - - return new SampleProfilerSession(session, stream, copyTask, stopRegistration); + return new SampleProfilerSession(session); } public void Stop() { if (!_stopped) { - _stopRegistration.Unregister(); - _session.Stop(); _stopped = true; + _session.Stop(); + _session.Dispose(); + _eventSource.Dispose(); } } - public async Task FinishAsync() - { - Stop(); - await _copyTask.ConfigureAwait(false); - _session.Dispose(); - _stream.Position = 0; - return _stream; - } + public void Dispose() => Stop(); } diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index 229693edf3..210b7a41a2 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -1,6 +1,3 @@ -using FastSerialization; -using Microsoft.Diagnostics.Tracing; -using Microsoft.Diagnostics.Tracing.Etlx; using Sentry.Extensibility; using Sentry.Internal; using Sentry.Protocol; @@ -10,60 +7,45 @@ namespace Sentry.Profiling; internal class SamplingTransactionProfiler : ITransactionProfiler { public Action? OnFinish; - private SampleProfilerSession? _session; private readonly CancellationToken _cancellationToken; - private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); private TimeSpan? _duration; - private Task? _data; - private Transaction? _transaction; private readonly SentryOptions _options; + private TraceLogProcessor _processor; + private SampleProfilerSession _session; - public SamplingTransactionProfiler(SentryOptions options, CancellationToken cancellationToken) + public SamplingTransactionProfiler(SentryOptions options, SampleProfilerSession session, int timeoutMs, CancellationToken cancellationToken) { _options = options; + _session = session; _cancellationToken = cancellationToken; - } - - public void Start(int timeoutMs) - { - _session = SampleProfilerSession.StartNew(_cancellationToken); - _cancellationToken.Register(() => + _processor = new TraceLogProcessor(options, session.TraceLog, -session.Elapsed.TotalMilliseconds); + session.SampleEventParser.ThreadSample += _processor.AddSample; + cancellationToken.Register(() => { if (Stop()) { - _options.LogDebug("Profiling cancelled."); + options.LogDebug("Profiling cancelled."); } }); - Task.Delay(timeoutMs, _cancellationToken).ContinueWith(_ => + Task.Delay(timeoutMs, cancellationToken).ContinueWith(_ => { if (Stop(TimeSpan.FromMilliseconds(timeoutMs))) { - _options.LogDebug("Profiling is being cut-of after {0} ms because the transaction takes longer than that.", 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) { - if (_duration is null && _session is not null) + if (_duration is null) { lock (_session) { if (_duration is null) { - _duration = duration ?? _stopwatch.Elapsed; - try - { - // Stop the session synchronously so we can let the factory know it can start a new one. - _session.Stop(); - OnFinish?.Invoke(); - // Then finish collecting the data asynchronously. - _data = _session.FinishAsync(); - } - catch (Exception e) - { - _options.LogWarning("Exception while stopping a profiler session.", e); - } + _duration = duration ?? _session.Elapsed; + _session.SampleEventParser.ThreadSample -= _processor.AddSample; return true; } } @@ -80,34 +62,16 @@ public void Finish() } } + // TODO doesn't need to be async anymore... /// public async Task CollectAsync(Transaction transaction) { - if (_data is null || _duration is null) + if (_duration is null) { throw new InvalidOperationException("Profiler.CollectAsync() called before Finish()"); } - _options.LogDebug("Starting profile processing."); - - _transaction = transaction; - using var nettraceStream = await _data.ConfigureAwait(false); - using var eventPipeEventSource = new EventPipeEventSource(nettraceStream); - using var traceLogEventSource = TraceLog.CreateFromEventPipeEventSource(eventPipeEventSource); - - // TODO make downsampling conditional once this is available: https://github.com/dotnet/runtime/issues/82939 - new Downsampler().AttachTo(traceLogEventSource); - - _options.LogDebug("Converting profile to Sentry format."); - _cancellationToken.ThrowIfCancellationRequested(); - var processor = new TraceLogProcessor(_options, traceLogEventSource) - { - MaxTimestampMs = _duration.Value.TotalMilliseconds - }; - - var profile = processor.Process(_cancellationToken); - _options.LogDebug("Profiling finished successfully."); - return CreateProfileInfo(transaction, profile); + return await Task.FromResult(CreateProfileInfo(transaction, _processor.Profile)).ConfigureAwait(false); } internal static ProfileInfo CreateProfileInfo(Transaction transaction, SampleProfile profile) diff --git a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs index 72337be066..cb3ae25538 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs @@ -3,7 +3,7 @@ namespace Sentry.Profiling; -internal class SamplingTransactionProfilerFactory : ITransactionProfilerFactory +internal class SamplingTransactionProfilerFactory : IDisposable, ITransactionProfilerFactory { // We only allow a single profile so let's keep track of the current status. internal int _inProgress = FALSE; @@ -15,10 +15,23 @@ internal class SamplingTransactionProfilerFactory : ITransactionProfilerFactory private const int TIME_LIMIT_MS = 30_000; private readonly SentryOptions _options; + private SampleProfilerSession _session; - public SamplingTransactionProfilerFactory(SentryOptions options) + public static SamplingTransactionProfilerFactory Create(SentryOptions options) + { + return new SamplingTransactionProfilerFactory(options, SampleProfilerSession.StartNew()); + } + + public static async Task CreateAsync(SentryOptions options) + { + var session = await Task.Run(() => SampleProfilerSession.StartNew()).ConfigureAwait(false); + return new SamplingTransactionProfilerFactory(options, session); + } + + private SamplingTransactionProfilerFactory(SentryOptions options, SampleProfilerSession session) { _options = options; + _session = session; } /// @@ -27,15 +40,13 @@ public SamplingTransactionProfilerFactory(SentryOptions options) // Start a profiler if one wasn't running yet. if (Interlocked.Exchange(ref _inProgress, TRUE) == FALSE) { - _options.LogDebug("Starting a sampling profiler session."); + _options.LogDebug("Starting a sampling profiler."); try { - var profiler = new SamplingTransactionProfiler(_options, cancellationToken) + return new SamplingTransactionProfiler(_options, _session, TIME_LIMIT_MS, cancellationToken) { OnFinish = () => _inProgress = FALSE }; - profiler.Start(TIME_LIMIT_MS); - return profiler; } catch (Exception e) { @@ -45,4 +56,9 @@ public SamplingTransactionProfilerFactory(SentryOptions options) } return null; } + + public void Dispose() + { + _session.Stop(); + } } diff --git a/src/Sentry.Profiling/SentryProfiling.cs b/src/Sentry.Profiling/SentryProfiling.cs index 13e8a3a980..7da7e8f33b 100644 --- a/src/Sentry.Profiling/SentryProfiling.cs +++ b/src/Sentry.Profiling/SentryProfiling.cs @@ -1,4 +1,3 @@ -using Sentry.Extensibility; using Sentry.Integrations; namespace Sentry.Profiling; @@ -21,6 +20,6 @@ public ProfilingIntegration(string tempDirectoryPath) /// public void Register(IHub hub, SentryOptions options) { - options.TransactionProfilerFactory = new SamplingTransactionProfilerFactory(options); + options.TransactionProfilerFactory = SamplingTransactionProfilerFactory.Create(options); } } diff --git a/src/Sentry.Profiling/TraceLogProcessor.cs b/src/Sentry.Profiling/TraceLogProcessor.cs index 2a3241a8e6..f4ed67e16f 100644 --- a/src/Sentry.Profiling/TraceLogProcessor.cs +++ b/src/Sentry.Profiling/TraceLogProcessor.cs @@ -17,10 +17,9 @@ internal class TraceLogProcessor { private readonly SentryOptions _options; private readonly TraceLog _traceLog; - private readonly TraceLogEventSource _eventSource; // Output profile being built. - private readonly SampleProfile _profile = new(); + public readonly SampleProfile Profile = new(); // A sparse array that maps from StackSourceFrameIndex to an index in the output Profile.frames. private readonly SparseScalarArray _frameIndexes = new(-1, 1000); @@ -31,26 +30,16 @@ internal class TraceLogProcessor // A sparse array mapping from a ThreadIndex to an index in Profile.Threads. private readonly SparseScalarArray _threadIndexes = new(-1, 10); - public double MaxTimestampMs { get; set; } = double.MaxValue; + private readonly double _timeShiftMs; - public TraceLogProcessor(SentryOptions options, TraceLogEventSource eventSource) + public TraceLogProcessor(SentryOptions options, TraceLog traceLog, double TimeShiftMs) { _options = options; - _traceLog = eventSource.TraceLog; - _eventSource = eventSource; - var sampleEventParser = new SampleProfilerTraceEventParser(_eventSource); - sampleEventParser.ThreadSample += AddSample; + _traceLog = traceLog; + _timeShiftMs = TimeShiftMs; } - public SampleProfile Process(CancellationToken cancellationToken) - { - var registration = cancellationToken.Register(_eventSource.StopProcessing); - _eventSource.Process(); - registration.Unregister(); - return _profile; - } - - private void AddSample(TraceEvent data) + internal void AddSample(TraceEvent data) { var thread = data.Thread(); if (thread.ThreadIndex == ThreadIndex.Invalid) @@ -66,15 +55,7 @@ private void AddSample(TraceEvent data) return; } - // Trim samples coming after the profiling has been stopped (i.e. after the Stop() IPC request has been sent). - var timestampMs = data.TimeStampRelativeMSec; - if (timestampMs > MaxTimestampMs) - { - // We can completely stop processing after the first sample that is after the timeout. Samples are - // ordered (I've checked this manually so I hope that assumption holds...) so no need to go through the rest. - _eventSource.StopProcessing(); - return; - } + var timestampMs = data.TimeStampRelativeMSec + _timeShiftMs; var stackIndex = AddStackTrace(callStackIndex); if (stackIndex < 0) @@ -88,7 +69,7 @@ private void AddSample(TraceEvent data) return; } - _profile.Samples.Add(new() + Profile.Samples.Add(new() { Timestamp = (ulong)(timestampMs * 1_000_000), StackId = stackIndex, @@ -125,8 +106,8 @@ private int AddStackTrace(CallStackIndex callstackIndex) if (!_stackIndexes.TryGetValue(stackTrace, out result)) { stackTrace.Trim(10); - _profile.Stacks.Add(stackTrace); - result = _profile.Stacks.Count - 1; + Profile.Stacks.Add(stackTrace); + result = Profile.Stacks.Count - 1; _stackIndexes[stackTrace] = result; } } @@ -144,8 +125,8 @@ private int AddStackFrame(CodeAddressIndex codeAddressIndex) if (!_frameIndexes.ContainsKey(key)) { - _profile.Frames.Add(CreateStackFrame(codeAddressIndex)); - _frameIndexes[key] = _profile.Frames.Count - 1; + Profile.Frames.Add(CreateStackFrame(codeAddressIndex)); + _frameIndexes[key] = Profile.Frames.Count - 1; } return _frameIndexes[key]; @@ -161,11 +142,11 @@ private int AddThread(TraceThread thread) if (!_threadIndexes.ContainsKey(key)) { - _profile.Threads.Add(new() + Profile.Threads.Add(new() { Name = thread.ThreadInfo ?? $"Thread {thread.ThreadID}", }); - _threadIndexes[key] = _profile.Threads.Count - 1; + _threadIndexes[key] = Profile.Threads.Count - 1; } return _threadIndexes[key]; diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 5a22f3cf5c..cfb6882689 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -62,7 +62,7 @@ public void Profiler_StartedNormally_Works() var hub = Substitute.For(); var transactionTracer = new TransactionTracer(hub, "test", ""); - var factory = new SamplingTransactionProfilerFactory(new SentryOptions { DiagnosticLogger = _testOutputLogger }); + using var factory = SamplingTransactionProfilerFactory.Create(new SentryOptions { DiagnosticLogger = _testOutputLogger }); var clock = SentryStopwatch.StartNew(); var sut = factory.Start(transactionTracer, CancellationToken.None); transactionTracer.TransactionProfiler = sut; @@ -83,9 +83,9 @@ public void Profiler_AfterTimeout_Stops() { var hub = Substitute.For(); var options = new SentryOptions { DiagnosticLogger = _testOutputLogger }; - var sut = new SamplingTransactionProfiler(options, CancellationToken.None); + using var session = SampleProfilerSession.StartNew(); var limitMs = 50; - sut.Start(limitMs); + var sut = new SamplingTransactionProfiler(options, session, limitMs, CancellationToken.None); RunForMs(limitMs * 4); sut.Finish(); From ad5e21f68c05b42b507489c58889a40128d53bc6 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 9 May 2023 18:01:28 +0200 Subject: [PATCH 06/21] fix sample collection --- .../Sentry.Benchmarks/ProfilingBenchmarks.cs | 2 +- ...ogProcessor.cs => SampleProfileBuilder.cs} | 12 +--- src/Sentry.Profiling/SampleProfilerSession.cs | 33 +++++++---- .../SamplingTransactionProfiler.cs | 56 ++++++++++++++----- .../SamplingTransactionProfilerFactory.cs | 7 ++- .../SamplingTransactionProfilerTests.cs | 9 +-- 6 files changed, 77 insertions(+), 42 deletions(-) rename src/Sentry.Profiling/{TraceLogProcessor.cs => SampleProfileBuilder.cs} (94%) diff --git a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs index 1fa676a9f2..22d5f53bde 100644 --- a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs @@ -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 diff --git a/src/Sentry.Profiling/TraceLogProcessor.cs b/src/Sentry.Profiling/SampleProfileBuilder.cs similarity index 94% rename from src/Sentry.Profiling/TraceLogProcessor.cs rename to src/Sentry.Profiling/SampleProfileBuilder.cs index f4ed67e16f..d971a2d910 100644 --- a/src/Sentry.Profiling/TraceLogProcessor.cs +++ b/src/Sentry.Profiling/SampleProfileBuilder.cs @@ -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; @@ -13,7 +12,7 @@ namespace Sentry.Profiling; /// /// Processes TraceLog to produce a SampleProfile. /// -internal class TraceLogProcessor +internal class SampleProfileBuilder { private readonly SentryOptions _options; private readonly TraceLog _traceLog; @@ -30,16 +29,13 @@ internal class TraceLogProcessor // A sparse array mapping from a ThreadIndex to an index in Profile.Threads. private readonly SparseScalarArray _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) @@ -55,8 +51,6 @@ internal void AddSample(TraceEvent data) return; } - var timestampMs = data.TimeStampRelativeMSec + _timeShiftMs; - var stackIndex = AddStackTrace(callStackIndex); if (stackIndex < 0) { diff --git a/src/Sentry.Profiling/SampleProfilerSession.cs b/src/Sentry.Profiling/SampleProfilerSession.cs index ed41bb2704..b8f2bfd15e 100644 --- a/src/Sentry.Profiling/SampleProfilerSession.cs +++ b/src/Sentry.Profiling/SampleProfilerSession.cs @@ -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; @@ -12,15 +13,18 @@ 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. @@ -28,9 +32,9 @@ private SampleProfilerSession(EventPipeSession session) { // 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. @@ -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); + } } } diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index 210b7a41a2..cd1f1c2403 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -1,3 +1,4 @@ +using Microsoft.Diagnostics.Tracing; using Sentry.Extensibility; using Sentry.Internal; using Sentry.Protocol; @@ -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()) @@ -29,23 +35,24 @@ 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; } } @@ -53,6 +60,24 @@ private bool Stop(TimeSpan? duration = null) 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(); + } + } + } + /// public void Finish() { @@ -62,16 +87,19 @@ public void Finish() } } - // TODO doesn't need to be async anymore... /// public async Task 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) diff --git a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs index cb3ae25538..bd1ef9fa8f 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfilerFactory.cs @@ -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 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); } @@ -59,6 +60,6 @@ private SamplingTransactionProfilerFactory(SentryOptions options, SampleProfiler public void Dispose() { - _session.Stop(); + _session.Dispose(); } } diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index cfb6882689..227463fb69 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -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)); } } @@ -83,7 +84,7 @@ public void Profiler_AfterTimeout_Stops() { var hub = Substitute.For(); 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); From 1510734d80b8fd6818c012a96f98f375bca49541 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 9 May 2023 20:39:15 +0200 Subject: [PATCH 07/21] migrate downsampling & fix tests --- src/Sentry.Profiling/Downsampler.cs | 52 +- src/Sentry.Profiling/SampleProfileBuilder.cs | 33 +- .../Resources/sample.etlx | Bin 537306 -> 575740 bytes .../SamplingTransactionProfilerTests.cs | 13 + ...ofileInfo_Serialization_Works.verified.txt | 892 ++++++++---------- ...s.Profile_Serialization_Works.verified.txt | 892 ++++++++---------- .../TraceLogProcessorTests.verify.cs | 16 +- 7 files changed, 815 insertions(+), 1083 deletions(-) diff --git a/src/Sentry.Profiling/Downsampler.cs b/src/Sentry.Profiling/Downsampler.cs index de8d62d69c..c343b6e93a 100644 --- a/src/Sentry.Profiling/Downsampler.cs +++ b/src/Sentry.Profiling/Downsampler.cs @@ -1,52 +1,34 @@ using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.EventPipe; -// TODO remove /// /// Reduce sampling rate from 1 Hz that is the default for the provider to the configured SamplingRateMs. /// internal class Downsampler { - public double SamplingRateMs { get; set; } = (double)1_000 / 101; // 101 Hz - private double NextSampleCounter = 0; - private double NextSampleLow = 0; - private double NextSampleHigh = -1; + private static double _samplingRateMs = (double)1_000 / 101; // 101 Hz + private double _samplingGapMs = _samplingRateMs * 0.9; - public void AttachTo(TraceEventDispatcher source) + // Maps from ThreadIndex to the last sample timestamp for that thread. + private GrowableArray _prevThreadSamples = new(10); + + public void NewThreadAdded(int threadIndex) { - source.AddDispatchHook(DispatchHook); + if (threadIndex >= _prevThreadSamples.Count) + { + _prevThreadSamples.Count = threadIndex + 1; + _prevThreadSamples[threadIndex] = Double.MinValue; + } } - // Downsamples to the configured SamplingRateMs by keeping a shifting window of where the timestamp must fall. - // Alternatively, we could keep a map of the previous sample for each thread and check that instead but that would - // be a bit less performant (albeit more precise). - private void DispatchHook(TraceEvent traceEvent, Action realDispatch) + public bool ShouldSample(int threadIndex, double timestampMs) { - if (traceEvent.ProviderGuid.Equals(SampleProfilerTraceEventParser.ProviderGuid)) + Debug.Assert(threadIndex < _prevThreadSamples.Count, "ThreadIndex too large - you must call NewThreadAdded() if a new thread is added."); + if (_prevThreadSamples[threadIndex] + _samplingRateMs <= timestampMs) { - var timestampMs = traceEvent.TimeStampRelativeMSec; - // Don't sample until the NextSampleLow is reached. - if (NextSampleLow >= timestampMs) - { - NextSampleHigh = -1; - return; // skip the event - } - - // This is the first sample after reaching the lower bound - configure the Upper bound to some reasonable value. - if (NextSampleHigh < 0) - { - NextSampleHigh = timestampMs + 0.9; - } - // After the upper bound is breached, advance the lower bound to the next window we care about. - else if (NextSampleHigh < timestampMs) - { - NextSampleCounter += 1; - NextSampleLow = SamplingRateMs * NextSampleCounter - 0.5; - return; // skip the event - } + _prevThreadSamples[threadIndex] = timestampMs; + return true; } - - // Process the event. - realDispatch(traceEvent); + return false; } } diff --git a/src/Sentry.Profiling/SampleProfileBuilder.cs b/src/Sentry.Profiling/SampleProfileBuilder.cs index d971a2d910..354ec68495 100644 --- a/src/Sentry.Profiling/SampleProfileBuilder.cs +++ b/src/Sentry.Profiling/SampleProfileBuilder.cs @@ -10,7 +10,7 @@ namespace Sentry.Profiling; using SentryProfileStackTrace = HashableGrowableArray; /// -/// Processes TraceLog to produce a SampleProfile. +/// Build a SampleProfile from TraceEvent data. /// internal class SampleProfileBuilder { @@ -20,6 +20,7 @@ internal class SampleProfileBuilder // Output profile being built. public readonly SampleProfile Profile = new(); + // TODO reevaluate the use of SparseArray after setting up continous profiling. Dictionary might be better. // A sparse array that maps from StackSourceFrameIndex to an index in the output Profile.frames. private readonly SparseScalarArray _frameIndexes = new(-1, 1000); @@ -29,6 +30,9 @@ internal class SampleProfileBuilder // A sparse array mapping from a ThreadIndex to an index in Profile.Threads. private readonly SparseScalarArray _threadIndexes = new(-1, 10); + // TODO make downsampling conditional once this is available: https://github.com/dotnet/runtime/issues/82939 + private readonly Downsampler _downsampler = new(); + public SampleProfileBuilder(SentryOptions options, TraceLog traceLog) { _options = options; @@ -51,14 +55,19 @@ internal void AddSample(TraceEvent data, double timestampMs) return; } - var stackIndex = AddStackTrace(callStackIndex); - if (stackIndex < 0) + var threadIndex = AddThread(thread); + if (threadIndex < 0) { return; } - var threadIndex = AddThread(thread); - if (threadIndex < 0) + if (!_downsampler.ShouldSample(threadIndex, timestampMs)) + { + return; + } + + var stackIndex = AddStackTrace(callStackIndex); + if (stackIndex < 0) { return; } @@ -141,24 +150,12 @@ private int AddThread(TraceThread thread) Name = thread.ThreadInfo ?? $"Thread {thread.ThreadID}", }); _threadIndexes[key] = Profile.Threads.Count - 1; + _downsampler.NewThreadAdded(_threadIndexes[key]); } return _threadIndexes[key]; } - private static string ActivityPath(TraceActivity activity) - { - var creator = activity.Creator; - if (creator is null || creator.IsThreadActivity) - { - return activity.Index.ToString(); - } - else - { - return $"{ActivityPath(creator)}/{activity.Index.ToString()}"; - } - } - private SentryStackFrame CreateStackFrame(CodeAddressIndex codeAddressIndex) { var frame = new SentryStackFrame(); diff --git a/test/Sentry.Profiling.Tests/Resources/sample.etlx b/test/Sentry.Profiling.Tests/Resources/sample.etlx index a6a52a4845d32a45cb555cb6575b172cb796eaa2..2639f75ee0b254e063910af3f49ef80256c32703 100644 GIT binary patch delta 85586 zcmeFad03TI7YBOY;cPjGh=70-f&&5~;+Q!gIN*%OF$YvoKv6sppgHB7nggOo*=DBL zXl9CH9xGEcGcz-_$xLxb%gjtG%go`fwbwfw2)p0C&wcK5|G3}t)wh4Yz1H4queJ8t z`+d(jd}ptQ++WqOU>}RwmtWFAUUg_H>n2*xSZOL7M$Kp?%z06=vrN4w%$X$H*<6CV ztvuOlZ zd!(~3$gGDR^Vu9?A1|lV6ay@Ye`Ksx5x{kOAv$YU(L>X5k!+0C(#Up z1f`qIiDgM7h&FEqs(N;!l^8VnElo8`SLLUCWq(;?pD|c;veI&tn1{_JeKdJ4Sh9>D zdie&x0)kz<)7fj(-0nT!R=b}QvKDQNul1C(p!zU2SuoC<)n~A|i`HODJA520pPINV zFHXyPK~SK|;}?K!#Il!35SJCJQEw52>l&=-zN71kP$X-DACTaQ>_F$=KK@#Y1085d6>emBmJClO!wi=1#eyj6*a0BNzK`Cql3B=ag#eSvc zpS5PNYBa*s+EZB=H8(;D@u*{G*#t+|aa5ZpnidNK$H>C=8z2xv0$u7l*h|#h1I>NZ zjI1}`)=sm*!V}3$>??{IZjIyycJ@6%%%OL$P!9bfC`>;nK1$G4O5{ID5OXL} zqaG23Ig~(@&9+!e1d&4nG{G8zb>b#C4s+Heh?}5YLpuv4nBOqnw#ZJd_jK+Yu$Z4* zCkH#Tn|V67zsQ`U|MGU0HnQ6~570%lWjUAcNph-Tc3b^{6?#f;gcVCR>);BsvuIYC(^IXiEw;T#-oXSatT$!|_n&N`#gJe)2MZ{g{evG@?bqo2Gqvo$a=#()BL1FS|!==j^z#Ht+E|iwbC7S^QX= z#{_rwmOac_FsQI(dslDCR)3;fSBazh44M6%iB!qW9+46|JXy4Zd(Dv81&3&F?A=mg zA7)|O8EfC=oHgi}=CyuyTi2(NC$G7j#f^NN3lr_kl4D3O@4t;%a>YQg_C+#_n4)); z%oJrGOO?#InsG6af7pL=va13bhv6yQDljiu9s}i0mJ&UU#6otpD|S_ zqlgZNh|Um0-b~u(Og`vaQamC^Vq<0@S8`%xZHd)=RrAw^QY)$q(eyfkvVD+fh zC8c8{C1+f?m$N9@&PL639}C>}9=~`|@+P?@5*xQbjFp($R$?6&iuTg9+A{m=Inmxa z9{7ukM7wNafyB%1LV7Kk2$zc`8aYB?5wvS!9S-&cHDM1sk2>ruUfW(->8v+3VbeN` zvY_}9gbnNT%C_4^35XjV&R*xVY&%OMffL#3ESs9RN$pSD*%X5L4FjC}0M1_~EP0r_ z2$ub)k8?||omE|-aSnw&OX65%3Z8*uk04f=Ui19Lonft@^eR(8EZazeSYq^RZf6$= zqH!HRC*yt+ly2NiV%cvbh{ipyQU4Hy#;w{(OdqAU)(2^QWDIXWw-5JRj$+Lcj>e%q_>y2A}!bo_lyFogus@n6E6 znF4a}PEw#VeqMj}G>PEY zpFVz<{PcsMbUy{YCs_6?38J5(H0mx<=%;w1Z1VH&IWSBU^c1Yff#gMYR+Av+z;S@J z38IDV7TZ|^g87RbEQXqB+?6T;B8cq;r5o4SL$ItX38HarG^#IAXj~7XY(ouQ4B`+? za5M>`>w=cpSq4GJl62;vCK`4NdZrS@_@zE?XA23UajS-qaruJMjoU>m<0Od2eV|cA zM4@q?Xu4YsT{P}1O>h?pqH!l-&JPHp=Q=L6vqJ>Yz%2le5q!AR!Ol<-2{4Kv*yfB|VYfXYpk|kI+X|@TBmg!!gYxaHH$g{!x^1u_i-~^= zI+LgqjYwK)XX6R(-yZ1vWMzMrNiet|-F9(}Ry~~Y{5Ab@;OV@-Y8cBSsr<$DozqwM zXY&c7?~Zg1Tli-E1J2^Nze+I?lU zoxM*G18aBMSt&tr=s4}RzJ>Eg9$DiYrDgBd1Ubh zQ&g}T?p1|`ZvMKlHp_Z9sgjK^kXy4p)LbBZ}!nS_(i+jDUxX3Jd+0IHf3#qb~M@VeP%c33oDqNY4zbe}MUyoM2 zw!J~xC)0b<4JPh&FQ-q@a=69#I*)IKs<{OPTqEWw_3+l0!q&Zs1TUKp*V4If4`WBk zS7mQI*w@s=$o0q<0kK^a6hw~4SB2CimipV0)>Wk%Xh z*luUl3GUyX&iu6|qZ;~HvI0#RsO z8=`D>Ll@I3P7@qPf|ynz#dbE1Ag0xFfD;7d8|%OU^>=Z9mQCG>I~{B`HPO185kaEV zE)kS&UCl_rvQ?T6SQm`c{6s4;Xj~)-*NXNY)x@mm$zY0$<23q zODz9w(T;VEm6>yg+d%vA*~b~P#n&0~uD>&;B+r@OWRcV7-Ijd29qyT#`|NDwE|llB zmnetmg8Tv8SW1v{B|N7zlbqELq_7^lh0xhW%4H#xTxz$qeFr(*1gnm;;J2QZrj{fh z>?qkj*i(ToEGatDR&tJR>+9To*lsJ`TcLj{+``X-p&`6DTk?07j=jeVY*HiV#gB)v zSKdeAlGvk>GRyuzv>nGMOhL z-iP{{;#>NaB%d2A(G!o?H+I|9kMusQWGBB162+p?8N03N5xobs3eLnywuT?OQO<%h zYf9#xJuKN;f8s{N_(2K_IF3}w>&(8!Ob~#$IeS1Pz+xhcb=5yGU zD|XwElX_R^PbJPv94x+zfRQE?C-st7q9w=C!vc! z*=T%_Nf1j*xJGp$3U_aNqHKK) zUEIArKMvxbr@~Ev1abFXyKQG<2*&=F&Qhs~$9?HMQ!P@UsPKY8=^vKSR3$EnPN3$jN^Y_^AnjuLo zCEKYh2=wZ4v5Pd2Cl8hG^VX_lw{`oWVyDZ;DxH&TJ+D>l;H4v^`%qxwBvVkOs_6_!eIY$vj`$`dyB@7wOqpAU(NEmG_ zLOh)?*vP%SAw~-Y);!3k0mbqK#ZP#{5;=*3UEE$B#5Wt_U=-g66nm3I(4uf3z`IF( zwhySkOMPHXVGZayNDz(5L;N{mblAOqwZ#VeR#19GFC_|=eNTeuF{M9IH;6)ybs)<2 zr=g1;>nm8@vuNH$oYR(X1hlU&U~j_cu)T-_1jffq2Ww1yXkYD@YEyL*ly2YOFOf0r zNf7O;xsj-@M4^3gMA`Zox@g}(O>hVaqJiuDz<^|d`At9YUmEqHaoPURH<>Ux@CxFY zgwep=KZ$v5&k0I5FzPy4lTU(ZV5~-QqR_yBMAO%wHd7=(QSWvow*NJ79Ne~UZ_e34qvqYhRRojv79YYrl ztVt|;K!RwXM<5vBu@XbfM+brjtBJM~jk}0_H3_2!2L}OeNEi&{rGRbG0&CXss9GS> zjzqvTz7BD`!2D({Dc#n`5C?;Jc5Ub$MB-@8ImDw0qq}<60X&Y>;VPTXCQ%<+6J8hk zrV~bYwf~Zqyg*R8yI%jYj(DuIk|4TkKMAt6M4`LRXu6vWU3AwqP4Ep8L<860q`L@% zfra$|x(LFN!!rTff_~PDH9r+1wc&b=|AWWXmxi$eLYi-^FF9-lH~uXN1;3l?Vu%E< z?LVV|adCe&s4H^e8$rS1_$f~~5*rGZT_QnToKGX7t`miedy**IAG)r1#u=mu-X}p^ z+=d3QnkogxcK2uq*h^qOx*-Iw=;pue#dFAIZ;bZ(lPU(szX_-|B#ic-ewyr$7L*>G z*K)|%cG?^rq(E7t;z?KJaxUriF?2CFwTWeeND%Fxi<6Gl6n6#kUz$k$**LC}e?9@$TmED)4#;OYZp;0h8%2k+9TLZZ;XuZXf08MR~mjPTZg#|q46w}w?rq(1biM;qvyMi>pevxN+tFDTu>m)@*L zC4&Ufz`bvh&1;E51Md=L+hpis&^@-2;2R`}2Ik?Uy9lF!7ZJM%W8nL?1$=-o8d-|? zGs0-(IskwBHnBRN?oLH0k(7tKypsyiew68_?U=ihJg3|3PA(lNsg6PD<-GeF0M4^G_ zG~M2YE*kiYraO=X(ZDM>=_tbR#*Ytp5>jofhQW#Zo`n91gwe>D_JF4m1|#_fz_$4U zYZh{k4j{5jP<(U;SmPQJ2fO%1=(BA!#BulZ?FizplL%V07x7L~hmQf$*&gb{W1yfD z^c}c^+k}^8Nzuj`aGl#bOUbO+UlqEr=yUr%sel(9mlpAYcqxz7|GR<^R@(AH0nUU| zzWi`Em~B7Jj%#n%9q@6&7@)5%Qh>e|lpdgJmjuf$kRS%A7E!iqhTuFue_11cGe`_k z8%_HjX`@44#c8chBZqtv0DBTfr_2IuJO2+Z3P1H}(MxK&r-uX|UXOX8bD_hfWjwB* z6pl}4d{!?|2zsb@3WeTMYspskkvmt|Lt5g3&nUl?Y`rC%-EDL`KDhbvxL#6S9wN(2 z$h`o?dt-E_iI~ zE3sr-1v+k+bVFh@t5%>Pc(2OLm)wC-S~3dU6G9lHWN%(yY@PQrBqmMP+1Va~N^o=H%NP-xnKXB6Fgs~PSj|M!NFxH~uh|>w9k#S=H=MhFD?>tLJ z&K8tzx`{23#UAcO=73 zd!PEyz`NM@31KvF_ormw7lP6a{DN3^js(%b^BQ%PC^Ybzru(a*iv|unPK*1C1ku3M z6qrBVTR7=kgwbV5;{g{F#^P`k@i=dE zsSCE)O^N+hUEe{wcxi|f;)-!d3F2)k@0ba~)qHi~#E* z>{}8<|Mk+S%S567MrgV>3|;hJx+eGs38McFrfdcKRL_V7_r69mq(iAVLh@|=m8A>xYw{Oco&R;17Kq5pV;2^UH*Sn1^ZLSkwD15(#7q5|CrqjV^ zjkJXAY@rX@R$(u;7T_$$bHLF?0N)oRCbPgN1PFc+U+vu!039v8Cb?WbFX36W^huOJ za^6m96Q8?5@+f=-{D=pf|0W1ekM_C%YVpxKq`o}6NJ_T#@8I6y^dHl}<@1^}TH-#h zNJ+LOo!omlOm-TNIuzL_c^1Y|Z^2JI_$~N8N;sCgYDu z>m)wahvj!#2=jmf5#__AvSJTZsMDG zo^%?DAj5ZN$g&2PP zTiCd($78>?O!1XIe&Z~R*nfhlS9(NB;=|4X`&0(9E*Py)W~(NdBF4}zNc^P|-)fiO zc)TpxJzsOs1j!fqiO-}+*Tu_{hs0)QiGgl@FTuCzo_tyW?;mjQHpoFB7Fv2v+F~yB z#x*eDo!oN0u+a4q?|TD#D5AKxN)dlZ7*dQ!{Q?X9Okgd)_&T82*Mj0Ve}RO!K;n>9 zJo_ey|73_mO7U|*vD+kq`PB1Q!1qagmvmWThi2(uF79#Fb?lz> zwZuk|j#$Wj(3wOS7ZTQ~fv9U!1f?%z3bAZ93F1QXHEJ`FqBdU-nTx1$iwjx6p7n!RGZXrQjq}>B1Eg_6i*oSzpz}(j+r?Wp>qk()j zv~7<>8@_SRFQv$VT=fLms%=F72v2z;3#9fjY&WF#O)t4NA6ylD(n1gxOI7^o1y{Y~ zVKo+Nl=y7}e2c%TQ>q=vZ>7i{e7Towukmt)elO>|+J1%AId^7x(f**!Hw(Lo>N2El_}`Y#N4T^|s? z)Lz8wL|_14;seH%b`x(8S2 z_C_CadjMEBS#?ToJ($T!rH?bX`r zL&2iN?>o3xU2s89jjj$Z7{d<(#a^UAYuA%!u)Wk?Uk`rBwv{Fb2nK^s3Er7lC4d(N z!|3meK8vNgF}hEE82yYky7(~+pM}%~gTnl}1{9Oq3J=AG$c`GfXTP&)%HipV$CDx;}ku~D_Ev_U%jX zSS%+m!b49upW8@&)wpAO@WV}EfOA5`b>bbRnT$WS;KQPUt=dZryFFT-E!jHsrcvOj zAy#hX%I+X1Vg2&p$6G*V9H0UKWw$=>)e=JZ84(*>!hZWkw6%J~Lt4Sm7X{^nV#Jpt z{)sSDB7PU~@1&mC8nWd+^u8cAovdX0P$TcS9UiU9=jF{tJqVBUm%}KFA(bX1mzz_ zR1}%Y;|9xDP!8^$4Mt

tP@HA)MZ05O7%fa(-)=+|C*Ej|x94@#W_d!7(e6skaZ5 z9C*6y!+Y4l;#X3{0Nd^IA(^pMeY6d9#rNqecM*@)$wTE0EN;9Q;ogVJRsPmGsRqCM z6pY(sf>25vCg;m+=tLp1{g85n9~mi+W&R>OJUR*XR09eX9#)&iHWb%}C_(9m)rZ8g zHYA9L)hUgNB?=F#YnpCPLl+OL+T+BCYyb&jzU;*xswN4{y+=VVrBEMU7Yar}Uj|`3 zP1VtWrx8X2i+7QM3k0Pb7*NuXo>52;4NNGZdDjw!22LW%w#m>%1FP;P!8b_|4a~+# zcN50b^c>=M1?DZsfWbagI5v)flIJ6i4U5GlN?uelBu71s@}w#V6l+R{%Wui@3|lX) zn~Kwt6zCpA-DN3IPwUg*iDMx&Da0SbcE3p?hHb~m^I3C}fuFL$LmlxgQ{kwK6Fl5! zupIKu6JsX+K#FAWOCL0a2G=w_eOy3yGgkI30)1*JQi z6U)9QL3H*Ojk-Y;I{On%_fJC?o&Ar-KGIoXqf*tEQMt(H7q>NFEP;A%rnczC_%dFq%HoP9wG#ly3U00fJ@SNDxh5rBVHeLeqaF%J!6@ zi>9|8NFyfeEScVmXqG|xm_Bh6z?MA1m_FMOPZyZW8L*E26g@|V{4#S=J76LldM{Cw z3MWDsc2L`M5@bXcWx})STEYuv z$l?6tba)xG%`SQI)Mw;jo<(^yolh1$Yu} z_^er&DHrRHnx4GTY_R0gboWkrq+UJ`I_}QG4t{+ec)|9pXpf!`-t?pPvH9Ss5Nfwy z0JpOmPoRv&;)g$UZNo1w0KL%JLT~6o*hBLuB4rDKUrEt|%;6KBgJ`X%zWvX^_^(hO zBnyvQ1by2GL#FU;fNgsO#uS4z;X#W*7Zk0{9Hc?pgw} z&B*-V=V2+sh50=HdAI>}Al`E+@V%%#1KPHsqK!+$pAU0&8JLr+E!$uGzL#e&lXtUJ z(urISI=PyTRvwC%gU(Eivrx&Xy8^g{rrh&z1$=%5+#iu&Pe_GA%06ZiK0*93r@V-b)%Z1# z2zQ7tTO%)J$7xlLwJ`P%nmyvyT386yE+uP+7Q$$^N%B}B`1C<}%Lj9?JVd+W*CESF zxjffaAZE2#8tA{?EoLL>*G)qqqs>^>9aG*#{BuuovZYFYjAhMJnIwb10nK zgOupPjdIZ1lgd&)ccJ9Nr)>b+eiSmU6B}fBEnD^*D!3vy$?$5YG`tK!dJ*=#8!5n{ zm$4_msQ?dOL68OF^1~Lne7GUffau;-;l;$D7NTd*TXLA> z71R=e#S(J#{prq=aH;iqTfWOK2-fAjUEU?JmaT>SksWdaiQOS^vsez0Sd+G*$8#4N z8Qe~Q^xZPNGrvt><2wjGep2*&y$8kicMzcEyU=4>)e!+?7vFgV_CoP6C5T^qSB95H zKg#(1_`$sP`w;B(PD1D9_vMWedo5Oggb#3)OFIjYzaQ_AS^IPhFMY=CNPM-hAC@Ih#c$3Tf{zU;_3xfi7RlGvM1=cF}X_D-6ny1nzw;!*82+ z4G=w%XXNz~`(>a2$G!yzd!8JGz+$=B0GAiT%g%s(N#Du0*js~z*uZn}Ov8>15#a0d zIP|8W0(f7*p);QnAod~{&sGi-;K(HmO8RgF7E5VE@?6X%SVG+QFm#&{Lagl-ILlb_ zNYc3qA+~i$BApOk;|sYNpZ$aE>-0L|@3gm{$-ns#?%+K}3+dQv@1TaW|rt%KTc%(@{%5@iap`M+R%hC2k<^DDY9EK7jvw-9WbAwckNGJHa` z;8_7i+>s|juFV$U*q;bC%n_jdU75+O>pTGx?&02H^96AHgW&zX7hv_S!uxpDzn&;S z2ro>#u0?ZCkpjPJZ6yUv3T*0az1i0Qnfe&i3uL%&{ zSb;s+=_dph%iQ+h{*bqnw!FBJ!uYFU;GTlNg4-L9nCDa}Vg8 zZL5UIZ0bb;*pmtr?@pHxK>3V4HnXso63y>Fp?Gjdd*wP~mxbKu4hsC9=!@?K$nONR zRxQ3lO1GlgIzNBdgu`=b2oLV8{Kn4wEW`rhlsz(Qep7(cT_9ts2LDPr1)**EqYstN zJUdS zZ3JjP3PFpu0ysu1g)*DaP5{po$i=FCovm$3jATZ(wegg5VA3`LJs~;$8Yduh|U7Q-{-&sG!B7tW79o6`%QR5dnFS* zJuO~Hz+cRO!unztA@N?vFZo8OiF`{INaS=C5)ZR*4e{L&lmz#xCGjyk9rj~J^T9CL=&w{vDKOr&yStUne^%DiS zHyc-QlK}jFH4GxxU-ZB)M`ugyOS=H@tI?K{*P8*M&A-7 z{+tV}sXknY!%w-unkxc;HEzEGBj;=@0L7gnP!@hQR8QjdS1T#(5Z~W$!S^>{JIqQI5(76XOlFPK1sGj~1N|ughc85&;yXGkHTblb z!TQhF36T>oD??>=WW4~9uR&-%UwRP%+)gTd9l@9PeFGHte=B6+WAgJ7pYtZHyB03CT6(X93RcM=yzbNcc#Br}74O1epCX6mw7MPXrJ~{VD#a z0#EUm1?ytRl+zL$a#zTgeToSb{I>v}pWzXEo<8him~sF&wM1Q6u7L*a0b2;$jaD%aRrHm2L3#src_7KZTI{`g@RT z%okjFyxj3C%Dd7%zUb=X7xd{>{MJ zlm{WOHu)#zMLzRq#go7BlTzz(9-Qf`4(1)MD}$tPzViCxXU+5jxu~C&{!#>=^|PCt zU%9wqfGtUUp0Aq7Yu!-BNRfQ{jsKP&0?$Q|XMK6#FN)n70{Sw~{N?eB+xd&~mehog zy6GlTi4nuZu>8-P%E$%^3@WjnYw1DE5)yiB=Kqi(F@Gw3xxJZMo&WZ$vPWvl-@N6v zk_!1OTpoKHT%PqixcvLuZZ5CHx;%dA4<+G^tltz#YR0pFcN@48X}gf-)<2Zx@QwA= zf4E6kFk?j%hpVPM$#zm%%{>3a~rR=nOnkDZyr&hN{2{i7`6OCP}OUGBTh zUWt2>dC_?#f!BYaz^@VRE3aEaA>LK^$_H*4Qo(3QqCWiOC8Y|V`w(V`d8j-mW%E4` z-83qhXb4V}C=KEEN6JPC4y{N3EkBBFg?fsYy0O8>VwB>#4jQFc zA0-_4$Jc$aQD3X39x?2%WSV5!aAn@=kA_%LAexcaQnUYK#-sny*N0E2qmGs)@sH~~)*2Ta`+jD4E3soZr3h?F55B&hYED>* z>|m1pDp)=EIJ;nb-jinWy&;b^i9_c8ETgD0B}4S#Eh5zfUeHvXV2G7BgV&Ez_dl)=DxNsxZwrA+(h}2mu=W7xK^>14PS@p3)gMgm)D&wbdXctiQOj@=io%a!)5 zNwHY0mG(@4Yp?igum7@OjSrlPmFtYNSn#&|Zwm@qz-RB3_8e`ieGvYydCs-ez72*w zKetp*%z1Mfc}G4yU2A+eLu(Yz)EfD-w8p2;YK`5qwZ zDnV-$_S70p)3nCB-E{LC; zt4>*>*5ogBRIB<9O3Uk(oe}>`N=}+1EjKqj;wx8iNA)LJ>cSg$QzOFrW~HPKngG42 z2~!;7@-ng<_N)v?URn-_Ubk5oY+6e){KGH3t=13HWuD4O$<9v8iAL>k5a$EZRewJE zkm}9Hq^pcYEP{RO>niN5zS&S3j^@PFgrNsb$w^Ncm)0d`O74WAgHy(4rty=D)Ii%- z)go3hLP!RHWcT#+v~hU}DdY08a;Ce9rjECgXbrHbW52A_DVb@p(b3~jE<6IdwjGHhy4LtwpBDL<$Sc`GNXb$Rh580gRhxa2o;s6HOBYf@5E@>2MKNoq&=sKvEp zqFPOo9`b@rwKfWS@n=(2TODC$Dnt?-9G{bx(se>k*5tIFkm$Mm3?twM8}I<&+V6% zk~<|Qjn{Li4cl#oVM0Ky;eV^@vrf~*x8SlHvu+tVuzUE<@v!ZO zIJ#zKX658Y56%*MIh=nEQ}wObAMs5U3k!x3%B`ZJRIZU(r#10apu%J3sdd_P%Mc|g zC1-ka%aMbo=jNqNhP~T0#gXNRGB$X{0Wm~1dGvfWpuy8qQZh60ro%qZh7y;fCCcth z3#$ur4AJMQS=l^8_`?!4sFouyCBu>1CvAE-r0A!bW|FSiyPW1@O^PaS`>5#n)KnNR zDzrt!5pBqxd~RE{f$Q0))r~UzFs=F%YJI+Sidx+xnrhoqF8@3g{?yX|eyS%V)-AhQ z)e9;^ddB!@drD4j8b;e+j>vV5TA+?p)nRC1&;zAj&1jGsHzfz^f1YV9_nxlSs?}pk zM(QYVzbKw15dTonO4c>|SE^|S0=56Ou-vq~F})y6+UibdgT9~-+Wr5|po4R!gTq7x zccf&((EQR?)juk6N@gbRAP6gL%~mZTqgzB_F4Mn4TZBa%*XD1|Z!J>m@vj%FOQjyz zZWclHIec7ZN^Wk57N5b>v(utrtcXC+Q^4l2y50Zb#)e>+Juhce^kdfk;{Py4Eq#p2 zmm2=RE|rg3qK=gM;W^r}N=vsR$W}u;V^hQ#8w54DTTaR|8IJMVp$rD#`6$j4TXRjg z1CJZ0Hst4)s156OgJo!L>Xw$7Ha;aU4PrTYY=$E(od328#2&3uJqARi!C8d63r_=0 zzTcG0yo_-vxp@^8@h~nQ0bL>@9u9gOCa>^$wPy34DUMVqm0h72=cV;> z<-|L(@+PF^^nlAvPR2Oc2q}}(U>)#XHnpZzM3X#yiqdLm)5OMRK1*aMKz7q3SJft>S9>yc%Ma5_tSl zI6s2(RUhlqQ_`lS@p&Uv|LP(OBbvp~I+`0o-mbiS^%)udWD}_Q7p_tRdS~8eWfbqIrCg8YN}A97XDv>KwN?4o;hv7d>QfLOXr2(LK`gy25QRx5BD% zPqbqjUIy0R=)pNDj$C91XY~TUMce=L z@uSN7JxrTCEaI* zQa2QYG37_l>s^pIOnd!n!YDqVy&BY@U)tocX*sALrE3U2%D72<^IX+`Af6v#T4t3CHgauSYyXC8 z{FJm1vZ4FX?umoPB=qV#xck5{-MSCzI% znvydvEqYLzBNYnOWGErw{QPz`%%g7xtOY8DI7WE=?P@JAxaUsIfEy|n3cmS$HH2tQ zPpr$7wu(!*i9O(Y@Jv=tYA#Qi4Rvt&Y&BXu2UU%Yh#61uo`9}sSVJ?Z8$NoN2G!P3 z_o?E-IX*X{g{IbBs6}gP-5q#C?3R{0E+-=!4(;4tj*PqvxZ=!6OAU``qv?DhbQ)v;6Z`k-U5$NXh>amL-29yqC}b9RW~BC!tnnxLVsF`&2;rHQD3tLxlKDHCj(B2 z>0~E8x%@Rp^=k+`W>Wbg1Y}smdbpysfw`aNm)=z)T(#a+lRW2Kf>!|2)U*3-vpEFr<2p4HOUMjdG2A=SG=K9mh3cp zpVw7>-eZz0baGZoot}S#<>;>o@IpzvOxkaT#5vl1D+Z6RHnb zU{1eVka)7*b7S}Dq6a327&c{s`FO#%6toI8$hkV%<6Bd5s!p~)Ym(D+^7RWQIfF=; zf9)-kodqmdV6kLc&6#HF?Ovzz7Iqsk-S*nw& zo8(ZPoLk#eAEA>??zWm^tbUQso{cp{Q+0CGVRNR#I$7jzN)FV?MFA!`SSOFytzk@b z4gxBi=W(<~&pG?Q6@U%{jyJ1Rxt&=75%hyNXVfu4Ia*n9v zSai1`yK=0lUkcL(`I5+rNmOuuh7W1kIVLnH$$Opl)vT52CN)mwvP0FnNf{F@yXD#!%gF|PEPG?4s#30o+vr3t4aO_ zq}cEw6-aTYe%;-a{1YVc7GMc=UtuJ$!nWgwOwlk{q9FV9F!y0PfBO>^{6na3Z4)!%^Wq2FT3U2SUA1xAoR9WlvBV8oh= zE2J0xc?FACz9|_5l48g1{KKrvu|PIL$<)TC72R-9!Fs(LVI<8~An~gomcsW;avOhe zOpTbRLLZoHv3xVeoR9%2j&{XilAby_({1Q&KnkzD@U?k~*b4(j;Ftw_&G|O+f=^X{ zX&Zm-Q#E49J>5;EJ&z9#vQH$1LJ$g&JS#!|!Q z+|?CFRk+Pg20dk+p6i(EAc?QDEH%%Vi+E2br#@p&;RBNj$<0plfPNLocBrnnoe^W_ z=V&zaK#hc`2oxIrHBwx|VxqVFXH6SI( zaf@=OsmbC@!U?YNCi$~YzL;T>caY@UKUXKjHNjC5OU%AyaQ;BIMH%?9Nv;4=46S}z zp7h}HCsqHZxP#@$YIBTLKL7=crcxAUk_}Rpl*CQ}_!QLDU?3h1G~9u6)^)v_j;Nl)G;+ z$toJDgzhlOYDm_^17!{CN<4Hq2%4MFZ{Vv+}e6n-1l+$2BL zC%n|cBtO#0^)V)CNLJ}!l3$=?2AbB^P2Uh59Bzu@^uh@QBwy6Yv+J80m7oZcLmZFIUogp+ zb+Q{b$zmko5^b^cD>T{lx@hqS=1i~XGyUjBzNV9F_M4J#04W^vY6TyNU`_eQbj%oV zjAG%JPpf`$qSJoO9OhennD`szNwCfWzx|wNWQP?lLNX8e0_u$DyJ|E0s$m~0Eb}qR z>N@$08(BjqJJv8I{dDq-8(C8)C-|F^fk=v^9$wL2e)J2Khiz_Y=DJaSu2v|Q4)}=; zEW?2o*X*;^SmG&gPZuQD4>HMLbW$2@lE3O?>)|H(yH19WG|9ho^3Yh5{2Ry?m=Vg| zJd>>k>4Z0b<=pF0WAr=93Gl0U9H{k%NuC4}HZq8QHPd(raZV?%*PLaLWjYzV$0RQb zvOnk#aGC67U9{|uNnX*(@CwsoMYddd)3oU{KlK$9-eaI3`ZC6veMfb2Z-z-8=RRLU z6u#8^9>p~?R{gb3mVRiGXLPdPQIkBYlkpV{#LZ!8Um?0tz$tg%F@4@D8k{cJ83#?a z6OfT8I{#yn?2IHo1vXC`Fq+Ckmk>tIUh^>pEH3&Af8s^B{hTZ3!qa$=V<($PE=F#Y0|#^(|wE;WsivdFUBP z6ufvV%c_}ugP>246PubOeg(4{PX>hnP{u6E!DeP3ehCsbl0}KJn-2!46hGFL%();EwHQy^7*2%SQk6d1`OfmFsH_{tOu)|`R)5&zmO;8uFkJ?oT z4+z8e!4{g50a#2y8lDE>O_RRqqji!mFv$R&jIA)JQ2##5lth~Z>62nEsT3GNjw<)2 zhD&EvzosYj&iEM9L0fckO_)i($~&J`>&=<)oEZoFQY?p2M!~Pa_W-V^(=G=vd(3b z#G8j8UvVSn>EzY#O-cNgw8--DZp*{3M+@?i8@UWgaeyqiX6modMYrBI$(2CjS)nw( zYm)foTtU9?M&fsx1=;m)Qxd;AFG!cX&Dh2-0O?`jzUSZ-3%&|bHhGzyFNsbd8&)&P zO*%Qy*CaOsDRQLc^XA7mx%vb2N5d07);Gj12rx=oo~fX&^0aem>YP2$k2zr3G0My~ zWCSVIG|96tj38T8c+XP@)g8@D$?G7AE4O5?H{UD22fBica+>7sNS*zGo9tel?6t=v z_wk(b>guLNFt%9F@#^N*dRZs$j4;XVym^_L#NR1X+sbh=|D{Zw6;}WwoZsDyjt8WCZ(ML=D#`H*Ang4cK z&GvY`mc_CQo_cxlgDTDV%EMJWdB0Zhk^A=V)k5VphjrDenuWEk&9$bpxQ_>a>x#Mw zF@O3HYgDcQ!o2vZ+FvK@ar=H&MbqUMTUtGM!Vl^bX7@`!sG=K(s^=L3D~8{b;m`wy zTI=y!KdIHrDcAndU3u~!R#HxB!0PdJKdQn?F%*x>t3rJPW>u-jy|1Z(`as$7@B_;3 z*Hjl-Q3F)#{-o9yNX$A?o0SIA7r>uJ$(`jSb^A1HJ*>jof1^-ujBsOPb?7GHjBVH5 z*E9PM%^Bqu|L@^U$NcYo#+2P|m{XPyUghBwc0Tb-1tlC#IP#re$dN!4{-eTQO1`N! zF0KQv4AIaP0XPaxjq zY8j6^fR|5!*Z!!G|CPo;IBwt_bJtN=#YDyFWHS1t`k(3r-C6aF4T3!)l5PH~Y)e45 zzWvSo5PRy~yR@gcK(}-xmSOBnVMOBJv<#A*`<)d6_qQ3idi;5sfy;wLdsOS--;?#fCr}VhM9RFzMib{hQWb!+RySm<2H_0e! z=w48*nl9F%>5t4fz|dnfQB3%?^{tfeV%^E-sUneG9&1-nCqQTE1dp{}T6d5^L#>n_ z6_wC%eRM3O9z0vN_7FNE=Mqm?efW>E8B?)P!&n%uybE?K+A_pMiNRV;)aA^GiqVTy zt0;s2_JXcp^gL@d2X;%h$M1JhMkab#pU@rfxFUwWy4A>oM^~{fCfQk4Lix8WTYKN%x8ENFMia=+QFF2Hw=u2 zS6P-sa}nZ%A6UcccEXqD=n-+dh1BCFjZw&M1jVMdF9oW?X(@7kp)ZAriy1!jb(_I6 z(3~G@T%i?$-pNl-L`=&(4H>ETh96}(1`DrB5B;p0bWcFqOwcYYV(KgY+SDSa=ho2< z7lC%@$k_J2ZNM6QwCZPAWz1Sy(|uoon?2xfR?@u-eeHJs=>Y*%5xMmTtq|$}Baub; zz6+r6=|Zam^~A=pVv6)d04fa905~X=$89F(=!&C*+!giJquC-6j|WjAl8t(Id4$4Z zQ(vD1md?Eb;XGK&$Ts6>0;jp{h1s=rs~$_k1>$K&*EQiGV|L;Gck8+b<8h%uZ~VWg zCo(jlo|d7)PJIoAmk!r6@{eZ^fIO%ZZ0)QkAQfso1&m$zUk#kaw-ICfmqTT1^!JXcnQHJgZ7GPpnkN<%*XkF(3X` zgju0NdFDo|2Qwu^qPB>nL`A2>eE_a6h_ucTPI0$JOJU*GrrMl(&{0#Ail3WmlL_0p zM49dvGtGiT(e zYap$t;#Bl-nHi%GkJ9ki;Kfh+R#83im9}=63zMJAa8Cj~YG_ML3q6s_)nah66cHJdO4+^c!{?XCH?Gnx-=XHi=Z-no^w!ON)%pI5C?h4#l|lJC9N`mE?B z_jQ+_mi$uK#*B15?!I35LlGXX`)#b%ji*Sl!LmOyHkfPc6V@!F*M6Oe+cYkx;7Q{e zB#!DP?e(J?Z4x)_4h}On;0|6tr!@t+T`j?)*^?dPD0{C8q z|`8fRDybRa)0vJ-$}DT6#rj3QSry|MZM}6YhJ_aasBCoPEhkA1d(;W z5FWhFLU~kY_d8K|XRDXnhAawz{a4u8dVx|O&pF-eG`2f3P8IW+QV6+Ei5cRC(SH0p zR<1em)|DbSl^iG{)!*o+M*ypLX&)2OE)zokKMlJQ56u6s`b71{4^Ubfxlh~rMsKs+ zRoM0-tj6QQ{}VyRloOTuY+t=nSKPI9<)}R4wB5?BiQ0{}lEQ{pr4U#nKDs!Kwc^C$ zJiC-w@jQcdh4Rv{Dpj?r%0=-i>u(eKRo}oPgg9Fi$`& zXDW9F+|5+}`9Q0%@PFCWaf8h3sdf%x^)TMX9vx)8VN^wYzpHwqyOz1TOFWn;Lrh0k zeiou*p@w_#c|)vOMxlEA5ys~Zwa(J#)@*}HUl?N^RZw9@>qoS{o4$C;+LyHOxcpZ? zSmAnUn6`549D!M&kVp4nKrz)YQ%?4*aQG?|o&Rs^k$N71~{gpbK>IS@~QhDSU z_wy@ljP7y_z`s{3QrAok6uMqc<$powY`Ct6^CQWccUo+QlXTP9%-9}=1I13BltMeX zlEbasl?g-5=ZvMy!&$^FcF$P-7VGv_PXxR5IBok@I973$#%ZjZQkbgOJ3XUyDI#7&Q0*y{Hl@d6H%oF|h#`0J%?32&cnR&cR5RQ_zb`{P#m60Se3^4sZF zD)`~!;V*6C;icPDNdv3DcqN42ocY~MDqOtz_${uTcTd7Q0>2D>ui-r|AwxG2gKpGF z9JB%w#R$(ShdGCcOr;lbaUbh7QQs!IZ?{g=4&iV0hOx5dldMU!R!c)BLE8O3stHTC@x+=I2_M3y;;v(;^<1ryUe4p%DN{4VKZD zV0^Ui5#>w#cAoV~RKvX|t|PstSbNxX;TnAX6zfK|Gn$7*2~l3 ztNc}Ub#?EaOSATmOwC^?IH`rB-LH5j_vbgij7z=5kJY;f+xE0W_Sb#9kyMqlb$l>q z@o$rYSdH(e%0>ZXF3GAUB86|;)m zmdYP?^c$p-sXsYVv6aoF$*EP2_s_~pn(Q|-C#8-ILR2y5Pf0!Cn$TO_X*M;rU2wjt zSv)oM;$WALlT3|dr<$oSEwWqPw4WBqT@ABmTI$O|*qUbT^wf*}%H%2bjMNi?yq!+o zR{U(HP3CIs=eHKe_Whik-`ZK9%}8Aqmbi|YJu6b;x@O<3NQU_I&+JHs&NK^Vr#>5G z=q%G`PHKx2{N%I@l&{qNrpKhz^1;D#IM%+*{btsr)Dc0zdRhJFrcMkqQQ!PBFH+Cv z5@!8brpL!K;Tz31M`OFodu~?Y+c#maq#xkazVYENBkMh z%%Afjb!C6Po1UCzBeTxrAa;EKMyISTr-w+#ZQ7-TY2~phV3n(cq4z&Z5)`L5LQn^WxM4!EQ^_YKcqV_Q0=~ zZFQ^<(v2Rr9M+*MJnDS`n+RrZc^QifBtr@Yxg{p>j zcCb%+gkfJ%396-y>9-^^0=7akp-gl`RGNN zb_lBUNOWczl*`YCKZ8+;-0iLM()iJ^PbHOAIj}c$_4>s+`jM0=q-(R^MN}D03da+L zBGani>TB=AHPU+4iq1X($-mKi0;0l?*ce@D)$0lOY;t!OwqNz=bko;(RF5}wQjp#evNqVZBNS6YSVXNw72-KpG(Gq%V1cH z{}A;_3yvmg)t2>z-OqZyzybfUtv2=2*udwq-zOaA_R4yh|HxG_+Xn!XpLcHQdBMgV zehuZizUDt1@Qj!43_CE73fgC#_<~!^QS|n1__7zn+he=n)OQn4w6?wI^$SIl%PO9q ziej@YkJpuTRxoJf$dlfg zNSJo7g>`Ie^p@9x8nwy2`(ZrEh}N?wBqJgCfG z=RrXfw$rYEfK|qId;OP(NY3@Gv8j?3S>fNF5 z_Jt|)Y(K?R`ylluKah3i?H`2o5Pd|YlJ|WjHLZ|tC87?<)BhY6H5;1l=QHs|80s-p8}?&+#cJ87{?r|pQl{`261j#qK)bIe5Gn%qh0Z3YL?eN{ty-<5^gBkAHH%G z<{u^s`&_;A*Zy;LH#~gyRl7PbPJ}5))yCi6AY~0boAgi;R^xTitHzQm?c;F zK*H4i&VN}zUAMz`p67VZwC_~haq@Tm-IiZmd&&6RSyCq3{-wY7M{LF8JGD1T&-y;} zN84~)z7HGD9ysZTu(y{n!OO+b5`<46{3Q4P;3cV+u~YME2mQKmSKu_W`k*V2b=o}# zlbn`E^CeBAAH&Sq>FR(V-MvLp>Pa(8Mc?$NY_03nneDIl|K!z~a%SHbdjBW?eWB=5 zq)yv4!lgfF@1)l2_njCkY#RUK50<{qQk1sV0WmtyA1RW@aGMt{&Hi;$@95Ykgj(%9 zk}%$?C>ii3F-ICIuUR{~WK?f?Ezr?~k@tFI=8={kJ04l&vDS_tYr*PT7e=RiRc|)$ z&5xhvhHAZdZq`(at;>r~s9z_lN*wpHP8K47w$Zbc6s;gyrv*0Ju25Z8t*>J zFj>Eqi%s&Dv5v6o<+-BC+0hL>d9Cjwayr|Eb&CSfKT8teF9uLN%uEv-hZdtQc- zqjsXBuZ^Q~iQqY3S9^ewu|4a+S+RP5Yl8%1O*eA4qS)x@g)!^A z`msHEOYLYzUi&U(xyAAK^V&GmgL8d2?K8 zkFHEd7s_v}pa*4W++RA{l)X7#r?~%OLJtl$aUTh6VJ>NyTG7k2e><0Lo{;y+b6%$~ zezt>Wnmx@28L_^>ZZFfmN6bAS)h*<*9`43FxFF@aOl#LGhW3p03)&>-`>Wx|KwR0M zr}>LpGK!vaE01>hz(x?{w72QsJ2uek|13xZ$NQK>pIBM%xJvL>KM-*%L@6fw`#?c# zeNE54vG7%jer8eMSom_()uw#E*ucQ}8e{s!ZdB4iKjEiEDW>k#F}slK6)t#^Zj1WA zvBx(IGGqLeLGLpb!6K23Ru4e!iwn9>kFFw~*WdjX(XN6YX7Tow{@#L3_SQ&h8xoAX zZv79XYAaShEk1CayE(Vz@LwF|XLJ+2m)yOq+viEY_=oKMdF4-Hr$l~$Vwb%t4)hkK zyxzfc`{@H?_PM%*|Mk zFq;O)p7T#cem5GH@wFkbV#x-<%!KhQ&Gry93REVX^T2xItN`Umsf=zHBwv{QHJj zw=nwyr$&7-ZL)JZZy9BUKk(>Q$~|}s{V{l7WC%qle}SnpEf&6gHk4ztnw##^Vx5Ab z4&zvw`}F84v+|Z$b>&0#u2rk1BiyQ0j?v96K=TVZ`&%r1JSpr>ZcWZ#4^6JOzyZ`z z!Z*U*Q!eXSqhiM8!gRkW=H~4=W6SNqm>qG8j&>uit+GX<{VJ172Fd#)YUrR!yUAV7 z1?`*Vvu68mmTSo5XP4!Y9KmK*FJWYijg>cNWX2+oY-z+qp^A}ek-HtPEt3WiQudwS z>tx?7^Vf!R)`4H7py974$uWJ+r#Hkp<<+D7Tm*65KwRywu}Of5g2J+rU!Ekk>`DRMq$0I*GK{UfnjcKte z!R`ptd76vWwr7ss^q<^jaUz~(R!xu93;p`H>3&<}^cUX6OEEha#|js1h+h>)dP~yx zFF&`ryU(99qVCgYX10F+S2>mYG+iHcpB?L??(=t#Klbrlg*nk$88OGJ6*`<9#qXID zj^f@gx&CU*&N(LAJAtJ4SMCoBjX zu8etjLG%)5JNj>d!C_l!jPdVt4?ip>4O{=sEaZ8Z(Pd-zMuxNpIxw*+zBpDRbnA@8 zVeM2fa~He0jqNtRTb~@72R8{hhQVG-qJEs`9>-Ok9w_g*qO;cdSTH}RQc}cP;}qj(V+;xNfgTdcB4J0pNx1TWuE3lLDh^l$E}Wa z3wCcY*RPJ;EoPegqPt_ves*(KTo8P$$y^hw6-?-FC36{#y)SZS6wP{iP3#wM5jn>s z)y_EVfP8M%z^#DfkG~Uo~PR9lKaVF z%6f}L{v6u7gYj>Eyn4xT-Dt4+|B35{w}ff0m|r~+^RAe|&8TiagO8&|W^_501%h#C zocZ#}SO?d;?Mqd?w)nSvxiyY`A?(P0$$O92`CZzPAMqY0mw7%Myn2>fne!amkwp?L zGK|-V?f&e>Qpmkdu*g%72z9Gf%QbJ_dVcQrxY;t*T#l~ zJ^vpYTiqRhq|g1IxnXmj=q(cbPna5cZIOGwI*gCmf#^T|_CM%>lgxcD#on~Z*agFt zFZ&CI(f${h-)0)`iZ!z4q!X-4MSir-5L?0gv@5!JSbeuQe%c{hORAlAdrPW1VBcW= zW_N6m7pX!&d0o7iU+|!p1uuo#d!Lk7{dsgWn|`3kYB7}*gtXU!a<92-0Yh~7p#ySa z-&1|!wb)EQmgI1AU(YREFv_VY#2;CyJvt8#vt)8yL-M`=ky}yzr)DwmvB{>yJF&9C z2sMQfYK;4XKjrhAHt)o)3C`&oEBk7Be;DT80%qqsu?@ixIgN8$-JkC{&CJ^CEjg>5 z?99slB#ovpfAL!vZ-!%k<#p_P!B4rfYP}cBvQuQ+?tFo(y8j?_PDWOR{ju;HV3DtU*)OK9`!Kdet*i9uLc?uK|A(U3 z4*uFlUI$k|a`BVzB4r94M=m3aIr>Gax$0xLTx(}ezpVa?nUlSI^UR~Fmla)j`Jq~( zav7r4_i$zJge4fjyQ<} zcTrLm+BH7DMw4b9{K0vw??*aq-(W9)F#6!ne)0UVgJBf**EQcAj5Q4Xou8tV2=~VO zdw$Hy@R7~)f=-#sbowYG35M61X2j32bAsJjChKQ^t$-_faD28Y^GmF3usg@3{}Nex zo@<7&8)P$l=%kx%o`pv44s-krTNTW2O%rCoud!WTbtO$D^h@?2-Fas5Z?Up<{Lytj z1HcOR=g{=+9Dj5z6buY(@V9Err-bsdr=Fktqxs-cVF)tD{Xw$^W=PxA(!r^PoZ9V9 zl{NXBM@}u`RGRyXXH!kNKVnV&!X#Z!?6|o856{a~c01$w=?`zlqXTw;nfRxFgNTzH zrao=c`yZsyRU>$+#ivCIo27@GGunav=R>*8A#HJ1=6AoPv^mQ06X_EQ_z|n}lYYq*#c}nNT47#}?yfX{ zM0f8nHII!L-)XuW8((0HVsjsStt!$I*(zF9t7;F%hgD`$;YgHsnMVu9TX<0(zD#$U zYmbYEcQHX^_m~fki+jU{o$uJo#>FRucO#LG>1GN+6Y>k~!e;C7@iW83lY{3HBZc#B zp=>Ex-tyeZcB>c#w_AaWzNqL|mSl-rMs z#qAuFZ9BC5d17>Ei3~h?>AbKRTs+<-D1<%+{(14R>v%Vdw!(N%hnEN{^1yQRNns&) z|M+7s|9;I=*DjQ@ua)CM`dMT+4^D2N6w|$=d+o;$sY2L0%MB&t?QG=!ndIxeU%-K- zyaFnmy$n85D!$7vlAV{VD(%&oEbP}Kek&b6ekifiOQ~Tgophm z_Kzqf+hRjbbuDIHhTnd=0bC=8{gW;ySBZ}I!I&QXdbRD4qpSEG@;~29+MnHOzS1%9 zGcuI5sA_z8Q1AZTUZe_?r$?)KIaC2`OMF?)wS*l&Z2p#w4*fHnY!ZUkzEaE=qvJ2A zEh7)+nMn)FL~&9&^*BF*$U)Av3#i0^9N z(CQaPQGRx2e2;RgZ7Lzfyn9x>mphPk<=OFm_7^HT`*p+kYLi*t)v{kjI3?Z?!2OgH7-YZq=z4aOY8`@sG z+IQ6(ToDe%k>5KUCJ)L{QT>r){c*<%L9KdO3T{bkTK;!wyha(+!EZ&sQNlMclG6&u zlB8iGs0M!=3Lo`{4rgbUyE=xKH0RIBe;Fz{XTitSf^69A`}G?{u71XvUQ1iaHlg92 zvd>j*NdGVU*-D8nN!y$^>>Rl}eZXASIUfGzWv$uPIsUBIivA4e00e>CKI-_@dlRrk-&xaq|H-3++9b0>^_H{ir z>UeG%qk6G&wlZlHtRV8snXYv;`1nnu8vO)7DOK=Q|s4znJUY+zaXk7 zF5XQn8~O4*Vm4nB_kP2a|aRvANi9bx~R!F?z&R^E_%Gz^iF8QJn zBY&|k`(IRK!W@|j2gP8cOd*Z0Zjr%1p_;P2ijO>{m}iH%r6m7!ZkY;JIgZy2rM}&q zOPuyr_nGUxTU{k$ry*zE;MPqJSA2hfR0z8c@aYZSIzTXD27NEAIT=5BWBhC1g|mHx zJou}qUSX0(((qA7a*l!~+nhCaM0`fUVE-{w^5*zCUPDK3Fav|f%@Y^|yHA+kZ;sy* zI>r&+E(ebSo}_x)FYavr$oOQ23ql6B1Z_{eOr z1a=jPJ6>I%4myj$0P*9{kAqdD^>9p)S#nYw$NkSp)Jvdl5ubc1dm#{q1GW;a=Vy zUS7YvWxo{=lyV29EMKXM{VBPhK=RFAP|V!x%MX;~^m+cA6;4vhEW6u(cG4lWl=<@R zV2H>nc~87v_{3?axpK9CeUA?N$_lx)H{HG01b2T~cdlW1&*oDX-r(Vy9hieB)pi`^ zGoQ6RLyfwJ9tfsF-Xq&aYr}^h70fGZgA95X)RcAMeZ9ToWKgSP=B~>&ZBT{UtNgd? zyt%UuunM2)XE9j&N&2+2&EV-9{5u~zuht(OAG5*BMsQIkIy+Z?b3-s!r%DQ&`47hJ z4~^|?hFgggv-d$4R0DI;5x*LFYMo+QKjZ~eKJBD<`a^y_1S^&09}b_f*fq~K4@cKL z?dwyG9tmu02Kzh`WYA12kdnwM|7bjXr^-II_$e!#k6V+MH#!sVAvHHRCC&PSl;yZG z@ihPU>9(qV*zC7uj%lR5xv;M=&wniYYE$xLE61aDJ8cPF=y2}0@E|Xi6nP?;=ITop z-p#vB+y15P&!6zqma|oP(%m{G&wZ><_=mFH@8ojMmb}6izvM*h|HjOlsvYq>L=CHj z>f^QM)&sd|Wib4Q8`(^_#SuNHn#|Bi?X^H>w?@6B+LujTsN}E z3p^e6gXG~VTk5vDb6*4tYO}iOu{}Po;F5L;Q}>y8@j3ZZOp#~eCl>yqU4nu$XK-gu z&vB$UKSha`%;oS#4j&&4&mYg59V<~jUp;Qmq`fINYE^60%x1t;e>T@hd*wL1_1SoF zvn02}+43sGx#dqzF-4!tUN9R+%ja@UaZHZG)_PO!hri4HaE<4)$896&o%`XXISxPd zd^}re)Jk(_b>Yvq?Z{pl+kj`Kn6|l|nY*HYikXn>DYJP;_G~IH^L_4zYwpZ$-F|0o z*)(ZA3#6DkcIK8(vnNL+Hp#RXvd3)?x5{yNzzf-vyyQi){8$b{d-#Xk51;;G_SCS> zR^c}M^4tuIo40a5T;!!(qAhM(=YDwHOYvUE4ZYp#6b0!T%d@_JDPB8&%Abc09WqsR z#V^la&D^vr-Z)c#b5?)yr#`BU^v50QAgx!|Mfzc~{wAkxug^sKPPP8@g`NxOZ}&7t z`g1)^P(5@3Qd~`u;?fO);z~z~t2xr2(rJN+D>Gk9HWZnD`K-wFXJ48i-CZcMHb{}R zMT+bqq{uEtimV+{Wct%5ic5Bi>r$k+E<=h-_dW`D1yVSzgS8-i`_x#8D1ZWYLQ14F zQX*ZD6493^l}J}~3F?LvraMw18AyrrKuSdKCo7R&NKdAFqbpG#q%``X&ZM8OA5tP$ zBjxZKq#X7~D%rJ2i7S8-AApqjKqUJ?NcMw~?Dbs@WlHuLXc&^+^+5y z_CzC)ayt?!x1*2}x(O+Qn~@?IjTFHxs1K68;uwQen6XHOxfLnHbyESx6CVM2cV&QUsfknnekYLyx2J=m}I0J&Ee0E$Cdd6*WLl zp@wK1IuAXK&PUr(BlHYvtUNu-MicZLQnf#inxY*@8QY1Ju@{gs_99ZoUP3LA?9|_P zp;l-&YK>k&3im2fxYv-vy^hqx%J^W_$(wAbjrSn6@mol3{5Dbq|3Zr39i#~Mq6tVf zl7ZesvU?xNZXc4}2j~h^6m>u!qK@d_=t}ev>O}ha4xrBHW7GwGf)wGWND+R96yfLS zDkOWg{gBRB-ge}}@4;V}3KydHiE749>5Bm5i=hhIRYqoHIZ)KD@?y;5Vz zO&s_JDo5YK(eOJ*jU}1vYb+T9H8hNc8cJ@3Ke>GkCF9xGP@?l1N@V{VoCGzLDBeHd zWcVkXqF#B3jj0^Sho+(YXgVr@WT>%ZCem0k3o(}Dn~gM-%t0DT=As0ehcuMTM;b~N zpkt9i8cG(Tx@s62WARX}S|Mbs5(Op(`CMjOzn z=s{EkJ%p;FhtX;15mXI5imIb5R0C~9HPI$?y4rs;8@145s5W{W)j?07y68!C2HJwo zM6y2%J%!Fj+t4}aX;cqwNA=M&NQ2F@r~!HoHAK&&^N`{>U+u3FG(s<+#^^=V1igeV zKrf@FXcuaRcB6E+-yFV*S~#|ZucHgm8>l-?tDcg9_OPE0--6BI+pq=c=ld5MJ)jJG zqP^_*g73oK@IBZKz7G|_KBx#jfGyyD*aynKFZ?&`2S0*W!vnA@{1_^pPoU!Y6lN-- z&)85zpTky20a~LkQ5*CX8prvsVO#VKx(I!XE=J#>cIbPggnxia_#jlmKSCw^6I8-K zL#6wR+FuF(%7OMs0WLwmqfs3H1KtGxgg3)Oa5T(Uz>7FPRKx|KA}$COaSBwtsqj`P zdleuK$HN4y2ahR0{nuxs5C_hM$HE4%Fl-2qgXh8H;rZ|c*a)V<#;^!%0*k^6U@_Pf zo(P-4;!qho2`XbHpfXkxwuC2UvT-3BrC}?0GHeY`feKg#DqvZtfaTyIcnlm2E5ISJ zA{+`U!C|m6RQOY&!dHO`UlmS(s*wzkS&a=DR);dI0cBVdUP^VVN&2&2i~S5(8_K>8 zlzm+&`!nD*P`)Gkv)IpoXG7Va17%+i%Dz6lmh`RHU&nz4&aj~~JkJ@P53k^SdDH-V?-fN(GZ-1!X zdo5J&y$&jw0Z_>dgi2-*R5F91$~y!qyF;O}I}EDxVxMVy?+qMK@4XSK_YQ~Zy(6G{ z??|YKM?pn=6I8@ELq$9qD&kw!28m&t~D z?|i6$3!nlTsDKNhdarV>-g`S#?_CVldzV1<-lb6CmqCSJ4i$a{oB&lL8SoA$yE~!m zGFP!7!@Hn*ui8+(@gA4~S3?C@0~O$2DEs@Mdary(_7A`exE9KO9hCigDEkeN-fMka zo%bQn&aC_}Ufeq2tsImSZsdfVa1$KIvCU9U_!yKEJ`UxCPe3{0lTc2$1Bi^349k` z0N;a6;rp-|+y|Ah51=x(A1Y%XLOG%A`ehKA-UqLzH*HGcVfeQaERQT`U1gILxfImRl9fY#W{E-bA{siTOYC}2IFE9iC z3Kiftr~to1+5Z7&!=`W!JOt;$dQ;HY|I&dHk zJHiCK5*`CP!8HWx43A~M3oHz;g2%zG@Oan_o&dYUG?)R4KsjeoDCaB&c5Ix(MtfKfUX4M0cnv%k_J0(b*#1#g6{;c(an zj(~09NO%z(1uup-!FKRw*dC6Cm%v-#r7#m-2FJk5;aGS@LF)fjHac)%9P9|k!zOd)f|FraI0bftQ(<>F4Q9aU?)VJYll_^n7n}uq!`ZM;CL43u=nLn< zesCVV8aAbhu7S;9f2cOS7OG9JgKEJ4TTCf3@Y69P!qLG zwat7Iyb)@KHXLe(HUesfHWGG$qo5+X2`ZwSp&}X$uY$5y_fmo6gkzxW$3ofP3S~bI zYTS@L<3?t_32bQKmiVvX=nnBIsLT5{sLT7)@IH=jSO34Cjb}KZ96t+{$&n9Z(7GglZ}!pr(2es;ORrvVR%MeixMeZuo$Ue;oU-vcH!7*Wfz%y88cmHs0XC z2KXlIjo}`sjJyRE@!L=l{|hSOci@B0{vr4-`)Y#spqk))r~>VSisu8Uc=kiZ^C46` z|5kTVRVjd~>Ht(#eGFAqpTLJX{waI}eg+ls=TH%U0TuC=P!WFx74g^bQS3XzEch+l z2)~1y;PX^hnkS6W^^??hW!j!2&Thhq3jDo z*&hd=#9qFX!Tt&Cr^7TTyCP6_MWN!AbM`0wd?#`sgA>JJIy?y~KnbV-C84Iz>aUtU zmu5d5o(yGo3Y1+LxP^#S#am%H_T{YQp`5h>l(SZZ>97*C*Z<0FD1uYr^-uwBfK}m* z@H99aR)guVI#j|npu*LJBjAlBJQCJoUp`bD%7^N}r^rxUxDB2GYqg zmO}ljO42!y4x2+o&;lxgmT(6FFC_yzVJr4ufUV(+unklO+QM{r5mfk#p~AO=FFE^{ z;U(-V1D8T&;Ib6jUm3WZ1L^Pzs0cbhMbHuMBH+bjK##6Eu|EcOhGStDs0>^M(_vSr z@ZF%ocZa)O_*Y;L_FsiPp~CgbWFwu8-cSMiKn3gzU&BysHIDtO;p^;Q1K)uC;hXSU zxCdSb-+}|6N;nXzgoB_8I2fkGAyDZIg-R!L7#nXBPzhyl;0BluZ-g=&4rMq3{tG)f z*E?_&%z!sRjeIvljeMh_oa+`S-|`DMhJD317An45Ap>8g4WN;4JO|{p6QI0yB9zxo zg7Vtipdy|O74a094yQszJPj)1>2NRhYO;6XO!nV{v*7!1HdNKmfp-2smkmWQ4=RHB zaGwjHtD0e7Rk{$WN*6&@>FqEbE`|!f1S0Biwdr9}g)sXC~BiYwLvagA-&&+o^8#1nibWeN+x&)nxG*dhW^+bwH5!6SDKsVM( zumMu|^O45QMo9J71Zg<(4Z45X6wKq_f(B)dM2eI3<5bX@&Iyv9+NHSO!_BC3|e0Web=1Kk0w0BBz;0OAlw zRj2mlj-sv}qOKm|jgG2r?T>IA={U+!{Y~d@b{wsi(Scjs0kxC%$2g94yw!1><9Npj zjuRaxIjWr%&t%6bj#C|{InGr3%WxJ4ROPdws(cPomCr@0@&%{@=Z)j-I?n!LsBlZ5 z!YxG#w+t!Vov143S3%j`1!Z?PlHEO|Zw>E-H88l(aV@OH{yM0D>!AW}KnnOEQUY11 z4(B&Q*=>Td+l*u<$CceybT;Rog0kBNWtaIh|H*JWlHm^20E3-ShA%)FzKCS^5|Z7k z=seE924(j;l%0Bp?A}DOdj~b*{9Y(K^%&W`hp@}EFRsY&->5MLA3+%&fHM3TDZ)>X z>^?_ovacM!hO++#%Klp<`|ptK4Q4OT_`BmDj(<8Ha#WvI zxcrU<91A+e-~}j-ltJ}JcwN#8!>ZN^R-lA#6%85V^yECyAQlCT9Tg=AM+ z;`qr>cBequm4nJyc}F>}_A5G8a+Cwx{(q`FP{mOm+ZvtbSk19I)HPI&D%NzY4K>%5 zBPwHckur7$RL0JP%Gf#ZGE~p8zAnr&}G&v$GDFGundv5BLqS^G^L zRh!yRcWmx>A?$=&A!W3+Zp@X_HtZ;;ZJ~0i(se^Bl?;^$*(p-lT>)ixCG3F|so2@E zi{n*}T^$vt?7BNeVi#ecDEI~3{ie$J9D#GPZcB|m^=q@C?yOHeffwEf-Wv3hR z8<1|qWw#E=ZatLU23T3+|HEvkT67;PZgkuP6>u|Bz{j8hJ`R<@7AX5|=w|dZlKplh z`)8o+pM|o20ggs5BH8JVp)~2+mkDIJivu!z9o~Z8Kr++>ToLYpvU>~4?j5M++Uxi} zR5R^E3ikn0xcyM!K7^P}o^V)q%1*TMuUBvFAV;)`K#v4`p~Rlwl(%4{i))*96M$0w_B@ zACX;4C=b67%B~fZU27=2Hc)mK!z|8c>H+B;t2c&X!MjvX93LPdNfRK%U2BJK^Q`6 zsAVRzT6f@jM?I=mf;T!2cO2n3(s7jIO^&xXs!3HtW1wniEL08M3ROcBpyHn>YW$z% z4&3HA*>M`&%K7O~5zc^$a3)lQv!EiJ3uQOYQBRt+zrfKrE_7VvcstCL;bM1SiQ`hZ zgX7B_mpiU-T!|y_kvhT@RG;F>PV`v{L zg+4&#(SCFq`VeU_`8PTfeT2?M2av{wkCFQPr$~MKbEK~Q1-cx4iLOFlA@#7Y(e)_v z8~umAMRU-1NDlHnQak^E)Q$&{ZXADvL*Y-5_OlOgAB4XmHOp^E_kq7dD%U>1)$Q0J z_Mb)h3eu!7Kcq03`3kV1+!lmNGzF?8sqkkQgDO!R{s|Ls2JsYvx;-llRnp@ek9R!5 zG0m}vV^POqmSpiHcc273i3m$VwP7i!HY^R5nKDQTm31uVSl+RMV@1bGj+LP@f2v~@ z$Exs{O#VAf8>pINb;lZxH62fPtmRl2sus>b%J`X389xgu<9hI_j5k2CYlvib9+chr zFjIz&*pOjUq*6CSDpfjC=9(iV+5#!^mPmnHqb~^G7V38E64)4N9rXfq8B#)*L#1;C zR61JdRa{;4VCF(Lu0lOfSEK;lpaOJ<3Xp*mpcfj5dZQaqA9M@qi^ij?(KK`onuq$M zCFokDpQ99T@C5#Br$OFvsiRa-_spA+3eqkA|a1kQVQsKsO^5Y&6=6 zlt~p#&8>osfhyQo9e4&Q_b;Fc=oK^(?M0K&N9Z>64VsL8MpID16z?%*0;+V!!|AXX zoB>P0nXoLJ4XeO8@N}r!sSo?iXQK%l3*d#&!1izfzL4S&#o7pIxwE^?8X$+9$rvuMLx4AaR%7_=1gNI6~b} zFw?p~V!4iIotKhmXic+5r6!6O2uzF_lbYy|^;JAkuz-!=hhq{u?8eqY-bU86WBs5z z3n$8Wk$iSsqIfKbWS%+xl$M#YB@$h;7M4m3@ba8E zIq_?W*XDXztweThc$@JCCV!2@bwT`B*j<~eKTb&u4UQ+0&3F!7BT?0yST@nh+GdoE z*cJ@qXk9MxvkbDDmrsoH^0KW$;*brqy<*~}ApUd1FvnC*+;0swR!%%;1K)aTqMBD3 zYpWzK@wN(_mZ+G&kYD{3^PBv25>-r-QxeT>*d^5xL+wV9>WQI&S*7e|zj4-@4X)uf zKB|#;ImlIu!M0@Kot#bO;uAVRV7qWlz*+Y&pG?tBozI=_xrx*`TMl*yVu%lt-aRTYp*@t zlUs8hJ$yPMbrU<-s9Dqb%AC)W*m){>W0WLjYm7*5w7r7&xZ+`<0*p~AczdcIR!M+s z)Fjr>AWUK&NtA8(uzmuppe3<2qPSF>#J(5BpfTazRt^t4DZn&GLcn<{+H9)#(wHLN z2<8d+)5Oe~T*NyAFt<%ilLAG&*O`a8nvkL$oCz#g6w}5P^JY6etdsyRxQlrkx;!jG zfOB05Y^-SXwrdirD6pu`y8#||9Rh8(K-)}*cbYqnjW&cNu<@d}Su5rpTF}E@m7J#v zdfZ+s=Z@ew_JgR8E}Xy)OU}%~V6)^b7vgbWvU0kL#<5yW$)v=h39P;-9)@CjQEX9c z5{pc3iJh~j*qVTmRJ7R?J~7o%#Jjk7KQ_(R3Tx4%awR$Y z`$2$-r4m^A9mILKR3fV@iXBQ1V$Xd|*v+LA1DX>Sy9nLiD$K*$3%azhL9Dwd289n| z{Y5c3Jb?`r#dG1q*c2-AmHe+d4(DK%d9K>yJ3b+{A1_J3u+N>W<)pUoIu6~ zl}%&=geH@qxIlG}C12`CL2i`;Fq6dltzIYCK70 z)l$uBlo|2mKHy%&hbqbfQ%vmo5bx-xJglgf`1U*%t*}#6_AU6{URLp5+)>zDyOoDI z=OY0R-*5lk8{0ZQ;3C1OmD1@0Kb@Pm^#t_hNNwoc_?eR44(;LtiY+v-$b%|pw2u!6 ze#45*>RG`X*L#7`vhg#;ywOcVybV2`fQoNg$qa)mq@$UVh9qk@KNF>}U7~!U^B9Gl zSwi^4F5u{I(y{Cf>bVA#4)vxa?C{p^?g?nS)Q~>0m(QD0J=oj12PDyVOyZu;tz@ou zNlsADDJrY+zF7rw*2eW7r@AYCAcVw?X*!PQl($EDvwAG{X2i#{>r}J0Sl=EB`*Q_J zN$OV@y8T0vvU$KJ98+2q3JF6iEOwgD_)<_i79L2l~$9Pz4ePoSEV4Xy9)?}Cr z#(LOu0*on8%zI?4C*XMkAuD^Ic0!&U=V31jg0yi7Y_uri_>36uVdDiDTdkOP27uEy zktTa4Oi_6C*-Ef?^aKwpvROwa+S#ju9@Bf=D=_a62oZ=g*1=cf*e+2YGbMrT6-At} zvZi?0A<0>Ls>glC%DM8jI2N(NOdOS_dsuk^CQnabb+%&reDgbM=d}?jwROi$p5b9x zpId8MC_MWewW&8X@rE~Z_G0#!5P`Z5P4&2+AW+x!?A$muLezKgCa{-95p~V>de}tC z*?gXdr3w%`A``$x0=zJ$n78zN4|_*|*csCRTq(e#^AiHrQPJp#xCI`zSybX8VHbd3 z3lKY_KKHOa0-VJY*nTSVw8lyq?;{I4x=#_TE1$Y3j)iSB-Q|AM!y*NU28;$UN`QOb zOmx?$qR!bbEsnh;Igh7#Sd!#yx5UGe1&B_|TH zlbrM3@wks$IoXtnvKJOpOX#N_miaA-8}up6J*nALPTRCc2~hW8 z+0^^<(v+9$Z}G5Csb=l0Es#!q-x0@!tT8J4{s$^2ei3bVe|%7IXD77MlX1@HZg1A6 z8IX`eyyLdRSUQ9hzIA8qVW>@+IXu#TsxJmzErg~j|r<)E{Z6>rSxc=pv9guOv$SFH8?vR+|3e%0%v z?ExR2$1;Y%+p`<-S-tG#)&^hdr_TVTfA<88zL1;18+>WH*YivC+SCi7YC!u-x$8l| zVmsR2(z!QBvs%}PAo*q& zg;lwZWpDD$6=GE0nunt@gJ%Bgw>_-k4P6)ne)!))Q06Y^k)aLjRe7AIFrNH}eF68_ zm2T_|5%@CfN|M5E-$rU*ltY2hcj68Ku}nFvuwDw<=8JJD#S~UdrErIuOh^?yc)vi{ z{f5{LWCp6mhXyDMd0Zi74eG^Dxq+mbgM!5TPb&q|ekVov?Qmr!PkvO{Mhg)>BTVrG z^mU_-B3Hf2vnwi5-q`gF_Yiz}IEX1-kZb@}=*^-*WHSIOd|Fv$2lQ{Sfm!JT$*_1Y zP=qigZ`${GZw+)QqsZU)=~BzI;rpng$umu?R5 zNC9K}sQ|lQu}W~Y1PPl6!xLMAwp%54PD|kaT5|Vmh1y%WkF){q;4D-7Zf$|1D8W3V zEl3L!b=Y?Oc2HME)Xi!Kb#-+<=z`$(P}f+%*ahPdZY|)=j~3^@wpTi`X9RCd2jG29 z)S>^gIsk2gfYJX|I|BTI&I=w~i12X1d!ZwEa3Ym;4^Hj`7Hl-R7d!*RTLcJMln(|t zAfhxH2n>907o`qwTw3YG8^5Jo;fT%lm-b1YK@C z$bCUjV>G44qc0@4))%-ZTDiwP58R&$3iRgn=Yb=OVBR4Cr0tO02NR&~fT)Y^4|OMW zKIqZR{!sUufU!qQ4*>XzfMI6i(-6Kbc#jSMUiLX!i<26U8whoU1&liELbw>gJmV>~ zIDdQ)gu~=WWbn-zr0h}HACDTqV1=jDRm=I3dnr*0FFi!*$HIjubl1B>z~HI`>+UK& z3=Do=P+(&Vhe2b52<8`tfnviX_vqokonqzA8427QCHLSY;NBu&bofDpw-d~xM?vuY zVCDYuW#A6}f(*yD8;=3%q5?+ll?aCk7`2ZWi=9oduKmLCs6AnDod|hQa;GE%_b|zQ z1>qC{V;+o~0Pu7HV;fJqQ^3uO1!7hTB*OQpY$U?qi9nwr__5o@A-qAr z9lS7TCj$Fs!5%XSblYBmI%&@oWsq22@p6-arA{S8b3nffi>e5oURX`#dtOy;u!>Oz zL!g~)6OxX;244K0%DN9m%m5!0{L=Jd&I}M5B48X$J!b-Zx+*mi)KhIL1~81Ib2z!i z1@w=`DiFqDjK0nz<|&nM7r=X_0$)V6TzoK`Yx9TbZ!5PbtlL zL{Ifyeslq>W`b%5@y_#ruv1Nc!e!}i!%11Jn(hO-5QVj=L-jlF*bnpUg^*mz)J23Z z`VFOp%2<5@#=MCW@$UriM4!@-4DvxnYNxOP4M>7SgTCh#_DaKCXlVFdm6a4;!q^({ zF2q(Pf_1+he;@qXNBXtrGO!>)z!;r75KbhRhcAa-8E57Gcm;5;libxm1nN&E_hN*< z5HPkMyb{{qYn3o910)1~MT%d@fcPmyW!(Spp{szqxPX!S6vE{NjIr&>1h~3@F}61$ z{J4NI9aUNla8rVL@@fd}c2w3wd%+s0?JTGNQNLYJL-M#)s%?>sNWrJjUs)bYXg3cUI;WhuY@i5ki)odeyI z5`>g<;0jh|00Gm_^gjhqt5`2bT4dfYjAl~>Wtl>bv z22@?i=8q=+WJQGom(^noVNLBURKyt$w0=(2P#eHR$(<>pQq{<21$!18EDnc21-Rf z4h2Of z$L?2W@QBK4G=_LEO-Id3sh)rhQ}Z(Blu{#xla%;@hI3mau)LX5+7AtC@gk10UOJyZB*8J4grA`v8L%<0%D`o zba6<{T~WY&+H0T(*_p-(*z=frSz(J7B7ml*@u9WV(mbU)xUK6OgawjKmi%z528imt z$f(-L!K`w=>a|oDp>HfEp%-ea@pksn5~8_KN;@iYK*Ps@b4ogq%za#i8;K@M30U)l z3ZWYHj$m$x8h!Sz0l)VAbiFPP9%lRLxE7;bImRu^byrEF$& zR}j0JV7{j-Xt_a@V^Y;Jyn8p$a=Vp1y9cn_c3{J#WxMBq-9a#4`W&zy&LpK^od=q9 zUV{J!U_GrY{a0k=;(&M3#|C{8Oo4q=I4F61<)lWywYjgF=x#-2-9?=fpz&7-Lyc!A zfPiU&I(e%K9p4{@=(Y@G*DtjBazL?f3B_;sR|l~lMLBT*l+TFrQ7r!<${hwmSy?4C z7^tpcokclmkUEA96XmOe)TDsdscbYe?FDFNuBblx0)*HbdbQ2F_oGnWXD}q7rJ{P> zV09RKZY{MC{i2%4)(iYnC>I<|c$=*N-mvqsU0@=rkqEr2KPLJqi9nya4$HpSSJf2E z?ty&rP_-KyL!90YPS`UJ1!Z0lEp&JZ?3pdfo3Xq|l%t11`8`oi!}3~D4jK+NGtt_% z;b3z)QEo8;Y<`l;`rSW2I0Bm4DypMLs)+&n^y-5A?P0J>+%yt;qq&Z=;UfZGDG6A< z680u10ZUu68g2`@YZO#>77}Aeslx(}>DBbufS-rzfU`QPp!Yyz|#r6q* zbV|lp5R$efLyYVZWq31^7!bN%=!^F|uku~f)e$@+1zIbK&<2 zw@lQ9&jAZEMIGkp^f^$sLBM!M;hqZ)`+{I2SC0l1+a=id%mwxXf*h9{9xv1#5in-V zO#lbH{gto`R*VbPPkHt{NFnjt5XV7beF@xn0bmoi8`#Zs_s-+qP{mRg3gvj#JXi4w2zW>RTxILNH_3cJwn+`Rd;l@HTA8y_eUcA_<*6@qmfA|;86T?g0do%- z)qOJ6t?+837|&S=ruRB*;7vB3G9PU2kpW!8f6QB(vp`+Q(>JJ9v5jf`!df+pc0ILM zgSh=awU5@5k~SY59O1VEGcOy&8ZcfuAO~0;e;BGCZ&Dd<o8=?N!GVev$NpAq+O)KZQBj5xfr)@dUlDFyKX&PXRP*B;2%rADFa+NPHPTs?#yQ zz&*+Lkd!8h1{(YTjcpg@lRrRSe(Vf7%m=I5NQGS{fCKB7=3>^x7as(E^;dE}RDCWM zv&RqN;-m9##13c}*h4Q65PnRJQP|TL35Y+g)>K)!O9U)Bi8aM86L9or6*k+e{}8}w zTn+EIiU4o%E2Qv4RK{tYtk3=xtp8cZq4kQuH$DerEyuv~moSPl5qE-&wAqUACx1}u z@R;AgHJ7fD{58Kp1U)T!24lY4d2nHG(K8w6p=Sn(I$RuA{~hXv3mB8`VuZ&D+L)K% z)G^=!tT(0zMC=6!>Dg2^6+C_c7#0cvxLj^`5#V%C=0a2PA&NHTT zCtwQ0n)NGxLbA`DShG5Vfbc-9nX-z29t9BSl}W$?w>?s25o-w89As~*xE*T|fT%e7 z>C5|WPXO=NL9B%9v_(58gFn8M0zq?&|BP~~Eq2>85|y(YZU5WtPn2yFR`YHEff z*}5$R%!+^-_iI@QfXelDj^z;r)CxT1h{_g3*o*O?Nc%Y!yOoG9(HcM$MPRqVqyUlR}*jX;5&1VmS}Cn+rQ8v<5Tx5Gis9|Aa9 z10DC-F2PgF9<8z&dkBcB17z;*-y&eM1=li6&8!V(#w=8(^O$<}cvdN!sNb(=p9EdA zmw@QUvCAX(5iqC$Hg`z?X;0Y4D9rsm)r3C@&I~w4fX$Z5-=1tQ?i(6ohi3^Zejth| zjqHmQc6vVn6`G(2cN`!fu_=1+or45qG_xlw?DIne)O-p%j~^yrPz!sM!utJ4z@nC@ zPB6v5&^`QJw9DdGn4Cx{Xc}&!Ud@G?gWvA|R%dy$!rGJx#!@ zXAqc_L%`9_*aJ^rCLp9M_CWX_2-s{nRiOv6@7SX_oM-8#=sBnx`m;mq_22LVg_-~hQTfSvIOtj(sH z%YE&zsW`chfGG*~7E0ju?+~!r;wtc2N%p991MDRfmhnANgb%dGLkie0$X~E`P+0l_ z0uB#GV9r4Tq7&^sR5t4{0keieP2kuc5wO{^EAXR(?X7&#!|bZU1|1=iy2I_^Dyw{y zfF2{EKLaikV6zS7O~%@b^WcR_2tVEhoZ=Y;PWkK@k!~DiuM30n1OZoH#@HEkl7N`8 z2ox%G4Ba(loc*f8-VY{VQZfu0_w$7ju-VcdH)5Wy-J*L#IvSAzeOjX$QSVH#Pf=O7 z8U%EC1%*CQlYq3z2t?E(;FnkJ7ZkRtHUWpHLfE;d)Iq>z%c=n_cuLsIEo=OmU16^c zBAOP{?QrZmU@!sGXW;Ou^CAJ8XCg3dJOP(y*^^baYytsEbL=q+o18+xMp)7-tnWku zZhMhz(klc^S^zcf-jfl40Dn>#Si*OT+aus<*sDYo_y+d-i&F_mdlUMdb(%)Nxy1dxW%Y7I%?bz$cZEv`Kn$0yt8Y^-tbn0-c$J#KbINM9dCE%Y49^u}*}u}>0{E{I z5V;CR4!b7!tCy=gZb6tmdO?Ogm@mqN1bvWdeA8Fki`m)i8^rVe$M$%I&AmxL&^iS8 zEdr8!7_swi6R>}SJyv1fI|MZN)ZPOQ^=f>C{#djL{ZX|R0oyi%3kv)}fX$X!u`VxD zK&^=L#6nJiv?B|2|Ex9*C)E2 zU)dL^Y-j@l5_Vv~J@W(s8DAq%zaarvccNcs#}Ke)H{_ZEV;UgRw*4~PwKXB2%|6Vi-!?_Sn^0^m0a zkXgq(MdW*)+Re)}f@E0#Adt6iK@_tN;w;dwB>_Q)QF^^rM85f%uX&*(_C8E)O_fPU z(7|^Eu>U6DQj5jv0QGr_=zQ+x-km zJkf!W;X?s=%HEBIc0?E-#ST+gMGsXD`g9k+@Fa})+Gl{{Dj|LFj26QBv!{t1o~_{g9;K?d13syga0Q6bzC$D^S6o_hf9z-Q~MjCjP`Iy4L{T)>N=vhQ~$DBMVRE)T2 z_qeBb5t95~b@J#Ou(4%VtisoWMez+9cy`kzkQ>_#(H!0p?ngNR{L*E6M|ZXELQdYd zh!@y(s>3&a@R)7(pP)+d=Y)(R3f!6 ze|ZCx_$Zc0;L#lTDXBLBa6Nw-6zvI$HoOfqz4{O*T-58FBklmUdOQ)slYdlnNg#jQcazV{Xa94_#V^7J6B8{76H)!q)$V06Cs69G*NYmkA59VJq@EZfcN zJprb5E~0r@lVe1(qKF3fKUGfzi9bgPRxM zDWYFf5>=UXngF<|0igdc1i%p=oXAj*4=4lN3(gW64*QaofKPr!07B3IkT8Uoiv-fw z&k-q{dliKwgdc zhe0hA_s}Z{*lhmiNDGSELwUQZz}fXrB3@cmgKc@#H3AMtYmoEojL7psPr<$ZjS_imC)(F_y z7=gIoqVH%%nU^^c-D@TcRn7Cd>7=Hk0sYLk_+yi+SJ(r&a~(64p=W-ILr zr4jGlS_?Nx@=>sxkebt4Tgf*r&;t4UZL~-3gP~?yZKd)kKi1ZwK|bPj`-ivF5+g5y zi3)4=T|;4daQU5M@6+_I!i&S&Yw?lg;aWjql@94rJg@@0PbqEDRPaa#EtxNF1E!wo zaBowe(VOk4^-vn{-5o8S%;oG3-L!T*)}y_tl;<}*_jXAaKGCZ^wP2hU!1>eKGdv=T zx%lO$wNm%;dS}Trt&`SKsmKp@vNV%RowCnpYx%jS!NjA_SWL`Cl`$HWPU)n%l)-#> zXU*fN3>{?SJ38Op&g#!=ZF#q6wK%0P-|+0+>25!(ty16!z02K6X7qyA8+Fyb;=9&> z1Kiy#4#=e_c=wD_h1czVSBrfUx@+wer7GXm!$Ol!JbFa9_jyj6!N2JN4!ZW7#XTEzsuse__tl0dwfOwLme%}j z;}=FkXD#ZcxdZzSN$fjv*s$jlNARbg(%`^+mR5-$?*mM?|J_?ZF#z)N@tHvUQ-AFzbL{yyIz%8~w>n?IYVoxTf|ug?&zsIl5f*CyhB+ln@`hui_PMEn zKa~%{uGT%(>nGegZOzM&53(1o95!{)w1o_+#U=ou(H4*6{4{y0`LL%FTSv3r^6U<*CCMuBbA7 zuj;E7*wy47T6fX9yP@H4FYR9j!mz@$=$fANa8-fSzMuIw%%=fY$y`(9+71zC+L+SYrK z^$=vwPt`Ia$B_Qh&aM&qr~azzZ&@tn<3Sn_?@iN|DK&Zf*X|uBy|J~H{Mc*SiF+y4 z70EpO$=9{4dy$R7&Then&d^RP5xmPxOWXdz=X-OeR`H%gsgEUdHJPpLH=E5RC?-{l z6oT%uZ0<+_$oI|BmXQo3^OwQX=V~9{t!}JW+kBTgA7srZ?`eeImoPuB;O9Sby7=b{ zv20GkK;Y)GDV<^|hq&ir}$+HAPz$lFe84Y*;?w`NCOyyef2=SWTc zSrux~a*?)YLi@tPJ8DvhUg%#$FLVyo3pI=Cg(1cCLbu|2p=AlZ5c7y$s9aJnlqjVa zoTc?blQ6w7Ib1KSpHN0GU60TUH6r!Glt=Z#7v=Oqg9>_KT1CC^QzgBy?X=j(soPKK zg>7f_!hxfDp}`5g@a^Avq1R6n^wPFKUC_$kbCqy|CdI zy|DVMUO4%yUij{uUdZ}QFMM`hFU-AYhuaVAsNZvRRQP4R@bMpdVgD7qQ01y#m~l-n zoV%_Uir&x*eQxT7@wfEC)!TZZ8igygm7?hO>{T@zwXastbW|6IURccZg3YBD8V2Zv za&EoQxu9N{8Kf7^6w(V7qVz(eDte)0RlRUWFECAm(a5hZ&}x=!|NMvnL;4M8^b)OD zEkRius$b!SPs8Fh=Qpj2uVk9`mjkkH>Ni?ZKI9#(fG_b~ZL8}0b(!}5qrT8R+G^F8 zvR6yfe9l8!RR`~PRC^Jg=U?2X74=0P*TNK~l_{-|uiejDn(C|ci`Kf3(w3K94J*>$ zKf+b{QmwQv?5gI_lxn>3EiH`a+=LzA=9}6?uKWe>!m|I?s`0OGYc1zL>Zqy2^Es*` z{F4KYo0_j2bEK=>5#VU)n-kz@R!Hf_%NB8z;_0Ovg?tH*IF@R@mSr5}RbNh|qrTJk zU6kXJqMSyWa9?6gM{9-GsqMI~bViZzmZ*-St#5H%#}bF~5?5jzm3-S8I(h|8cop2D zyjYDUtvm3~Zb(F!2JD-vbvKdUKt3VlW7Q>cw}G_Pk;tD6WE%_en1P)1q@+ASNWQeC zqevB!-L8kMd>meHU~02(j+4knfK>SLmX6Q~ptQ|iWPq$|k9CNAdAvjpG?3dfC332P zoVHpbUo()ut&_-^269}VQmy`i&_@4YP$?AV>$hCrVPG2rwjeZbE3#W6TN%hL6$_dC zJwVI%kkb28u!$T8$dZV>cvd1O8OR^bN#tZg^2@CpMJ5nOwNGT_7^oyIo_<9(IKV*O zupkE+$XmJmi!+V=<6vp;d7wn3%~@C?FCdbfP^Xf_-Y`frc}>%_n&1TDAKgq+YJkKU z#WwA8>8|Dma&-wwc@<<5W#6rmvMEtw`r2^E3j>)RDk*b>OlW^fF^Roww6v>qVY8*_fW$bqxpqk8K|mHkWYJX-CS@?RGz5{s zsS+7zAb)BtkuDus-&Q79OE~4*%4f)yc0o&oToEjhQ+SQ`j?h7SjJn%LWCUR#CZm+u z@e(-Ghp9ZVf015nSvmNLyTlg3-w0N;6O5}P2 zIo2YqI+#pOdt<-Mql1C6GRjO{FPn`tC=)kIWOW|a$x*q=a$q3eWwn&;tpFrBgte8( zmHeenj?iMC8w_eYYu%e5OwCmIwoZ;p%KQ9cCr61vaiE8NVXnX??TQ|hDkmB$CykN< zHyg;ez2!~!RWj_bTQVYQ@8&N(1A~`x*pOFym@9(cU~WV^?8RS`$lnpkGl79W*4fdW zf7RI$#kW4|D9Q(Ranx1dHF_85qfY#jE{?+!#^zddOhIXxx5k@I%!Ihdp>A*Tw?xi1 zkacfMQx;X|+D2`$}td(`I zgJ7~=`$i&X8pw_}B=V+?RHF(`Fje~-kkAV@dwK=-rxg5w)$DhJOJ#9Z2;)d3R%|Gixz3mC}L0TNlzK)xO< zk%bV+FZF_UmYmUTr8hJzMlQDUeb3u%WKy60Aq_lR|H&U!n7tMtXbZP;pssqDAl zeL@y(E0KEv*@!pk4JtH32BovCyNGp&tleEAaYx#SrvSqQs{1@%*8K%_)sW%(2#Lh~ zA&rG2?^o?@th_zYnTWJKvP>d70TPBU@c%Hjr9l~MAkP&YXCmVeNf8pWPGaK?(x-hA z+0Q^8{Zk?n3}mJJnm_e{)Ty&Uc`{xi+Yz!OXgbh>ZDWwGA1FyX7|6OYkD1nYF_3o} zOJp~GA`W_?hf&vlhpg*qAe&hA^gQYnWHyZ(uEIPRPYIcBRwJ_*Q3q@1)}A{!aV zsy|6&69akHf^3FJexi>fv|#=+y5Lf938j>=@UzT+V2J+xExq=_Tvw&ejl2JF`vI5#@AV0PsHvtk_w%H~; zDLt|nkhBn}Z|xpHf=_L>0h1-=mw<$SZMFrLZO>s`Q4^NGk`HN2E}=yE^AJgiIuSCs zkF*T;CWP$yoJ0;akhi-?$HXZyI%&}Rl615|+UtZwCL73sKTBi^-`fweWly7SUIi(; z8^77l5$4&RBDKu|I{f*CE$U_2n|;8ih_rvbUm}O-NVUfyiF^r>eDCwn&BW08h^!lF zNLgh;jv|KO1n6dBn0r(*jD2J-aJlJXTmQY803 zBau@PiP_YC$AW#`AbsVmB%Ns>S6q_F*?`2nZ7+XaBIg*$Pc6v#2C~^-l9CgWNA`y! zCEQiopK`A>2jrVjNysG@wtr#iq}TYFfsPpU7ll_G)&x0Hz)ne-ml^l&^WmTT|qGMU{(uTBt z2c(-$D!gi)R& zQ1Hep9q_njsH3G(6UI~Cb~tqbyxKr`dXn;zqn=qEh?^&P(}nGU#4vvQB}tw#5~M|p zauo98pVplP^M1pGTErBHlZX(4N@RWnt?V1-sAjgp)2}j80@g_^jJF!@D6Wh2ZW{06 z6NWo{MEmq7P`m3@M;W6*(qR4wp@C4)u!j@zZzFOMTh#?_qaAK7AOF_j=8ZpbXbbQ$ zrc|8V^UFw!d-#({_i%?*CTV()#{C^><)l`VN6A+GyA`eW$DogiDJUfu69x%7^x|mU zp%g;fUN%Dr-Edb;e0|3_`YY5CCz?X&og3@u=+_bXnJ0R*{UW$H+%it~6KQNJT6DZr z)ITEcrO!Q;Q~#a!io&znc+^2cbVWQVpM}NZa#B@ zE|P{rp8h{JA=hxo(+QlMHzP$j&+I;QM*5pFAX`s#93$1K9bU4LjwPzmEyU@WB{dBVUQN%V!a7YwnL%8s^)%VMaepZ-WX9~Wc+ef{`tM7M;dIK{u5^?~OtnYqCVk&~!qv!#Yw`1>tu3wT7SFJCC7yl)da>&qE?4|?Cs==Z3}336JTVr`Lp+(`E^ zh1k7&!OsAs|4$1b9dY%WvRniyA`JVmO!0SnxxN~rk@f5cVr2P^kX$(*47&>kg=V#9 z?jYQFZSoroc~?CYrL{gZquQHQ-NlD}=$LAZY@_>7D$H*Gy!t~zW4vP75J1rr*j(yE zO>bETNo~w=E^VHK*aa`?}8V#b6F zqw$PYV#XAxUssu)zSYRF5aTKGTr4ZAnKei+?eFr5L-$e7mCvt}9{Rd>rJ)bzn z<_;_wL$>zw(CD-dt&d+ZsciDQ!9ofY@ zH_8q(q86!WQCEATSk(DBl9Vvm#IiTwQ)AhiUtbYJ+IeR%e7@FW zE7th1_9AIBY?HpkG?KhkExjDfhu$%kxVXiR{Y=-);6a7+gcCB8yCXN7ed`_C)Wu)k ztS{Hf%EB*9@%z+8pG)K=sAGL*{^};r} ziOQoeHhX28IG_|7dQeZH4;+SK>(uCwzJ$Iff9iNx9OoPoCZi27 z54w1%!(wTH2x~w?+07?(lUoYNZbOWWL0NgpkNQFpTD4Bfxh36W5)r$%HAnO{CQY4% zf09$Dq0n8Y2a*j3e;N-sYF%sOKk$XOCLJ|*Fs8*KrMl6>qB$EylarU6lXHik zp=Lywq(i#NDVcchA0ZBPkN+n3L|L&SQp_bSHbV=P_s;A4gnT2>8mD1=!|&3+H0E=D z*T+0WiRcyT-^2_02=<$2&9-HMsGDAt(O^cqzrbrciAWG_+KM%kh;Z7A!Ai(D826?R zcJbqFozwV;OEQCp@n!9V3(#_F>Na+~yvk(<<#`@|*>QzNkf}tT0t&&CC4TV_OQ7dE zk{PfE2-;uK2L}a2%9UIJVXUUjZqI|jbQXj+`iW+w3d5}{b6S>#Kf|i!NgJpBbW|l{ zid@wTrZEGrI(kwSsdoCR?k(pvM`uFEUUS^aBQM`D0g-ZHN2W}M)1{cd^hG|&H`cFM zou``-NjBX0OFuBHdc!d|PvfTQ)Sz*GDdUOIe#_5Xhv_SB=1un4c76eYcr!Ri@<+FH zgA6^8gvKV#Id^W!STNV1mShDh?O;CbZ#jlcH|+ge4?Swex|R(KEHAsA{5br!-cN=S zeA8{m4C=79cl0hEb4Q;8zSawdf9KbcA%bg-`IA}80GKSG4S3W(|9h53g3QZYv#v1z zTw%#37{Xh_pI$J*B0NiFEle^Gd5k4oFZ8pJ@yjx)wV}hmllEUnL6D-X;gs_+( z!&-cIlzN266=!8lTsYvekMP;W*~=zd8DHBX4F0mE!56`Yg<70lmTwKUI6IQ(gjzc4 zQ9i0D`vt8Plfw97tfMKZ9RHyh>tKTAdDY?;Nfr2WRP^Rp#b z2g9p0Cb~Sr3Yqm$Jn<2W>?-`NM=Zjs`pT7LY0|{U_&23ki$aFQxED-uujGA-vj`K3 zFAuW-nOdBUHk+x=vrDsWCRl?n3A4DWCchA7QKA+P3%4jy+xKobo1&O~f#*uW@F$n* zS^B3g;4*9Th7DL{vypoIjR@gl2%-gb#i2RIlQp)mY;x4+MI%L&iUWn}QLK#F?Bmcx zTr>Y>8vxGM+`ri;_^*-o)vPJGAqYut&u2ujvZg9e@_FT0vA|zhv59`M zRJjr}&nI%b)n6Bb4XI4APl4FrSpGx>R@SJe^-J5ztX^(J3BIwir8`=n(Y^T_R)zCyn}Pp&JyVotB7S51RD@KW_GW3?l1 zQ_m7v9zL<28T`Vz_X-hH>eKx1`YhVNBhAWAeE8#*CZFL;AGb8wnID2$)2GjZFOFx? zU@!%A;q@Bm(y7CY4J1u2c+;}7ft-FJN4j}t2rFQ+cjZN%kVa)TVa5jA@T{1HzHP`V z8eNs|SirUe@e?olp38Ku&gI+pBx~uv`Fp&P9+`AoFrX3ZNIs$wexQ*|X|zTQYAmb} z$*Wgmu_Z+#O`C`t^q|!b*3e$#+$C`sDi~jGA{i~liuVzXnzF8jkg!1fg_$shTnY1Z z5S|hG@7?IuGq{;NqM#d=Cz`R|hTUtL>1VGfh1LTnnzNQ>Gv;X_R#Sc$@|4UEezuVs zvV+982*$Nwcl4&{{PxY3Y>5u@>=Vx5z(@Veu&y~lcRFGfnc<89Q55BWc!3(w9(QnS z;VDG?*qU7=bExCawUHgSfnn$XCZs1$JM;=@@MMPjH5fQ>x<2G49 z%RA*iFz>!OqyDvABKtmlhRF*AT1CNr&=n~2u2%lY z5c4<$2ZY#_Q%KWh|HYpAg8^#PJQAbBgkwFWQQ-GrK6^7cL>=gc_ zR^V1$<5yo~PZ%kMrrV3DPV-$OA*`>CmL;+?Mu&v)j3Kgn?s8^IZp`@@9N%%MKE$Ez zM|kqk{5R~BQi5MH^9{U@)c6xGv9+RA9P2^D^dTrZ1)oX`9Sz%jPkSg093~eF6aw%P zMuR`Z%;=Gt7=AIVJCsloRxUQ;E2mR<@FFgGT38)z5O8$&te{@aYxy;nB=zbgiMM zIq+nGghh`L$;3p1K)Q*aH-^oYM#1J$<1NSPs|T9v%n@TaO9W*0SUrSk`B`n8zWg+a z?xTPNcUh=tC{a)Qqj?a+9z zWdn!8s3-qQmOW|AEA_}p=n=~kXVaTlkhc}0>C!2B9FvForsy6rf+v`-pY3#8ejPvo zQFx+}(Qqih>6J0Rx${D<+O{lXCU7@ZZTv(tq^)^O0BBDAOu#9ATmt zD0~mRBElCDhQCDv5gTAQzC1baAi|&)jshzil57}tc{95higWd_HWgf;q^i<%ZAJ~M4VSbpQg3f(dK5M%9U+&w7@0Ewo z2>%nVeb}!2S8?~yV+B3*Z0 z`S8qC>{uUgoIVWSe?DVBd;tDWaQ>^T`!5v!R}18aNuK{IsUY*iM*G7kD_#-A9+Lad z2U$`*bmD#Z{zL~ooS}H0`*2$<{5|@^xai+m`S26de>~(L-q3%+`LC?}H|xKD8Gq*e za5rA+JWDXa?tJ=r>s|K2^YVz_ObPj&nBb70``DGg>jyw|gmRas!e)9wHp{7DzGZPZ zlv|Riau5FQMV3gLMm%$hG9LKAx7cl2JH)pFyFl4^_?7#YxA*)>4~_KvpKzH9^yG^# zS?=_E@$W8KUI4}N9+xez+IsW7mst{Oi=tt8O*P8Ky9LBQXc_Kkf&=l&Nxz*p4&P7~ zoFTe-+dt*0G@WQB{b@b9%bV?X;T|Xrx zg^j1Xt|0g(r41kHm{)LgF^BK7@k6k>r|2Ll>@OMRbh6m%Ad5}w(`*)o3*Agk37 zLl+_^x%Uw%_uoEwGLIAdypczniiL#o*b1dMcx$BvTG9!4Mm)RYy9+s;vd0cIcLs;x zi_4<$UrW=I|ETSpVBK2}s4WFeY&C1pG=3T$Ewl}h?>FfA5Pqt~OpyOIvGYIMUuG8Q z&!dVxSe7w*!xS-qKVRIER|fKV#q-N6X3Urcl$0lU_LT4o9v83ki0BL)`uRo8f{M^% zAL>Mgau`!swhpZ7C7m5*tSu?2C(S%LCbwD?CF7p3MhT@BXT$%7)I#@^&xJYZ`i(5W zSqeX&j-SMT(He)8rB8&(EKQ%P%A1g_PiH81>iJ&(K^rzgyTi3*oW0FX9mGpTIMYn< z1^#)2(_D~w$_h^!`tdDgWgo#$f1AUTrmF^u#w3nxUMErxt9xcY_^rTwX21WKCJ&fd z#L5SaZdSp+FZrSh(pP2q?h4}PQ`9FA%ElCY@9E*KiVv6Y{ufRbFpd5%Nri*?7mqpR z^6W((8SPw03FDrNj{ordA?B)b`O^eO-utZD;di>Yx$v;dZO9Kt}P7|$tKo@zHB19iZ=9BPdHtH_*1V3TFn`);1MkS zG*av?Uu>rD6z&$Y#w=haIMM+oOIINN!5Cva;xCzD&ZIyRb2ChrW{6omVqMOV@DWe_ zU!Mq!iOx*H0X*H|VR+@1FsqnTKS^NMYpT}0l-O!rnBu|>;w>nN^*?(b*K z%Z#}|{-PO|viWKFz-aSb;eUy=`xk=aMx3v7bXGHR5uLbx+0i+~F!=5}?s5P9neqRx zS?H6?=I?oo9n*uw9(FWkpZoikfjOE+@YCI#`;Ew;Q2DXD2$eho%48bJU+&?IF_^3; zLzZtR!YD4wYdmLuJ%E1IAB2D7sE(N%QENKA^gibtWyquOjOr=FlXivTLxyIKFbXJv zA}FSpo+a6<2QEW94qg_?iQVrBkbGvEtM!7t8M6d0BFiz&9cy_*;GD zsJovNKD5R|OrvC}{>;5`Q|u8Eymz?PO;sID6{|`X)tCe{hQc%`Ek79Y!4HP?2kd^LP`wk0&g}*ti~@5+{Il9La0|X9qYd=9hN4Kr zK=|uCmK71I6jByYcx2bZtP+c!TD9-7=)sj*a}rB)G<;E1d;!+~S5&!8zUSxJgbO>I zww@D~ND|8<8LiY@a*|v?Sf)S<|08)=sB6scO5~Rn(B=Ps_Q`+t`QPUoyqV!+CWwJ8 zV)eZVA|Oy%I#`3R<^=Qe6Z8e4b&jA}rbmhxQ-Yhm`~GK&em+BkvC>3mBN^WGdzsfK z>I4*mnr-~o}ZVZF@Ta(2IMTs=P@Aj*3_&w|alwdSX(r7T^Z|==6l8l4Ald1nv zv~5s^`#Jij9Q?1?I1xCW-d7b()+`{Bp6_xu(RxFP<(;b3SUAO)HohuZa~5$`e`UHGPG0 zH`j`eHGatB=RbTc$&1jzj<({8eGW%m9?={O3!jt&oS>ixi__*e5c@amS zhZ;ZI$OQT?aQr{DnXeYSBbOaX9&Dc~r%XWTKWhCrRpu4HALE&c!ySabF05)s9d(KI z^J>B`DRcCRjQVfO9KAP;0MLo<`88F87F#fWQHW*3K%r9ZrwgWAU*Vg*`fUv*u)7jY z=C9AQ`2SVk&UsEIZ>y*9L-Q@eWh!?quz=HeJuCPcPq2d1`TPaW1$kn^zc=uCNuJ+4 zBEmG~b-tBb+ML02xU&UH{RbD!sM;XYo>Rh_$7V0e`=QVmI$Kwm3+D-$297 z#~XU+lS54wKgilcST+{*Vu+g`A0Zz2J*;SyG8ddaYJBXQ;_3;t%6D^438nO6%hCFq zF!OczTVF*{xUE?%(*?9It4X1F_|gOZWi|N_2+@?ehP);BUi7KOU>=kvv%g^r(o>@B zm!>clF`a~TdQ z(yvNaFL(Ngj5IL*PzV}8OPy68nA-j zY4h5u+?xcm-Yn2hIzPo7t}OG39IiC!{NBbTzN-%A$QT1n;Zc;vvp;cuWAwl2eCyDpQB>Hc zk0R40WVX+zJ7KTSY33JlHXJHwyz+V{ea0={^dkoM<@GYiDHtB#AZOB)4bm*@_h(3@ zn2qMhvd&Guh6mj#A^m3^ zDb>Do>YvUwgj>`e?tFw-+U68Lswa=(XT=P)$lYtUNq5WE_HNe)C^1Fs@QZabBrSS~ zvH95!Gs5Y0!ucKg>jXnLG~5V6Q_uenq$yk$DES`!M#iO8eONlVgYXYF!e7C(9EBLV zf{K(?*X)v6Rg6<|5Cjo`b-K)8p0Q6~h)|!HA9pkY6F%$~94I?Nti8n7DZbNp z+lHDV+dy+P*aT#JW#*oBWP1I5PB-8Cy|IHw%}r*SUfcelPp<^Qv7U>s`62ge26PEC z{}dP62m`CXVT%!D!U)HKTrLs)Z`~h3$jPOc;M%Kmfo#}oi&O&MH#4)@hV%+vky$@e zizMdWdN#y(YPIh4w?Faz+}I`KUq-d;ty6h}LvqhclW)o)`FJIauR0`@$TWE{MvdD1iSyO`?@Bd*YWST^1F1~2!!Gr`acJnQBezZ=Iln8{*8T<#$JYOj1YffnK4Fb%#S zW@HpfyVEll%yTtg;zj*hhU|y0lRtIIya^)zxG(GD)fzFD1voV9;sJliBv$@f={Q>tT}-Qn zjdBzctKS!B-{PfjI)5=OPvZq{Ij0zXN|QnE6Je8f3BP_zo<~DySSCPdS{^S7V;BFe z8*5c7&!4-oq&|I;)mO372$G#~TVIP9(y(i2Y}~q??@VuA6O#{&BL=tibK!<4aVl`< zj=sUf)HOfR)yVKlK9*W06tNY5RB_Q-!qgho^J7HBb;Wg&x|B>Zz5yf>o`3rDhpf$+ zLd||j_kC)2&9a*xetsK9VCK{w+;f&yHv-6S5n^&K<=31pdExL5uj_Ip8)iSi?iFqO zFD2)T3%IuDt2-enXVKq1``zs_emLTbDCnACE;A?rZV7T#lMHkw7g9(vJXOdgcK4(| z{(cD=%=Op1;K%HZJ!t)fSEI)IOWsld(q?;)KT+5vA6dT7hZS~FmK4K)A`kx%)&2d3 zi=q|snYqrO<QQ&1x#usq9toB4Ob74dLiJ+_@+GWnHU43! z=^75cboqV>&CfqZx4fe1<04D<@q8{Xj-4KrT z9+dBXZ8Qh|GKj{At37z(3a(1n7={M@;jXx{F8%ZYdi(BbBRzdrV%K9X@m&eTG^(tp zHVRFn&#}h%ssogLz<0nLd?w31n-k;15O}}V<`aG*gdZXn(1`o%8Kis=*U%d0VZJ-1 ztEBORHC*Ph8Rlh;SFGux*HcIVpBr)cden4PlUF;-`P^DAn#s|xe)(Rw4EJ$qwaujA z`>D2z+o{TOIhE&T|SC zohb9o;>bGDgcEn9Sp2NI$u1_>Si`y}pE)OQHFjgrq%4ryRlJp;6w@TTk>zutXyEloFe>BMgzGJS^EZP>@+1fnXmASSOH-rx$i5E7{nmACg zMHq)u$Lo}@z7l`ckvPP!WX+sHnQv%Lj2Z_D{(swvi|%pRsnhbrx6Fw#@z(sW-@IqO zyRc~DE5-AylNCg$Em@$8XB| zMK?4fs$fR^ovaTxex6&C(Jia~$1m%a#VIr5GdX

66SuQod3!!+isKf;aOQ9 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 MN7X1%Zg22^0E)&LOaK4? diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 227463fb69..529612ab0b 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -195,4 +195,17 @@ async Task VerifyAsync(HttpRequestMessage message) } } } + + [Fact] + public void Downsampler_ShouldSample_Works() + { + var sut = new Downsampler(); + sut.NewThreadAdded(0); + Assert.True(sut.ShouldSample(0, 5)); + Assert.False(sut.ShouldSample(0, 3)); + Assert.False(sut.ShouldSample(0, 6)); + Assert.True(sut.ShouldSample(0, 15)); + Assert.False(sut.ShouldSample(0, 6)); + Assert.False(sut.ShouldSample(0, 16)); + } } 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 991c1e8fb6..f03a7eeefa 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.ProfileInfo_Serialization_Works.verified.txt +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.ProfileInfo_Serialization_Works.verified.txt @@ -39,16 +39,16 @@ name: Thread 2152 }, 5: { - name: Activity 18 + name: Thread 7860 }, 6: { - name: Activity 18/24/28/32 + name: Thread 7860 }, 7: { - name: Activity 23 + name: Thread 7860 }, 8: { - name: Activity 23/39 + name: Thread 12688 } }, stacks: [ @@ -86,101 +86,85 @@ 22 ], [ - 26 - ], - [ - 0, - 1, - 27 - ], - [ + 26, + 27, 28, 29, 30, - 26 - ], - [ 31, - 30, - 26 - ], - [ 32, 33, - 26 - ], - [ 34, 35, - 36, + 36 + ], + [ 37, 38, 39, 40, - 41 - ], - [ + 41, 42, 43, 44, + 32, + 33, + 34, + 35, + 36 + ], + [ 45, 46, 47, 48, 49, - 50, - 51, - 38, - 39, - 40, - 41 + 32, + 33, + 34, + 35, + 36 ], [ - 52, - 53, - 51, - 38, - 39, - 40, - 41 + 50 ], [ - 54, - 53, + 0, + 1, 51, - 38, - 39, - 40, - 41 - ], - [ + 52, + 53, + 54, 55, 56, 57, 58, 59, + 22 + ], + [ 60, 61, 62, 63, 64, 65, + 50 + ], + [ 66, 67, 68, 69, 70, + 50 + ], + [ 71, 72, 73, 74, - 75, - 53, - 51, - 38, - 39, - 40, - 41 + 75 ], [ 76, @@ -193,47 +177,55 @@ 83, 84, 85, - 86, - 41 + 71, + 72, + 73, + 74, + 75 ], [ + 86, 87, 88, 89, 90, 91, + 71, + 72, + 73, + 74, + 75 + ], + [ 92, 93, + 91, + 71, + 72, + 73, + 74, + 75 + ], + [ 94, - 95, - 96, - 85, - 86, - 41 + 72, + 73, + 74, + 75 ], [ + 95, + 96, 97, 98, 99, 100, 101, 102, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 85, - 86, - 41 + 103, + 75 ], [ - 103, 104, 105, 106, @@ -244,30 +236,21 @@ 111, 112, 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120, - 121, - 122, - 123, - 124, - 125, - 126, - 127, - 128, - 129, - 130, - 131, - 132 + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 75 ], [ - 133, - 134, - 135 + 18, + 19, + 20, + 114, + 22 ] ], frames: [ @@ -400,323 +383,350 @@ in_app: false }, { - function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), - module: Aura.UI.Gallery.NetCore, - in_app: true + function: System.Text.Unicode.Utf8Utility.TranscodeToUtf16(unsigned int8*,int32,wchar*,int32,unsigned int8*&,wchar*&), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.IO.Stream+<>c.b__40_0(class System.Object), + function: System.Text.UTF8Encoding.GetChars(unsigned int8*,int32,wchar*,int32), module: System.Private.CoreLib.il, in_app: false }, { - function: Avalonia.Win32.WinRT.Impl.__MicroComICompositorVTable.__MicroComModuleInit(), - module: avalonia.win32, - in_app: true + function: System.String.CreateStringFromEncoding(unsigned int8*,int32,class System.Text.Encoding), + module: System.Private.CoreLib.il, + in_app: false }, { - function: ..cctor(), - module: avalonia.win32, - in_app: true + function: System.Reflection.MdFieldInfo.get_Name(), + module: System.Private.CoreLib.il, + in_app: false }, { + function: System.Diagnostics.Tracing.EventSource.AddProviderEnumKind(class System.Diagnostics.Tracing.ManifestBuilder,class System.Reflection.FieldInfo,class System.String), + module: System.Private.CoreLib.il, in_app: false }, { - function: ..cctor(), - module: avalonia.win32, - in_app: true + function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.AppBuilderDesktopExtensions.UsePlatformDetect(!!0), - module: avalonia.desktop, - in_app: true + function: System.Diagnostics.Tracing.EventSource.EnsureDescriptorsInitialized(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Aura.UI.Gallery.NetCore.Program.BuildAvaloniaApp(), - module: Aura.UI.Gallery.NetCore, - in_app: true + function: System.Diagnostics.Tracing.EventSource.DoCommand(class System.Diagnostics.Tracing.EventCommandEventArgs), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.StyledPropertyBase`1[Avalonia.Controls.WindowState]..ctor(class System.String,class System.Type,class Avalonia.StyledPropertyMetadata`1,bool,class System.Func`2,class System.Action`2), - module: Avalonia.Base, - in_app: true + function: System.Diagnostics.Tracing.EventSource.SendCommand(class System.Diagnostics.Tracing.EventListener,value class System.Diagnostics.Tracing.EventProviderType,int32,int32,value class System.Diagnostics.Tracing.EventCommand,bool,value class System.Diagnostics.Tracing.EventLevel,value class System.Diagnostics.Tracing.EventKeywords,class System.Collections.Generic.IDictionary`2), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.StyledProperty`1[Avalonia.Controls.WindowState]..ctor(class System.String,class System.Type,class Avalonia.StyledPropertyMetadata`1,bool,class System.Func`2,class System.Action`2), - module: Avalonia.Base, - in_app: true + function: System.Diagnostics.Tracing.EventSource+OverrideEventProvider.OnControllerCommand(value class System.Diagnostics.Tracing.ControllerCommand,class System.Collections.Generic.IDictionary`2,int32,int32), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.AvaloniaProperty.Register(class System.String,!!1,bool,value class Avalonia.Data.BindingMode,class System.Func`2,class System.Func`3,class System.Action`2), - module: Avalonia.Base, - in_app: true + function: System.Diagnostics.Tracing.EventProvider.EtwEnableCallBack(value class System.Guid&,int32,unsigned int8,int64,int64,value class EVENT_FILTER_DESCRIPTOR*,void*), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.Window..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter(class System.RuntimeType,class System.RuntimeType), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Collections.Generic.ArraySortHelper`1[System.Int32].CreateArraySortHelper(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime..ctor(), - module: Avalonia.Controls, - in_app: true + function: System.Collections.Generic.ArraySortHelper`1[System.Int32]..cctor(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(!!0,class System.String[],value class Avalonia.Controls.ShutdownMode), - module: Avalonia.Controls, - in_app: true + function: System.Array.Sort(!!0[],int32,int32,class System.Collections.Generic.IComparer`1), + 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.Collections.Generic.List`1[System.Int32].Sort(int32,int32,class System.Collections.Generic.IComparer`1), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Animation.Animation.RegisterAnimator(class System.Func`2), - module: Avalonia.Animation, - in_app: true + function: System.Diagnostics.Tracing.ManifestBuilder.CreateManifestString(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Point..cctor(), - module: Avalonia.Visuals, - in_app: true + function: System.Diagnostics.Tracing.ManifestBuilder.CreateManifest(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Point..ctor(float64,float64), - module: Avalonia.Visuals, - in_app: true + function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.RelativePoint..ctor(float64,float64,value class Avalonia.RelativeUnit), - module: Avalonia.Visuals, - in_app: true + function: System.Runtime.CompilerServices.CastHelpers.StelemRef_Helper(class System.Object&,void*,class System.Object), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.RelativePoint..cctor(), - module: Avalonia.Visuals, - in_app: true + function: System.Reflection.RuntimeParameterInfo.GetParameters(class System.IRuntimeMethodInfo,class System.Reflection.MemberInfo,class System.Signature,class System.Reflection.ParameterInfo&,bool), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Visual..cctor(), - module: Avalonia.Visuals, - in_app: true + function: System.Reflection.RuntimeMethodInfo.FetchNonReturnParameters(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Visual.AffectsRender(class Avalonia.AvaloniaProperty[]), - module: Avalonia.Visuals, - in_app: true + function: System.Reflection.RuntimeMethodInfo.GetParameters(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.Border..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), - module: Avalonia.Controls, + function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), + module: Aura.UI.Gallery.NetCore, in_app: true }, { - function: Avalonia.Controls.Window..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.IO.Stream+<>c.b__40_0(class System.Object), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.TextBlock..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Threading.Tasks.Task`1[System.Int32].InnerInvoke(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Threading.Tasks.Task+<>c.<.cctor>b__272_0(class System.Object), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.TextBlock..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(class System.Threading.Thread,class System.Threading.ExecutionContext,class System.Threading.ContextCallback,class System.Object), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Concurrency.AsyncLock.Wait(!!0,class System.Action`1), - module: system.reactive, - in_app: true + function: System.Threading.Tasks.Task.ExecuteWithThreadLocal(class System.Threading.Tasks.Task&,class System.Threading.Thread), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Concurrency.ImmediateScheduler+AsyncLockScheduler.Schedule(!!0,class System.Func`3), - module: system.reactive, - in_app: true + function: System.Threading.Tasks.Task.ExecuteEntryUnsafe(class System.Threading.Thread), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1+_[System.__Canon].LoopRec(class System.Reactive.Concurrency.IScheduler), - module: system.reactive, - in_app: true + function: System.Threading.Tasks.Task.ExecuteFromThreadPool(class System.Threading.Thread), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1+_+<>c[System.__Canon].b__3_0(class System.Reactive.Concurrency.IScheduler,class _), - module: system.reactive, - in_app: true + function: System.Threading.ThreadPoolWorkQueue.Dispatch(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Concurrency.ImmediateScheduler.Schedule(!!0,class System.Func`3), - module: system.reactive, - in_app: true + function: System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1+_[System.__Canon].Run(class System.Collections.Generic.IEnumerable`1,class System.Reactive.Concurrency.IScheduler), - module: system.reactive, + 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, + in_app: false + }, + { + function: System.Collections.Concurrent.ConcurrentDictionary`2[System.__Canon,System.IntPtr].set_Item(!0,!1), + module: System.Collections.Concurrent.il, + in_app: false + }, + { + function: Avalonia.MicroCom.MicroComRuntime.RegisterVTable(class System.Type,int), + module: avalonia.microcom, in_app: true }, { - function: System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1[System.__Canon].Run(class _), - module: system.reactive, + function: Avalonia.Win32.WinRT.Impl.__MicroComICompositor2VTable.__MicroComModuleInit(), + module: avalonia.win32, in_app: true }, { - function: System.Reactive.Producer`2[System.__Canon,System.__Canon].SubscribeRaw(class System.IObserver`1,bool), - module: system.reactive, + function: ..cctor(), + module: avalonia.win32, in_app: true }, { - function: System.ObservableExtensions.SubscribeSafe(class System.IObservable`1,class System.IObserver`1), - module: system.reactive, + in_app: false + }, + { + function: System.MulticastDelegate.CombineImpl(class System.Delegate), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Delegate.Combine(class System.Delegate,class System.Delegate), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Avalonia.Controls.AppBuilderBase`1[System.__Canon].AfterPlatformServicesSetup(class System.Action`1), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Sink`2[System.__Canon,System.__Canon].Run(class System.IObservable`1), - module: system.reactive, + function: Avalonia.ReactiveUI.AppBuilderExtensions.UseReactiveUI(!!0), + module: avalonia.reactiveui, in_app: true }, { - function: System.Reactive.Linq.ObservableImpl.Merge`1+Observables[System.__Canon].Run(class _), - module: system.reactive, + function: Aura.UI.Gallery.NetCore.Program.BuildAvaloniaApp(), + module: Aura.UI.Gallery.NetCore, in_app: true }, { - function: System.Reactive.Producer`2+<>c[System.__Canon,System.__Canon].b__1_0(value class System.ValueTuple`2,!1>), - module: system.reactive, + function: Avalonia.Controls.Window..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Concurrency.Scheduler+<>c__75`1[System.ValueTuple`2[System.__Canon,System.__Canon]].b__75_0(class System.Reactive.Concurrency.IScheduler,value class System.ValueTuple`2,!0>), - module: system.reactive, + function: Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Concurrency.CurrentThreadScheduler.Schedule(!!0,value class System.TimeSpan,class System.Func`3), - module: system.reactive, + function: Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime..ctor(), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Concurrency.LocalScheduler.Schedule(!!0,class System.Func`3), - module: system.reactive, + function: Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(!!0,class System.String[],value class Avalonia.Controls.ShutdownMode), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Concurrency.Scheduler.ScheduleAction(class System.Reactive.Concurrency.IScheduler,!!0,class System.Action`1), - module: system.reactive, + function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), + module: Aura.UI.Gallery.NetCore, in_app: true }, { - function: System.Reactive.Producer`2[System.__Canon,System.__Canon].SubscribeRaw(class System.IObserver`1,bool), - module: system.reactive, + function: Avalonia.Animation.Animation..cctor(), + module: Avalonia.Animation, in_app: true }, { - function: System.Reactive.Producer`2[System.__Canon,System.__Canon].Subscribe(class System.IObserver`1), - module: system.reactive, + function: Avalonia.Animation.Animation.RegisterAnimator(class System.Func`2), + module: Avalonia.Animation, in_app: true }, { - function: System.ObservableExtensions.Subscribe(class System.IObservable`1,class System.Action`1), - module: system.reactive, + function: Avalonia.Point..cctor(), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.AvaloniaObjectExtensions.AddClassHandler(class System.IObservable`1,class System.Action`2), - module: Avalonia.Base, + function: Avalonia.Point..ctor(float64,float64), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Controls.TextBlock..cctor(), - module: Avalonia.Controls, + function: Avalonia.RelativePoint..ctor(float64,float64,value class Avalonia.RelativeUnit), + module: Avalonia.Visuals, in_app: true }, { - function: System.Xml.Linq.XDocument.Load(class System.IO.Stream,value class System.Xml.Linq.LoadOptions), - module: system.private.xml.linq.il, + function: Avalonia.RelativePoint..cctor(), + module: Avalonia.Visuals, in_app: true }, { - function: System.Xml.Linq.XDocument.Load(class System.IO.Stream), - module: system.private.xml.linq.il, + function: Avalonia.Visual..cctor(), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.Read(class System.IO.Stream), - module: Avalonia.Base, + function: Avalonia.Visual.AffectsRender(class Avalonia.AvaloniaProperty[]), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Shared.PlatformSupport.AssetLoader+AssemblyDescriptor..ctor(class System.Reflection.Assembly), - module: Avalonia.DesktopRuntime, + function: Avalonia.Controls.Border..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: Avalonia.Shared.PlatformSupport.AssetLoader..ctor(class System.Reflection.Assembly), - module: Avalonia.DesktopRuntime, + function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: Avalonia.Shared.PlatformSupport.StandardRuntimePlatformServices.Register(class System.Reflection.Assembly), - module: Avalonia.DesktopRuntime, + function: System.Collections.Generic.Dictionary`2[System.__Canon,Avalonia.Media.KnownColor]..ctor(class System.Collections.Generic.IEqualityComparer`1), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Avalonia.Media.KnownColors..cctor(), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.AppBuilder+<>c.<.ctor>b__0_0(class Avalonia.AppBuilder), - module: Avalonia.DesktopRuntime, + function: Avalonia.Media.KnownColors.ToBrush(value class Avalonia.Media.KnownColor), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Controls.AppBuilderBase`1+<>c__DisplayClass44_0[System.__Canon].<.ctor>b__0(), - module: Avalonia.Controls, + function: Avalonia.Media.Brushes.get_Black(), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Controls.AppBuilderBase`1[System.__Canon].Setup(), + function: Avalonia.Controls.TextBlock..cctor(), module: Avalonia.Controls, in_app: true }, { - function: Avalonia.Controls.AppBuilderBase`1[System.__Canon].SetupWithLifetime(class Avalonia.Controls.ApplicationLifetimes.IApplicationLifetime), + function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), module: Avalonia.Controls, in_app: true }, { - function: Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(!!0,class System.String[],value class Avalonia.Controls.ShutdownMode), - module: Avalonia.Controls, + function: System.Reactive.Linq.Observable.Merge(class System.IObservable`1[]), + module: system.reactive, in_app: true }, { - function: Avalonia.Win32.TrayIconImpl.ProcWnd(int,unsigned int32,int,int), - module: avalonia.win32, + function: Avalonia.Controls.TextBlock..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: Avalonia.Win32.Win32Platform.WndProc(int,unsigned int32,int,int), - module: avalonia.win32, + function: Avalonia.Controls.Window..cctor(), + module: Avalonia.Controls, in_app: true }, { - in_app: false - }, - { - function: Avalonia.Win32.Win32Platform.CreateMessageWindow(), + function: Avalonia.Win32.Win32Platform.SetDpiAwareness(), module: avalonia.win32, in_app: true }, @@ -751,8 +761,13 @@ in_app: true }, { - function: System.Drawing.SafeNativeMethods+Gdip.PlatformInitialize(), - module: system.drawing.common, + function: Avalonia.Controls.AppBuilderBase`1[System.__Canon].SetupWithLifetime(class Avalonia.Controls.ApplicationLifetimes.IApplicationLifetime), + 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 }, { @@ -781,167 +796,30 @@ in_app: true }, { - function: System.Runtime.CompilerServices.TaskAwaiter.UnsafeOnCompletedInternal(class System.Threading.Tasks.Task,class System.Runtime.CompilerServices.IAsyncStateMachineBox,bool), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].AwaitUnsafeOnCompleted(!!0&,class System.Runtime.CompilerServices.IAsyncStateMachineBox), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].AwaitUnsafeOnCompleted(!!0&,!!1&,class System.Threading.Tasks.Task`1&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].AwaitUnsafeOnCompleted(!!0&,!!1&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.PidIpcEndpoint+d__10.MoveNext(), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.PidIpcEndpoint.ConnectAsync(value class System.Threading.CancellationToken), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.IpcClient+d__4.MoveNext(), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[Microsoft.Diagnostics.NETCore.Client.IpcResponse].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.IpcClient.SendMessageGetContinuationAsync(class Microsoft.Diagnostics.NETCore.Client.IpcEndpoint,class Microsoft.Diagnostics.NETCore.Client.IpcMessage,value class System.Threading.CancellationToken), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.IpcClient+d__3.MoveNext(), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.IpcClient.SendMessageAsync(class Microsoft.Diagnostics.NETCore.Client.IpcEndpoint,class Microsoft.Diagnostics.NETCore.Client.IpcMessage,value class System.Threading.CancellationToken), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.EventPipeSession+d__11.MoveNext(), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.EventPipeSession.StopAsync(value class System.Threading.CancellationToken), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: Sentry.Profiling.SampleProfilerSession+d__7.MoveNext(), - module: Sentry.Profiling, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Sentry.Profiling.SampleProfilerSession.Finish(), - module: Sentry.Profiling, - in_app: false - }, - { - function: Sentry.Profiling.SamplingTransactionProfiler.Stop(value class System.DateTimeOffset), - module: Sentry.Profiling, - in_app: false - }, - { - function: Sentry.Profiling.SamplingTransactionProfiler.OnTransactionFinish(value class System.DateTimeOffset), - module: Sentry.Profiling, - in_app: false - }, - { - function: Sentry.TransactionTracer.Finish(), - module: Sentry, - in_app: false - }, - { - function: Aura.UI.Gallery.NetCore.Program+<>c__DisplayClass1_0.

b__1(class System.Threading.Tasks.Task), - module: Aura.UI.Gallery.NetCore, + function: Avalonia.Win32.TrayIconImpl.ProcWnd(int,unsigned int32,int,int), + module: avalonia.win32, in_app: true }, { - function: System.Threading.Tasks.ContinuationTaskFromTask.InnerInvoke(), - module: System.Private.CoreLib.il, - in_app: false + function: Avalonia.Win32.Win32Platform.WndProc(int,unsigned int32,int,int), + module: avalonia.win32, + in_app: true }, { - function: System.Threading.Tasks.Task+<>c.<.cctor>b__272_0(class System.Object), - module: System.Private.CoreLib.il, in_app: false }, { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[System.__Canon,Microsoft.Diagnostics.NETCore.Client.IpcEndpointHelper+d__1].MoveNext(), - module: System.Private.CoreLib.il, - in_app: false + function: Avalonia.Win32.Win32Platform.CreateMessageWindow(), + module: avalonia.win32, + in_app: true }, { - function: System.Runtime.CompilerServices.TaskAwaiter+<>c.b__12_0(class System.Action,class System.Threading.Tasks.Task), - module: System.Private.CoreLib.il, - in_app: false + function: Avalonia.Win32.Win32Platform..ctor(), + module: avalonia.win32, + in_app: true }, { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore+ContinuationWrapper.Invoke(), + function: System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart(), module: System.Private.CoreLib.il, in_app: false } @@ -972,15 +850,30 @@ thread_id: 4, stack_id: 1 }, + { + elapsed_since_start_ns: 1760800, + thread_id: 5, + stack_id: 3 + }, + { + elapsed_since_start_ns: 3741300, + thread_id: 6, + stack_id: 4 + }, + { + elapsed_since_start_ns: 5782600, + thread_id: 7, + stack_id: 5 + }, { elapsed_since_start_ns: 11253400, thread_id: 0, - stack_id: 3 + stack_id: 6 }, { elapsed_since_start_ns: 11260100, - thread_id: 5, - stack_id: 4 + thread_id: 1, + stack_id: 7 }, { elapsed_since_start_ns: 11261700, @@ -998,282 +891,257 @@ stack_id: 1 }, { - elapsed_since_start_ns: 19306300, + elapsed_since_start_ns: 21259900, thread_id: 0, - stack_id: 5 + stack_id: 8 }, { - elapsed_since_start_ns: 19314800, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 21267900, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 19316900, + elapsed_since_start_ns: 21270000, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 19320000, + elapsed_since_start_ns: 21272100, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 19324300, + elapsed_since_start_ns: 21274400, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 29299500, + elapsed_since_start_ns: 31243800, thread_id: 0, stack_id: 6 }, { - elapsed_since_start_ns: 29316000, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 31248600, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 29319500, + elapsed_since_start_ns: 31249800, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 29322300, + elapsed_since_start_ns: 31251200, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 29324000, + elapsed_since_start_ns: 31252200, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 39242400, + elapsed_since_start_ns: 41251400, thread_id: 0, - stack_id: 7 + stack_id: 9 }, { - elapsed_since_start_ns: 39246300, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 41261500, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 39247600, + elapsed_since_start_ns: 41263200, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 39248800, + elapsed_since_start_ns: 41265000, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 39249800, + elapsed_since_start_ns: 41266400, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 49342400, + elapsed_since_start_ns: 51268200, thread_id: 0, - stack_id: 8 + stack_id: 10 }, { - elapsed_since_start_ns: 49374000, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 51291300, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 49377700, + elapsed_since_start_ns: 51294700, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 49386100, + elapsed_since_start_ns: 51300300, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 49388800, + elapsed_since_start_ns: 51302000, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 59314300, + elapsed_since_start_ns: 61258600, thread_id: 0, - stack_id: 9 + stack_id: 11 }, { - elapsed_since_start_ns: 59327100, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 61269400, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 59331500, + elapsed_since_start_ns: 61271800, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 59335300, + elapsed_since_start_ns: 61274400, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 59336700, + elapsed_since_start_ns: 61276000, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 69285100, + elapsed_since_start_ns: 71256100, thread_id: 0, - stack_id: 10 + stack_id: 12 }, { - elapsed_since_start_ns: 69308800, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 71268300, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 69313800, + elapsed_since_start_ns: 71271900, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 69318900, + elapsed_since_start_ns: 71279300, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 69320700, + elapsed_since_start_ns: 71280500, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 79256000, + elapsed_since_start_ns: 81242100, thread_id: 0, - stack_id: 11 + stack_id: 13 }, { - elapsed_since_start_ns: 79267700, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 81247700, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 79269800, + elapsed_since_start_ns: 81248900, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 79272700, + elapsed_since_start_ns: 81250500, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 79274300, + elapsed_since_start_ns: 81251700, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 89281600, + elapsed_since_start_ns: 91316900, thread_id: 0, - stack_id: 12 + stack_id: 14 }, { - elapsed_since_start_ns: 89286500, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 91324700, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 89287400, + elapsed_since_start_ns: 91326400, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 89289600, + elapsed_since_start_ns: 91328400, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 89290800, + elapsed_since_start_ns: 91329900, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 99260200, + elapsed_since_start_ns: 101264900, thread_id: 0, - stack_id: 13 + stack_id: 15 }, { - elapsed_since_start_ns: 99267900, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 101272700, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 99269600, + elapsed_since_start_ns: 101274300, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 99271500, + elapsed_since_start_ns: 101276100, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 99272800, + elapsed_since_start_ns: 101277800, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 108784600, + elapsed_since_start_ns: 112828800, thread_id: 0, - stack_id: 14 + stack_id: 16 }, { - elapsed_since_start_ns: 108788100, + elapsed_since_start_ns: 112839000, thread_id: 1, stack_id: 1 }, { - elapsed_since_start_ns: 108790300, - thread_id: 2, - stack_id: 2 - }, - { - elapsed_since_start_ns: 108792800, - thread_id: 3, - stack_id: 1 - }, - { - elapsed_since_start_ns: 108797900, - thread_id: 6, - stack_id: 4 - }, - { - elapsed_since_start_ns: 118800200, - thread_id: 0, - stack_id: 15 - }, - { - elapsed_since_start_ns: 118810700, - thread_id: 7, - stack_id: 16 - }, - { - elapsed_since_start_ns: 118815700, + elapsed_since_start_ns: 112843800, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 118818500, + elapsed_since_start_ns: 112847500, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 118824700, - thread_id: 6, - stack_id: 4 + elapsed_since_start_ns: 112863500, + thread_id: 4, + stack_id: 7 }, { - elapsed_since_start_ns: 118832200, + elapsed_since_start_ns: 114809100, thread_id: 8, stack_id: 17 } 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 09a42d7f7d..4bfa8aa916 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.Profile_Serialization_Works.verified.txt +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.Profile_Serialization_Works.verified.txt @@ -16,16 +16,16 @@ name: Thread 2152 }, 5: { - name: Activity 18 + name: Thread 7860 }, 6: { - name: Activity 18/24/28/32 + name: Thread 7860 }, 7: { - name: Activity 23 + name: Thread 7860 }, 8: { - name: Activity 23/39 + name: Thread 12688 } }, stacks: [ @@ -63,101 +63,85 @@ 22 ], [ - 26 - ], - [ - 0, - 1, - 27 - ], - [ + 26, + 27, 28, 29, 30, - 26 - ], - [ 31, - 30, - 26 - ], - [ 32, 33, - 26 - ], - [ 34, 35, - 36, + 36 + ], + [ 37, 38, 39, 40, - 41 - ], - [ + 41, 42, 43, 44, + 32, + 33, + 34, + 35, + 36 + ], + [ 45, 46, 47, 48, 49, - 50, - 51, - 38, - 39, - 40, - 41 + 32, + 33, + 34, + 35, + 36 ], [ - 52, - 53, - 51, - 38, - 39, - 40, - 41 + 50 ], [ - 54, - 53, + 0, + 1, 51, - 38, - 39, - 40, - 41 - ], - [ + 52, + 53, + 54, 55, 56, 57, 58, 59, + 22 + ], + [ 60, 61, 62, 63, 64, 65, + 50 + ], + [ 66, 67, 68, 69, 70, + 50 + ], + [ 71, 72, 73, 74, - 75, - 53, - 51, - 38, - 39, - 40, - 41 + 75 ], [ 76, @@ -170,47 +154,55 @@ 83, 84, 85, - 86, - 41 + 71, + 72, + 73, + 74, + 75 ], [ + 86, 87, 88, 89, 90, 91, + 71, + 72, + 73, + 74, + 75 + ], + [ 92, 93, + 91, + 71, + 72, + 73, + 74, + 75 + ], + [ 94, - 95, - 96, - 85, - 86, - 41 + 72, + 73, + 74, + 75 ], [ + 95, + 96, 97, 98, 99, 100, 101, 102, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 85, - 86, - 41 + 103, + 75 ], [ - 103, 104, 105, 106, @@ -221,30 +213,21 @@ 111, 112, 113, - 114, - 115, - 116, - 117, - 118, - 119, - 120, - 121, - 122, - 123, - 124, - 125, - 126, - 127, - 128, - 129, - 130, - 131, - 132 + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 75 ], [ - 133, - 134, - 135 + 18, + 19, + 20, + 114, + 22 ] ], frames: [ @@ -377,323 +360,350 @@ in_app: false }, { - function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), - module: Aura.UI.Gallery.NetCore, - in_app: true + function: System.Text.Unicode.Utf8Utility.TranscodeToUtf16(unsigned int8*,int32,wchar*,int32,unsigned int8*&,wchar*&), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.IO.Stream+<>c.b__40_0(class System.Object), + function: System.Text.UTF8Encoding.GetChars(unsigned int8*,int32,wchar*,int32), module: System.Private.CoreLib.il, in_app: false }, { - function: Avalonia.Win32.WinRT.Impl.__MicroComICompositorVTable.__MicroComModuleInit(), - module: avalonia.win32, - in_app: true + function: System.String.CreateStringFromEncoding(unsigned int8*,int32,class System.Text.Encoding), + module: System.Private.CoreLib.il, + in_app: false }, { - function: ..cctor(), - module: avalonia.win32, - in_app: true + function: System.Reflection.MdFieldInfo.get_Name(), + module: System.Private.CoreLib.il, + in_app: false }, { + function: System.Diagnostics.Tracing.EventSource.AddProviderEnumKind(class System.Diagnostics.Tracing.ManifestBuilder,class System.Reflection.FieldInfo,class System.String), + module: System.Private.CoreLib.il, in_app: false }, { - function: ..cctor(), - module: avalonia.win32, - in_app: true + function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.AppBuilderDesktopExtensions.UsePlatformDetect(!!0), - module: avalonia.desktop, - in_app: true + function: System.Diagnostics.Tracing.EventSource.EnsureDescriptorsInitialized(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Aura.UI.Gallery.NetCore.Program.BuildAvaloniaApp(), - module: Aura.UI.Gallery.NetCore, - in_app: true + function: System.Diagnostics.Tracing.EventSource.DoCommand(class System.Diagnostics.Tracing.EventCommandEventArgs), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.StyledPropertyBase`1[Avalonia.Controls.WindowState]..ctor(class System.String,class System.Type,class Avalonia.StyledPropertyMetadata`1,bool,class System.Func`2,class System.Action`2), - module: Avalonia.Base, - in_app: true + function: System.Diagnostics.Tracing.EventSource.SendCommand(class System.Diagnostics.Tracing.EventListener,value class System.Diagnostics.Tracing.EventProviderType,int32,int32,value class System.Diagnostics.Tracing.EventCommand,bool,value class System.Diagnostics.Tracing.EventLevel,value class System.Diagnostics.Tracing.EventKeywords,class System.Collections.Generic.IDictionary`2), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.StyledProperty`1[Avalonia.Controls.WindowState]..ctor(class System.String,class System.Type,class Avalonia.StyledPropertyMetadata`1,bool,class System.Func`2,class System.Action`2), - module: Avalonia.Base, - in_app: true + function: System.Diagnostics.Tracing.EventSource+OverrideEventProvider.OnControllerCommand(value class System.Diagnostics.Tracing.ControllerCommand,class System.Collections.Generic.IDictionary`2,int32,int32), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.AvaloniaProperty.Register(class System.String,!!1,bool,value class Avalonia.Data.BindingMode,class System.Func`2,class System.Func`3,class System.Action`2), - module: Avalonia.Base, - in_app: true + function: System.Diagnostics.Tracing.EventProvider.EtwEnableCallBack(value class System.Guid&,int32,unsigned int8,int64,int64,value class EVENT_FILTER_DESCRIPTOR*,void*), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.Window..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter(class System.RuntimeType,class System.RuntimeType), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Collections.Generic.ArraySortHelper`1[System.Int32].CreateArraySortHelper(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime..ctor(), - module: Avalonia.Controls, - in_app: true + function: System.Collections.Generic.ArraySortHelper`1[System.Int32]..cctor(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(!!0,class System.String[],value class Avalonia.Controls.ShutdownMode), - module: Avalonia.Controls, - in_app: true + function: System.Array.Sort(!!0[],int32,int32,class System.Collections.Generic.IComparer`1), + 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.Collections.Generic.List`1[System.Int32].Sort(int32,int32,class System.Collections.Generic.IComparer`1), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Animation.Animation.RegisterAnimator(class System.Func`2), - module: Avalonia.Animation, - in_app: true + function: System.Diagnostics.Tracing.ManifestBuilder.CreateManifestString(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Point..cctor(), - module: Avalonia.Visuals, - in_app: true + function: System.Diagnostics.Tracing.ManifestBuilder.CreateManifest(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Point..ctor(float64,float64), - module: Avalonia.Visuals, - in_app: true + function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.RelativePoint..ctor(float64,float64,value class Avalonia.RelativeUnit), - module: Avalonia.Visuals, - in_app: true + function: System.Runtime.CompilerServices.CastHelpers.StelemRef_Helper(class System.Object&,void*,class System.Object), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.RelativePoint..cctor(), - module: Avalonia.Visuals, - in_app: true + function: System.Reflection.RuntimeParameterInfo.GetParameters(class System.IRuntimeMethodInfo,class System.Reflection.MemberInfo,class System.Signature,class System.Reflection.ParameterInfo&,bool), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Visual..cctor(), - module: Avalonia.Visuals, - in_app: true + function: System.Reflection.RuntimeMethodInfo.FetchNonReturnParameters(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Visual.AffectsRender(class Avalonia.AvaloniaProperty[]), - module: Avalonia.Visuals, - in_app: true + function: System.Reflection.RuntimeMethodInfo.GetParameters(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.Border..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), - module: Avalonia.Controls, + function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), + module: Aura.UI.Gallery.NetCore, in_app: true }, { - function: Avalonia.Controls.Window..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.IO.Stream+<>c.b__40_0(class System.Object), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.TextBlock..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Threading.Tasks.Task`1[System.Int32].InnerInvoke(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Threading.Tasks.Task+<>c.<.cctor>b__272_0(class System.Object), + module: System.Private.CoreLib.il, + in_app: false }, { - function: Avalonia.Controls.TextBlock..cctor(), - module: Avalonia.Controls, - in_app: true + function: System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(class System.Threading.Thread,class System.Threading.ExecutionContext,class System.Threading.ContextCallback,class System.Object), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Concurrency.AsyncLock.Wait(!!0,class System.Action`1), - module: system.reactive, - in_app: true + function: System.Threading.Tasks.Task.ExecuteWithThreadLocal(class System.Threading.Tasks.Task&,class System.Threading.Thread), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Concurrency.ImmediateScheduler+AsyncLockScheduler.Schedule(!!0,class System.Func`3), - module: system.reactive, - in_app: true + function: System.Threading.Tasks.Task.ExecuteEntryUnsafe(class System.Threading.Thread), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1+_[System.__Canon].LoopRec(class System.Reactive.Concurrency.IScheduler), - module: system.reactive, - in_app: true + function: System.Threading.Tasks.Task.ExecuteFromThreadPool(class System.Threading.Thread), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1+_+<>c[System.__Canon].b__3_0(class System.Reactive.Concurrency.IScheduler,class _), - module: system.reactive, - in_app: true + function: System.Threading.ThreadPoolWorkQueue.Dispatch(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Concurrency.ImmediateScheduler.Schedule(!!0,class System.Func`3), - module: system.reactive, - in_app: true + function: System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart(), + module: System.Private.CoreLib.il, + in_app: false }, { - function: System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1+_[System.__Canon].Run(class System.Collections.Generic.IEnumerable`1,class System.Reactive.Concurrency.IScheduler), - module: system.reactive, + 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, + in_app: false + }, + { + function: System.Collections.Concurrent.ConcurrentDictionary`2[System.__Canon,System.IntPtr].set_Item(!0,!1), + module: System.Collections.Concurrent.il, + in_app: false + }, + { + function: Avalonia.MicroCom.MicroComRuntime.RegisterVTable(class System.Type,int), + module: avalonia.microcom, in_app: true }, { - function: System.Reactive.Linq.ObservableImpl.ToObservableRecursive`1[System.__Canon].Run(class _), - module: system.reactive, + function: Avalonia.Win32.WinRT.Impl.__MicroComICompositor2VTable.__MicroComModuleInit(), + module: avalonia.win32, in_app: true }, { - function: System.Reactive.Producer`2[System.__Canon,System.__Canon].SubscribeRaw(class System.IObserver`1,bool), - module: system.reactive, + function: ..cctor(), + module: avalonia.win32, in_app: true }, { - function: System.ObservableExtensions.SubscribeSafe(class System.IObservable`1,class System.IObserver`1), - module: system.reactive, + in_app: false + }, + { + function: System.MulticastDelegate.CombineImpl(class System.Delegate), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Delegate.Combine(class System.Delegate,class System.Delegate), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Avalonia.Controls.AppBuilderBase`1[System.__Canon].AfterPlatformServicesSetup(class System.Action`1), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Sink`2[System.__Canon,System.__Canon].Run(class System.IObservable`1), - module: system.reactive, + function: Avalonia.ReactiveUI.AppBuilderExtensions.UseReactiveUI(!!0), + module: avalonia.reactiveui, in_app: true }, { - function: System.Reactive.Linq.ObservableImpl.Merge`1+Observables[System.__Canon].Run(class _), - module: system.reactive, + function: Aura.UI.Gallery.NetCore.Program.BuildAvaloniaApp(), + module: Aura.UI.Gallery.NetCore, in_app: true }, { - function: System.Reactive.Producer`2+<>c[System.__Canon,System.__Canon].b__1_0(value class System.ValueTuple`2,!1>), - module: system.reactive, + function: Avalonia.Controls.Window..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Concurrency.Scheduler+<>c__75`1[System.ValueTuple`2[System.__Canon,System.__Canon]].b__75_0(class System.Reactive.Concurrency.IScheduler,value class System.ValueTuple`2,!0>), - module: system.reactive, + function: Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Concurrency.CurrentThreadScheduler.Schedule(!!0,value class System.TimeSpan,class System.Func`3), - module: system.reactive, + function: Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime..ctor(), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Concurrency.LocalScheduler.Schedule(!!0,class System.Func`3), - module: system.reactive, + function: Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(!!0,class System.String[],value class Avalonia.Controls.ShutdownMode), + module: Avalonia.Controls, in_app: true }, { - function: System.Reactive.Concurrency.Scheduler.ScheduleAction(class System.Reactive.Concurrency.IScheduler,!!0,class System.Action`1), - module: system.reactive, + function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), + module: Aura.UI.Gallery.NetCore, in_app: true }, { - function: System.Reactive.Producer`2[System.__Canon,System.__Canon].SubscribeRaw(class System.IObserver`1,bool), - module: system.reactive, + function: Avalonia.Animation.Animation..cctor(), + module: Avalonia.Animation, in_app: true }, { - function: System.Reactive.Producer`2[System.__Canon,System.__Canon].Subscribe(class System.IObserver`1), - module: system.reactive, + function: Avalonia.Animation.Animation.RegisterAnimator(class System.Func`2), + module: Avalonia.Animation, in_app: true }, { - function: System.ObservableExtensions.Subscribe(class System.IObservable`1,class System.Action`1), - module: system.reactive, + function: Avalonia.Point..cctor(), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.AvaloniaObjectExtensions.AddClassHandler(class System.IObservable`1,class System.Action`2), - module: Avalonia.Base, + function: Avalonia.Point..ctor(float64,float64), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Controls.TextBlock..cctor(), - module: Avalonia.Controls, + function: Avalonia.RelativePoint..ctor(float64,float64,value class Avalonia.RelativeUnit), + module: Avalonia.Visuals, in_app: true }, { - function: System.Xml.Linq.XDocument.Load(class System.IO.Stream,value class System.Xml.Linq.LoadOptions), - module: system.private.xml.linq.il, + function: Avalonia.RelativePoint..cctor(), + module: Avalonia.Visuals, in_app: true }, { - function: System.Xml.Linq.XDocument.Load(class System.IO.Stream), - module: system.private.xml.linq.il, + function: Avalonia.Visual..cctor(), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Utilities.AvaloniaResourcesIndexReaderWriter.Read(class System.IO.Stream), - module: Avalonia.Base, + function: Avalonia.Visual.AffectsRender(class Avalonia.AvaloniaProperty[]), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Shared.PlatformSupport.AssetLoader+AssemblyDescriptor..ctor(class System.Reflection.Assembly), - module: Avalonia.DesktopRuntime, + function: Avalonia.Controls.Border..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: Avalonia.Shared.PlatformSupport.AssetLoader..ctor(class System.Reflection.Assembly), - module: Avalonia.DesktopRuntime, + function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: Avalonia.Shared.PlatformSupport.StandardRuntimePlatformServices.Register(class System.Reflection.Assembly), - module: Avalonia.DesktopRuntime, + function: System.Collections.Generic.Dictionary`2[System.__Canon,Avalonia.Media.KnownColor]..ctor(class System.Collections.Generic.IEqualityComparer`1), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Avalonia.Media.KnownColors..cctor(), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.AppBuilder+<>c.<.ctor>b__0_0(class Avalonia.AppBuilder), - module: Avalonia.DesktopRuntime, + function: Avalonia.Media.KnownColors.ToBrush(value class Avalonia.Media.KnownColor), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Controls.AppBuilderBase`1+<>c__DisplayClass44_0[System.__Canon].<.ctor>b__0(), - module: Avalonia.Controls, + function: Avalonia.Media.Brushes.get_Black(), + module: Avalonia.Visuals, in_app: true }, { - function: Avalonia.Controls.AppBuilderBase`1[System.__Canon].Setup(), + function: Avalonia.Controls.TextBlock..cctor(), module: Avalonia.Controls, in_app: true }, { - function: Avalonia.Controls.AppBuilderBase`1[System.__Canon].SetupWithLifetime(class Avalonia.Controls.ApplicationLifetimes.IApplicationLifetime), + function: Avalonia.Controls.Primitives.TemplatedControl..cctor(), module: Avalonia.Controls, in_app: true }, { - function: Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(!!0,class System.String[],value class Avalonia.Controls.ShutdownMode), - module: Avalonia.Controls, + function: System.Reactive.Linq.Observable.Merge(class System.IObservable`1[]), + module: system.reactive, in_app: true }, { - function: Avalonia.Win32.TrayIconImpl.ProcWnd(int,unsigned int32,int,int), - module: avalonia.win32, + function: Avalonia.Controls.TextBlock..cctor(), + module: Avalonia.Controls, in_app: true }, { - function: Avalonia.Win32.Win32Platform.WndProc(int,unsigned int32,int,int), - module: avalonia.win32, + function: Avalonia.Controls.Window..cctor(), + module: Avalonia.Controls, in_app: true }, { - in_app: false - }, - { - function: Avalonia.Win32.Win32Platform.CreateMessageWindow(), + function: Avalonia.Win32.Win32Platform.SetDpiAwareness(), module: avalonia.win32, in_app: true }, @@ -728,8 +738,13 @@ in_app: true }, { - function: System.Drawing.SafeNativeMethods+Gdip.PlatformInitialize(), - module: system.drawing.common, + function: Avalonia.Controls.AppBuilderBase`1[System.__Canon].SetupWithLifetime(class Avalonia.Controls.ApplicationLifetimes.IApplicationLifetime), + 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 }, { @@ -758,167 +773,30 @@ in_app: true }, { - function: System.Runtime.CompilerServices.TaskAwaiter.UnsafeOnCompletedInternal(class System.Threading.Tasks.Task,class System.Runtime.CompilerServices.IAsyncStateMachineBox,bool), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].AwaitUnsafeOnCompleted(!!0&,class System.Runtime.CompilerServices.IAsyncStateMachineBox), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].AwaitUnsafeOnCompleted(!!0&,!!1&,class System.Threading.Tasks.Task`1&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].AwaitUnsafeOnCompleted(!!0&,!!1&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.PidIpcEndpoint+d__10.MoveNext(), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.PidIpcEndpoint.ConnectAsync(value class System.Threading.CancellationToken), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.IpcClient+d__4.MoveNext(), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[Microsoft.Diagnostics.NETCore.Client.IpcResponse].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.IpcClient.SendMessageGetContinuationAsync(class Microsoft.Diagnostics.NETCore.Client.IpcEndpoint,class Microsoft.Diagnostics.NETCore.Client.IpcMessage,value class System.Threading.CancellationToken), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.IpcClient+d__3.MoveNext(), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.IpcClient.SendMessageAsync(class Microsoft.Diagnostics.NETCore.Client.IpcEndpoint,class Microsoft.Diagnostics.NETCore.Client.IpcMessage,value class System.Threading.CancellationToken), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.EventPipeSession+d__11.MoveNext(), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Microsoft.Diagnostics.NETCore.Client.EventPipeSession.StopAsync(value class System.Threading.CancellationToken), - module: Microsoft.Diagnostics.NETCore.Client, - in_app: false - }, - { - function: Sentry.Profiling.SampleProfilerSession+d__7.MoveNext(), - module: Sentry.Profiling, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Sentry.Profiling.SampleProfilerSession.Finish(), - module: Sentry.Profiling, - in_app: false - }, - { - function: Sentry.Profiling.SamplingTransactionProfiler.Stop(value class System.DateTimeOffset), - module: Sentry.Profiling, - in_app: false - }, - { - function: Sentry.Profiling.SamplingTransactionProfiler.OnTransactionFinish(value class System.DateTimeOffset), - module: Sentry.Profiling, - in_app: false - }, - { - function: Sentry.TransactionTracer.Finish(), - module: Sentry, - in_app: false - }, - { - function: Aura.UI.Gallery.NetCore.Program+<>c__DisplayClass1_0.
b__1(class System.Threading.Tasks.Task), - module: Aura.UI.Gallery.NetCore, + function: Avalonia.Win32.TrayIconImpl.ProcWnd(int,unsigned int32,int,int), + module: avalonia.win32, in_app: true }, { - function: System.Threading.Tasks.ContinuationTaskFromTask.InnerInvoke(), - module: System.Private.CoreLib.il, - in_app: false + function: Avalonia.Win32.Win32Platform.WndProc(int,unsigned int32,int,int), + module: avalonia.win32, + in_app: true }, { - function: System.Threading.Tasks.Task+<>c.<.cctor>b__272_0(class System.Object), - module: System.Private.CoreLib.il, in_app: false }, { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[System.__Canon,Microsoft.Diagnostics.NETCore.Client.IpcEndpointHelper+d__1].MoveNext(), - module: System.Private.CoreLib.il, - in_app: false + function: Avalonia.Win32.Win32Platform.CreateMessageWindow(), + module: avalonia.win32, + in_app: true }, { - function: System.Runtime.CompilerServices.TaskAwaiter+<>c.b__12_0(class System.Action,class System.Threading.Tasks.Task), - module: System.Private.CoreLib.il, - in_app: false + function: Avalonia.Win32.Win32Platform..ctor(), + module: avalonia.win32, + in_app: true }, { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore+ContinuationWrapper.Invoke(), + function: System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart(), module: System.Private.CoreLib.il, in_app: false } @@ -949,15 +827,30 @@ thread_id: 4, stack_id: 1 }, + { + elapsed_since_start_ns: 1760800, + thread_id: 5, + stack_id: 3 + }, + { + elapsed_since_start_ns: 3741300, + thread_id: 6, + stack_id: 4 + }, + { + elapsed_since_start_ns: 5782600, + thread_id: 7, + stack_id: 5 + }, { elapsed_since_start_ns: 11253400, thread_id: 0, - stack_id: 3 + stack_id: 6 }, { elapsed_since_start_ns: 11260100, - thread_id: 5, - stack_id: 4 + thread_id: 1, + stack_id: 7 }, { elapsed_since_start_ns: 11261700, @@ -975,282 +868,257 @@ stack_id: 1 }, { - elapsed_since_start_ns: 19306300, + elapsed_since_start_ns: 21259900, thread_id: 0, - stack_id: 5 + stack_id: 8 }, { - elapsed_since_start_ns: 19314800, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 21267900, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 19316900, + elapsed_since_start_ns: 21270000, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 19320000, + elapsed_since_start_ns: 21272100, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 19324300, + elapsed_since_start_ns: 21274400, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 29299500, + elapsed_since_start_ns: 31243800, thread_id: 0, stack_id: 6 }, { - elapsed_since_start_ns: 29316000, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 31248600, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 29319500, + elapsed_since_start_ns: 31249800, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 29322300, + elapsed_since_start_ns: 31251200, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 29324000, + elapsed_since_start_ns: 31252200, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 39242400, + elapsed_since_start_ns: 41251400, thread_id: 0, - stack_id: 7 + stack_id: 9 }, { - elapsed_since_start_ns: 39246300, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 41261500, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 39247600, + elapsed_since_start_ns: 41263200, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 39248800, + elapsed_since_start_ns: 41265000, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 39249800, + elapsed_since_start_ns: 41266400, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 49342400, + elapsed_since_start_ns: 51268200, thread_id: 0, - stack_id: 8 + stack_id: 10 }, { - elapsed_since_start_ns: 49374000, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 51291300, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 49377700, + elapsed_since_start_ns: 51294700, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 49386100, + elapsed_since_start_ns: 51300300, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 49388800, + elapsed_since_start_ns: 51302000, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 59314300, + elapsed_since_start_ns: 61258600, thread_id: 0, - stack_id: 9 + stack_id: 11 }, { - elapsed_since_start_ns: 59327100, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 61269400, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 59331500, + elapsed_since_start_ns: 61271800, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 59335300, + elapsed_since_start_ns: 61274400, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 59336700, + elapsed_since_start_ns: 61276000, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 69285100, + elapsed_since_start_ns: 71256100, thread_id: 0, - stack_id: 10 + stack_id: 12 }, { - elapsed_since_start_ns: 69308800, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 71268300, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 69313800, + elapsed_since_start_ns: 71271900, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 69318900, + elapsed_since_start_ns: 71279300, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 69320700, + elapsed_since_start_ns: 71280500, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 79256000, + elapsed_since_start_ns: 81242100, thread_id: 0, - stack_id: 11 + stack_id: 13 }, { - elapsed_since_start_ns: 79267700, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 81247700, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 79269800, + elapsed_since_start_ns: 81248900, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 79272700, + elapsed_since_start_ns: 81250500, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 79274300, + elapsed_since_start_ns: 81251700, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 89281600, + elapsed_since_start_ns: 91316900, thread_id: 0, - stack_id: 12 + stack_id: 14 }, { - elapsed_since_start_ns: 89286500, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 91324700, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 89287400, + elapsed_since_start_ns: 91326400, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 89289600, + elapsed_since_start_ns: 91328400, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 89290800, + elapsed_since_start_ns: 91329900, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 99260200, + elapsed_since_start_ns: 101264900, thread_id: 0, - stack_id: 13 + stack_id: 15 }, { - elapsed_since_start_ns: 99267900, - thread_id: 5, - stack_id: 4 + elapsed_since_start_ns: 101272700, + thread_id: 1, + stack_id: 7 }, { - elapsed_since_start_ns: 99269600, + elapsed_since_start_ns: 101274300, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 99271500, + elapsed_since_start_ns: 101276100, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 99272800, + elapsed_since_start_ns: 101277800, thread_id: 4, stack_id: 1 }, { - elapsed_since_start_ns: 108784600, + elapsed_since_start_ns: 112828800, thread_id: 0, - stack_id: 14 + stack_id: 16 }, { - elapsed_since_start_ns: 108788100, + elapsed_since_start_ns: 112839000, thread_id: 1, stack_id: 1 }, { - elapsed_since_start_ns: 108790300, - thread_id: 2, - stack_id: 2 - }, - { - elapsed_since_start_ns: 108792800, - thread_id: 3, - stack_id: 1 - }, - { - elapsed_since_start_ns: 108797900, - thread_id: 6, - stack_id: 4 - }, - { - elapsed_since_start_ns: 118800200, - thread_id: 0, - stack_id: 15 - }, - { - elapsed_since_start_ns: 118810700, - thread_id: 7, - stack_id: 16 - }, - { - elapsed_since_start_ns: 118815700, + elapsed_since_start_ns: 112843800, thread_id: 2, stack_id: 2 }, { - elapsed_since_start_ns: 118818500, + elapsed_since_start_ns: 112847500, thread_id: 3, stack_id: 1 }, { - elapsed_since_start_ns: 118824700, - thread_id: 6, - stack_id: 4 + elapsed_since_start_ns: 112863500, + thread_id: 4, + stack_id: 7 }, { - elapsed_since_start_ns: 118832200, + elapsed_since_start_ns: 114809100, thread_id: 8, stack_id: 17 } diff --git a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs index a9659b32ec..a8798b3354 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs @@ -1,5 +1,6 @@ using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Etlx; +using Microsoft.Diagnostics.Tracing.EventPipe; namespace Sentry.Profiling.Tests; @@ -40,7 +41,6 @@ private SampleProfile GetProfile() { var etlFilePath = Path.ChangeExtension(etlxFilePath, "nettrace"); var source = new EventPipeEventSource(etlFilePath); - new Downsampler().AttachTo(source); typeof(TraceLog) .GetMethod( "CreateFromEventPipeEventSources", @@ -49,11 +49,15 @@ private SampleProfile GetProfile() .Invoke(null, new object[] { source, etlxFilePath, new TraceLogOptions() { ContinueOnError = true } }); } - using var eventLog = new TraceLog(etlxFilePath); - // FIXME - // var processor = new TraceLogProcessor(new(), eventLog); - // return processor.Process(CancellationToken.None); - return new(); + using var traceLog = new TraceLog(etlxFilePath); + var builder = new SampleProfileBuilder(new() { DiagnosticLogger = _testOutputLogger }, traceLog); + var eventSource = traceLog.Events.GetSource(); + new SampleProfilerTraceEventParser(eventSource).ThreadSample += delegate (ClrThreadSampleTraceData data) + { + builder.AddSample(data, data.TimeStampRelativeMSec); + }; + eventSource.Process(); + return builder.Profile; } [Fact] From 886a2e981e8e015eb6d197efed44d0e9cc705431 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 10 May 2023 21:22:15 +0200 Subject: [PATCH 08/21] fix: SparseArray --- src/Sentry/Internal/SparseArray.cs | 4 ++- .../Internals/SparseArrayTests.cs | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/Sentry.Tests/Internals/SparseArrayTests.cs diff --git a/src/Sentry/Internal/SparseArray.cs b/src/Sentry/Internal/SparseArray.cs index e4becdfae5..d8ee2ca627 100644 --- a/src/Sentry/Internal/SparseArray.cs +++ b/src/Sentry/Internal/SparseArray.cs @@ -25,11 +25,13 @@ public T this[int index] { get { + Debug.Assert(index < _items.Count, "Index out of range."); return _items[index]; } set { // Increase the capacity of the sparse array so that the key can fit. + _items.Reserve(index + 1); while (_items.Count <= index) { _items.Add(_uninitializedValue); @@ -40,6 +42,6 @@ public T this[int index] public bool ContainsKey(int key) { - return key > 0 && key < _items.Count && !_uninitializedValue.Equals(_items[key]); + return key >= 0 && key < _items.Count && !_uninitializedValue.Equals(_items[key]); } } diff --git a/test/Sentry.Tests/Internals/SparseArrayTests.cs b/test/Sentry.Tests/Internals/SparseArrayTests.cs new file mode 100644 index 0000000000..205fcf9b57 --- /dev/null +++ b/test/Sentry.Tests/Internals/SparseArrayTests.cs @@ -0,0 +1,36 @@ +using Sentry.Tests.Helpers.Reflection; + +namespace Sentry.Tests.Internals; + +public class SparseArrayTests +{ + private void Test(SparseScalarArray sut, int setDefault) + { + sut.ContainsKey(0).Should().BeFalse(); + sut.ContainsKey(1).Should().BeFalse(); + sut[0] = setDefault; + sut.ContainsKey(0).Should().BeFalse(); + sut.ContainsKey(1).Should().BeFalse(); + sut[0] = setDefault + 1; + sut.ContainsKey(0).Should().BeTrue(); + sut.ContainsKey(1).Should().BeFalse(); + } + + [Theory] + [InlineData(0)] + [InlineData(42)] + public void ContainsKey_WhenInitializedEmpty_Works(int defaultValue) + { + var sut = new SparseScalarArray(defaultValue); + Test(sut, defaultValue); + } + + [Theory] + [InlineData(0)] + [InlineData(42)] + public void ContainsKey_WhenGivenCapacity_Works(int defaultValue) + { + var sut = new SparseScalarArray(defaultValue, 10); + Test(sut, defaultValue); + } +} From 2b70a798a7530a3617012bd3874a5efa6e50f1da Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 10 May 2023 21:24:51 +0200 Subject: [PATCH 09/21] update benchmark & profile builder --- .../Sentry.Benchmarks/ProfilingBenchmarks.cs | 44 +-- src/Sentry.Profiling/SampleProfileBuilder.cs | 38 ++- src/Sentry/Protocol/SampleProfile.cs | 5 +- ...ofileInfo_Serialization_Works.verified.txt | 300 +++++++----------- ...s.Profile_Serialization_Works.verified.txt | 300 +++++++----------- .../TraceLogProcessorTests.verify.cs | 41 ++- 6 files changed, 291 insertions(+), 437 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs index 22d5f53bde..86421a4c0e 100644 --- a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs @@ -11,10 +11,28 @@ namespace Sentry.Benchmarks; public class ProfilingBenchmarks { private IHub _hub = Substitute.For(); - private ITransactionProfilerFactory _factory = SamplingTransactionProfilerFactory.Create(new()); + private SamplingTransactionProfilerFactory _factory; + private ITransactionProfiler _profiler; + + [GlobalSetup(Targets = new string[] { nameof(Transaction), nameof(DoHardWorkWhileProfiling) })] + public void StartProfiler() + { + _factory = SamplingTransactionProfilerFactory.Create(new()); + _profiler = _factory.Start(new TransactionTracer(_hub, "", ""), CancellationToken.None); + } + + [GlobalCleanup(Targets = new string[] { nameof(Transaction), nameof(DoHardWorkWhileProfiling) })] + public void StopProfiler() + { + _profiler?.Finish(); + _profiler?.CollectAsync(new Transaction("", "")).Wait(); + _profiler = null; + _factory.Dispose(); + _factory = null; + } #region full transaction profiling - public IEnumerable ProfilerArguments() + public IEnumerable TransactionBenchmarkArguments() { foreach (var runtimeMs in new[] { 25, 100, 1000, 10000 }) { @@ -27,7 +45,7 @@ public IEnumerable ProfilerArguments() // Run a profiled transaction. Profiler starts and stops for each transaction separately. [Benchmark] - [ArgumentsSource(nameof(ProfilerArguments))] + [ArgumentsSource(nameof(TransactionBenchmarkArguments))] public long Transaction(int runtimeMs, bool processing) { var tt = new TransactionTracer(_hub, "test", ""); @@ -154,30 +172,14 @@ public void SampleProfilerSessionStartStop() [ArgumentsSource(nameof(OverheadRunArguments))] public long DoHardWork(int n) { - return ProfilingBenchmarks.FindPrimeNumber(n); + return FindPrimeNumber(n); } [BenchmarkCategory("overhead"), Benchmark] [ArgumentsSource(nameof(OverheadRunArguments))] public long DoHardWorkWhileProfiling(int n) { - return ProfilingBenchmarks.FindPrimeNumber(n); - } - - private ITransactionProfiler _profiler; - - [GlobalSetup(Target = nameof(DoHardWorkWhileProfiling))] - public void StartProfiler() - { - _profiler = _factory.Start(new TransactionTracer(_hub, "", ""), CancellationToken.None); - } - - [GlobalCleanup(Target = nameof(DoHardWorkWhileProfiling))] - public void StopProfiler() - { - _profiler?.Finish(); - _profiler?.CollectAsync(new Transaction("", "")).Wait(); - _profiler = null; + return FindPrimeNumber(n); } #endregion } diff --git a/src/Sentry.Profiling/SampleProfileBuilder.cs b/src/Sentry.Profiling/SampleProfileBuilder.cs index 354ec68495..9e9b86f71a 100644 --- a/src/Sentry.Profiling/SampleProfileBuilder.cs +++ b/src/Sentry.Profiling/SampleProfileBuilder.cs @@ -6,9 +6,6 @@ namespace Sentry.Profiling; -// A list of frame indexes. -using SentryProfileStackTrace = HashableGrowableArray; - /// /// Build a SampleProfile from TraceEvent data. /// @@ -24,8 +21,8 @@ internal class SampleProfileBuilder // A sparse array that maps from StackSourceFrameIndex to an index in the output Profile.frames. private readonly SparseScalarArray _frameIndexes = new(-1, 1000); - // A dictionary from a StackTrace sealed array to an index in the output Profile.stacks. - private readonly Dictionary _stackIndexes = new(100); + // A dictionary from a CallStackIndex to an index in the output Profile.stacks. + private readonly SparseScalarArray _stackIndexes = new(100); // A sparse array mapping from a ThreadIndex to an index in Profile.Threads. private readonly SparseScalarArray _threadIndexes = new(-1, 10); @@ -86,7 +83,20 @@ internal void AddSample(TraceEvent data, double timestampMs) /// The index into the Profile's stacks list private int AddStackTrace(CallStackIndex callstackIndex) { - SentryProfileStackTrace stackTrace = new(10); + var key = (int)callstackIndex; + + if (!_stackIndexes.ContainsKey(key)) + { + Profile.Stacks.Add(CreateStackTrace(callstackIndex)); + _stackIndexes[key] = Profile.Stacks.Count - 1; + } + + return _stackIndexes[key]; + } + + private Internal.GrowableArray CreateStackTrace(CallStackIndex callstackIndex) + { + var stackTrace = new Internal.GrowableArray(10); while (callstackIndex != CallStackIndex.Invalid) { var codeAddressIndex = _traceLog.CallStacks.CodeAddressIndex(callstackIndex); @@ -102,20 +112,8 @@ private int AddStackTrace(CallStackIndex callstackIndex) } } - int result = -1; - if (stackTrace.Count > 0) - { - stackTrace.Seal(); - if (!_stackIndexes.TryGetValue(stackTrace, out result)) - { - stackTrace.Trim(10); - Profile.Stacks.Add(stackTrace); - result = Profile.Stacks.Count - 1; - _stackIndexes[stackTrace] = result; - } - } - - return result; + stackTrace.Trim(10); + return stackTrace; } /// diff --git a/src/Sentry/Protocol/SampleProfile.cs b/src/Sentry/Protocol/SampleProfile.cs index 8d70e22415..d7179dcbea 100644 --- a/src/Sentry/Protocol/SampleProfile.cs +++ b/src/Sentry/Protocol/SampleProfile.cs @@ -8,9 +8,6 @@ namespace Sentry.Protocol; -// A list of frame indexes. -using SentryProfileStackTrace = HashableGrowableArray; - /// /// Sentry sampling profiler output profile /// @@ -19,7 +16,7 @@ internal sealed class SampleProfile : IJsonSerializable // Note: changing these to properties would break because GrowableArray is a struct. internal GrowableArray Samples = new(10000); internal GrowableArray Frames = new(100); - internal GrowableArray Stacks = new(100); + internal GrowableArray> Stacks = new(100); internal List Threads = new(10); public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger) 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 f03a7eeefa..06d141fc7b 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.ProfileInfo_Serialization_Works.verified.txt +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.ProfileInfo_Serialization_Works.verified.txt @@ -42,12 +42,6 @@ name: Thread 7860 }, 6: { - name: Thread 7860 - }, - 7: { - name: Thread 7860 - }, - 8: { name: Thread 12688 } }, @@ -85,6 +79,20 @@ 25, 22 ], + [ + 18, + 19, + 20, + 21, + 22 + ], + [ + 18, + 19, + 20, + 21, + 22 + ], [ 26, 27, @@ -99,7 +107,11 @@ 36 ], [ - 37, + 37 + ], + [ + 0, + 1, 38, 39, 40, @@ -107,149 +119,132 @@ 42, 43, 44, - 32, - 33, - 34, - 35, - 36 - ], - [ 45, 46, + 22 + ], + [ 47, 48, 49, - 32, - 33, - 34, - 35, - 36 - ], - [ - 50 - ], - [ - 0, - 1, + 50, 51, 52, + 37 + ], + [ 53, 54, 55, 56, 57, - 58, - 59, - 22 + 37 ], [ + 58, + 59, 60, 61, - 62, + 62 + ], + [ 63, 64, 65, - 50 - ], - [ 66, 67, 68, 69, 70, - 50 - ], - [ 71, 72, - 73, - 74, - 75 + 58, + 59, + 60, + 61, + 62 ], [ + 73, + 74, + 75, 76, 77, 78, + 58, + 59, + 60, + 61, + 62 + ], + [ 79, 80, + 78, + 58, + 59, + 60, + 61, + 62 + ], + [ 81, + 59, + 60, + 61, + 62 + ], + [ 82, 83, 84, 85, - 71, - 72, - 73, - 74, - 75 - ], - [ 86, 87, 88, 89, 90, - 91, - 71, - 72, - 73, - 74, - 75 + 62 ], [ + 91, 92, 93, - 91, - 71, - 72, - 73, - 74, - 75 - ], - [ 94, - 72, - 73, - 74, - 75 - ], - [ 95, 96, 97, 98, 99, 100, - 101, - 102, - 103, - 75 + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 62 ], [ - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 97, - 98, - 99, - 100, - 101, - 102, - 103, - 75 + 0, + 1, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 22 ], [ 18, 19, 20, - 114, + 101, 22 ] ], @@ -437,71 +432,6 @@ module: System.Private.CoreLib.il, in_app: false }, - { - function: System.RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter(class System.RuntimeType,class System.RuntimeType), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Collections.Generic.ArraySortHelper`1[System.Int32].CreateArraySortHelper(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Collections.Generic.ArraySortHelper`1[System.Int32]..cctor(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Array.Sort(!!0[],int32,int32,class System.Collections.Generic.IComparer`1), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Collections.Generic.List`1[System.Int32].Sort(int32,int32,class System.Collections.Generic.IComparer`1), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Diagnostics.Tracing.ManifestBuilder.CreateManifestString(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Diagnostics.Tracing.ManifestBuilder.CreateManifest(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.CastHelpers.StelemRef_Helper(class System.Object&,void*,class System.Object), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Reflection.RuntimeParameterInfo.GetParameters(class System.IRuntimeMethodInfo,class System.Reflection.MemberInfo,class System.Signature,class System.Reflection.ParameterInfo&,bool), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Reflection.RuntimeMethodInfo.FetchNonReturnParameters(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Reflection.RuntimeMethodInfo.GetParameters(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), - module: System.Private.CoreLib.il, - in_app: false - }, { function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), module: Aura.UI.Gallery.NetCore, @@ -843,26 +773,16 @@ { elapsed_since_start_ns: 423600, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 424900, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 1760800, thread_id: 5, - stack_id: 3 - }, - { - elapsed_since_start_ns: 3741300, - thread_id: 6, - stack_id: 4 - }, - { - elapsed_since_start_ns: 5782600, - thread_id: 7, stack_id: 5 }, { @@ -883,12 +803,12 @@ { elapsed_since_start_ns: 11264500, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 11265600, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 21259900, @@ -908,12 +828,12 @@ { elapsed_since_start_ns: 21272100, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 21274400, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 31243800, @@ -933,12 +853,12 @@ { elapsed_since_start_ns: 31251200, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 31252200, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 41251400, @@ -958,12 +878,12 @@ { elapsed_since_start_ns: 41265000, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 41266400, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 51268200, @@ -983,12 +903,12 @@ { elapsed_since_start_ns: 51300300, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 51302000, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 61258600, @@ -1008,12 +928,12 @@ { elapsed_since_start_ns: 61274400, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 61276000, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 71256100, @@ -1033,12 +953,12 @@ { elapsed_since_start_ns: 71279300, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 71280500, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 81242100, @@ -1058,12 +978,12 @@ { elapsed_since_start_ns: 81250500, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 81251700, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 91316900, @@ -1083,12 +1003,12 @@ { elapsed_since_start_ns: 91328400, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 91329900, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 101264900, @@ -1108,12 +1028,12 @@ { elapsed_since_start_ns: 101276100, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 101277800, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 112828800, @@ -1133,17 +1053,17 @@ { elapsed_since_start_ns: 112847500, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 112863500, thread_id: 4, - stack_id: 7 + stack_id: 17 }, { elapsed_since_start_ns: 114809100, - thread_id: 8, - stack_id: 17 + thread_id: 6, + stack_id: 18 } ] } 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 4bfa8aa916..267679d444 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.Profile_Serialization_Works.verified.txt +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.Profile_Serialization_Works.verified.txt @@ -19,12 +19,6 @@ name: Thread 7860 }, 6: { - name: Thread 7860 - }, - 7: { - name: Thread 7860 - }, - 8: { name: Thread 12688 } }, @@ -62,6 +56,20 @@ 25, 22 ], + [ + 18, + 19, + 20, + 21, + 22 + ], + [ + 18, + 19, + 20, + 21, + 22 + ], [ 26, 27, @@ -76,7 +84,11 @@ 36 ], [ - 37, + 37 + ], + [ + 0, + 1, 38, 39, 40, @@ -84,149 +96,132 @@ 42, 43, 44, - 32, - 33, - 34, - 35, - 36 - ], - [ 45, 46, + 22 + ], + [ 47, 48, 49, - 32, - 33, - 34, - 35, - 36 - ], - [ - 50 - ], - [ - 0, - 1, + 50, 51, 52, + 37 + ], + [ 53, 54, 55, 56, 57, - 58, - 59, - 22 + 37 ], [ + 58, + 59, 60, 61, - 62, + 62 + ], + [ 63, 64, 65, - 50 - ], - [ 66, 67, 68, 69, 70, - 50 - ], - [ 71, 72, - 73, - 74, - 75 + 58, + 59, + 60, + 61, + 62 ], [ + 73, + 74, + 75, 76, 77, 78, + 58, + 59, + 60, + 61, + 62 + ], + [ 79, 80, + 78, + 58, + 59, + 60, + 61, + 62 + ], + [ 81, + 59, + 60, + 61, + 62 + ], + [ 82, 83, 84, 85, - 71, - 72, - 73, - 74, - 75 - ], - [ 86, 87, 88, 89, 90, - 91, - 71, - 72, - 73, - 74, - 75 + 62 ], [ + 91, 92, 93, - 91, - 71, - 72, - 73, - 74, - 75 - ], - [ 94, - 72, - 73, - 74, - 75 - ], - [ 95, 96, 97, 98, 99, 100, - 101, - 102, - 103, - 75 + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 62 ], [ - 104, - 105, - 106, - 107, - 108, - 109, - 110, - 111, - 112, - 113, - 97, - 98, - 99, - 100, - 101, - 102, - 103, - 75 + 0, + 1, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 22 ], [ 18, 19, 20, - 114, + 101, 22 ] ], @@ -414,71 +409,6 @@ module: System.Private.CoreLib.il, in_app: false }, - { - function: System.RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter(class System.RuntimeType,class System.RuntimeType), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Collections.Generic.ArraySortHelper`1[System.Int32].CreateArraySortHelper(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Collections.Generic.ArraySortHelper`1[System.Int32]..cctor(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Array.Sort(!!0[],int32,int32,class System.Collections.Generic.IComparer`1), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Collections.Generic.List`1[System.Int32].Sort(int32,int32,class System.Collections.Generic.IComparer`1), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Diagnostics.Tracing.ManifestBuilder.CreateManifestString(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Diagnostics.Tracing.ManifestBuilder.CreateManifest(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.CastHelpers.StelemRef_Helper(class System.Object&,void*,class System.Object), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Reflection.RuntimeParameterInfo.GetParameters(class System.IRuntimeMethodInfo,class System.Reflection.MemberInfo,class System.Signature,class System.Reflection.ParameterInfo&,bool), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Reflection.RuntimeMethodInfo.FetchNonReturnParameters(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Reflection.RuntimeMethodInfo.GetParameters(), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Diagnostics.Tracing.EventSource.CreateManifestAndDescriptors(class System.Type,class System.String,class System.Diagnostics.Tracing.EventSource,value class System.Diagnostics.Tracing.EventManifestOptions), - module: System.Private.CoreLib.il, - in_app: false - }, { function: Aura.UI.Gallery.NetCore.Program.Main(class System.String[]), module: Aura.UI.Gallery.NetCore, @@ -820,26 +750,16 @@ { elapsed_since_start_ns: 423600, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 424900, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 1760800, thread_id: 5, - stack_id: 3 - }, - { - elapsed_since_start_ns: 3741300, - thread_id: 6, - stack_id: 4 - }, - { - elapsed_since_start_ns: 5782600, - thread_id: 7, stack_id: 5 }, { @@ -860,12 +780,12 @@ { elapsed_since_start_ns: 11264500, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 11265600, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 21259900, @@ -885,12 +805,12 @@ { elapsed_since_start_ns: 21272100, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 21274400, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 31243800, @@ -910,12 +830,12 @@ { elapsed_since_start_ns: 31251200, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 31252200, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 41251400, @@ -935,12 +855,12 @@ { elapsed_since_start_ns: 41265000, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 41266400, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 51268200, @@ -960,12 +880,12 @@ { elapsed_since_start_ns: 51300300, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 51302000, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 61258600, @@ -985,12 +905,12 @@ { elapsed_since_start_ns: 61274400, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 61276000, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 71256100, @@ -1010,12 +930,12 @@ { elapsed_since_start_ns: 71279300, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 71280500, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 81242100, @@ -1035,12 +955,12 @@ { elapsed_since_start_ns: 81250500, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 81251700, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 91316900, @@ -1060,12 +980,12 @@ { elapsed_since_start_ns: 91328400, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 91329900, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 101264900, @@ -1085,12 +1005,12 @@ { elapsed_since_start_ns: 101276100, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 101277800, thread_id: 4, - stack_id: 1 + stack_id: 4 }, { elapsed_since_start_ns: 112828800, @@ -1110,17 +1030,17 @@ { elapsed_since_start_ns: 112847500, thread_id: 3, - stack_id: 1 + stack_id: 3 }, { elapsed_since_start_ns: 112863500, thread_id: 4, - stack_id: 7 + stack_id: 17 }, { elapsed_since_start_ns: 114809100, - thread_id: 8, - stack_id: 17 + thread_id: 6, + stack_id: 18 } ] } \ No newline at end of file diff --git a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs index a8798b3354..c998370d6d 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs @@ -33,6 +33,18 @@ public TraceLogProcessorTests(ITestOutputHelper output) // var json = profile.ToJsonString(_testOutputLogger); // } + + private SampleProfile BuilProfile(TraceLogEventSource eventSource) + { + var builder = new SampleProfileBuilder(new() { DiagnosticLogger = _testOutputLogger }, eventSource.TraceLog); + new SampleProfilerTraceEventParser(eventSource).ThreadSample += delegate (ClrThreadSampleTraceData data) + { + builder.AddSample(data, data.TimeStampRelativeMSec); + }; + eventSource.Process(); + return builder.Profile; + } + private SampleProfile GetProfile() { var etlxFilePath = Path.Combine(_resourcesPath, "sample.etlx"); @@ -50,22 +62,27 @@ private SampleProfile GetProfile() } using var traceLog = new TraceLog(etlxFilePath); - var builder = new SampleProfileBuilder(new() { DiagnosticLogger = _testOutputLogger }, traceLog); - var eventSource = traceLog.Events.GetSource(); - new SampleProfilerTraceEventParser(eventSource).ThreadSample += delegate (ClrThreadSampleTraceData data) - { - builder.AddSample(data, data.TimeStampRelativeMSec); - }; - eventSource.Process(); - return builder.Profile; + using var eventSource = traceLog.Events.GetSource(); + return BuilProfile(eventSource); } - [Fact] - public Task Profile_Serialization_Works() + private SampleProfile GetStreamedProfile() { - var profile = GetProfile(); + var etlFilePath = Path.Combine(_resourcesPath, "sample.nettrace"); + using var fileStream = File.Open(etlFilePath, FileMode.Open); + using var eventPipeEventSource = new EventPipeEventSource(fileStream); + using var eventSource = TraceLog.CreateFromEventPipeEventSource(eventPipeEventSource); + return BuilProfile(eventSource); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public Task Profile_Serialization_Works(bool streaming) + { + var profile = streaming ? GetStreamedProfile() : GetProfile(); var json = profile.ToJsonString(_testOutputLogger); - return VerifyJson(json); + return VerifyJson(json).DisableRequireUniquePrefix(); } [Fact] From ba3b4e1607b4ce7195b45301e5189295528b7168 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 11 May 2023 17:21:12 +0200 Subject: [PATCH 10/21] minor changes rundown issue investigation --- .../Sentry.Benchmarks/ProfilingBenchmarks.cs | 6 +--- src/Sentry.Profiling/SampleProfilerSession.cs | 29 ++++++++++++------- .../SamplingTransactionProfilerTests.cs | 7 +++-- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs index 86421a4c0e..f9209b515a 100644 --- a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs @@ -115,11 +115,7 @@ public DiagnosticsClient DiagnosticsClientNew() [Benchmark] public void DiagnosticsSessionStartStop() { - var session = DiagnosticsClientNew().StartEventPipeSession( - SampleProfilerSession.Providers, - SampleProfilerSession.RequestRundown, - SampleProfilerSession.CircularBufferMB - ); + var session = DiagnosticsClientNew().StartEventPipeSession(SampleProfilerSession.Providers, true, SampleProfilerSession.CircularBufferMB); session.EventStream.Dispose(); session.Dispose(); } diff --git a/src/Sentry.Profiling/SampleProfilerSession.cs b/src/Sentry.Profiling/SampleProfilerSession.cs index b8f2bfd15e..54be5c49d9 100644 --- a/src/Sentry.Profiling/SampleProfilerSession.cs +++ b/src/Sentry.Profiling/SampleProfilerSession.cs @@ -15,16 +15,14 @@ internal class SampleProfilerSession : IDisposable private readonly SampleProfilerTraceEventParser _sampleEventParser; private readonly IDiagnosticLogger? _logger; private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); - private bool _stopped; + private bool _stopped = false; - private SampleProfilerSession(EventPipeSession session, IDiagnosticLogger? logger) + private SampleProfilerSession(EventPipeSession session, TraceLogEventSource eventSource, IDiagnosticLogger? logger) { _session = session; _logger = logger; - _eventSource = TraceLog.CreateFromEventPipeSession(_session); + _eventSource = eventSource; _sampleEventParser = new SampleProfilerTraceEventParser(_eventSource); - // 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. @@ -32,14 +30,15 @@ private SampleProfilerSession(EventPipeSession session, IDiagnosticLogger? logge { // 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 + // TODO replace Keywords.Default with a subset. Currently it is: + // 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(SampleProfilerTraceEventParser.ProviderName, EventLevel.Informational), // new EventPipeProvider(TplEtwProviderTraceEventParser.ProviderName, EventLevel.Informational, (long) TplEtwProviderTraceEventParser.Keywords.Default) }; - // Exposed only for benchmarks. - internal static bool RequestRundown = true; - // Exposed only for benchmarks. // The size of the runtime's buffer for collecting events in MB, same as the current default in StartEventPipeSession(). internal static int CircularBufferMB = 256; @@ -53,8 +52,18 @@ private SampleProfilerSession(EventPipeSession session, IDiagnosticLogger? logge 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, logger); + + // Rundown events only come in after the session is stopped but we need them right from the start so that + // TraceLog has all the info about loaded moodules and methods at the time a sample is handled. + // As is, we can just disable the rundown and rely on the currently loaded modules. + var session = client.StartEventPipeSession(Providers, false, CircularBufferMB); + + var eventSource = TraceLog.CreateFromEventPipeSession(session); + + // Process() blocks until the session is stopped so we need to run it on a separate thread. + Task.Factory.StartNew(eventSource.Process, TaskCreationOptions.LongRunning); + + return new SampleProfilerSession(session, eventSource, logger); } public void Stop() diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 529612ab0b..f20d24601a 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -8,10 +8,12 @@ namespace Sentry.Profiling.Tests; public class SamplingTransactionProfilerTests { private readonly IDiagnosticLogger _testOutputLogger; + private readonly SentryOptions _testSentryOptions; public SamplingTransactionProfilerTests(ITestOutputHelper output) { _testOutputLogger = new TestOutputDiagnosticLogger(output); + _testSentryOptions = new SentryOptions { Debug = true, DiagnosticLogger = _testOutputLogger }; } private void ValidateProfile(SampleProfile profile, ulong maxTimestampNs) @@ -63,7 +65,7 @@ public void Profiler_StartedNormally_Works() var hub = Substitute.For(); var transactionTracer = new TransactionTracer(hub, "test", ""); - using var factory = SamplingTransactionProfilerFactory.Create(new SentryOptions { DiagnosticLogger = _testOutputLogger }); + using var factory = SamplingTransactionProfilerFactory.Create(_testSentryOptions); var clock = SentryStopwatch.StartNew(); var sut = factory.Start(transactionTracer, CancellationToken.None); transactionTracer.TransactionProfiler = sut; @@ -83,10 +85,9 @@ public void Profiler_StartedNormally_Works() public void Profiler_AfterTimeout_Stops() { var hub = Substitute.For(); - var options = new SentryOptions { DiagnosticLogger = _testOutputLogger }; using var session = SampleProfilerSession.StartNew(_testOutputLogger); var limitMs = 50; - var sut = new SamplingTransactionProfiler(options, session, limitMs, CancellationToken.None); + var sut = new SamplingTransactionProfiler(_testSentryOptions, session, limitMs, CancellationToken.None); RunForMs(limitMs * 4); sut.Finish(); From 25592ba0d369354085d817d6028d58f347dba731 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 19 May 2023 20:35:34 +0200 Subject: [PATCH 11/21] separate rundown session during tracelog init --- src/Sentry.Profiling/SampleProfilerSession.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Sentry.Profiling/SampleProfilerSession.cs b/src/Sentry.Profiling/SampleProfilerSession.cs index 54be5c49d9..b6db829d5a 100644 --- a/src/Sentry.Profiling/SampleProfilerSession.cs +++ b/src/Sentry.Profiling/SampleProfilerSession.cs @@ -53,12 +53,16 @@ public static SampleProfilerSession StartNew(IDiagnosticLogger? logger = null) { var client = new DiagnosticsClient(Process.GetCurrentProcess().Id); - // Rundown events only come in after the session is stopped but we need them right from the start so that - // TraceLog has all the info about loaded moodules and methods at the time a sample is handled. - // As is, we can just disable the rundown and rely on the currently loaded modules. - var session = client.StartEventPipeSession(Providers, false, CircularBufferMB); - - var eventSource = TraceLog.CreateFromEventPipeSession(session); + // Rundown events only come in after the session is stopped but we need them right from the start so that we + // can recognize loaded moodules and methods. Therefore, we start an additional session which will only collect + // rundown events and shut down immediately and feed this as an additional session to the TraceLog. + // Note: it doesn't matter what the actual provider is, just that we request rundown in the constructor. + using var rundownSession = client.StartEventPipeSession( + new EventPipeProvider(ClrTraceEventParser.ProviderName, EventLevel.Informational, (long)ClrTraceEventParser.Keywords.Default), + requestRundown: true + ); + var session = client.StartEventPipeSession(Providers, requestRundown: false, CircularBufferMB); + var eventSource = TraceLog.CreateFromEventPipeSession(session, rundownSession); // Process() blocks until the session is stopped so we need to run it on a separate thread. Task.Factory.StartNew(eventSource.Process, TaskCreationOptions.LongRunning); From aa3ff4d5afc3dca17acc2e85cce0e9657f261fb0 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sat, 20 May 2023 13:24:44 +0200 Subject: [PATCH 12/21] fixes --- .../Sentry.Benchmarks/ProfilingBenchmarks.cs | 12 +- .../Program.cs | 274 +----------------- .../SamplingTransactionProfiler.cs | 10 +- src/Sentry.Profiling/SentryProfiling.cs | 10 - .../SamplingTransactionProfilerTests.cs | 28 +- 5 files changed, 52 insertions(+), 282 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs index f9209b515a..1afe9c7413 100644 --- a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs @@ -18,7 +18,6 @@ public class ProfilingBenchmarks public void StartProfiler() { _factory = SamplingTransactionProfilerFactory.Create(new()); - _profiler = _factory.Start(new TransactionTracer(_hub, "", ""), CancellationToken.None); } [GlobalCleanup(Targets = new string[] { nameof(Transaction), nameof(DoHardWorkWhileProfiling) })] @@ -36,9 +35,9 @@ public IEnumerable TransactionBenchmarkArguments() { foreach (var runtimeMs in new[] { 25, 100, 1000, 10000 }) { - foreach (var processing in new[] { true, false }) + foreach (var collect in new[] { true, false }) { - yield return new object[] { runtimeMs, processing }; + yield return new object[] { runtimeMs, collect }; } } } @@ -46,14 +45,14 @@ public IEnumerable TransactionBenchmarkArguments() // Run a profiled transaction. Profiler starts and stops for each transaction separately. [Benchmark] [ArgumentsSource(nameof(TransactionBenchmarkArguments))] - public long Transaction(int runtimeMs, bool processing) + public long Transaction(int runtimeMs, bool collect) { var tt = new TransactionTracer(_hub, "test", ""); tt.TransactionProfiler = _factory.Start(tt, CancellationToken.None); var result = RunForMs(runtimeMs); - tt.TransactionProfiler?.Finish(); + tt.TransactionProfiler.Finish(); var transaction = new Transaction(tt); - if (processing) + if (collect) { var collectTask = tt.TransactionProfiler.CollectAsync(transaction); collectTask.Wait(); @@ -175,6 +174,7 @@ public long DoHardWork(int n) [ArgumentsSource(nameof(OverheadRunArguments))] public long DoHardWorkWhileProfiling(int n) { + _profiler ??= _factory.Start(new TransactionTracer(_hub, "", ""), CancellationToken.None); return FindPrimeNumber(n); } #endregion diff --git a/samples/Sentry.Samples.Console.Profiling/Program.cs b/samples/Sentry.Samples.Console.Profiling/Program.cs index bf3fdf1dc1..f92a304ad2 100644 --- a/samples/Sentry.Samples.Console.Profiling/Program.cs +++ b/samples/Sentry.Samples.Console.Profiling/Program.cs @@ -1,210 +1,45 @@ using System.Reflection; using System.Xml.Xsl; using Sentry; -using Sentry.Extensibility; using Sentry.Profiling; internal static class Program { private static async Task Main() { - // When the SDK is disabled, no callback is executed: - await SentrySdk.ConfigureScopeAsync(async scope => - { - // Never executed: - // This could be any async I/O operation, like a DB query - await Task.Yield(); - scope.SetExtra("Key", "Value"); - }); - // Enable the SDK - using (SentrySdk.Init(o => + using (SentrySdk.Init(options => { // A Sentry Data Source Name (DSN) is required. // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ // You can set it in the SENTRY_DSN environment variable, or you can set it in code here. - // o.Dsn = "... Your DSN ..."; - - // Send stack trace for events that were not created from an exception - // e.g: CaptureMessage, log.LogDebug, log.LogInformation ... - o.AttachStacktrace = true; - - // Sentry won't consider code from namespace LibraryX.* as part of the app code and will hide it from the stacktrace by default - // To see the lines from non `AppCode`, select `Full`. Will include non App code like System.*, Microsoft.* and LibraryX.* - o.AddInAppExclude("LibraryX."); - - // Before excluding all prefixed 'LibraryX.', any stack trace from a type namespaced 'LibraryX.Core' will be considered InApp. - o.AddInAppInclude("LibraryX.Core"); - - // Send personal identifiable information like the username logged on to the computer and machine name - o.SendDefaultPii = true; - - // To enable event sampling, uncomment: - // o.SampleRate = 0.5f; // Randomly drop (don't send to Sentry) half of events - - // Modifications to event before it goes out. Could replace the event altogether - o.SetBeforeSend((@event, _) => - { - // Drop an event altogether: - if (@event.Tags.ContainsKey("SomeTag")) - { - return null; - } - - return @event; - } - ); + // options.Dsn = "... Your DSN ..."; - // Allows inspecting and modifying, returning a new or simply rejecting (returning null) - o.SetBeforeBreadcrumb((crumb, _) => - { - // Don't add breadcrumbs with message containing: - if (crumb.Message?.Contains("bad breadcrumb") == true) - { - return null; - } + // When debug is enabled, the Sentry client will emit detailed debugging information to the console. + // This might be helpful, or might interfere with the normal operation of your application. + // We enable it here for demonstration purposes. + // You should not do this in your applications unless you are troubleshooting issues with Sentry. + options.Debug = true; - return crumb; - }); + // This option is recommended, which enables Sentry's "Release Health" feature. + options.AutoSessionTracking = true; - // Ignore exception by its type: - o.AddExceptionFilterForType(); + // This option is recommended for client applications only. It ensures all threads use the same global scope. + // If you are writing a background service of any kind, you should remove this. + options.IsGlobalModeEnabled = true; - // Configure the background worker which sends events to sentry: - // Wait up to 5 seconds before shutdown while there are events to send. - o.ShutdownTimeout = TimeSpan.FromSeconds(5); + // This option will enable Sentry's tracing features. You still need to start transactions and spans. + options.EnableTracing = true; - // Enable SDK logging with Debug level - o.Debug = true; - // To change the verbosity, use: - // o.DiagnosticLevel = SentryLevel.Info; - // To use a custom logger: - // o.DiagnosticLogger = ... - - // Using a proxy: - o.HttpProxy = null; //new WebProxy("https://localhost:3128"); - - // Example customizing the HttpClientHandlers created - o.CreateHttpClientHandler = () => new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, certificate, _, _) => - !certificate.Archived - }; - - // Access to the HttpClient created to serve the SentryClint - o.ConfigureClient = client => client.DefaultRequestHeaders.TryAddWithoutValidation("CustomHeader", new[] { "my value" }); - - // Control/override how to apply the State object into the scope - o.SentryScopeStateProcessor = new MyCustomerScopeStateProcessor(); - - o.TracesSampleRate = 1.0; - - o.AddIntegration(new ProfilingIntegration(Path.GetTempPath())); + options.AddIntegration(new ProfilingIntegration()); })) { var tx = SentrySdk.StartTransaction("app", "run"); - // Ignored by its type due to the setting above - SentrySdk.CaptureException(new XsltCompileException()); - - SentrySdk.AddBreadcrumb( - "A 'bad breadcrumb' that will be rejected because of 'BeforeBreadcrumb callback above.'"); - - // Data added to the root scope (no PushScope called up to this point) - // The modifications done here will affect all events sent and will propagate to child scopes. - await SentrySdk.ConfigureScopeAsync(async scope => - { - scope.AddEventProcessor(new SomeEventProcessor()); - scope.AddExceptionProcessor(new ArgumentExceptionProcessor()); - - // This could be any async I/O operation, like a DB query - await Task.Yield(); - scope.SetExtra("SomeExtraInfo", - new - { - Data = "Value fetched asynchronously", - ManaLevel = 199 - }); - }); - - // Configures a scope which is only valid within the callback - SentrySdk.CaptureMessage("Fatal message!", s => - { - s.Level = SentryLevel.Fatal; - s.TransactionName = "main"; - s.Environment = "SpecialEnvironment"; - - // Add a file attachment for upload - s.AddAttachment(typeof(Program).Assembly.Location); - }); - - FindPrimeNumber(100000); - var eventId = SentrySdk.CaptureMessage("Some warning!", SentryLevel.Warning); - - // Send an user feedback linked to the warning. - var timestamp = DateTime.Now.Ticks; - var user = $"user{timestamp}"; - var email = $"user{timestamp}@user{timestamp}.com"; - - SentrySdk.CaptureUserFeedback(new UserFeedback(eventId, user, email, "this is a sample user feedback")); - - var error = new Exception("Attempting to send this multiple times"); - - // Only the first capture will be sent to Sentry - for (var i = 0; i < 3; i++) - { - // The SDK is able to detect duplicate events: - // This is useful, for example, when multiple loggers log the same exception. Or exception is re-thrown and recaptured. - SentrySdk.CaptureException(error); - } - var count = 10; for (var i = 0; i < count; i++) { - const string msg = "{0} of {1} items we'll wait to flush to Sentry!"; - SentrySdk.CaptureEvent(new SentryEvent - { - Message = new SentryMessage - { - Message = msg, - Formatted = string.Format(msg, i, count) - }, - Level = SentryLevel.Debug - }); - FindPrimeNumber(100000); } - // Console output will show queue being flushed. - await SentrySdk.FlushAsync(); - - // ------------------------- - - // A custom made client, that could be registered with DI, - // would get disposed by the container on app shutdown - - var evt = new SentryEvent - { - Message = "Starting new client" - }; - evt.AddBreadcrumb("Breadcrumb directly to the event"); - evt.User.Username = "some@user"; - // Group all events with the following fingerprint: - evt.SetFingerprint("NewClientDebug"); - evt.Level = SentryLevel.Debug; - SentrySdk.CaptureEvent(evt); - - // Using a different DSN for a section of the app (i.e: admin) - const string AdminDsn = "https://f670c444cca14cf2bb4bfc403525b6a3@sentry.io/259314"; - using (var adminClient = new SentryClient(new SentryOptions { Dsn = AdminDsn })) - { - // Make believe web framework middleware - var middleware = new AdminPartMiddleware(adminClient, null); - var request = new { Path = "/admin" }; // made up request - middleware.Invoke(request); - } // Dispose the client which flushes any queued events - - SentrySdk.CaptureException( - new Exception("Error outside of the admin section: Goes to the default DSN")); - tx.Finish(); } // On Dispose: SDK closed, events queued are flushed/sent to Sentry } @@ -234,83 +69,4 @@ private static long FindPrimeNumber(int n) } return (--a); } - - private class AdminPartMiddleware - { - private readonly ISentryClient _adminClient; - private readonly dynamic _middleware; - - public AdminPartMiddleware(ISentryClient adminClient, dynamic middleware) - { - _adminClient = adminClient; - _middleware = middleware; - } - - public void Invoke(dynamic request) - { - using (SentrySdk.PushScope(new SpecialContextObject())) - { - SentrySdk.AddBreadcrumb(request.Path, "request-path"); - - // Change the SentryClient in case the request is to the admin part: - if (request.Path.StartsWith("/admin")) - { - // Within this scope, the _adminClient will be used instead of whatever - // client was defined before this point: - SentrySdk.BindClient(_adminClient); - } - - SentrySdk.CaptureException(new Exception("Error at the admin section")); - // Else it uses the default client - - _middleware?.Invoke(request); - } // Scope is disposed. - } - } - - private class SomeEventProcessor : ISentryEventProcessor - { - public SentryEvent Process(SentryEvent @event) - { - // Here you can modify the event as you need - if (@event.Level > SentryLevel.Info) - { - @event.AddBreadcrumb("Processed by " + nameof(SomeEventProcessor)); - } - - return @event; - } - } - - private class ArgumentExceptionProcessor : SentryEventExceptionProcessor - { - protected override void ProcessException(ArgumentException exception, SentryEvent sentryEvent) - { - // Handle specific types of exceptions and add more data to the event - sentryEvent.SetTag("parameter-name", exception.ParamName); - } - } - - private class MyCustomerScopeStateProcessor : ISentryScopeStateProcessor - { - private readonly ISentryScopeStateProcessor _fallback = new DefaultSentryScopeStateProcessor(); - - public void Apply(Scope scope, object state) - { - if (state is SpecialContextObject specialState) - { - scope.SetTag("SpecialContextObject", specialState.A + specialState.B); - } - else - { - _fallback.Apply(scope, state); - } - } - } - - private class SpecialContextObject - { - public string A { get; } = "hello"; - public string B { get; } = "world"; - } } diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index cd1f1c2403..ad37b084ca 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -53,6 +53,7 @@ private bool Stop(double? endTimeMs = null) { _stopped = true; _endTimeMs = endTimeMs.Value; + OnFinish?.Invoke(); return true; } } @@ -68,7 +69,14 @@ private void OnThreadSample(TraceEvent data) { if (timestampMs <= _endTimeMs) { - _processor.AddSample(data, timestampMs - _startTimeMs); + try + { + _processor.AddSample(data, timestampMs - _startTimeMs); + } + catch (Exception e) + { + _options.LogWarning("Failed to process a profile sample.", e); + } } else { diff --git a/src/Sentry.Profiling/SentryProfiling.cs b/src/Sentry.Profiling/SentryProfiling.cs index 7da7e8f33b..db9798749b 100644 --- a/src/Sentry.Profiling/SentryProfiling.cs +++ b/src/Sentry.Profiling/SentryProfiling.cs @@ -7,16 +7,6 @@ namespace Sentry.Profiling; /// public class ProfilingIntegration : ISdkIntegration { - private string _tempDirectoryPath { get; set; } - - /// - /// Initializes the the profiling integration. - /// - public ProfilingIntegration(string tempDirectoryPath) - { - _tempDirectoryPath = tempDirectoryPath; - } - /// public void Register(IHub hub, SentryOptions options) { diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index f20d24601a..4afbbbe0e1 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -59,14 +59,11 @@ private void RunForMs(int milliseconds) } } - [Fact] - public void Profiler_StartedNormally_Works() + private void CaptureAndValidate(ITransactionProfilerFactory factory) { + var clock = SentryStopwatch.StartNew(); var hub = Substitute.For(); var transactionTracer = new TransactionTracer(hub, "test", ""); - - using var factory = SamplingTransactionProfilerFactory.Create(_testSentryOptions); - var clock = SentryStopwatch.StartNew(); var sut = factory.Start(transactionTracer, CancellationToken.None); transactionTracer.TransactionProfiler = sut; RunForMs(100); @@ -81,6 +78,25 @@ public void Profiler_StartedNormally_Works() ValidateProfile(profileInfo.Profile, elapsedNanoseconds); } + + [Fact] + public void Profiler_SingleProfile_Works() + { + using var factory = SamplingTransactionProfilerFactory.Create(_testSentryOptions); + CaptureAndValidate(factory); + } + + [Fact] + public void Profiler_MultipleProfiles_Works() + { + using var factory = SamplingTransactionProfilerFactory.Create(_testSentryOptions); + CaptureAndValidate(factory); + Thread.Sleep(100); + CaptureAndValidate(factory); + Thread.Sleep(300); + CaptureAndValidate(factory); + } + [Fact] public void Profiler_AfterTimeout_Stops() { @@ -144,7 +160,7 @@ async Task VerifyAsync(HttpRequestMessage message) // Disable process exit flush to resolve "There is no currently active test." errors. options.DisableAppDomainProcessExitFlush(); - options.AddIntegration(new ProfilingIntegration(tempDir.Path)); + options.AddIntegration(new ProfilingIntegration()); try { From 9a780eb6c0e300f19a323c2bef94efd3e5272ee8 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 22 May 2023 11:55:55 +0200 Subject: [PATCH 13/21] update profiling benchmark results --- ...marks.ProfilingBenchmarks-report-github.md | 93 +++++++++---------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.ProfilingBenchmarks-report-github.md b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.ProfilingBenchmarks-report-github.md index 723000f439..5f57df1070 100644 --- a/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.ProfilingBenchmarks-report-github.md +++ b/benchmarks/Sentry.Benchmarks/BenchmarkDotNet.Artifacts/results/Sentry.Benchmarks.ProfilingBenchmarks-report-github.md @@ -1,54 +1,53 @@ ``` ini -BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1555/22H2/2022Update/SunValley2) +BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1702/22H2/2022Update/SunValley2) 12th Gen Intel Core i7-12700K, 1 CPU, 20 logical and 12 physical cores -.NET SDK=7.0.203 - [Host] : .NET 6.0.16 (6.0.1623.17311), X64 RyuJIT AVX2 - Job-HRXEOC : .NET 6.0.16 (6.0.1623.17311), X64 RyuJIT AVX2 +.NET SDK=7.0.302 + [Host] : .NET 6.0.16 (6.0.1623.17311), X64 RyuJIT AVX2 DEBUG + Job-XAKHAH : .NET 6.0.16 (6.0.1623.17311), X64 RyuJIT AVX2 Runtime=.NET 6.0 ``` -| Method | runtimeMs | processing | rundown | provider | n | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | -|----------------------------------------- |---------- |----------- |-------- |--------- |------- |--------------:|-----------:|-----------:|--------------:|------:|--------:|----------:|----------:|---------:|-----------:|------------:| -| **DiagnosticsSessionStartStop** | **?** | **?** | **?** | **?** | **?** | **11.002 ms** | **0.2181 ms** | **0.2142 ms** | **11.002 ms** | **?** | **?** | **-** | **-** | **-** | **9587 B** | **?** | -| SampleProfilerSessionStartStopFinishWait | ? | ? | ? | ? | ? | 118.284 ms | 4.0881 ms | 12.0539 ms | 123.832 ms | ? | ? | - | - | - | 3508694 B | ? | -| SampleProfilerSessionStartStop | ? | ? | ? | ? | ? | 115.846 ms | 4.5379 ms | 13.3802 ms | 121.593 ms | ? | ? | - | - | - | 3501074 B | ? | -| | | | | | | | | | | | | | | | | | -| **Transaction** | **25** | **False** | **?** | **?** | **?** | **121.313 ms** | **1.6881 ms** | **1.5790 ms** | **120.924 ms** | **?** | **?** | **-** | **-** | **-** | **3826061 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **Transaction** | **25** | **True** | **?** | **?** | **?** | **148.345 ms** | **2.2705 ms** | **2.1238 ms** | **148.186 ms** | **?** | **?** | **750.0000** | **250.0000** | **-** | **31036228 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **Transaction** | **100** | **False** | **?** | **?** | **?** | **219.679 ms** | **2.8684 ms** | **2.5428 ms** | **218.968 ms** | **?** | **?** | **-** | **-** | **-** | **4447075 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **Transaction** | **100** | **True** | **?** | **?** | **?** | **244.955 ms** | **2.0558 ms** | **1.7167 ms** | **245.007 ms** | **?** | **?** | **500.0000** | **-** | **-** | **25737928 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **Transaction** | **1000** | **False** | **?** | **?** | **?** | **1,035.830 ms** | **3.3787 ms** | **2.9952 ms** | **1,036.068 ms** | **?** | **?** | **-** | **-** | **-** | **4043360 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **Transaction** | **1000** | **True** | **?** | **?** | **?** | **1,071.220 ms** | **3.8655 ms** | **3.0180 ms** | **1,070.841 ms** | **?** | **?** | **1000.0000** | **-** | **-** | **35168504 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **Transaction** | **10000** | **False** | **?** | **?** | **?** | **10,171.186 ms** | **26.7803 ms** | **25.0503 ms** | **10,180.066 ms** | **?** | **?** | **-** | **-** | **-** | **10177136 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **Transaction** | **10000** | **True** | **?** | **?** | **?** | **10,200.367 ms** | **9.7616 ms** | **9.1310 ms** | **10,199.573 ms** | **?** | **?** | **3000.0000** | **1000.0000** | **-** | **71216048 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **False** | **all** | **?** | **110.255 ms** | **2.1973 ms** | **2.2565 ms** | **109.539 ms** | **?** | **?** | **-** | **-** | **-** | **35720 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **False** | **runtime** | **?** | **9.025 ms** | **1.2790 ms** | **3.6491 ms** | **8.711 ms** | **?** | **?** | **-** | **-** | **-** | **14612 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **False** | **sample** | **?** | **105.218 ms** | **3.3185 ms** | **9.7846 ms** | **109.640 ms** | **?** | **?** | **-** | **-** | **-** | **19185 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **False** | **tpl** | **?** | **9.023 ms** | **1.4060 ms** | **4.0790 ms** | **10.077 ms** | **?** | **?** | **-** | **-** | **-** | **26514 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **True** | **all** | **?** | **120.379 ms** | **3.1807 ms** | **9.3783 ms** | **123.728 ms** | **?** | **?** | **-** | **-** | **-** | **3492258 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **True** | **runtime** | **?** | **12.597 ms** | **0.4213 ms** | **1.2021 ms** | **12.038 ms** | **?** | **?** | **500.0000** | **500.0000** | **500.0000** | **3266302 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **True** | **sample** | **?** | **121.085 ms** | **2.4076 ms** | **6.9463 ms** | **122.342 ms** | **?** | **?** | **200.0000** | **200.0000** | **200.0000** | **3275838 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **True** | **tpl** | **?** | **26.265 ms** | **4.1051 ms** | **11.8442 ms** | **23.362 ms** | **?** | **?** | **181.8182** | **181.8182** | **181.8182** | **3394838 B** | **?** | -| | | | | | | | | | | | | | | | | | -| **DoHardWork** | **?** | **?** | **?** | **?** | **10000** | **6.030 ms** | **0.0293 ms** | **0.0259 ms** | **6.031 ms** | **1.00** | **0.00** | **-** | **-** | **-** | **4 B** | **1.00** | -| DoHardWorkWhileProfiling | ? | ? | ? | ? | 10000 | 6.284 ms | 0.0201 ms | 0.0188 ms | 6.284 ms | 1.04 | 0.00 | - | - | - | 107 B | 26.75 | -| | | | | | | | | | | | | | | | | | -| **DoHardWork** | **?** | **?** | **?** | **?** | **100000** | **199.135 ms** | **1.4575 ms** | **1.3633 ms** | **199.928 ms** | **1.00** | **0.00** | **-** | **-** | **-** | **1739 B** | **1.00** | -| DoHardWorkWhileProfiling | ? | ? | ? | ? | 100000 | 206.346 ms | 0.3495 ms | 0.3269 ms | 206.320 ms | 1.04 | 0.01 | - | - | - | 2685 B | 1.54 | +| Method | runtimeMs | collect | rundown | provider | n | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | +|-------------------------------- |---------- |-------- |-------- |--------- |------- |--------------:|------------:|------------:|--------------:|------:|--------:|----------:|---------:|---------:|-----------:|------------:| +| **DiagnosticsSessionStartStop** | **?** | **?** | **?** | **?** | **?** | **12.764 ms** | **0.2544 ms** | **0.6289 ms** | **12.750 ms** | **?** | **?** | **-** | **-** | **-** | **8080 B** | **?** | +| SampleProfilerSessionStartStop | ? | ? | ? | ? | ? | 202.801 ms | 0.2305 ms | 0.2156 ms | 202.759 ms | ? | ? | 666.6667 | 333.3333 | 333.3333 | 10294405 B | ? | +| | | | | | | | | | | | | | | | | | +| **Transaction** | **25** | **False** | **?** | **?** | **?** | **26.836 ms** | **0.0554 ms** | **0.0518 ms** | **26.836 ms** | **?** | **?** | **-** | **-** | **-** | **209695 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **Transaction** | **25** | **True** | **?** | **?** | **?** | **1,869.938 ms** | **110.4774 ms** | **325.7451 ms** | **1,988.836 ms** | **?** | **?** | **-** | **-** | **-** | **2300928 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **Transaction** | **100** | **False** | **?** | **?** | **?** | **109.096 ms** | **0.2472 ms** | **0.2313 ms** | **109.037 ms** | **?** | **?** | **-** | **-** | **-** | **285309 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **Transaction** | **100** | **True** | **?** | **?** | **?** | **1,849.174 ms** | **118.8488 ms** | **348.5630 ms** | **1,989.864 ms** | **?** | **?** | **-** | **-** | **-** | **4384000 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **Transaction** | **1000** | **False** | **?** | **?** | **?** | **1,006.442 ms** | **0.7579 ms** | **0.7089 ms** | **1,006.476 ms** | **?** | **?** | **-** | **-** | **-** | **1841000 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **Transaction** | **1000** | **True** | **?** | **?** | **?** | **2,740.554 ms** | **147.6695 ms** | **435.4070 ms** | **2,989.115 ms** | **?** | **?** | **-** | **-** | **-** | **7482952 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **Transaction** | **10000** | **False** | **?** | **?** | **?** | **10,067.206 ms** | **1.5642 ms** | **1.4631 ms** | **10,066.989 ms** | **?** | **?** | **1000.0000** | **-** | **-** | **21200216 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **Transaction** | **10000** | **True** | **?** | **?** | **?** | **11,874.396 ms** | **236.6765 ms** | **323.9655 ms** | **11,989.353 ms** | **?** | **?** | **1000.0000** | **-** | **-** | **24871080 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **False** | **all** | **?** | **98.983 ms** | **4.0416 ms** | **11.9167 ms** | **106.218 ms** | **?** | **?** | **-** | **-** | **-** | **18041 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **False** | **runtime** | **?** | **12.333 ms** | **1.9275 ms** | **5.6225 ms** | **11.957 ms** | **?** | **?** | **-** | **-** | **-** | **14052 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **False** | **sample** | **?** | **98.474 ms** | **3.4511 ms** | **10.1755 ms** | **103.118 ms** | **?** | **?** | **-** | **-** | **-** | **12977 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **False** | **tpl** | **?** | **9.190 ms** | **2.8708 ms** | **8.2828 ms** | **11.774 ms** | **?** | **?** | **-** | **-** | **-** | **26449 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **True** | **all** | **?** | **112.681 ms** | **4.1611 ms** | **12.2690 ms** | **119.549 ms** | **?** | **?** | **166.6667** | **166.6667** | **166.6667** | **3296852 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **True** | **runtime** | **?** | **12.433 ms** | **0.4167 ms** | **1.2088 ms** | **11.861 ms** | **?** | **?** | **500.0000** | **500.0000** | **500.0000** | **3269935 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **True** | **sample** | **?** | **107.295 ms** | **4.8091 ms** | **14.1797 ms** | **110.104 ms** | **?** | **?** | **166.6667** | **166.6667** | **166.6667** | **3263068 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **DiagnosticsSessionStartCopyStop** | **?** | **?** | **True** | **tpl** | **?** | **27.430 ms** | **2.6175 ms** | **7.6768 ms** | **27.496 ms** | **?** | **?** | **133.3333** | **133.3333** | **133.3333** | **3369689 B** | **?** | +| | | | | | | | | | | | | | | | | | +| **DoHardWork** | **?** | **?** | **?** | **?** | **10000** | **6.077 ms** | **0.0235 ms** | **0.0220 ms** | **6.080 ms** | **1.00** | **0.00** | **-** | **-** | **-** | **4 B** | **1.00** | +| DoHardWorkWhileProfiling | ? | ? | ? | ? | 10000 | 6.313 ms | 0.0245 ms | 0.0205 ms | 6.308 ms | 1.04 | 0.00 | - | - | - | 51446 B | 12,861.50 | +| | | | | | | | | | | | | | | | | | +| **DoHardWork** | **?** | **?** | **?** | **?** | **100000** | **199.804 ms** | **0.2485 ms** | **0.2325 ms** | **199.814 ms** | **1.00** | **0.00** | **-** | **-** | **-** | **1747 B** | **1.00** | +| DoHardWorkWhileProfiling | ? | ? | ? | ? | 100000 | 210.870 ms | 4.0807 ms | 4.5357 ms | 209.802 ms | 1.06 | 0.02 | - | - | - | 2221048 B | 1,271.35 | From cc6119f9989f82549242b12c0fc592079be16905 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 23 May 2023 19:31:43 +0200 Subject: [PATCH 14/21] update tests --- .../Program.cs | 2 +- ....Profiler_SingleProfile_Works.verified.txt | 662 ++++++++++++++++++ .../SamplingTransactionProfilerTests.cs | 24 +- 3 files changed, 683 insertions(+), 5 deletions(-) create mode 100644 test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt diff --git a/samples/Sentry.Samples.Console.Profiling/Program.cs b/samples/Sentry.Samples.Console.Profiling/Program.cs index f92a304ad2..2a717392bc 100644 --- a/samples/Sentry.Samples.Console.Profiling/Program.cs +++ b/samples/Sentry.Samples.Console.Profiling/Program.cs @@ -5,7 +5,7 @@ internal static class Program { - private static async Task Main() + private static void Main() { // Enable the SDK using (SentrySdk.Init(options => diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt new file mode 100644 index 0000000000..9ec88724bf --- /dev/null +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt @@ -0,0 +1,662 @@ +{ + stacks: [ + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + [ + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108 + ] + ], + frames: [ + { + function: System.Threading.ManualResetEventSlim.Wait(int32,value class System.Threading.CancellationToken), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Threading.Tasks.Task.SpinThenBlockingWait(int32,value class System.Threading.CancellationToken), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Threading.Tasks.Task.InternalWaitCore(int32,value class System.Threading.CancellationToken), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Threading.Tasks.Task.Wait(int32,value class System.Threading.CancellationToken), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Threading.Tasks.Task.Wait(), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Microsoft.VisualStudio.TestPlatform.TestHost.DefaultEngineInvoker.Invoke(class System.Collections.Generic.IDictionary`2), + module: testhost, + in_app: true + }, + { + function: Microsoft.VisualStudio.TestPlatform.TestHost.Program.Run(class System.String[],class Microsoft.VisualStudio.TestPlatform.Execution.UiLanguageOverride), + module: testhost, + in_app: true + }, + { + function: Microsoft.VisualStudio.TestPlatform.TestHost.Program.Run(class System.String[]), + module: testhost, + in_app: true + }, + { + function: Microsoft.VisualStudio.TestPlatform.TestHost.Program.Main(class System.String[]), + module: testhost, + in_app: true + }, + { + function: Microsoft.Diagnostics.Tracing.Parsers.FrameworkEventSourceTraceEventParser.EnumerateTemplates(class System.Func`3,class System.Action`1), + module: Microsoft.Diagnostics.Tracing.TraceEvent, + in_app: false + }, + { + function: Microsoft.Diagnostics.Tracing.TraceEventParser.ConfirmAllEventsAreInEnumeration(), + module: Microsoft.Diagnostics.Tracing.TraceEvent, + in_app: false + }, + { + function: Microsoft.Diagnostics.Tracing.TraceEventParser..ctor(class Microsoft.Diagnostics.Tracing.TraceEventSource,bool), + module: Microsoft.Diagnostics.Tracing.TraceEvent, + in_app: false + }, + { + function: Microsoft.Diagnostics.Tracing.Parsers.FrameworkEventSourceTraceEventParser..ctor(class Microsoft.Diagnostics.Tracing.TraceEventSource), + module: Microsoft.Diagnostics.Tracing.TraceEvent, + in_app: false + }, + { + function: Microsoft.Diagnostics.Tracing.Etlx.TraceLog.SetupCallbacks(class Microsoft.Diagnostics.Tracing.TraceEventDispatcher), + module: Microsoft.Diagnostics.Tracing.TraceEvent, + in_app: false + }, + { + function: Microsoft.Diagnostics.Tracing.Etlx.TraceLog..ctor(class Microsoft.Diagnostics.Tracing.TraceEventDispatcher), + module: Microsoft.Diagnostics.Tracing.TraceEvent, + in_app: false + }, + { + function: Microsoft.Diagnostics.Tracing.Etlx.TraceLog.CreateFromEventPipeEventSource(class Microsoft.Diagnostics.Tracing.EventPipeEventSource), + module: Microsoft.Diagnostics.Tracing.TraceEvent, + in_app: false + }, + { + function: Microsoft.Diagnostics.Tracing.Etlx.TraceLog.CreateFromEventPipeSession(class Microsoft.Diagnostics.NETCore.Client.EventPipeSession,class Microsoft.Diagnostics.NETCore.Client.EventPipeSession), + module: Microsoft.Diagnostics.Tracing.TraceEvent, + in_app: false + }, + { + function: Sentry.Profiling.SampleProfilerSession.StartNew(class Sentry.Extensibility.IDiagnosticLogger), + module: Sentry.Profiling, + in_app: false + }, + { + function: Sentry.Profiling.SamplingTransactionProfilerFactory.Create(class Sentry.SentryOptions), + module: Sentry.Profiling, + in_app: false + }, + { + function: Sentry.Profiling.Tests.SamplingTransactionProfilerTests.Profiler_SingleProfile_Works(), + module: Sentry.Profiling.Tests, + in_app: false + }, + { + in_app: false + }, + { + function: System.Reflection.MethodInvoker.Invoke(class System.Object,int*,value class System.Reflection.BindingFlags), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Reflection.RuntimeMethodInfo.Invoke(class System.Object,value class System.Reflection.BindingFlags,class System.Reflection.Binder,class System.Object[],class System.Globalization.CultureInfo), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Reflection.MethodBase.Invoke(class System.Object,class System.Object[]), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestInvoker`1[System.__Canon].CallTestMethod(class System.Object), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestInvoker`1+<>c__DisplayClass48_0+<b__1>d[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestInvoker`1+<>c__DisplayClass48_0[System.__Canon].b__1(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.ExecutionTimer+d__4.MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.ExecutionTimer.AggregateAsync(class System.Func`1), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestInvoker`1+<>c__DisplayClass48_0[System.__Canon].b__0(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.ExceptionAggregator+d__9.MoveNext(), + module: xunit.core, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.ExceptionAggregator.RunAsync(class System.Func`1), + module: xunit.core, + in_app: true + }, + { + function: Xunit.Sdk.TestInvoker`1+d__48[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.Decimal].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestInvoker`1[System.__Canon].InvokeTestMethodAsync(class System.Object), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestInvoker.InvokeTestMethodAsync(class System.Object), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestInvoker`1+<b__47_0>d[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.Decimal].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestInvoker`1[System.__Canon].b__47_0(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.ExceptionAggregator+d__10`1[System.Decimal].MoveNext(), + module: xunit.core, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.Decimal].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.ExceptionAggregator.RunAsync(class System.Func`1>), + module: xunit.core, + in_app: true + }, + { + function: Xunit.Sdk.TestInvoker`1[System.__Canon].RunAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestRunner.InvokeTestMethodAsync(class Xunit.Sdk.ExceptionAggregator), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestRunner+d__4.MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.XunitTestRunner.InvokeTestAsync(class Xunit.Sdk.ExceptionAggregator), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestRunner`1+<>c__DisplayClass43_0[System.__Canon].b__0(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.ExceptionAggregator+d__10`1[System.__Canon].MoveNext(), + module: xunit.core, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.ExceptionAggregator.RunAsync(class System.Func`1>), + module: xunit.core, + in_app: true + }, + { + function: Xunit.Sdk.TestRunner`1+d__43[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestRunner`1[System.__Canon].RunAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestCaseRunner.RunTestAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestCaseRunner`1+d__19[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestCaseRunner`1[System.__Canon].RunAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestCase.RunAsync(class Xunit.Abstractions.IMessageSink,class Xunit.Sdk.IMessageBus,class System.Object[],class Xunit.Sdk.ExceptionAggregator,class System.Threading.CancellationTokenSource), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestMethodRunner.RunTestCaseAsync(class Xunit.Sdk.IXunitTestCase), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestMethodRunner`1+d__32[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestMethodRunner`1[System.__Canon].RunTestCasesAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestMethodRunner`1+d__31[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestMethodRunner`1[System.__Canon].RunAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestClassRunner.RunTestMethodAsync(class Xunit.Abstractions.ITestMethod,class Xunit.Abstractions.IReflectionMethodInfo,class System.Collections.Generic.IEnumerable`1,class System.Object[]), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestClassRunner`1+d__38[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestClassRunner`1[System.__Canon].RunTestMethodsAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestClassRunner`1+d__37[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestClassRunner`1[System.__Canon].RunAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestCollectionRunner.RunTestClassAsync(class Xunit.Abstractions.ITestClass,class Xunit.Abstractions.IReflectionTypeInfo,class System.Collections.Generic.IEnumerable`1), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestCollectionRunner`1+d__28[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestCollectionRunner`1[System.__Canon].RunTestClassesAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestCollectionRunner`1+d__27[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestCollectionRunner`1[System.__Canon].RunAsync(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestAssemblyRunner.RunTestCollectionAsync(class Xunit.Sdk.IMessageBus,class Xunit.Abstractions.ITestCollection,class System.Collections.Generic.IEnumerable`1,class System.Threading.CancellationTokenSource), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.TestAssemblyRunner`1+d__42[System.__Canon].MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.TestAssemblyRunner`1[System.__Canon].RunTestCollectionsAsync(class Xunit.Sdk.IMessageBus,class System.Threading.CancellationTokenSource), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestAssemblyRunner.<>n__0(class Xunit.Sdk.IMessageBus,class System.Threading.CancellationTokenSource), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: Xunit.Sdk.XunitTestAssemblyRunner+d__14.MoveNext(), + module: xunit.execution.dotnet, + in_app: true + }, + { + function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Xunit.Sdk.XunitTestAssemblyRunner.RunTestCollectionsAsync(class Xunit.Sdk.IMessageBus,class System.Threading.CancellationTokenSource), + module: xunit.execution.dotnet, + in_app: true + } + ] +} \ No newline at end of file diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 4afbbbe0e1..b284b96f05 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -5,6 +5,7 @@ namespace Sentry.Profiling.Tests; // Note: we must not run tests in parallel because we only support profiling one transaction at a time. // That means setting up a test-collection with parallelization disabled and NOT using any async test functions. [CollectionDefinition("SamplingProfiler tests", DisableParallelization = true)] +[UsesVerify] public class SamplingTransactionProfilerTests { private readonly IDiagnosticLogger _testOutputLogger; @@ -59,7 +60,7 @@ private void RunForMs(int milliseconds) } } - private void CaptureAndValidate(ITransactionProfilerFactory factory) + private SampleProfile CaptureAndValidate(ITransactionProfilerFactory factory) { var clock = SentryStopwatch.StartNew(); var hub = Substitute.For(); @@ -76,14 +77,29 @@ private void CaptureAndValidate(ITransactionProfilerFactory factory) var profileInfo = collectTask.Result; Assert.NotNull(profileInfo); ValidateProfile(profileInfo.Profile, elapsedNanoseconds); + return profileInfo.Profile; } - [Fact] - public void Profiler_SingleProfile_Works() + public Task Profiler_SingleProfile_Works() { using var factory = SamplingTransactionProfilerFactory.Create(_testSentryOptions); - CaptureAndValidate(factory); + var profile = CaptureAndValidate(factory); + + // We "Verify" part of a profile that seems to be stable. + var profileToVerify = new SampleProfile + { + }; + for (var i = 0; i < 2; i++) + { + profileToVerify.Stacks.Add(profile.Stacks[i]); + } + for (var i = 0; i < 109; i++) + { + profileToVerify.Frames.Add(profile.Frames[i]); + } + var json = profileToVerify.ToJsonString(_testOutputLogger); + return VerifyJson(json).DisableRequireUniquePrefix(); } [Fact] From f88a20d80e94b77b2e86988f0071b346d887e21b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 23 May 2023 19:58:36 +0200 Subject: [PATCH 15/21] update tests --- ....Profiler_SingleProfile_Works.verified.txt | 600 ------------------ .../SamplingTransactionProfilerTests.cs | 9 +- 2 files changed, 3 insertions(+), 606 deletions(-) diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt index 9ec88724bf..e9e219e497 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt @@ -10,108 +10,6 @@ 6, 7, 8 - ], - [ - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99, - 100, - 101, - 102, - 103, - 104, - 105, - 106, - 107, - 108 ] ], frames: [ @@ -159,504 +57,6 @@ function: Microsoft.VisualStudio.TestPlatform.TestHost.Program.Main(class System.String[]), module: testhost, in_app: true - }, - { - function: Microsoft.Diagnostics.Tracing.Parsers.FrameworkEventSourceTraceEventParser.EnumerateTemplates(class System.Func`3,class System.Action`1), - module: Microsoft.Diagnostics.Tracing.TraceEvent, - in_app: false - }, - { - function: Microsoft.Diagnostics.Tracing.TraceEventParser.ConfirmAllEventsAreInEnumeration(), - module: Microsoft.Diagnostics.Tracing.TraceEvent, - in_app: false - }, - { - function: Microsoft.Diagnostics.Tracing.TraceEventParser..ctor(class Microsoft.Diagnostics.Tracing.TraceEventSource,bool), - module: Microsoft.Diagnostics.Tracing.TraceEvent, - in_app: false - }, - { - function: Microsoft.Diagnostics.Tracing.Parsers.FrameworkEventSourceTraceEventParser..ctor(class Microsoft.Diagnostics.Tracing.TraceEventSource), - module: Microsoft.Diagnostics.Tracing.TraceEvent, - in_app: false - }, - { - function: Microsoft.Diagnostics.Tracing.Etlx.TraceLog.SetupCallbacks(class Microsoft.Diagnostics.Tracing.TraceEventDispatcher), - module: Microsoft.Diagnostics.Tracing.TraceEvent, - in_app: false - }, - { - function: Microsoft.Diagnostics.Tracing.Etlx.TraceLog..ctor(class Microsoft.Diagnostics.Tracing.TraceEventDispatcher), - module: Microsoft.Diagnostics.Tracing.TraceEvent, - in_app: false - }, - { - function: Microsoft.Diagnostics.Tracing.Etlx.TraceLog.CreateFromEventPipeEventSource(class Microsoft.Diagnostics.Tracing.EventPipeEventSource), - module: Microsoft.Diagnostics.Tracing.TraceEvent, - in_app: false - }, - { - function: Microsoft.Diagnostics.Tracing.Etlx.TraceLog.CreateFromEventPipeSession(class Microsoft.Diagnostics.NETCore.Client.EventPipeSession,class Microsoft.Diagnostics.NETCore.Client.EventPipeSession), - module: Microsoft.Diagnostics.Tracing.TraceEvent, - in_app: false - }, - { - function: Sentry.Profiling.SampleProfilerSession.StartNew(class Sentry.Extensibility.IDiagnosticLogger), - module: Sentry.Profiling, - in_app: false - }, - { - function: Sentry.Profiling.SamplingTransactionProfilerFactory.Create(class Sentry.SentryOptions), - module: Sentry.Profiling, - in_app: false - }, - { - function: Sentry.Profiling.Tests.SamplingTransactionProfilerTests.Profiler_SingleProfile_Works(), - module: Sentry.Profiling.Tests, - in_app: false - }, - { - in_app: false - }, - { - function: System.Reflection.MethodInvoker.Invoke(class System.Object,int*,value class System.Reflection.BindingFlags), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Reflection.RuntimeMethodInfo.Invoke(class System.Object,value class System.Reflection.BindingFlags,class System.Reflection.Binder,class System.Object[],class System.Globalization.CultureInfo), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Reflection.MethodBase.Invoke(class System.Object,class System.Object[]), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestInvoker`1[System.__Canon].CallTestMethod(class System.Object), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestInvoker`1+<>c__DisplayClass48_0+<b__1>d[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestInvoker`1+<>c__DisplayClass48_0[System.__Canon].b__1(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.ExecutionTimer+d__4.MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.ExecutionTimer.AggregateAsync(class System.Func`1), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestInvoker`1+<>c__DisplayClass48_0[System.__Canon].b__0(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.ExceptionAggregator+d__9.MoveNext(), - module: xunit.core, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.ExceptionAggregator.RunAsync(class System.Func`1), - module: xunit.core, - in_app: true - }, - { - function: Xunit.Sdk.TestInvoker`1+d__48[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.Decimal].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestInvoker`1[System.__Canon].InvokeTestMethodAsync(class System.Object), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestInvoker.InvokeTestMethodAsync(class System.Object), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestInvoker`1+<b__47_0>d[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.Decimal].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestInvoker`1[System.__Canon].b__47_0(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.ExceptionAggregator+d__10`1[System.Decimal].MoveNext(), - module: xunit.core, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.Decimal].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.ExceptionAggregator.RunAsync(class System.Func`1>), - module: xunit.core, - in_app: true - }, - { - function: Xunit.Sdk.TestInvoker`1[System.__Canon].RunAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestRunner.InvokeTestMethodAsync(class Xunit.Sdk.ExceptionAggregator), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestRunner+d__4.MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.XunitTestRunner.InvokeTestAsync(class Xunit.Sdk.ExceptionAggregator), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestRunner`1+<>c__DisplayClass43_0[System.__Canon].b__0(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.ExceptionAggregator+d__10`1[System.__Canon].MoveNext(), - module: xunit.core, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.ExceptionAggregator.RunAsync(class System.Func`1>), - module: xunit.core, - in_app: true - }, - { - function: Xunit.Sdk.TestRunner`1+d__43[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestRunner`1[System.__Canon].RunAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestCaseRunner.RunTestAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestCaseRunner`1+d__19[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestCaseRunner`1[System.__Canon].RunAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestCase.RunAsync(class Xunit.Abstractions.IMessageSink,class Xunit.Sdk.IMessageBus,class System.Object[],class Xunit.Sdk.ExceptionAggregator,class System.Threading.CancellationTokenSource), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestMethodRunner.RunTestCaseAsync(class Xunit.Sdk.IXunitTestCase), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestMethodRunner`1+d__32[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestMethodRunner`1[System.__Canon].RunTestCasesAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestMethodRunner`1+d__31[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestMethodRunner`1[System.__Canon].RunAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestClassRunner.RunTestMethodAsync(class Xunit.Abstractions.ITestMethod,class Xunit.Abstractions.IReflectionMethodInfo,class System.Collections.Generic.IEnumerable`1,class System.Object[]), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestClassRunner`1+d__38[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestClassRunner`1[System.__Canon].RunTestMethodsAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestClassRunner`1+d__37[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestClassRunner`1[System.__Canon].RunAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestCollectionRunner.RunTestClassAsync(class Xunit.Abstractions.ITestClass,class Xunit.Abstractions.IReflectionTypeInfo,class System.Collections.Generic.IEnumerable`1), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestCollectionRunner`1+d__28[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestCollectionRunner`1[System.__Canon].RunTestClassesAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestCollectionRunner`1+d__27[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestCollectionRunner`1[System.__Canon].RunAsync(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestAssemblyRunner.RunTestCollectionAsync(class Xunit.Sdk.IMessageBus,class Xunit.Abstractions.ITestCollection,class System.Collections.Generic.IEnumerable`1,class System.Threading.CancellationTokenSource), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.TestAssemblyRunner`1+d__42[System.__Canon].MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.TestAssemblyRunner`1[System.__Canon].RunTestCollectionsAsync(class Xunit.Sdk.IMessageBus,class System.Threading.CancellationTokenSource), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestAssemblyRunner.<>n__0(class Xunit.Sdk.IMessageBus,class System.Threading.CancellationTokenSource), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: Xunit.Sdk.XunitTestAssemblyRunner+d__14.MoveNext(), - module: xunit.execution.dotnet, - in_app: true - }, - { - function: System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1[System.__Canon].Start(!!0&), - module: System.Private.CoreLib.il, - in_app: false - }, - { - function: Xunit.Sdk.XunitTestAssemblyRunner.RunTestCollectionsAsync(class Xunit.Sdk.IMessageBus,class System.Threading.CancellationTokenSource), - module: xunit.execution.dotnet, - in_app: true } ] } \ No newline at end of file diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index b284b96f05..c2a2d3e17a 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -90,16 +90,13 @@ public Task Profiler_SingleProfile_Works() var profileToVerify = new SampleProfile { }; - for (var i = 0; i < 2; i++) - { - profileToVerify.Stacks.Add(profile.Stacks[i]); - } - for (var i = 0; i < 109; i++) + profileToVerify.Stacks.Add(profile.Stacks[0]); + for (var i = 0; i < profile.Stacks[0].Count; i++) { profileToVerify.Frames.Add(profile.Frames[i]); } var json = profileToVerify.ToJsonString(_testOutputLogger); - return VerifyJson(json).DisableRequireUniquePrefix(); + return VerifyJson(json); } [Fact] From f864e7cabd4971b6a1b3bbc673e47fa5ead7b4b2 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 23 May 2023 21:30:41 +0200 Subject: [PATCH 16/21] fixes --- src/Sentry.Profiling/SampleProfilerSession.cs | 8 +++++--- src/Sentry/Internal/FastSerialization/GrowableArray.cs | 5 ----- .../SamplingTransactionProfilerTests.cs | 4 +--- test/Sentry.Tests/Protocol/ProfilerTests.verify.cs | 3 +-- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Sentry.Profiling/SampleProfilerSession.cs b/src/Sentry.Profiling/SampleProfilerSession.cs index b6db829d5a..3276fd1a4c 100644 --- a/src/Sentry.Profiling/SampleProfilerSession.cs +++ b/src/Sentry.Profiling/SampleProfilerSession.cs @@ -14,15 +14,16 @@ internal class SampleProfilerSession : IDisposable private readonly TraceLogEventSource _eventSource; private readonly SampleProfilerTraceEventParser _sampleEventParser; private readonly IDiagnosticLogger? _logger; - private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); + private readonly SentryStopwatch _stopwatch; private bool _stopped = false; - private SampleProfilerSession(EventPipeSession session, TraceLogEventSource eventSource, IDiagnosticLogger? logger) + private SampleProfilerSession(SentryStopwatch stopatch, EventPipeSession session, TraceLogEventSource eventSource, IDiagnosticLogger? logger) { _session = session; _logger = logger; _eventSource = eventSource; _sampleEventParser = new SampleProfilerTraceEventParser(_eventSource); + _stopwatch = stopatch; } // Exposed only for benchmarks. @@ -62,12 +63,13 @@ public static SampleProfilerSession StartNew(IDiagnosticLogger? logger = null) requestRundown: true ); var session = client.StartEventPipeSession(Providers, requestRundown: false, CircularBufferMB); + var stopWatch = SentryStopwatch.StartNew(); var eventSource = TraceLog.CreateFromEventPipeSession(session, rundownSession); // Process() blocks until the session is stopped so we need to run it on a separate thread. Task.Factory.StartNew(eventSource.Process, TaskCreationOptions.LongRunning); - return new SampleProfilerSession(session, eventSource, logger); + return new SampleProfilerSession(stopWatch, session, eventSource, logger); } public void Stop() diff --git a/src/Sentry/Internal/FastSerialization/GrowableArray.cs b/src/Sentry/Internal/FastSerialization/GrowableArray.cs index 1c99541b67..a852834fdf 100644 --- a/src/Sentry/Internal/FastSerialization/GrowableArray.cs +++ b/src/Sentry/Internal/FastSerialization/GrowableArray.cs @@ -358,11 +358,6 @@ private void Realloc(int minSize) // Implementation of IEnumerable. public IEnumerator GetEnumerator() => new GrowableArrayEnumerator(this); - internal void Add(object value) - { - throw new NotImplementedException(); - } - /// /// IEnumerator implementation. /// diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index c2a2d3e17a..86ef096326 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -87,9 +87,7 @@ public Task Profiler_SingleProfile_Works() var profile = CaptureAndValidate(factory); // We "Verify" part of a profile that seems to be stable. - var profileToVerify = new SampleProfile - { - }; + var profileToVerify = new SampleProfile(); profileToVerify.Stacks.Add(profile.Stacks[0]); for (var i = 0; i < profile.Stacks[0].Count; i++) { diff --git a/test/Sentry.Tests/Protocol/ProfilerTests.verify.cs b/test/Sentry.Tests/Protocol/ProfilerTests.verify.cs index fbf28e37ea..e2d7b84dd1 100644 --- a/test/Sentry.Tests/Protocol/ProfilerTests.verify.cs +++ b/test/Sentry.Tests/Protocol/ProfilerTests.verify.cs @@ -12,12 +12,11 @@ public ProfilerTests(ITestOutputHelper output) private static void AddStack(SampleProfile sut, List frames) { - var stack = new HashableGrowableArray(); + var stack = new GrowableArray(); foreach (var frame in frames) { stack.Add(frame); } - stack.Seal(); sut.Stacks.Add(stack); } From 04d38a5f73f26a205717dfb5630135d535b40d26 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 30 May 2023 22:19:46 +0200 Subject: [PATCH 17/21] update tests to support streaming tracelog --- test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs index c998370d6d..f33d58a797 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs @@ -69,8 +69,8 @@ private SampleProfile GetProfile() private SampleProfile GetStreamedProfile() { var etlFilePath = Path.Combine(_resourcesPath, "sample.nettrace"); - using var fileStream = File.Open(etlFilePath, FileMode.Open); - using var eventPipeEventSource = new EventPipeEventSource(fileStream); + using var eventPipeEventSource = new EventPipeEventSource(new MemoryStream(File.ReadAllBytes(etlFilePath))); + using var rundownSource = new EventPipeEventSource(new MemoryStream(File.ReadAllBytes(etlFilePath))); using var eventSource = TraceLog.CreateFromEventPipeEventSource(eventPipeEventSource); return BuilProfile(eventSource); } From c7f6929f7094a5e97ebf24377f111b42a97f52d4 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 4 Aug 2023 18:53:45 +0200 Subject: [PATCH 18/21] update to latest perfview --- src/Sentry.Profiling/SampleProfilerSession.cs | 11 +--- ...ingleProfile_Works.DotNet6_0.verified.txt} | 10 ++-- ...SingleProfile_Works.DotNet7_0.verified.txt | 56 +++++++++++++++++++ .../SamplingTransactionProfilerTests.cs | 6 +- .../TraceLogProcessorTests.verify.cs | 22 ++------ 5 files changed, 71 insertions(+), 34 deletions(-) rename test/Sentry.Profiling.Tests/{SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt => SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.DotNet6_0.verified.txt} (88%) create mode 100644 test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.DotNet7_0.verified.txt diff --git a/src/Sentry.Profiling/SampleProfilerSession.cs b/src/Sentry.Profiling/SampleProfilerSession.cs index 3276fd1a4c..f07ab0b02a 100644 --- a/src/Sentry.Profiling/SampleProfilerSession.cs +++ b/src/Sentry.Profiling/SampleProfilerSession.cs @@ -53,18 +53,9 @@ private SampleProfilerSession(SentryStopwatch stopatch, EventPipeSession session public static SampleProfilerSession StartNew(IDiagnosticLogger? logger = null) { var client = new DiagnosticsClient(Process.GetCurrentProcess().Id); - - // Rundown events only come in after the session is stopped but we need them right from the start so that we - // can recognize loaded moodules and methods. Therefore, we start an additional session which will only collect - // rundown events and shut down immediately and feed this as an additional session to the TraceLog. - // Note: it doesn't matter what the actual provider is, just that we request rundown in the constructor. - using var rundownSession = client.StartEventPipeSession( - new EventPipeProvider(ClrTraceEventParser.ProviderName, EventLevel.Informational, (long)ClrTraceEventParser.Keywords.Default), - requestRundown: true - ); var session = client.StartEventPipeSession(Providers, requestRundown: false, CircularBufferMB); var stopWatch = SentryStopwatch.StartNew(); - var eventSource = TraceLog.CreateFromEventPipeSession(session, rundownSession); + var eventSource = TraceLog.CreateFromEventPipeSession(session, TraceLog.EventPipeRundownConfiguration.Enable(client)); // Process() blocks until the session is stopped so we need to run it on a separate thread. Task.Factory.StartNew(eventSource.Process, TaskCreationOptions.LongRunning); diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.DotNet6_0.verified.txt similarity index 88% rename from test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt rename to test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.DotNet6_0.verified.txt index e9e219e497..075e30de9f 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.verified.txt +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.DotNet6_0.verified.txt @@ -13,6 +13,11 @@ ] ], frames: [ + { + function: System.Threading.Monitor.Wait(class System.Object,int32), + module: System.Private.CoreLib.il, + in_app: false + }, { function: System.Threading.ManualResetEventSlim.Wait(int32,value class System.Threading.CancellationToken), module: System.Private.CoreLib.il, @@ -43,11 +48,6 @@ module: testhost, in_app: true }, - { - function: Microsoft.VisualStudio.TestPlatform.TestHost.Program.Run(class System.String[],class Microsoft.VisualStudio.TestPlatform.Execution.UiLanguageOverride), - module: testhost, - in_app: true - }, { function: Microsoft.VisualStudio.TestPlatform.TestHost.Program.Run(class System.String[]), module: testhost, diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.DotNet7_0.verified.txt b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.DotNet7_0.verified.txt new file mode 100644 index 0000000000..e2c460e5f2 --- /dev/null +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.Profiler_SingleProfile_Works.DotNet7_0.verified.txt @@ -0,0 +1,56 @@ +{ + stacks: [ + [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + ], + frames: [ + { + function: System.Threading.ManualResetEventSlim.Wait(int32,value class System.Threading.CancellationToken), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Threading.Tasks.Task.SpinThenBlockingWait(int32,value class System.Threading.CancellationToken), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Threading.Tasks.Task.InternalWaitCore(int32,value class System.Threading.CancellationToken), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Threading.Tasks.Task.Wait(int32,value class System.Threading.CancellationToken), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: System.Threading.Tasks.Task.Wait(), + module: System.Private.CoreLib.il, + in_app: false + }, + { + function: Microsoft.VisualStudio.TestPlatform.TestHost.DefaultEngineInvoker.Invoke(class System.Collections.Generic.IDictionary`2), + module: testhost, + in_app: true + }, + { + function: Microsoft.VisualStudio.TestPlatform.TestHost.Program.Run(class System.String[]), + module: testhost, + in_app: true + }, + { + function: Microsoft.VisualStudio.TestPlatform.TestHost.Program.Main(class System.String[]), + module: testhost, + in_app: true + } + ] +} \ No newline at end of file diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 6d8f419b19..1d5d47d87e 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -89,12 +89,12 @@ public Task Profiler_SingleProfile_Works() // We "Verify" part of a profile that seems to be stable. var profileToVerify = new SampleProfile(); profileToVerify.Stacks.Add(profile.Stacks[0]); - for (var i = 0; i < profile.Stacks[0].Count; i++) + for (var i = 0; i < profileToVerify.Stacks[0].Count; i++) { - profileToVerify.Frames.Add(profile.Frames[i]); + profileToVerify.Frames.Add(profile.Frames[profileToVerify.Stacks[0][i]]); } var json = profileToVerify.ToJsonString(_testOutputLogger); - return VerifyJson(json); + return VerifyJson(json).UniqueForRuntimeAndVersion(); } [Fact] diff --git a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs index f33d58a797..cac77b2821 100644 --- a/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs +++ b/test/Sentry.Profiling.Tests/TraceLogProcessorTests.verify.cs @@ -1,6 +1,9 @@ +using System.Diagnostics.Tracing; +using Microsoft.Diagnostics.NETCore.Client; using Microsoft.Diagnostics.Tracing; using Microsoft.Diagnostics.Tracing.Etlx; using Microsoft.Diagnostics.Tracing.EventPipe; +using Microsoft.Diagnostics.Tracing.Parsers; namespace Sentry.Profiling.Tests; @@ -33,7 +36,6 @@ public TraceLogProcessorTests(ITestOutputHelper output) // var json = profile.ToJsonString(_testOutputLogger); // } - private SampleProfile BuilProfile(TraceLogEventSource eventSource) { var builder = new SampleProfileBuilder(new() { DiagnosticLogger = _testOutputLogger }, eventSource.TraceLog); @@ -66,22 +68,10 @@ private SampleProfile GetProfile() return BuilProfile(eventSource); } - private SampleProfile GetStreamedProfile() - { - var etlFilePath = Path.Combine(_resourcesPath, "sample.nettrace"); - using var eventPipeEventSource = new EventPipeEventSource(new MemoryStream(File.ReadAllBytes(etlFilePath))); - using var rundownSource = new EventPipeEventSource(new MemoryStream(File.ReadAllBytes(etlFilePath))); - using var eventSource = TraceLog.CreateFromEventPipeEventSource(eventPipeEventSource); - return BuilProfile(eventSource); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public Task Profile_Serialization_Works(bool streaming) + [Fact] + public Task Profile_Serialization_Works() { - var profile = streaming ? GetStreamedProfile() : GetProfile(); - var json = profile.ToJsonString(_testOutputLogger); + var json = GetProfile().ToJsonString(_testOutputLogger); return VerifyJson(json).DisableRequireUniquePrefix(); } From 4ffb45a15b6bdb8be8758f4ba22e9925e746e50c Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Mon, 14 Aug 2023 15:08:31 -0400 Subject: [PATCH 19/21] submodule perfview sentry fork --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 1ea998c72d..484ac2d87b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,4 +10,4 @@ branch = xcframework-catalyst [submodule "modules/perfview"] path = modules/perfview - url = https://github.com/vaind/perfview.git + url = https://github.com/getsentry/perfview.git From 5b159e1e14befaeb2b52cb5c9ded75cb0842651e Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 16 Aug 2023 19:20:31 +0200 Subject: [PATCH 20/21] fix test code --- test/Sentry.Tests/Protocol/ProfilerTests.verify.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Tests/Protocol/ProfilerTests.verify.cs b/test/Sentry.Tests/Protocol/ProfilerTests.verify.cs index e2d7b84dd1..dec900ed0e 100644 --- a/test/Sentry.Tests/Protocol/ProfilerTests.verify.cs +++ b/test/Sentry.Tests/Protocol/ProfilerTests.verify.cs @@ -12,7 +12,7 @@ public ProfilerTests(ITestOutputHelper output) private static void AddStack(SampleProfile sut, List frames) { - var stack = new GrowableArray(); + var stack = new GrowableArray(frames.Count); foreach (var frame in frames) { stack.Add(frame); From b4f94853866a9af6934476aa9f51d4ee74921e4e Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 16 Aug 2023 20:26:55 +0200 Subject: [PATCH 21/21] script & project updates --- modules/make-internal-perfview.ps1 | 33 ++++++++++++++++++++ modules/perfview | 2 +- src/Sentry.Profiling/Sentry.Profiling.csproj | 3 -- src/Sentry/Sentry.csproj | 1 + 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 modules/make-internal-perfview.ps1 diff --git a/modules/make-internal-perfview.ps1 b/modules/make-internal-perfview.ps1 new file mode 100644 index 0000000000..5db3db9e81 --- /dev/null +++ b/modules/make-internal-perfview.ps1 @@ -0,0 +1,33 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function UpdateSourceFiles([string]$Path) +{ + $files = Get-ChildItem -Recurse "$PSScriptRoot/perfview/$Path" -Filter '*.cs' + foreach ($file in $files) + { + $oldText = Get-Content $file.FullName -Raw + $text = $oldText + + # Make types internal. + $text = $text -replace 'public( +([a-z ]+)?(class|struct|enum|interface|delegate) +)', 'internal$1' + $text = $text -creplace "(`n +)(class | struct | enum | interface) ", '$1internal $2 ' + + # Allow nullable types. + $text = "#nullable disable`n`n" + ($text -replace "#nullable disable[`n`r]+", '') + + # Don't error out on obsolete code usage. + $text = $text -replace '(? - - - diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index 8c35e78f9b..9c5c6088f8 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -55,6 +55,7 @@ + %(RecursiveDir)\%(Filename)%(Extension)