From 3f8f0362ee2e9d52f58d690b08696fb25a190da3 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 30 Nov 2023 15:58:53 +0100 Subject: [PATCH 01/43] wip: cocoa profiling --- samples/Sentry.Samples.Ios/AppDelegate.cs | 38 ++++++++++++++++ .../SamplingTransactionProfiler.cs | 4 +- src/Sentry/Internal/Hub.cs | 3 +- src/Sentry/Internal/ITransactionProfiler.cs | 3 +- src/Sentry/Internal/SentryStopwatch.cs | 16 ++++--- src/Sentry/Platforms/iOS/CocoaProfiler.cs | 45 +++++++++++++++++++ .../Platforms/iOS/CocoaProfilerFactory.cs | 26 +++++++++++ .../iOS/Facades/SerializableNSObject.cs | 33 ++++++++++++++ src/Sentry/Platforms/iOS/SentrySdk.cs | 1 + src/Sentry/Protocol/Envelopes/Envelope.cs | 2 +- src/Sentry/Protocol/Envelopes/EnvelopeItem.cs | 11 ++++- 11 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 src/Sentry/Platforms/iOS/CocoaProfiler.cs create mode 100644 src/Sentry/Platforms/iOS/CocoaProfilerFactory.cs create mode 100644 src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs diff --git a/samples/Sentry.Samples.Ios/AppDelegate.cs b/samples/Sentry.Samples.Ios/AppDelegate.cs index 39f319624f..105a53dc66 100644 --- a/samples/Sentry.Samples.Ios/AppDelegate.cs +++ b/samples/Sentry.Samples.Ios/AppDelegate.cs @@ -16,6 +16,7 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l { o.Debug = true; o.Dsn = "https://eb18e953812b41c3aeb042e666fd3b5c@o447951.ingest.sentry.io/5428537"; + o.TracesSampleRate = 1.0; }); // create a new window instance based on the screen size @@ -50,6 +51,43 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l // throw new Exception("Test Unhandled Managed Exception"); // SentrySdk.CauseCrash(CrashType.Native); + { + var tx = SentrySdk.StartTransaction("app", "run"); + var count = 10; + for (var i = 0; i < count; i++) + { + FindPrimeNumber(100000); + } + + tx.Finish(); + } + return true; } + + private static long FindPrimeNumber(int n) + { + int count = 0; + long a = 2; + while (count < n) + { + long b = 2; + int prime = 1;// to check if found a prime + while (b * b <= a) + { + if (a % b == 0) + { + prime = 0; + break; + } + b++; + } + if (prime > 0) + { + count++; + } + a++; + } + return (--a); + } } diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index d98607eca4..35625203a6 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -96,11 +96,11 @@ public void Finish() } /// - public async Task CollectAsync(Transaction transaction) + public async object Collect(Transaction transaction) { if (!_stopped) { - throw new InvalidOperationException("Profiler.CollectAsync() called before Finish()"); + throw new InvalidOperationException("Profiler.Collect() called before Finish()"); } // Wait for the last sample (<= _endTimeMs), or at most 1 second. The timeout shouldn't happen because diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index c8c2e1da86..1da7b7e2ce 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -154,6 +154,7 @@ internal ITransactionTracer StartTransaction( transaction.SampleRate = sampleRate; } + // TODO profileSampleRate if (transaction.IsSampled is true && _options.TransactionProfilerFactory is { } profilerFactory) { // TODO cancellation token based on Hub being closed? @@ -200,7 +201,7 @@ public SentryTraceHeader GetTraceHeader() public BaggageHeader GetBaggage() { - if (GetSpan() is TransactionTracer { DynamicSamplingContext: { IsEmpty: false } dsc } ) + if (GetSpan() is TransactionTracer { DynamicSamplingContext: { IsEmpty: false } dsc }) { return dsc.ToBaggageHeader(); } diff --git a/src/Sentry/Internal/ITransactionProfiler.cs b/src/Sentry/Internal/ITransactionProfiler.cs index 71b8280654..abfef5a6be 100644 --- a/src/Sentry/Internal/ITransactionProfiler.cs +++ b/src/Sentry/Internal/ITransactionProfiler.cs @@ -26,5 +26,6 @@ internal interface ITransactionProfiler /// /// Process and collect the profile. /// - Task CollectAsync(Transaction transaction); + /// The collected profile. See EnvelopeItem.FromProfileInfo() for supported return types. + object Collect(Transaction transaction); } diff --git a/src/Sentry/Internal/SentryStopwatch.cs b/src/Sentry/Internal/SentryStopwatch.cs index f9d86b2430..ac3174e044 100644 --- a/src/Sentry/Internal/SentryStopwatch.cs +++ b/src/Sentry/Internal/SentryStopwatch.cs @@ -8,6 +8,7 @@ internal struct SentryStopwatch { private static readonly double StopwatchTicksPerTimeSpanTick = (double)Stopwatch.Frequency / TimeSpan.TicksPerSecond; + private static readonly double StopwatchTicksPerNs = (double)Stopwatch.Frequency / 1000000000.0; private long _startTimestamp; private DateTimeOffset _startDateTimeOffset; @@ -21,14 +22,15 @@ internal struct SentryStopwatch public DateTimeOffset StartDateTimeOffset => _startDateTimeOffset; public DateTimeOffset CurrentDateTimeOffset => _startDateTimeOffset + Elapsed; + private long Diff() => Stopwatch.GetTimestamp() - _startTimestamp; + public TimeSpan Elapsed { - get - { - var now = Stopwatch.GetTimestamp(); - var diff = now - _startTimestamp; - var ticks = (long)(diff / StopwatchTicksPerTimeSpanTick); - return TimeSpan.FromTicks(ticks); - } + get => TimeSpan.FromTicks((long)(Diff() / StopwatchTicksPerTimeSpanTick)); + } + + public ulong ElapsedNanoseconds + { + get => (ulong)(Diff() / StopwatchTicksPerNs); } } diff --git a/src/Sentry/Platforms/iOS/CocoaProfiler.cs b/src/Sentry/Platforms/iOS/CocoaProfiler.cs new file mode 100644 index 0000000000..da22c92ac9 --- /dev/null +++ b/src/Sentry/Platforms/iOS/CocoaProfiler.cs @@ -0,0 +1,45 @@ +using Sentry.Extensibility; +using Sentry.Internal; +using Sentry.Protocol; +using Sentry.iOS.Extensions; +using Sentry.iOS.Facades; + +namespace Sentry.iOS; + +internal class CocoaProfiler : ITransactionProfiler +{ + private readonly SentryOptions _options; + private readonly SentryId _traceId; + private readonly CocoaSdk.SentryId _cocoaTraceId; + private readonly ulong _starTimeNs; + private ulong _endTimeNs; + private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); + + public CocoaProfiler(SentryOptions options, ulong starTimeNs, SentryId traceId, CocoaSdk.SentryId cocoaTraceId) + { + _options = options; + _starTimeNs = starTimeNs; + _traceId = traceId; + _cocoaTraceId = cocoaTraceId; + _options.LogDebug("Trace {0} profile start timestamp: {1} ns", _traceId, _starTimeNs); + } + + /// + public void Finish() + { + if (_endTimeNs == 0) + { + _endTimeNs = _starTimeNs + (ulong)_stopwatch.ElapsedNanoseconds; + _options.LogDebug("Trace {0} profile end timestamp: {1} ns", _traceId, _endTimeNs); + } + } + + public object Collect(Transaction transaction) + { + var payload = SentryCocoaHybridSdk.CollectProfileBetween(_starTimeNs, _endTimeNs, _cocoaTraceId); + ArgumentNullException.ThrowIfNull(payload, "profile payload"); + + _options.LogDebug("Trace {0} profile payload collected", _traceId); + return new SerializableNSObject(payload); + } +} diff --git a/src/Sentry/Platforms/iOS/CocoaProfilerFactory.cs b/src/Sentry/Platforms/iOS/CocoaProfilerFactory.cs new file mode 100644 index 0000000000..76ff0432ec --- /dev/null +++ b/src/Sentry/Platforms/iOS/CocoaProfilerFactory.cs @@ -0,0 +1,26 @@ +using Sentry.Internal; +using Sentry.iOS.Extensions; + +namespace Sentry.iOS; + +internal class CocoaProfilerFactory : ITransactionProfilerFactory +{ + private readonly SentryOptions _options; + + internal CocoaProfilerFactory(SentryOptions options) + { + _options = options; + } + + /// + public ITransactionProfiler? Start(ITransactionTracer tracer, CancellationToken cancellationToken) + { + var traceId = tracer.TraceId.ToCocoaSentryId(); + var startTime = SentryCocoaHybridSdk.StartProfilerForTrace(traceId); + if (startTime == 0) + { + return null; + } + return new CocoaProfiler(_options, startTime, tracer.TraceId, traceId); + } +} diff --git a/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs b/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs new file mode 100644 index 0000000000..0a2abe42bc --- /dev/null +++ b/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs @@ -0,0 +1,33 @@ +using Sentry.Extensibility; +using Sentry.Internal; +using ISerializable = Sentry.Protocol.Envelopes.ISerializable; + +namespace Sentry.iOS.Facades; + +internal class SerializableNSObject : ISerializable +{ + private readonly NSObject _value; + + public SerializableNSObject(NSObject value) + { + _value = value; + } + + public Task SerializeAsync(Stream stream, IDiagnosticLogger? logger, CancellationToken cancellationToken = default) + => Task.Run(() => Serialize(stream, logger)); // TODO do we need a better implementation? + + public void Serialize(Stream stream, IDiagnosticLogger? logger) { + // For types that implement Sentry Cocoa's SentrySerializable protocol (interface), + // We should call that first, and then serialize the result to JSON later. + var obj = _value is CocoaSdk.ISentrySerializable serializable + ? serializable.Serialize() + : _value; + + // Now we will use Apple's JSON Serialization functions. + // See https://developer.apple.com/documentation/foundation/nsjsonserialization + // TODO can we pipe NSOutputStream directly? It can be passed as a second argument + // TODO how do we check if the error happened? Is it non-null? Then we can rethrow as NSErrorException? + var data = NSJsonSerialization.Serialize(obj, 0, out NSError error); + data.AsStream().CopyTo(stream); + } +} diff --git a/src/Sentry/Platforms/iOS/SentrySdk.cs b/src/Sentry/Platforms/iOS/SentrySdk.cs index 2a7d600283..feb551a6bf 100644 --- a/src/Sentry/Platforms/iOS/SentrySdk.cs +++ b/src/Sentry/Platforms/iOS/SentrySdk.cs @@ -187,6 +187,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) options.CrashedLastRun = () => SentryCocoaSdk.CrashedLastRun; options.EnableScopeSync = true; options.ScopeObserver = new IosScopeObserver(options); + options.TransactionProfilerFactory = new CocoaProfilerFactory(options); // TODO: Pause/Resume } diff --git a/src/Sentry/Protocol/Envelopes/Envelope.cs b/src/Sentry/Protocol/Envelopes/Envelope.cs index 488f07ed93..6131b87a63 100644 --- a/src/Sentry/Protocol/Envelopes/Envelope.cs +++ b/src/Sentry/Protocol/Envelopes/Envelope.cs @@ -314,7 +314,7 @@ public static Envelope FromTransaction(Transaction transaction) if (transaction.TransactionProfiler is { } profiler) { // Profiler.CollectAsync() may throw in which case the EnvelopeItem won't serialize. - items.Add(EnvelopeItem.FromProfileInfo(profiler.CollectAsync(transaction))); + items.Add(EnvelopeItem.FromProfileInfo(profiler.Collect(transaction))); } return new Envelope(eventId, header, items); diff --git a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs index 022fb75dcc..41d5cfe616 100644 --- a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs +++ b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs @@ -224,14 +224,21 @@ public static EnvelopeItem FromTransaction(Transaction transaction) /// /// Creates an from . /// - internal static EnvelopeItem FromProfileInfo(Task source) + internal static EnvelopeItem FromProfileInfo(object source) { var header = new Dictionary(1, StringComparer.Ordinal) { [TypeKey] = TypeValueProfile }; - return new EnvelopeItem(header, AsyncJsonSerializable.CreateFrom(source)); + ISerializable payload = source switch + { + ISerializable serializable => serializable, + Task task => AsyncJsonSerializable.CreateFrom(task), + _ => throw new ArgumentException("Unsupported profile info source type.", nameof(source)) + }; + + return new EnvelopeItem(header, payload); } /// From cd439746dc5d9b8f8d2bad6f42cfec763ed8a236 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 1 Dec 2023 09:44:19 +0100 Subject: [PATCH 02/43] use ISerializable in ITransactionProfiler --- src/Sentry/Internal/ITransactionProfiler.cs | 8 +++----- src/Sentry/Internal/SerializableExtensions.cs | 1 - src/Sentry/Platforms/iOS/CocoaProfiler.cs | 2 +- .../Platforms/iOS/Facades/SerializableNSObject.cs | 1 - src/Sentry/Protocol/Envelopes/EnvelopeItem.cs | 11 ++--------- src/Sentry/Sentry.csproj | 4 ++++ 6 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/Sentry/Internal/ITransactionProfiler.cs b/src/Sentry/Internal/ITransactionProfiler.cs index abfef5a6be..7bff440e70 100644 --- a/src/Sentry/Internal/ITransactionProfiler.cs +++ b/src/Sentry/Internal/ITransactionProfiler.cs @@ -23,9 +23,7 @@ internal interface ITransactionProfiler /// void Finish(); - /// - /// Process and collect the profile. - /// - /// The collected profile. See EnvelopeItem.FromProfileInfo() for supported return types. - object Collect(Transaction transaction); + /// Process and collect the profile. + /// The collected profile. + ISerializable Collect(Transaction transaction); } diff --git a/src/Sentry/Internal/SerializableExtensions.cs b/src/Sentry/Internal/SerializableExtensions.cs index 9c52ac4b39..b16f250e5c 100644 --- a/src/Sentry/Internal/SerializableExtensions.cs +++ b/src/Sentry/Internal/SerializableExtensions.cs @@ -1,7 +1,6 @@ using Sentry.Extensibility; using Sentry.Infrastructure; using Sentry.Protocol.Envelopes; -using ISerializable = Sentry.Protocol.Envelopes.ISerializable; namespace Sentry.Internal; diff --git a/src/Sentry/Platforms/iOS/CocoaProfiler.cs b/src/Sentry/Platforms/iOS/CocoaProfiler.cs index da22c92ac9..384afe3266 100644 --- a/src/Sentry/Platforms/iOS/CocoaProfiler.cs +++ b/src/Sentry/Platforms/iOS/CocoaProfiler.cs @@ -34,7 +34,7 @@ public void Finish() } } - public object Collect(Transaction transaction) + public ISerializable Collect(Transaction transaction) { var payload = SentryCocoaHybridSdk.CollectProfileBetween(_starTimeNs, _endTimeNs, _cocoaTraceId); ArgumentNullException.ThrowIfNull(payload, "profile payload"); diff --git a/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs b/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs index 0a2abe42bc..f16dc4abdb 100644 --- a/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs +++ b/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs @@ -1,6 +1,5 @@ using Sentry.Extensibility; using Sentry.Internal; -using ISerializable = Sentry.Protocol.Envelopes.ISerializable; namespace Sentry.iOS.Facades; diff --git a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs index 41d5cfe616..a2557cf8ef 100644 --- a/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs +++ b/src/Sentry/Protocol/Envelopes/EnvelopeItem.cs @@ -224,21 +224,14 @@ public static EnvelopeItem FromTransaction(Transaction transaction) /// /// Creates an from . /// - internal static EnvelopeItem FromProfileInfo(object source) + internal static EnvelopeItem FromProfileInfo(ISerializable source) { var header = new Dictionary(1, StringComparer.Ordinal) { [TypeKey] = TypeValueProfile }; - ISerializable payload = source switch - { - ISerializable serializable => serializable, - Task task => AsyncJsonSerializable.CreateFrom(task), - _ => throw new ArgumentException("Unsupported profile info source type.", nameof(source)) - }; - - return new EnvelopeItem(header, payload); + return new EnvelopeItem(header, source); } /// diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index 606ec281de..dbd19e70e6 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -95,6 +95,10 @@ + + + + From fad8efff08958f281251f990ef26af41d57915b6 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 1 Dec 2023 13:49:38 +0100 Subject: [PATCH 03/43] fixup profile content --- src/Sentry/Platforms/iOS/CocoaProfiler.cs | 13 +++++++++++-- .../Platforms/iOS/Extensions/CocoaExtensions.cs | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Platforms/iOS/CocoaProfiler.cs b/src/Sentry/Platforms/iOS/CocoaProfiler.cs index 384afe3266..962ed7fdbe 100644 --- a/src/Sentry/Platforms/iOS/CocoaProfiler.cs +++ b/src/Sentry/Platforms/iOS/CocoaProfiler.cs @@ -36,10 +36,19 @@ public void Finish() public ISerializable Collect(Transaction transaction) { - var payload = SentryCocoaHybridSdk.CollectProfileBetween(_starTimeNs, _endTimeNs, _cocoaTraceId); + // TODO change return type of CocoaSDKs CollectProfileBetween to NSMutableDictionary + var payload = SentryCocoaHybridSdk.CollectProfileBetween(_starTimeNs, _endTimeNs, _cocoaTraceId)?.MutableCopy() as NSMutableDictionary; + _options.LogDebug("Trace {0} profile payload collected", _traceId); + ArgumentNullException.ThrowIfNull(payload, "profile payload"); + payload["timestamp"] = transaction.StartTimestamp.ToString("o", CultureInfo.InvariantCulture).ToNSString(); - _options.LogDebug("Trace {0} profile payload collected", _traceId); + var payloadTx = payload["transaction"]?.MutableCopy() as NSMutableDictionary; + ArgumentNullException.ThrowIfNull(payloadTx, "profile payload transaction"); + payloadTx["id"] = transaction.EventId.ToString().ToNSString(); + payloadTx["trace_id"] = _traceId.ToString().ToNSString(); + payloadTx["name"] = transaction.Name.ToNSString(); + payload["transaction"] = payloadTx; return new SerializableNSObject(payload); } } diff --git a/src/Sentry/Platforms/iOS/Extensions/CocoaExtensions.cs b/src/Sentry/Platforms/iOS/Extensions/CocoaExtensions.cs index f66dcc24a1..bb0f2b76bb 100644 --- a/src/Sentry/Platforms/iOS/Extensions/CocoaExtensions.cs +++ b/src/Sentry/Platforms/iOS/Extensions/CocoaExtensions.cs @@ -8,6 +8,8 @@ internal static class CocoaExtensions public static NSDate ToNSDate(this DateTimeOffset timestamp) => (NSDate)timestamp.UtcDateTime; + public static NSString ToNSString(this string str) => new NSString(str); + public static string? ToJsonString(this NSObject? obj, IDiagnosticLogger? logger = null) { using var data = obj.ToJsonData(logger); From c2adb5a5b58ef7eb0bb94a89d72725a7e6f88952 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 1 Dec 2023 13:52:44 +0100 Subject: [PATCH 04/43] dispose stream --- src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs b/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs index f16dc4abdb..d5929928d6 100644 --- a/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs +++ b/src/Sentry/Platforms/iOS/Facades/SerializableNSObject.cs @@ -27,6 +27,7 @@ public void Serialize(Stream stream, IDiagnosticLogger? logger) { // TODO can we pipe NSOutputStream directly? It can be passed as a second argument // TODO how do we check if the error happened? Is it non-null? Then we can rethrow as NSErrorException? var data = NSJsonSerialization.Serialize(obj, 0, out NSError error); - data.AsStream().CopyTo(stream); + using var dataStream = data.AsStream(); + dataStream.CopyTo(stream); } } From 0aa3bf1dce9dc230791917041f82486ef9167d9e Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 1 Dec 2023 14:01:40 +0100 Subject: [PATCH 05/43] fixup sentry.profiling --- src/Sentry.Profiling/SamplingTransactionProfiler.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index 35625203a6..2a323877d0 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -96,7 +96,10 @@ public void Finish() } /// - public async object Collect(Transaction transaction) + public Protocol.Envelopes.ISerializable Collect(Transaction transaction) + => Protocol.Envelopes.AsyncJsonSerializable.CreateFrom(CollectAsync(transaction)); + + private async Task CollectAsync(Transaction transaction) { if (!_stopped) { From ba4982adbb01a95c081e403d830642f96012a531 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 1 Dec 2023 14:37:02 +0100 Subject: [PATCH 06/43] build fixes --- benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs | 4 ++-- src/Sentry.Profiling/SamplingTransactionProfiler.cs | 2 +- src/Sentry/Protocol/Envelopes/Envelope.cs | 2 +- .../SamplingTransactionProfilerTests.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs index bc0f33a3f5..dc64852d04 100644 --- a/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/ProfilingBenchmarks.cs @@ -25,7 +25,7 @@ public void StartProfiler() public void StopProfiler() { _profiler?.Finish(); - _profiler?.CollectAsync(new Transaction("", "")).Wait(); + (_profiler as SamplingTransactionProfiler)?.CollectAsync(new Transaction("", "")).Wait(); _profiler = null; _factory.Dispose(); _factory = null; @@ -55,7 +55,7 @@ public long Transaction(int runtimeMs, bool collect) var transaction = new Transaction(tt); if (collect) { - var collectTask = tt.TransactionProfiler.CollectAsync(transaction); + var collectTask = (tt.TransactionProfiler as SamplingTransactionProfiler).CollectAsync(transaction); collectTask.Wait(); } return result; diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index 2a323877d0..416d2d7f16 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -99,7 +99,7 @@ public void Finish() public Protocol.Envelopes.ISerializable Collect(Transaction transaction) => Protocol.Envelopes.AsyncJsonSerializable.CreateFrom(CollectAsync(transaction)); - private async Task CollectAsync(Transaction transaction) + internal async Task CollectAsync(Transaction transaction) { if (!_stopped) { diff --git a/src/Sentry/Protocol/Envelopes/Envelope.cs b/src/Sentry/Protocol/Envelopes/Envelope.cs index 6131b87a63..0e711fc6a4 100644 --- a/src/Sentry/Protocol/Envelopes/Envelope.cs +++ b/src/Sentry/Protocol/Envelopes/Envelope.cs @@ -313,7 +313,7 @@ public static Envelope FromTransaction(Transaction transaction) if (transaction.TransactionProfiler is { } profiler) { - // Profiler.CollectAsync() may throw in which case the EnvelopeItem won't serialize. + // Profiler.Collect() may throw in which case the EnvelopeItem won't serialize. items.Add(EnvelopeItem.FromProfileInfo(profiler.Collect(transaction))); } diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 56e2e13edb..647c793519 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -78,7 +78,7 @@ private SampleProfile CaptureAndValidate(ITransactionProfilerFactory factory) var elapsedNanoseconds = (ulong)((clock.CurrentDateTimeOffset - clock.StartDateTimeOffset).TotalMilliseconds * 1_000_000); var transaction = new Transaction(transactionTracer); - var collectTask = sut.CollectAsync(transaction); + var collectTask = (sut as SamplingTransactionProfiler).CollectAsync(transaction); collectTask.Wait(); var profileInfo = collectTask.Result; Assert.NotNull(profileInfo); From efa45524db218aa150a00c6bd9e0b9c569fc5aa9 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 4 Dec 2023 21:13:09 +0100 Subject: [PATCH 07/43] feat: add ProfilesSampleRate --- src/Sentry.Profiling/SentryProfiling.cs | 4 +- src/Sentry/BindableSentryOptions.cs | 2 + src/Sentry/Internal/Hub.cs | 15 ++-- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 4 + src/Sentry/SentryOptions.cs | 47 ++++++++++ ...piApprovalTests.Run.DotNet8_0.verified.txt | 1 + test/Sentry.Tests/SentryOptionsTests.cs | 90 +++++++++++++++++++ 7 files changed, 155 insertions(+), 8 deletions(-) diff --git a/src/Sentry.Profiling/SentryProfiling.cs b/src/Sentry.Profiling/SentryProfiling.cs index db9798749b..f38d31d2bf 100644 --- a/src/Sentry.Profiling/SentryProfiling.cs +++ b/src/Sentry.Profiling/SentryProfiling.cs @@ -10,6 +10,8 @@ public class ProfilingIntegration : ISdkIntegration /// public void Register(IHub hub, SentryOptions options) { - options.TransactionProfilerFactory = SamplingTransactionProfilerFactory.Create(options); + if (options.IsProfilingEnabled) { + options.TransactionProfilerFactory = SamplingTransactionProfilerFactory.Create(options); + } } } diff --git a/src/Sentry/BindableSentryOptions.cs b/src/Sentry/BindableSentryOptions.cs index 366658c053..a770b6ca22 100644 --- a/src/Sentry/BindableSentryOptions.cs +++ b/src/Sentry/BindableSentryOptions.cs @@ -40,6 +40,7 @@ internal partial class BindableSentryOptions public bool? EnableTracing { get; set; } public double? TracesSampleRate { get; set; } public List? TracePropagationTargets { get; set; } + public double? ProfilesSampleRate { get; set; } public StackTraceMode? StackTraceMode { get; set; } public long? MaxAttachmentSize { get; set; } public StartupTimeDetectionMode? DetectStartupTime { get; set; } @@ -82,6 +83,7 @@ public void ApplyTo(SentryOptions options) options.DefaultTags = DefaultTags ?? options.DefaultTags; options.EnableTracing = EnableTracing ?? options.EnableTracing; options.TracesSampleRate = TracesSampleRate ?? options.TracesSampleRate; + options.ProfilesSampleRate = ProfilesSampleRate ?? options.ProfilesSampleRate; options.TracePropagationTargets = TracePropagationTargets?.Select(s => new SubstringOrRegexPattern(s)).ToList() ?? options.TracePropagationTargets; options.StackTraceMode = StackTraceMode ?? options.StackTraceMode; options.MaxAttachmentSize = MaxAttachmentSize ?? options.MaxAttachmentSize; diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 1da7b7e2ce..1d342205ee 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -154,8 +154,9 @@ internal ITransactionTracer StartTransaction( transaction.SampleRate = sampleRate; } - // TODO profileSampleRate - if (transaction.IsSampled is true && _options.TransactionProfilerFactory is { } profilerFactory) + if (transaction.IsSampled is true && + _options.TransactionProfilerFactory is { } profilerFactory && + _randomValuesFactory.NextBool(_options.ProfilesSampleRate ?? 0.0)) { // TODO cancellation token based on Hub being closed? transaction.TransactionProfiler = profilerFactory.Start(transaction, CancellationToken.None); @@ -529,11 +530,11 @@ public void Dispose() #elif ANDROID // TODO #elif NET8_0_OR_GREATER - if (AotHelper.IsNativeAot) - { - _options?.LogDebug("Closing native SDK"); - SentrySdk.CloseNativeSdk(); - } + if (AotHelper.IsNativeAot) + { + _options?.LogDebug("Closing native SDK"); + SentrySdk.CloseNativeSdk(); + } #endif } diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index d6b8c62267..e5f741edc2 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -188,6 +188,10 @@ private static void InitSentryCocoaSdk(SentryOptions options) options.EnableScopeSync = true; options.ScopeObserver = new CocoaScopeObserver(options); + if (options.IsProfilingEnabled) { + options.TransactionProfilerFactory = new CocoaProfilerFactory(options); + } + // TODO: Pause/Resume } diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index 4419343b00..e2ab8d46eb 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -746,6 +746,12 @@ public Dictionary DefaultTags true => TracesSampler is not null || TracesSampleRate is > 0.0 or null }; + /// + /// Indicates whether profiling is enabled, via any combination of + /// , , or . + /// + internal bool IsProfilingEnabled => IsPerformanceMonitoringEnabled && ProfilesSampleRate > 0.0; + /// /// Simplified option for enabling or disabling tracing. /// @@ -824,6 +830,47 @@ public double? TracesSampleRate } } + private double? _profilesSampleRate; + + /// + /// The sampling rate for profiling is relative to . + /// Setting to 1.0 will profile 100% of sampled transactions. + /// + /// + /// Value + /// Effect + /// + /// + /// >= 0.0 and <=1.0 + /// + /// A custom sample rate is. Values outside of this range are invalid. + /// Setting to 0.0 will disable profiling. + /// + /// + /// + /// null + /// + /// The default setting. + /// At this time, this is equivalent to 0.0, i.e. disabling profiling, but that may change in the future. + /// + /// + /// + /// + public double? ProfilesSampleRate + { + get => _profilesSampleRate; + set + { + if (value is < 0.0 or > 1.0) + { + throw new ArgumentOutOfRangeException(nameof(value), value, + "The profiles sample rate must be between 0.0 and 1.0, inclusive."); + } + + _profilesSampleRate = value; + } + } + /// /// Custom delegate that returns sample rate dynamically for a specific transaction context. /// diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 4ed6442010..2dfa09aef3 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -634,6 +634,7 @@ namespace Sentry public int MaxCacheItems { get; set; } public int MaxQueueItems { get; set; } public Sentry.Extensibility.INetworkStatusListener? NetworkStatusListener { get; set; } + public double? ProfilesSampleRate { get; set; } public string? Release { get; set; } public Sentry.ReportAssembliesMode ReportAssembliesMode { get; set; } public bool RequestBodyCompressionBuffered { get; set; } diff --git a/test/Sentry.Tests/SentryOptionsTests.cs b/test/Sentry.Tests/SentryOptionsTests.cs index 6afd837660..70fe8b6eee 100644 --- a/test/Sentry.Tests/SentryOptionsTests.cs +++ b/test/Sentry.Tests/SentryOptionsTests.cs @@ -174,6 +174,96 @@ public void IsPerformanceMonitoringEnabled_EnableTracing_False_TracesSampler_Pro Assert.False(sut.IsPerformanceMonitoringEnabled); } + [Fact] + public void ProfilesSampleRate_Default_Null() + { + var sut = new SentryOptions(); + Assert.Null(sut.ProfilesSampleRate); + } + + [Fact] + public void IsProfilingEnabled_Default_False() + { + var sut = new SentryOptions(); + Assert.False(sut.IsProfilingEnabled); + } + + [Fact] + public void IsProfilingEnabled_EnableTracing_True() + { + var sut = new SentryOptions + { + EnableTracing = true, + ProfilesSampleRate = double.Epsilon + }; + + Assert.True(sut.IsProfilingEnabled); + } + + [Fact] + public void IsProfilingEnabled_EnableTracing_False() + { + var sut = new SentryOptions + { + EnableTracing = false, + ProfilesSampleRate = double.Epsilon + }; + + Assert.False(sut.IsProfilingEnabled); + } + + [Fact] + public void IsProfilingEnabled_TracesSampleRate_Zero() + { + var sut = new SentryOptions + { + TracesSampleRate = 0.0, + ProfilesSampleRate = double.Epsilon + }; + + Assert.False(sut.IsProfilingEnabled); + } + + [Fact] + public void IsProfilingEnabled_ProfilessSampleRate_Zero() + { + var sut = new SentryOptions + { + TracesSampleRate = double.Epsilon, + ProfilesSampleRate = 0.0 + }; + + Assert.False(sut.IsProfilingEnabled); + } + + [Fact] + public void IsProfilingEnabled_TracesSampleRate_GreaterThanZero() + { + var sut = new SentryOptions + { + TracesSampleRate = double.Epsilon, + ProfilesSampleRate = double.Epsilon + }; + + Assert.True(sut.IsProfilingEnabled); + } + + [Fact] + public void IsProfilingEnabled_TracesSampleRate_LessThanZero() + { + var sut = new SentryOptions(); + Assert.Throws(() => + sut.ProfilesSampleRate = -double.Epsilon); + } + + [Fact] + public void IsProfilingEnabled_TracesSampleRate_GreaterThanOne() + { + var sut = new SentryOptions(); + Assert.Throws(() => + sut.ProfilesSampleRate = 1.0000000000000002); + } + [Fact] public void CaptureFailedRequests_ByDefault_IsTrue() { From 4533cd95f44b5754309f2269d14f80863a2f88cb Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Tue, 5 Dec 2023 08:39:23 +0100 Subject: [PATCH 08/43] Apply suggestions from code review Co-authored-by: Filip Navara --- src/Sentry/Platforms/Cocoa/CocoaProfiler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs index 546b6f6e62..330899d2b6 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs @@ -11,14 +11,14 @@ internal class CocoaProfiler : ITransactionProfiler private readonly SentryOptions _options; private readonly SentryId _traceId; private readonly CocoaSdk.SentryId _cocoaTraceId; - private readonly ulong _starTimeNs; + private readonly ulong _startTimeNs; private ulong _endTimeNs; private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); - public CocoaProfiler(SentryOptions options, ulong starTimeNs, SentryId traceId, CocoaSdk.SentryId cocoaTraceId) + public CocoaProfiler(SentryOptions options, ulong startTimeNs, SentryId traceId, CocoaSdk.SentryId cocoaTraceId) { _options = options; - _starTimeNs = starTimeNs; + _startTimeNs = startTimeNs; _traceId = traceId; _cocoaTraceId = cocoaTraceId; _options.LogDebug("Trace {0} profile start timestamp: {1} ns", _traceId, _starTimeNs); @@ -29,7 +29,7 @@ public void Finish() { if (_endTimeNs == 0) { - _endTimeNs = _starTimeNs + (ulong)_stopwatch.ElapsedNanoseconds; + _endTimeNs = _startTimeNs + (ulong)_stopwatch.ElapsedNanoseconds; _options.LogDebug("Trace {0} profile end timestamp: {1} ns", _traceId, _endTimeNs); } } @@ -37,7 +37,7 @@ public void Finish() public ISerializable Collect(Transaction transaction) { // TODO change return type of CocoaSDKs CollectProfileBetween to NSMutableDictionary - var payload = SentryCocoaHybridSdk.CollectProfileBetween(_starTimeNs, _endTimeNs, _cocoaTraceId)?.MutableCopy() as NSMutableDictionary; + var payload = SentryCocoaHybridSdk.CollectProfileBetween(_startTimeNs, _endTimeNs, _cocoaTraceId)?.MutableCopy() as NSMutableDictionary; _options.LogDebug("Trace {0} profile payload collected", _traceId); ArgumentNullException.ThrowIfNull(payload, "profile payload"); From 4e185970207dc08d41cf5bc4d8685f354c77b30d Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 5 Dec 2023 09:52:06 +0100 Subject: [PATCH 09/43] fixup naming --- src/Sentry/Platforms/Cocoa/CocoaProfiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs index 330899d2b6..4ea7df69d2 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs @@ -21,7 +21,7 @@ public CocoaProfiler(SentryOptions options, ulong startTimeNs, SentryId traceId, _startTimeNs = startTimeNs; _traceId = traceId; _cocoaTraceId = cocoaTraceId; - _options.LogDebug("Trace {0} profile start timestamp: {1} ns", _traceId, _starTimeNs); + _options.LogDebug("Trace {0} profile start timestamp: {1} ns", _traceId, _startTimeNs); } /// From 1727f446841e798c55fb684d8be39a57a9f957e1 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 5 Dec 2023 09:55:55 +0100 Subject: [PATCH 10/43] update verifier tests --- test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt | 1 + test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt | 1 + test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt | 1 + 3 files changed, 3 insertions(+) diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index 60d2cee9db..a6416b2f02 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -633,6 +633,7 @@ namespace Sentry public int MaxCacheItems { get; set; } public int MaxQueueItems { get; set; } public Sentry.Extensibility.INetworkStatusListener? NetworkStatusListener { get; set; } + public double? ProfilesSampleRate { get; set; } public string? Release { get; set; } public Sentry.ReportAssembliesMode ReportAssembliesMode { get; set; } public bool RequestBodyCompressionBuffered { get; set; } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index 60d2cee9db..a6416b2f02 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -633,6 +633,7 @@ namespace Sentry public int MaxCacheItems { get; set; } public int MaxQueueItems { get; set; } public Sentry.Extensibility.INetworkStatusListener? NetworkStatusListener { get; set; } + public double? ProfilesSampleRate { get; set; } public string? Release { get; set; } public Sentry.ReportAssembliesMode ReportAssembliesMode { get; set; } public bool RequestBodyCompressionBuffered { get; set; } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index c2ad004e64..72783bc5b4 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -631,6 +631,7 @@ namespace Sentry public int MaxCacheItems { get; set; } public int MaxQueueItems { get; set; } public Sentry.Extensibility.INetworkStatusListener? NetworkStatusListener { get; set; } + public double? ProfilesSampleRate { get; set; } public string? Release { get; set; } public Sentry.ReportAssembliesMode ReportAssembliesMode { get; set; } public bool RequestBodyCompressionBuffered { get; set; } From bf8e17dd8d96aa2b81bc8b1258188c2db7f236c2 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 5 Dec 2023 10:09:11 +0100 Subject: [PATCH 11/43] update profiler tests --- ...ryProfiling.cs => ProfilingIntegration.cs} | 0 .../SamplingTransactionProfilerTests.cs | 42 +++++++++++++++++++ 2 files changed, 42 insertions(+) rename src/Sentry.Profiling/{SentryProfiling.cs => ProfilingIntegration.cs} (100%) diff --git a/src/Sentry.Profiling/SentryProfiling.cs b/src/Sentry.Profiling/ProfilingIntegration.cs similarity index 100% rename from src/Sentry.Profiling/SentryProfiling.cs rename to src/Sentry.Profiling/ProfilingIntegration.cs diff --git a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs index 647c793519..b2265ce850 100644 --- a/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs +++ b/test/Sentry.Profiling.Tests/SamplingTransactionProfilerTests.cs @@ -231,6 +231,48 @@ async Task VerifyAsync(HttpRequestMessage message) } } + [Fact] + public void ProfilerIntegration_WithProfilingDisabled_LeavesFactoryNull() + { + var options = new SentryOptions + { + Dsn = ValidDsn, + TracesSampleRate = 1.0, + ProfilesSampleRate = 0, + }; + options.AddIntegration(new ProfilingIntegration()); + using var hub = new Hub(options); + Assert.Null(hub.Options.TransactionProfilerFactory); + } + + [Fact] + public void ProfilerIntegration_WithTracingDisabled_LeavesFactoryNull() + { + var options = new SentryOptions + { + Dsn = ValidDsn, + TracesSampleRate = 0, + ProfilesSampleRate = 1.0, + }; + options.AddIntegration(new ProfilingIntegration()); + using var hub = new Hub(options); + Assert.Null(hub.Options.TransactionProfilerFactory); + } + + [Fact] + public void ProfilerIntegration_WithProfilingEnabled_SetsFactory() + { + var options = new SentryOptions + { + Dsn = ValidDsn, + TracesSampleRate = 1.0, + ProfilesSampleRate = 1.0, + }; + options.AddIntegration(new ProfilingIntegration()); + using var hub = new Hub(options); + Assert.NotNull(hub.Options.TransactionProfilerFactory); + } + [Fact] public void Downsampler_ShouldSample_Works() { From e5383e2c2ec35f530c7d1ed4199ce6b5a8eab8d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 14:14:26 +0100 Subject: [PATCH 12/43] build(deps): bump actions/setup-java from 3 to 4 (#2942) --- .github/workflows/device-tests-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/device-tests-android.yml b/.github/workflows/device-tests-android.yml index 0197641e47..6c7be879f7 100644 --- a/.github/workflows/device-tests-android.yml +++ b/.github/workflows/device-tests-android.yml @@ -26,7 +26,7 @@ jobs: submodules: recursive - name: Set Java Version - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: "11" From 221439c2975e694c39cff723de940d522ae59c10 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:06:56 +0100 Subject: [PATCH 13/43] workaround .net nativeAOT crash (#2943) --- CHANGELOG.md | 4 ++++ src/Sentry/TransactionTracer.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa4470722..d3d004b045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixes + +- Workaround a .NET 8 NativeAOT crash on transaction finish. ([#2943](https://github.com/getsentry/sentry-dotnet/pull/2943)) + ### API breaking Changes #### Changed APIs diff --git a/src/Sentry/TransactionTracer.cs b/src/Sentry/TransactionTracer.cs index ba89291d91..b8d0503541 100644 --- a/src/Sentry/TransactionTracer.cs +++ b/src/Sentry/TransactionTracer.cs @@ -165,7 +165,7 @@ public IReadOnlyList Fingerprint /// public IReadOnlyDictionary Tags => _tags; - private readonly ConcurrentBag _spans = new(); + private readonly ConcurrentBag _spans = new(); /// public IReadOnlyCollection Spans => _spans; From cf734bd541b81a100a2fe3e32a685659eed6695c Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 5 Dec 2023 01:25:49 +0000 Subject: [PATCH 14/43] release: 4.0.0-beta.4 --- CHANGELOG.md | 2 +- Directory.Build.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d004b045..6c31aabf97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased +## 4.0.0-beta.4 ### Fixes diff --git a/Directory.Build.props b/Directory.Build.props index 868cdef116..60702f803d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 4.0.0-beta.3 + 4.0.0-beta.4 12 true $(MSBuildThisFileDirectory).assets\Sentry.snk From 5bfcdf0e94137287b0ff72ff88811e51ef901e92 Mon Sep 17 00:00:00 2001 From: Bruno Garcia Date: Tue, 5 Dec 2023 01:39:44 -0500 Subject: [PATCH 15/43] fix: gcp link (#2944) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3197ee487..05c13769d1 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Sentry SDK for .NET | **Sentry.DiagnosticSource** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.DiagnosticSource.svg)](https://www.nuget.org/packages/Sentry.DiagnosticSource) | [![NuGet](https://img.shields.io/nuget/v/Sentry.DiagnosticSource.svg)](https://www.nuget.org/packages/Sentry.DiagnosticSource) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.DiagnosticSource.svg)](https://www.nuget.org/packages/Sentry.DiagnosticSource) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/performance/instrumentation/automatic-instrumentation/#diagnosticsource-integration) | | **Sentry.EntityFramework** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.EntityFramework.svg)](https://www.nuget.org/packages/Sentry.EntityFramework) | [![NuGet](https://img.shields.io/nuget/v/Sentry.EntityFramework.svg)](https://www.nuget.org/packages/Sentry.EntityFramework) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.EntityFramework.svg)](https://www.nuget.org/packages/Sentry.EntityFramework) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/entityframework) | **Sentry.Azure.Functions.Worker** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Azure.Functions.Worker.svg)](https://www.nuget.org/packages/Sentry.Azure.Functions.Worker) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Azure.Functions.Worker.svg)](https://www.nuget.org/packages/Sentry.Azure.Functions.Worker) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Azure.Functions.Worker.svg)](https://www.nuget.org/packages/Sentry.Azure.Functions.Worker) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/azure-functions-worker/) | -| **Sentry.Google.Cloud.Functions** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Google.Cloud.Functions.svg)](https://www.nuget.org/packages/Sentry.Google.Cloud.Functions) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Google.Cloud.Functions.svg)](https://www.nuget.org/packages/Sentry.Google.Cloud.Functions) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Google.Cloud.Functions.svg)](https://www.nuget.org/packages/Sentry.Google.Cloud.Functions) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/gcp-functions/) | +| **Sentry.Google.Cloud.Functions** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Google.Cloud.Functions.svg)](https://www.nuget.org/packages/Sentry.Google.Cloud.Functions) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Google.Cloud.Functions.svg)](https://www.nuget.org/packages/Sentry.Google.Cloud.Functions) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Google.Cloud.Functions.svg)](https://www.nuget.org/packages/Sentry.Google.Cloud.Functions) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/google-cloud-functions/) | | **Sentry.Maui** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Maui.svg)](https://www.nuget.org/packages/Sentry.Maui) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Maui.svg)](https://www.nuget.org/packages/Sentry.Maui) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Maui.svg)](https://www.nuget.org/packages/Sentry.Maui) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/maui) | | **Sentry.Serilog** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.Serilog.svg)](https://www.nuget.org/packages/Serilog) | [![NuGet](https://img.shields.io/nuget/v/Sentry.Serilog.svg)](https://www.nuget.org/packages/Sentry.Serilog) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.Serilog.svg)](https://www.nuget.org/packages/Sentry.Serilog) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/serilog) | | **Sentry.NLog** | [![Downloads](https://img.shields.io/nuget/dt/Sentry.NLog.svg)](https://www.nuget.org/packages/Sentry.NLog) | [![NuGet](https://img.shields.io/nuget/v/Sentry.NLog.svg)](https://www.nuget.org/packages/Sentry.NLog) | [![NuGet](https://img.shields.io/nuget/vpre/Sentry.NLog.svg)](https://www.nuget.org/packages/Sentry.NLog) | [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dotnet/guides/nlog) | From 6d4f658bc6b8c8059a4651a081283b3bd42341cb Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 5 Dec 2023 11:11:06 +0100 Subject: [PATCH 16/43] update sample --- samples/Sentry.Samples.Ios/AppDelegate.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/Sentry.Samples.Ios/AppDelegate.cs b/samples/Sentry.Samples.Ios/AppDelegate.cs index 105a53dc66..9f0557234b 100644 --- a/samples/Sentry.Samples.Ios/AppDelegate.cs +++ b/samples/Sentry.Samples.Ios/AppDelegate.cs @@ -17,6 +17,7 @@ public override bool FinishedLaunching(UIApplication application, NSDictionary l o.Debug = true; o.Dsn = "https://eb18e953812b41c3aeb042e666fd3b5c@o447951.ingest.sentry.io/5428537"; o.TracesSampleRate = 1.0; + o.ProfilesSampleRate = 1.0; }); // create a new window instance based on the screen size From 8246932a9575900798d8498c96a0f54939042d58 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 5 Dec 2023 12:07:57 +0100 Subject: [PATCH 17/43] chore: update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c31aabf97..6f94abccea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- iOS profiling support (alpha). ([#2930](https://github.com/getsentry/sentry-dotnet/pull/2930)) + ## 4.0.0-beta.4 ### Fixes From 5d12ed07f927efa9d0230b89c92ceb8ef3cf470d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 5 Dec 2023 01:25:49 +0000 Subject: [PATCH 18/43] release: 4.0.0-beta.4 From 7fce7374075ad00db7b7575889defddf1439f2d3 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 5 Dec 2023 12:13:26 +0100 Subject: [PATCH 19/43] fixup changelog --- CHANGELOG.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7a29c3b9b..6f94abccea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,6 @@ # Changelog -## 4.0.0-beta.4 - -### Fixes - -- Workaround a .NET 8 NativeAOT crash on transaction finish. ([#2943](https://github.com/getsentry/sentry-dotnet/pull/2943)) +## Unreleased ### Features From f91846aa196e8d48ea8bb68f291f904223b9dcf6 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 8 Dec 2023 13:34:53 +0100 Subject: [PATCH 20/43] profiler hub tests --- src/Sentry/Platforms/Cocoa/CocoaProfiler.cs | 1 - test/Sentry.Tests/HubTests.cs | 140 ++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs index 4ea7df69d2..d62e651b3d 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs @@ -1,6 +1,5 @@ using Sentry.Extensibility; using Sentry.Internal; -using Sentry.Protocol; using Sentry.Cocoa.Extensions; using Sentry.Cocoa.Facades; diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index b79a5fb4fa..6efa0eb35d 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -983,6 +983,146 @@ public void CaptureTransaction_AfterTransactionFinishes_ResetsTransactionOnScope hub.ConfigureScope(scope => scope.Transaction.Should().BeNull()); } +#nullable enable + private class ThrowingProfilerFactory : ITransactionProfilerFactory + { + public ITransactionProfiler? Start(ITransactionTracer _, CancellationToken __) => new ThrowingProfiler(); + } + + internal class ThrowingProfiler : ITransactionProfiler + { + public void Finish() {} + + public Sentry.Protocol.Envelopes.ISerializable Collect(Transaction _) => throw new Exception("test"); + } + + private class AsyncThrowingProfilerFactory : ITransactionProfilerFactory + { + public ITransactionProfiler? Start(ITransactionTracer _, CancellationToken __) => new AsyncThrowingProfiler(); + } + + internal class AsyncThrowingProfiler : ITransactionProfiler + { + public void Finish() {} + + public Sentry.Protocol.Envelopes.ISerializable Collect(Transaction transaction) + => AsyncJsonSerializable.CreateFrom(CollectAsync(transaction)); + + private async Task CollectAsync(Transaction transaction) + { + await Task.Delay(1); + throw new Exception("test"); + } + } + private class TestProfilerFactory : ITransactionProfilerFactory + { + public ITransactionProfiler? Start(ITransactionTracer _, CancellationToken __) => new TestProfiler(); + } + + internal class TestProfiler : ITransactionProfiler + { + public void Finish() {} + + public Sentry.Protocol.Envelopes.ISerializable Collect(Transaction _) => new JsonSerializable(new ProfileInfo()); + } + +#nullable disable + + [Fact] + public void CaptureTransaction_WithSyncThrowingTransactionProfiler_DoesntSendTransaction() + { + // Arrange + var transport = new FakeTransport(); + using var hub = new Hub(new SentryOptions + { + Dsn = ValidDsn, + TracesSampleRate = 1.0, + ProfilesSampleRate = 1.0, + Transport = transport, + TransactionProfilerFactory = new ThrowingProfilerFactory() + }); + + // Act + hub.StartTransaction("foo", "bar").Finish(); + + // Assert + transport.GetSentEnvelopes().Should().BeEmpty(); + } + + [Fact] + public void CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsTransactionWithoutProfile() + { + // Arrange + var transport = new FakeTransport(); + var logger = new TestOutputDiagnosticLogger(_output); + using var hub = new Hub(new SentryOptions + { + Dsn = ValidDsn, + TracesSampleRate = 1.0, + ProfilesSampleRate = 1.0, + Transport = transport, + TransactionProfilerFactory = new AsyncThrowingProfilerFactory(), + DiagnosticLogger = logger, + }); + + // Act + hub.StartTransaction("foo", "bar").Finish(); + hub.FlushAsync().Wait(); + + // Assert + transport.GetSentEnvelopes().Should().HaveCount(1); + var envelope = transport.GetSentEnvelopes().Single(); + + using var stream = new MemoryStream(); + envelope.Serialize(stream, logger); + stream.Flush(); + var envelopeStr = Encoding.UTF8.GetString(stream.ToArray()); + var lines = envelopeStr.Split('\n'); + lines.Should().HaveCount(4); + lines[0].Should().StartWith("{\"sdk\""); + lines[1].Should().StartWith("{\"type\":\"transaction\",\"length\":"); + lines[2].Should().StartWith("{\"type\":\"transaction\""); + lines[3].Should().BeEmpty(); + } + + [Fact] + public void CaptureTransaction_WithTransactionProfiler_SendsTransactionWithProfile() + { + // Arrange + var transport = new FakeTransport(); + var logger = new TestOutputDiagnosticLogger(_output); + using var hub = new Hub(new SentryOptions + { + Dsn = ValidDsn, + TracesSampleRate = 1.0, + ProfilesSampleRate = 1.0, + Transport = transport, + TransactionProfilerFactory = new TestProfilerFactory(), + DiagnosticLogger = logger, + }); + + // Act + hub.StartTransaction("foo", "bar").Finish(); + hub.FlushAsync().Wait(); + + // Assert + transport.GetSentEnvelopes().Should().HaveCount(1); + var envelope = transport.GetSentEnvelopes().Single(); + + using var stream = new MemoryStream(); + envelope.Serialize(stream, logger); + stream.Flush(); + var envelopeStr = Encoding.UTF8.GetString(stream.ToArray()); + var lines = envelopeStr.Split('\n'); + lines.Should().HaveCount(6); + lines[0].Should().StartWith("{\"sdk\""); + lines[1].Should().StartWith("{\"type\":\"transaction\",\"length\":"); + lines[2].Should().StartWith("{\"type\":\"transaction\""); + lines[3].Should().StartWith("{\"type\":\"profile\",\"length\":"); + lines[4].Should().Contain("\"profile\":{"); + lines[5].Should().BeEmpty(); + } + [Fact] public void Dispose_IsEnabled_SetToFalse() { From 5acfdc23e947c8b7f902fdfa1aa80993aeb75f70 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 8 Dec 2023 14:04:52 +0100 Subject: [PATCH 21/43] improve profiler collect() error handling --- .../SamplingTransactionProfiler.cs | 2 +- src/Sentry/Internal/ITransactionProfiler.cs | 2 +- src/Sentry/Platforms/Cocoa/CocoaProfiler.cs | 15 ++++++++++++--- src/Sentry/Protocol/Envelopes/Envelope.cs | 9 +++++++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Sentry.Profiling/SamplingTransactionProfiler.cs b/src/Sentry.Profiling/SamplingTransactionProfiler.cs index 416d2d7f16..ebfa873b57 100644 --- a/src/Sentry.Profiling/SamplingTransactionProfiler.cs +++ b/src/Sentry.Profiling/SamplingTransactionProfiler.cs @@ -96,7 +96,7 @@ public void Finish() } /// - public Protocol.Envelopes.ISerializable Collect(Transaction transaction) + public Protocol.Envelopes.ISerializable? Collect(Transaction transaction) => Protocol.Envelopes.AsyncJsonSerializable.CreateFrom(CollectAsync(transaction)); internal async Task CollectAsync(Transaction transaction) diff --git a/src/Sentry/Internal/ITransactionProfiler.cs b/src/Sentry/Internal/ITransactionProfiler.cs index 7bff440e70..14e90fdd23 100644 --- a/src/Sentry/Internal/ITransactionProfiler.cs +++ b/src/Sentry/Internal/ITransactionProfiler.cs @@ -25,5 +25,5 @@ internal interface ITransactionProfiler /// Process and collect the profile. /// The collected profile. - ISerializable Collect(Transaction transaction); + ISerializable? Collect(Transaction transaction); } diff --git a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs index d62e651b3d..be8045ef85 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs @@ -33,17 +33,26 @@ public void Finish() } } - public ISerializable Collect(Transaction transaction) + public ISerializable? Collect(Transaction transaction) { // TODO change return type of CocoaSDKs CollectProfileBetween to NSMutableDictionary var payload = SentryCocoaHybridSdk.CollectProfileBetween(_startTimeNs, _endTimeNs, _cocoaTraceId)?.MutableCopy() as NSMutableDictionary; + if (payload is null) + { + _options.LogWarning("Trace {0} collected profile payload is null", _traceId); + return null; + } _options.LogDebug("Trace {0} profile payload collected", _traceId); - ArgumentNullException.ThrowIfNull(payload, "profile payload"); payload["timestamp"] = transaction.StartTimestamp.ToString("o", CultureInfo.InvariantCulture).ToNSString(); var payloadTx = payload["transaction"]?.MutableCopy() as NSMutableDictionary; - ArgumentNullException.ThrowIfNull(payloadTx, "profile payload transaction"); + if (payloadTx is null) + { + _options.LogWarning("Trace {0} collected profile payload doesn't have transaction information", _traceId); + return null; + } + payloadTx["id"] = transaction.EventId.ToString().ToNSString(); payloadTx["trace_id"] = _traceId.ToString().ToNSString(); payloadTx["name"] = transaction.Name.ToNSString(); diff --git a/src/Sentry/Protocol/Envelopes/Envelope.cs b/src/Sentry/Protocol/Envelopes/Envelope.cs index 0e711fc6a4..ac1ef7a09e 100644 --- a/src/Sentry/Protocol/Envelopes/Envelope.cs +++ b/src/Sentry/Protocol/Envelopes/Envelope.cs @@ -313,8 +313,13 @@ public static Envelope FromTransaction(Transaction transaction) if (transaction.TransactionProfiler is { } profiler) { - // Profiler.Collect() may throw in which case the EnvelopeItem won't serialize. - items.Add(EnvelopeItem.FromProfileInfo(profiler.Collect(transaction))); + // Profiler.Collect() returns an ISerializable which may also throw asynchronously, which is handled down + // the road in AsyncJsonSerializable and the EnvelopeItem won't serialize and is omitted. + // However, it mustn't throw synchronously because that would prevent the whole transaction being sent. + if (profiler.Collect(transaction) is {} profileInfo) + { + items.Add(EnvelopeItem.FromProfileInfo(profileInfo)); + } } return new Envelope(eventId, header, items); From c56da69e22b5ac73d4dfa9bab856b5e5de56ee22 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 8 Dec 2023 14:59:59 +0100 Subject: [PATCH 22/43] fixup device test script --- scripts/device-test.ps1 | 8 ++++++-- scripts/parse-xunit2-xml.ps1 | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/device-test.ps1 b/scripts/device-test.ps1 index 75de2c4373..da724fb412 100644 --- a/scripts/device-test.ps1 +++ b/scripts/device-test.ps1 @@ -22,7 +22,7 @@ Push-Location $PSScriptRoot/.. try { $tfm = 'net7.0-' - $arch = $(uname -m) -eq 'arm64' ? 'arm64' : 'x64' + $arch = (!$IsWindows -and $(uname -m) -eq 'arm64') ? 'arm64' : 'x64' if ($Platform -eq 'android') { $tfm += 'android' @@ -75,7 +75,11 @@ try } finally { - scripts/parse-xunit2-xml.ps1 ./test_output/TestResults.xml | Out-File $env:GITHUB_STEP_SUMMARY + if ($CI) + { + scripts/parse-xunit2-xml.ps1 (Get-Item ./test_output/*.xml).FullName | Out-File $env:GITHUB_STEP_SUMMARY + } + } } } diff --git a/scripts/parse-xunit2-xml.ps1 b/scripts/parse-xunit2-xml.ps1 index 3d2d2f72fe..4ef816bbd8 100644 --- a/scripts/parse-xunit2-xml.ps1 +++ b/scripts/parse-xunit2-xml.ps1 @@ -4,7 +4,7 @@ Set-StrictMode -Version Latest if ([string]::IsNullOrEmpty($File) -or !(Test-Path($File))) { - Write-Warning "Test output file was not found." + Write-Warning "Test output file was not found: '$File'." # Return success exit code so that GitHub Actions highlights the failure in the test run, rather than in this script. return From 4a92fac89e08e3169a29f5e32a6d91b67d7fdb71 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 8 Dec 2023 15:39:11 +0100 Subject: [PATCH 23/43] fix: hub tests --- test/Sentry.Tests/HubTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 6efa0eb35d..bf2127d0dc 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1036,6 +1036,7 @@ public void CaptureTransaction_WithSyncThrowingTransactionProfiler_DoesntSendTra using var hub = new Hub(new SentryOptions { Dsn = ValidDsn, + AutoSessionTracking = false, TracesSampleRate = 1.0, ProfilesSampleRate = 1.0, Transport = transport, @@ -1058,6 +1059,7 @@ public void CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsTransac using var hub = new Hub(new SentryOptions { Dsn = ValidDsn, + AutoSessionTracking = false, TracesSampleRate = 1.0, ProfilesSampleRate = 1.0, Transport = transport, @@ -1094,6 +1096,7 @@ public void CaptureTransaction_WithTransactionProfiler_SendsTransactionWithProfi using var hub = new Hub(new SentryOptions { Dsn = ValidDsn, + AutoSessionTracking = false, TracesSampleRate = 1.0, ProfilesSampleRate = 1.0, Transport = transport, From 6772bc1eb030e547525981661d23d79570294e55 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Fri, 8 Dec 2023 19:49:39 +0100 Subject: [PATCH 24/43] Update CHANGELOG.md Co-authored-by: Bruno Garcia --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 702718d6d7..0cfb914d7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - iOS profiling support (alpha). ([#2930](https://github.com/getsentry/sentry-dotnet/pull/2930)) + ### Fixes - Stop Sentry for MacCatalyst from creating `default.profraw` in the app bundle using xcodebuild archive to build sentry-cocoa ([#2960](https://github.com/getsentry/sentry-dotnet/pull/2960)) From 07eb383e29c6f5cf27fee4afae723bd068ca1938 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Fri, 8 Dec 2023 19:49:50 +0100 Subject: [PATCH 25/43] Update src/Sentry.Profiling/ProfilingIntegration.cs Co-authored-by: Bruno Garcia --- src/Sentry.Profiling/ProfilingIntegration.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sentry.Profiling/ProfilingIntegration.cs b/src/Sentry.Profiling/ProfilingIntegration.cs index f38d31d2bf..c38550c77d 100644 --- a/src/Sentry.Profiling/ProfilingIntegration.cs +++ b/src/Sentry.Profiling/ProfilingIntegration.cs @@ -10,7 +10,8 @@ public class ProfilingIntegration : ISdkIntegration /// public void Register(IHub hub, SentryOptions options) { - if (options.IsProfilingEnabled) { + if (options.IsProfilingEnabled) + { options.TransactionProfilerFactory = SamplingTransactionProfilerFactory.Create(options); } } From 37ca589ce4801ab676991704ca0b0146ab2eec1c Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Fri, 8 Dec 2023 19:51:00 +0100 Subject: [PATCH 26/43] Update src/Sentry/Platforms/Cocoa/CocoaProfiler.cs Co-authored-by: Bruno Garcia --- src/Sentry/Platforms/Cocoa/CocoaProfiler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs index be8045ef85..2229f96ba2 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs @@ -44,8 +44,6 @@ public void Finish() } _options.LogDebug("Trace {0} profile payload collected", _traceId); - payload["timestamp"] = transaction.StartTimestamp.ToString("o", CultureInfo.InvariantCulture).ToNSString(); - var payloadTx = payload["transaction"]?.MutableCopy() as NSMutableDictionary; if (payloadTx is null) { @@ -53,6 +51,7 @@ public void Finish() return null; } + payload["timestamp"] = transaction.StartTimestamp.ToString("o", CultureInfo.InvariantCulture).ToNSString(); payloadTx["id"] = transaction.EventId.ToString().ToNSString(); payloadTx["trace_id"] = _traceId.ToString().ToNSString(); payloadTx["name"] = transaction.Name.ToNSString(); From 6364fd063023a3f061645aa8632f0c25f804e483 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Fri, 8 Dec 2023 19:54:45 +0100 Subject: [PATCH 27/43] Update src/Sentry/Platforms/Cocoa/SentrySdk.cs Co-authored-by: Bruno Garcia --- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index e5f741edc2..58786b4da0 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -188,7 +188,8 @@ private static void InitSentryCocoaSdk(SentryOptions options) options.EnableScopeSync = true; options.ScopeObserver = new CocoaScopeObserver(options); - if (options.IsProfilingEnabled) { + if (options.IsProfilingEnabled) + { options.TransactionProfilerFactory = new CocoaProfilerFactory(options); } From 20807abe09dbd2991375726da8c3aebe8f313036 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 8 Dec 2023 20:01:48 +0100 Subject: [PATCH 28/43] review changes --- src/Sentry/Platforms/Cocoa/CocoaProfiler.cs | 3 ++- .../Cocoa/Facades/SerializableNSObject.cs | 20 +++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs index 2229f96ba2..369b258ce8 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs @@ -12,10 +12,11 @@ internal class CocoaProfiler : ITransactionProfiler private readonly CocoaSdk.SentryId _cocoaTraceId; private readonly ulong _startTimeNs; private ulong _endTimeNs; - private readonly SentryStopwatch _stopwatch = SentryStopwatch.StartNew(); + private readonly SentryStopwatch _stopwatch; public CocoaProfiler(SentryOptions options, ulong startTimeNs, SentryId traceId, CocoaSdk.SentryId cocoaTraceId) { + _stopwatch = SentryStopwatch.StartNew(); _options = options; _startTimeNs = startTimeNs; _traceId = traceId; diff --git a/src/Sentry/Platforms/Cocoa/Facades/SerializableNSObject.cs b/src/Sentry/Platforms/Cocoa/Facades/SerializableNSObject.cs index 8344f9b089..b82d51850e 100644 --- a/src/Sentry/Platforms/Cocoa/Facades/SerializableNSObject.cs +++ b/src/Sentry/Platforms/Cocoa/Facades/SerializableNSObject.cs @@ -12,10 +12,20 @@ public SerializableNSObject(NSObject value) _value = value; } - public Task SerializeAsync(Stream stream, IDiagnosticLogger? logger, CancellationToken cancellationToken = default) - => Task.Run(() => Serialize(stream, logger)); // TODO do we need a better implementation? + public async Task SerializeAsync(Stream stream, IDiagnosticLogger? logger, CancellationToken cancellationToken = default) + { + using var dataStream = Serialize().AsStream(); + await dataStream.CopyToAsync(stream, cancellationToken).ConfigureAwait(false); + } + + public void Serialize(Stream stream, IDiagnosticLogger? logger) + { + using var dataStream = Serialize().AsStream(); + dataStream.CopyTo(stream); + } - public void Serialize(Stream stream, IDiagnosticLogger? logger) { + private NSData Serialize() + { // For types that implement Sentry Cocoa's SentrySerializable protocol (interface), // We should call that first, and then serialize the result to JSON later. var obj = _value is CocoaSdk.ISentrySerializable serializable @@ -26,8 +36,6 @@ public void Serialize(Stream stream, IDiagnosticLogger? logger) { // See https://developer.apple.com/documentation/foundation/nsjsonserialization // TODO can we pipe NSOutputStream directly? It can be passed as a second argument // TODO how do we check if the error happened? Is it non-null? Then we can rethrow as NSErrorException? - var data = NSJsonSerialization.Serialize(obj, 0, out NSError error); - using var dataStream = data.AsStream(); - dataStream.CopyTo(stream); + return NSJsonSerialization.Serialize(obj, 0, out NSError error); } } From 539cc8a69c66a1f3958b9ee009a5bcd32d8887fd Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 8 Dec 2023 20:05:49 +0100 Subject: [PATCH 29/43] review changes --- src/Sentry/Platforms/Cocoa/CocoaProfilerFactory.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/CocoaProfilerFactory.cs b/src/Sentry/Platforms/Cocoa/CocoaProfilerFactory.cs index bc9953ed13..a58bd12e57 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaProfilerFactory.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaProfilerFactory.cs @@ -17,10 +17,6 @@ internal CocoaProfilerFactory(SentryOptions options) { var traceId = tracer.TraceId.ToCocoaSentryId(); var startTime = SentryCocoaHybridSdk.StartProfilerForTrace(traceId); - if (startTime == 0) - { - return null; - } return new CocoaProfiler(_options, startTime, tracer.TraceId, traceId); } } From 1f93d199cbee608e315ac893814cc44599bf1086 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 8 Dec 2023 20:28:23 +0100 Subject: [PATCH 30/43] profiler integration test --- src/Sentry/Platforms/Cocoa/CocoaProfiler.cs | 2 +- test/Sentry.Tests/ProfilerTests.cs | 96 +++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 test/Sentry.Tests/ProfilerTests.cs diff --git a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs index 369b258ce8..9f73e53703 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaProfiler.cs @@ -52,11 +52,11 @@ public void Finish() return null; } - payload["timestamp"] = transaction.StartTimestamp.ToString("o", CultureInfo.InvariantCulture).ToNSString(); payloadTx["id"] = transaction.EventId.ToString().ToNSString(); payloadTx["trace_id"] = _traceId.ToString().ToNSString(); payloadTx["name"] = transaction.Name.ToNSString(); payload["transaction"] = payloadTx; + payload["timestamp"] = transaction.StartTimestamp.ToString("o", CultureInfo.InvariantCulture).ToNSString(); return new SerializableNSObject(payload); } } diff --git a/test/Sentry.Tests/ProfilerTests.cs b/test/Sentry.Tests/ProfilerTests.cs new file mode 100644 index 0000000000..7c76f72ae9 --- /dev/null +++ b/test/Sentry.Tests/ProfilerTests.cs @@ -0,0 +1,96 @@ +namespace Sentry.Tests; + +public class ProfilerTests +{ + private readonly IDiagnosticLogger _testOutputLogger; + + public ProfilerTests(ITestOutputHelper output) + { + _testOutputLogger = new TestOutputDiagnosticLogger(output); + } + +#if __IOS__ + [Fact] +#else + [Theory(Skip = "Profiling is not supported on this platform")] +#endif + public async Task Profiler_RunningUnderFullClient_SendsProfileData() + { + var tcs = new TaskCompletionSource(); + async Task VerifyAsync(HttpRequestMessage message) + { + var payload = await message.Content!.ReadAsStringAsync(); + // We're actually looking for type:profile but it must be sent in the same envelope as the transaction. + if (payload.Contains("\"type\":\"transaction\"")) + { + tcs.TrySetResult(payload); + } + } + + var cts = new CancellationTokenSource(); + cts.Token.Register(() => tcs.TrySetCanceled()); + + var options = new SentryOptions + { + Dsn = ValidDsn, + // So we don't need to deal with gzip'ed payload + RequestBodyCompressionLevel = CompressionLevel.NoCompression, + CreateHttpMessageHandler = () => new CallbackHttpClientHandler(VerifyAsync), + // Not to send some session envelope + AutoSessionTracking = false, + Debug = true, + DiagnosticLogger = _testOutputLogger, + TracesSampleRate = 1.0, + ProfilesSampleRate = 1.0, + }; + + try + { + using var hub = new Hub(options); + + var clock = SentryStopwatch.StartNew(); + var tx = hub.StartTransaction("name", "op"); + RunForMs(500); + tx.Finish(); + await hub.FlushAsync(); + + // Synchronizing in the tests to go through the caching and http transports + cts.CancelAfter(options.FlushTimeout + TimeSpan.FromSeconds(1)); + var ex = Record.Exception(() => tcs.Task.Wait()); + ex.Should().BeNull(); + tcs.Task.IsCompleted.Should().BeTrue(); + + var envelopeLines = tcs.Task.Result.Split('\n'); + envelopeLines.Length.Should().Be(6); + + // header rows before payloads + envelopeLines[1].Should().StartWith("{\"type\":\"transaction\""); + envelopeLines[3].Should().StartWith("{\"type\":\"profile\""); + + var transaction = Json.Parse(envelopeLines[2], Transaction.FromJson); + + // TODO do we want to bother with JSON parsing just to do this? Doing at least simple checks for now... + // var profileInfo = Json.Parse(envelopeLines[4], ProfileInfo.FromJson); + // ValidateProfile(profileInfo.Profile, elapsedNanoseconds); + envelopeLines[4].Should().Contain("\"profile\":{"); + envelopeLines[4].Should().Contain($"\"id\":\"{transaction.EventId}\""); + envelopeLines[4].Length.Should().BeGreaterThan(10000); + } + finally + { + // Ensure the task is complete before leaving the test so there's no async code left running in next tests. + tcs.TrySetResult(""); + await tcs.Task; + } + } + + private void RunForMs(int milliseconds) + { + var clock = Stopwatch.StartNew(); + while (clock.ElapsedMilliseconds < milliseconds) + { + _testOutputLogger.LogDebug("Sleeping... time remaining: {0} ms", milliseconds - clock.ElapsedMilliseconds); + Thread.Sleep((int)Math.Min(milliseconds / 5, milliseconds - clock.ElapsedMilliseconds)); + } + } +} From bf113644d8526812427ba4fe90fea601e167b920 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:31:27 +0100 Subject: [PATCH 31/43] Update ProfilerTests.cs --- test/Sentry.Tests/ProfilerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Tests/ProfilerTests.cs b/test/Sentry.Tests/ProfilerTests.cs index 7c76f72ae9..7b90b660fb 100644 --- a/test/Sentry.Tests/ProfilerTests.cs +++ b/test/Sentry.Tests/ProfilerTests.cs @@ -12,7 +12,7 @@ public ProfilerTests(ITestOutputHelper output) #if __IOS__ [Fact] #else - [Theory(Skip = "Profiling is not supported on this platform")] + [Fact(Skip = "Profiling is not supported on this platform")] #endif public async Task Profiler_RunningUnderFullClient_SendsProfileData() { From a17b3a077f5c9861cc5e4ce4763fcdc4ca477007 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 8 Dec 2023 22:27:00 +0100 Subject: [PATCH 32/43] test cleanup --- test/Sentry.Tests/ProfilerTests.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/test/Sentry.Tests/ProfilerTests.cs b/test/Sentry.Tests/ProfilerTests.cs index 7b90b660fb..a02ab82861 100644 --- a/test/Sentry.Tests/ProfilerTests.cs +++ b/test/Sentry.Tests/ProfilerTests.cs @@ -27,9 +27,6 @@ async Task VerifyAsync(HttpRequestMessage message) } } - var cts = new CancellationTokenSource(); - cts.Token.Register(() => tcs.TrySetCanceled()); - var options = new SentryOptions { Dsn = ValidDsn, @@ -54,12 +51,9 @@ async Task VerifyAsync(HttpRequestMessage message) tx.Finish(); await hub.FlushAsync(); - // Synchronizing in the tests to go through the caching and http transports - cts.CancelAfter(options.FlushTimeout + TimeSpan.FromSeconds(1)); - var ex = Record.Exception(() => tcs.Task.Wait()); - ex.Should().BeNull(); - tcs.Task.IsCompleted.Should().BeTrue(); - + // Asserts + var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(1_000)).ConfigureAwait(false); + completedTask.Should().Be(tcs.Task); var envelopeLines = tcs.Task.Result.Split('\n'); envelopeLines.Length.Should().Be(6); From 0c4ebc3c35c7971a9bede41d5b88626fddff45f3 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sun, 10 Dec 2023 13:51:16 +0100 Subject: [PATCH 33/43] only set profiler factory if not previously set --- src/Sentry.Profiling/ProfilingIntegration.cs | 2 +- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry.Profiling/ProfilingIntegration.cs b/src/Sentry.Profiling/ProfilingIntegration.cs index c38550c77d..c94cd2d147 100644 --- a/src/Sentry.Profiling/ProfilingIntegration.cs +++ b/src/Sentry.Profiling/ProfilingIntegration.cs @@ -12,7 +12,7 @@ public void Register(IHub hub, SentryOptions options) { if (options.IsProfilingEnabled) { - options.TransactionProfilerFactory = SamplingTransactionProfilerFactory.Create(options); + options.TransactionProfilerFactory ??= SamplingTransactionProfilerFactory.Create(options); } } } diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 58786b4da0..eafa6995ed 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -190,7 +190,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) if (options.IsProfilingEnabled) { - options.TransactionProfilerFactory = new CocoaProfilerFactory(options); + options.TransactionProfilerFactory ??= new CocoaProfilerFactory(options); } // TODO: Pause/Resume From 45617e74fd4aeacdca09225a0767c0e1d39a3cde Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sun, 10 Dec 2023 14:38:04 +0100 Subject: [PATCH 34/43] trying to find stuck test --- .github/workflows/device-tests-ios.yml | 6 +++--- scripts/parse-xunit2-xml.ps1 | 10 ++++++---- test/Sentry.Tests/HubTests.cs | 23 ++++++++++++----------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/.github/workflows/device-tests-ios.yml b/.github/workflows/device-tests-ios.yml index 241c70be38..34e67aab3b 100644 --- a/.github/workflows/device-tests-ios.yml +++ b/.github/workflows/device-tests-ios.yml @@ -68,9 +68,9 @@ jobs: continue-on-error: true run: pwsh scripts/device-test.ps1 ios -Run - - name: Retry Tests (if previous failed to run) - if: steps.first-run.outcome == 'failure' - run: pwsh scripts/device-test.ps1 ios -Run + # - name: Retry Tests (if previous failed to run) + # if: steps.first-run.outcome == 'failure' + # run: pwsh scripts/device-test.ps1 ios -Run - name: Upload results if: success() || failure() diff --git a/scripts/parse-xunit2-xml.ps1 b/scripts/parse-xunit2-xml.ps1 index 4ef816bbd8..fb08cfaead 100644 --- a/scripts/parse-xunit2-xml.ps1 +++ b/scripts/parse-xunit2-xml.ps1 @@ -20,7 +20,7 @@ function ElementText([System.Xml.XmlElement] $element) $summary = "## Summary`n`n" $summary += "| Assembly | Passed | Failed | Skipped |`n" $summary += "| -------- | -----: | -----: | ------: |`n" -$failures = "" +$failures = '' foreach ($assembly in $xml.assemblies.assembly) { $summary += "| $($assembly.name) | $($assembly.passed) | $($assembly.failed) | $($assembly.skipped) |`n" @@ -30,13 +30,13 @@ foreach ($assembly in $xml.assemblies.assembly) $failures += "### $($assembly.name)`n" foreach ($test in $assembly.collection.test) { - if ($test.result -eq "Pass") + if ($test.result -eq 'Pass') { continue } $failures += "#### $($test.name.Replace('\"', '"'))" - if ($test.result -eq "Skip") + if ($test.result -eq 'Skip') { $failures += " - Skipped`n" $failures += "$(ElementText $test.reason)" @@ -44,9 +44,11 @@ foreach ($assembly in $xml.assemblies.assembly) else { $failures += " - $($test.result)ed`n" - if ($test.PSobject.Properties.name -match "output") + if ($test.PSobject.Properties.name -match 'output') { + $failures += '```' + "`n" $failures += "$(ElementText $test.output)`n" + $failures += '```' } $failures += '```' + "`n" $failures += "$(ElementText $test.failure.message)`n" diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index bf2127d0dc..dba7eba7dc 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -11,7 +11,7 @@ private class Fixture { public SentryOptions Options { get; } - public ISentryClient Client { get; set; } + public ISentryClient Client { get; set; } public ISessionManager SessionManager { get; set; } @@ -948,7 +948,7 @@ public void ContinueTrace_ReceivesHeadersAsStrings_SetsPropagationContextAndRetu SentryId.Parse("43365712692146d08ee11a729dfbcaca"), SpanId.Parse("1000000000000000")); hub.ConfigureScope(scope => scope.PropagationContext = propagationContext); var traceHeader = "5bd5f6d346b442dd9177dce9302fd737-2000000000000000"; - var baggageHeader ="sentry-trace_id=5bd5f6d346b442dd9177dce9302fd737, sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=1.0"; + var baggageHeader = "sentry-trace_id=5bd5f6d346b442dd9177dce9302fd737, sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=1.0"; hub.ConfigureScope(scope => scope.PropagationContext.TraceId.Should().Be("43365712692146d08ee11a729dfbcaca")); // Sanity check @@ -991,7 +991,7 @@ private class ThrowingProfilerFactory : ITransactionProfilerFactory internal class ThrowingProfiler : ITransactionProfiler { - public void Finish() {} + public void Finish() { } public Sentry.Protocol.Envelopes.ISerializable Collect(Transaction _) => throw new Exception("test"); } @@ -1003,7 +1003,7 @@ private class AsyncThrowingProfilerFactory : ITransactionProfilerFactory internal class AsyncThrowingProfiler : ITransactionProfiler { - public void Finish() {} + public void Finish() { } public Sentry.Protocol.Envelopes.ISerializable Collect(Transaction transaction) => AsyncJsonSerializable.CreateFrom(CollectAsync(transaction)); @@ -1021,7 +1021,7 @@ private class TestProfilerFactory : ITransactionProfilerFactory internal class TestProfiler : ITransactionProfiler { - public void Finish() {} + public void Finish() { } public Sentry.Protocol.Envelopes.ISerializable Collect(Transaction _) => new JsonSerializable(new ProfileInfo()); } @@ -1029,7 +1029,7 @@ public void Finish() {} #nullable disable [Fact] - public void CaptureTransaction_WithSyncThrowingTransactionProfiler_DoesntSendTransaction() + public async Task CaptureTransaction_WithSyncThrowingTransactionProfiler_DoesntSendTransaction() { // Arrange var transport = new FakeTransport(); @@ -1045,13 +1045,14 @@ public void CaptureTransaction_WithSyncThrowingTransactionProfiler_DoesntSendTra // Act hub.StartTransaction("foo", "bar").Finish(); + await hub.FlushAsync(); // Assert transport.GetSentEnvelopes().Should().BeEmpty(); } [Fact] - public void CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsTransactionWithoutProfile() + public async Task CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsTransactionWithoutProfile() { // Arrange var transport = new FakeTransport(); @@ -1069,7 +1070,7 @@ public void CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsTransac // Act hub.StartTransaction("foo", "bar").Finish(); - hub.FlushAsync().Wait(); + await hub.FlushAsync(); // Assert transport.GetSentEnvelopes().Should().HaveCount(1); @@ -1088,7 +1089,7 @@ public void CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsTransac } [Fact] - public void CaptureTransaction_WithTransactionProfiler_SendsTransactionWithProfile() + public async Task CaptureTransaction_WithTransactionProfiler_SendsTransactionWithProfile() { // Arrange var transport = new FakeTransport(); @@ -1106,7 +1107,7 @@ public void CaptureTransaction_WithTransactionProfiler_SendsTransactionWithProfi // Act hub.StartTransaction("foo", "bar").Finish(); - hub.FlushAsync().Wait(); + await hub.FlushAsync(); // Assert transport.GetSentEnvelopes().Should().HaveCount(1); @@ -1452,7 +1453,7 @@ public void CaptureTransaction_HubEnabled(bool enabled) transaction.Finish(); // Assert - _fixture.Client.Received().CaptureTransaction(Arg.Is(t => t.IsSampled == enabled),Arg.Any(), Arg.Any()); + _fixture.Client.Received().CaptureTransaction(Arg.Is(t => t.IsSampled == enabled), Arg.Any(), Arg.Any()); } [Fact] From f030857e8af2db9b063b5f60eadcd119561bcb56 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sun, 10 Dec 2023 15:03:18 +0100 Subject: [PATCH 35/43] improve logs --- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index eafa6995ed..518fc66591 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -1,5 +1,6 @@ using Sentry.Cocoa; using Sentry.Cocoa.Extensions; +using Sentry.Extensibility; // ReSharper disable once CheckNamespace namespace Sentry; @@ -8,6 +9,7 @@ public static partial class SentrySdk { private static void InitSentryCocoaSdk(SentryOptions options) { + options.LogDebug("Initializing native SDK"); // Workaround for https://github.com/xamarin/xamarin-macios/issues/15252 ObjCRuntime.Runtime.MarshalManagedException += (_, args) => { @@ -29,14 +31,14 @@ private static void InitSentryCocoaSdk(SentryOptions options) nativeOptions.EnableAutoSessionTracking = options.AutoSessionTracking; nativeOptions.EnableCaptureFailedRequests = options.CaptureFailedRequests; nativeOptions.FailedRequestStatusCodes = GetFailedRequestStatusCodes(options.FailedRequestStatusCodes); - nativeOptions.MaxAttachmentSize = (nuint) options.MaxAttachmentSize; - nativeOptions.MaxBreadcrumbs = (nuint) options.MaxBreadcrumbs; - nativeOptions.MaxCacheItems = (nuint) options.MaxCacheItems; + nativeOptions.MaxAttachmentSize = (nuint)options.MaxAttachmentSize; + nativeOptions.MaxBreadcrumbs = (nuint)options.MaxBreadcrumbs; + nativeOptions.MaxCacheItems = (nuint)options.MaxCacheItems; nativeOptions.ReleaseName = options.Release; nativeOptions.SampleRate = options.SampleRate; nativeOptions.SendClientReports = options.SendClientReports; nativeOptions.SendDefaultPii = options.SendDefaultPii; - nativeOptions.SessionTrackingIntervalMillis = (nuint) options.AutoSessionTrackingInterval.TotalMilliseconds; + nativeOptions.SessionTrackingIntervalMillis = (nuint)options.AutoSessionTrackingInterval.TotalMilliseconds; if (options.Environment is { } environment) { @@ -71,7 +73,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) } // These options we have behind feature flags - if (options is {IsPerformanceMonitoringEnabled: true, Native.EnableTracing: true}) + if (options is { IsPerformanceMonitoringEnabled: true, Native.EnableTracing: true }) { if (options.EnableTracing != null) { @@ -190,6 +192,7 @@ private static void InitSentryCocoaSdk(SentryOptions options) if (options.IsProfilingEnabled) { + options.LogDebug("Profiling is enabled, attaching native SDK profiler factory"); options.TransactionProfilerFactory ??= new CocoaProfilerFactory(options); } From 24d1656ddf144cae45245cf1ca6b4d97f6da3627 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sun, 10 Dec 2023 15:53:30 +0100 Subject: [PATCH 36/43] fixup device tests --- .github/workflows/device-tests-ios.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/device-tests-ios.yml b/.github/workflows/device-tests-ios.yml index 34e67aab3b..94ea77faa9 100644 --- a/.github/workflows/device-tests-ios.yml +++ b/.github/workflows/device-tests-ios.yml @@ -65,12 +65,12 @@ jobs: - name: Run Tests id: first-run - continue-on-error: true + # continue-on-error: true run: pwsh scripts/device-test.ps1 ios -Run - # - name: Retry Tests (if previous failed to run) - # if: steps.first-run.outcome == 'failure' - # run: pwsh scripts/device-test.ps1 ios -Run + - name: Retry Tests (if previous failed to run) + if: steps.first-run.outcome == 'failure' + run: pwsh scripts/device-test.ps1 ios -Run - name: Upload results if: success() || failure() From c2e63ecf17f5b271c1d3db42aeeee5d102e33ced Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sun, 10 Dec 2023 19:26:14 +0100 Subject: [PATCH 37/43] include cocoa in device test app --- test/Directory.Build.targets | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets index 7b662c5861..7a61eef231 100644 --- a/test/Directory.Build.targets +++ b/test/Directory.Build.targets @@ -32,4 +32,7 @@ true + + From 1dddef1236558299f0723f1f047dddb0e557ba4b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sun, 10 Dec 2023 20:21:58 +0100 Subject: [PATCH 38/43] fix profiler test --- test/Directory.Build.targets | 3 --- test/Sentry.Tests/ProfilerTests.cs | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets index 7a61eef231..7b662c5861 100644 --- a/test/Directory.Build.targets +++ b/test/Directory.Build.targets @@ -32,7 +32,4 @@ true - - diff --git a/test/Sentry.Tests/ProfilerTests.cs b/test/Sentry.Tests/ProfilerTests.cs index a02ab82861..7f1d81c7c1 100644 --- a/test/Sentry.Tests/ProfilerTests.cs +++ b/test/Sentry.Tests/ProfilerTests.cs @@ -43,7 +43,8 @@ async Task VerifyAsync(HttpRequestMessage message) try { - using var hub = new Hub(options); + using var hub = SentrySdk.InitHub(options) as Hub; + options.TransactionProfilerFactory.Should().NotBeNull(); var clock = SentryStopwatch.StartNew(); var tx = hub.StartTransaction("name", "op"); From fb339d9faf616a8ac106a76d4a0edd53bc40d473 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Sun, 10 Dec 2023 22:19:53 +0100 Subject: [PATCH 39/43] try disabling hub tests --- test/Sentry.Tests/HubTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index dba7eba7dc..798639e9c4 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1028,7 +1028,7 @@ public void Finish() { } #nullable disable - [Fact] + [Fact(Skip = "times out?")] public async Task CaptureTransaction_WithSyncThrowingTransactionProfiler_DoesntSendTransaction() { // Arrange @@ -1051,7 +1051,7 @@ public async Task CaptureTransaction_WithSyncThrowingTransactionProfiler_DoesntS transport.GetSentEnvelopes().Should().BeEmpty(); } - [Fact] + [Fact(Skip = "times out?")] public async Task CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsTransactionWithoutProfile() { // Arrange @@ -1088,7 +1088,7 @@ public async Task CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsT lines[3].Should().BeEmpty(); } - [Fact] + [Fact(Skip = "times out?")] public async Task CaptureTransaction_WithTransactionProfiler_SendsTransactionWithProfile() { // Arrange From 25099bac0500556c6d52e0bbc05b4e069462c177 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 11 Dec 2023 20:34:48 +0100 Subject: [PATCH 40/43] try enabling a potential flaky test --- test/Sentry.Tests/HubTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 798639e9c4..7d84b96514 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1028,7 +1028,7 @@ public void Finish() { } #nullable disable - [Fact(Skip = "times out?")] + [Fact] public async Task CaptureTransaction_WithSyncThrowingTransactionProfiler_DoesntSendTransaction() { // Arrange From 2b78e3bd792edd73bfbf150179a8ac54933e9241 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 12 Dec 2023 11:35:46 +0100 Subject: [PATCH 41/43] try to enable a test case --- test/Sentry.Tests/HubTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index 7d84b96514..c98b65308d 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1051,7 +1051,7 @@ public async Task CaptureTransaction_WithSyncThrowingTransactionProfiler_DoesntS transport.GetSentEnvelopes().Should().BeEmpty(); } - [Fact(Skip = "times out?")] + [Fact] public async Task CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsTransactionWithoutProfile() { // Arrange From e5a069aa363c547d986a6c2b6727207fff03ff73 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 12 Dec 2023 12:03:54 +0100 Subject: [PATCH 42/43] try enabling a test case --- test/Sentry.Tests/HubTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index c98b65308d..dba7eba7dc 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -1088,7 +1088,7 @@ public async Task CaptureTransaction_WithAsyncThrowingTransactionProfiler_SendsT lines[3].Should().BeEmpty(); } - [Fact(Skip = "times out?")] + [Fact] public async Task CaptureTransaction_WithTransactionProfiler_SendsTransactionWithProfile() { // Arrange From ed6dd63c5e98c3e00e763e5f8743bb81441bbcf6 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 12 Dec 2023 12:48:52 +0100 Subject: [PATCH 43/43] restore CI --- .github/workflows/device-tests-ios.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/device-tests-ios.yml b/.github/workflows/device-tests-ios.yml index 94ea77faa9..0022d1975f 100644 --- a/.github/workflows/device-tests-ios.yml +++ b/.github/workflows/device-tests-ios.yml @@ -7,7 +7,7 @@ on: - release/* pull_request: paths-ignore: - - '**.md' + - "**.md" jobs: build: @@ -65,7 +65,7 @@ jobs: - name: Run Tests id: first-run - # continue-on-error: true + continue-on-error: true run: pwsh scripts/device-test.ps1 ios -Run - name: Retry Tests (if previous failed to run)