From 9282ef78ff89fba59d7bb085baa087f7042eeb4b Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 25 Feb 2025 12:01:47 -0500 Subject: [PATCH 01/10] Initial --- src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs | 3 +++ src/Sentry/SentrySdk.cs | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs index 1cbefb19bf..d2516987f9 100644 --- a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.Maui.LifecycleEvents; +using Sentry; using Sentry.Extensibility; using Sentry.Extensions.Logging.Extensions.DependencyInjection; using Sentry.Maui; @@ -42,6 +43,8 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, string dsn) public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, Action? configureOptions) { + _ = SentrySdk.AppStartTicks; // ensure start ticks is hit, this is almost immediately in range of startup + var services = builder.Services; if (configureOptions != null) diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index 505bf7bb1b..e51f68ee6b 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -15,6 +15,13 @@ namespace Sentry; /// public static partial class SentrySdk { + /// + /// The starting ticks + /// + public static long AppStartTicks => _appStartTicks ??= Stopwatch.GetTimestamp(); + private static long? _appStartTicks; + + internal static IHub CurrentHub = DisabledHub.Instance; internal static SentryOptions? CurrentOptions => CurrentHub.GetSentryOptions(); From f21f84ed222746bbce10c3249ec1913b5b4bbfc8 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 25 Feb 2025 18:15:43 -0500 Subject: [PATCH 02/10] WIP --- src/Sentry.Maui/Internal/MauiEventsBinder.cs | 77 ++++++++++++++++++++ src/Sentry/Internal/ProcessInfo.cs | 13 +++- src/Sentry/SentrySdk.cs | 7 -- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/Sentry.Maui/Internal/MauiEventsBinder.cs b/src/Sentry.Maui/Internal/MauiEventsBinder.cs index 093bfc095b..bc0a4a5323 100644 --- a/src/Sentry.Maui/Internal/MauiEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiEventsBinder.cs @@ -1,4 +1,7 @@ +using System; using Microsoft.Extensions.Options; +using Microsoft.Maui.Controls; +using Sentry.Internal; namespace Sentry.Maui.Internal; @@ -245,6 +248,75 @@ internal void HandleShellEvents(Shell shell, bool bind = true) } } + + /* +class NativeAppStartHandler { + NativeAppStartHandler(this._native); + + final SentryNativeBinding _native; + + late final Hub _hub; + late final SentryFlutterOptions _options; + + /// We filter out App starts more than 60s + static const _maxAppStartMillis = 60000; + + Future call(Hub hub, SentryFlutterOptions options, + {required DateTime? appStartEnd}) async { + _hub = hub; + _options = options; + + final nativeAppStart = await _native.fetchNativeAppStart(); + if (nativeAppStart == null) { + return; + } + final appStartInfo = _infoNativeAppStart(nativeAppStart, appStartEnd); + if (appStartInfo == null) { + return; + } + + // Create Transaction & Span + + const screenName = 'root /'; + final transaction = _hub.startTransaction( + screenName, + SentrySpanOperations.uiLoad, + startTimestamp: appStartInfo.start, + ); + final ttidSpan = transaction.startChild( + SentrySpanOperations.uiTimeToInitialDisplay, + description: '$screenName initial display', + startTimestamp: appStartInfo.start, + ); + + // Enrich Transaction + + SentryTracer sentryTracer; + if (transaction is SentryTracer) { + sentryTracer = transaction; + } else { + return; + } + + SentryMeasurement? measurement; + if (options.autoAppStart) { + measurement = appStartInfo.toMeasurement(); + } else if (appStartEnd != null) { + appStartInfo.end = appStartEnd; + measurement = appStartInfo.toMeasurement(); + } + + if (measurement != null) { + sentryTracer.measurements[measurement.name] = measurement; + await _attachAppStartSpans(appStartInfo, sentryTracer); + } + + // Finish Transaction & Span + + await ttidSpan.finish(endTimestamp: appStartInfo.end); + await transaction.finish(endTimestamp: appStartInfo.end); + */ + internal void HandlePageEvents(Page page, bool bind = true) { if (bind) @@ -254,6 +326,11 @@ internal void HandlePageEvents(Page page, bool bind = true) page.Appearing += OnPageOnAppearing; page.Disappearing += OnPageOnDisappearing; + + // TODO: if I haven't already done the TTID, perform it here and end it in OnPageOnNavigatedTo + var timestamp = ProcessInfo.Instance!.StartupTimestamp; + + // Navigation events // https://github.com/dotnet/docs-maui/issues/583 page.NavigatedTo += OnPageOnNavigatedTo; diff --git a/src/Sentry/Internal/ProcessInfo.cs b/src/Sentry/Internal/ProcessInfo.cs index 414c93e394..5f132d2865 100644 --- a/src/Sentry/Internal/ProcessInfo.cs +++ b/src/Sentry/Internal/ProcessInfo.cs @@ -6,6 +6,12 @@ internal class ProcessInfo { internal static ProcessInfo? Instance; + /// + /// The timespan.GetTimestamp() value at init + /// More precise for determining TTID + /// + internal long StartupTimestamp { get; private set; } = 0L; + /// /// When the code was initialized. /// @@ -58,11 +64,10 @@ internal ProcessInfo( // Fast var now = DateTimeOffset.UtcNow; StartupTime = now; - long? timestamp = 0; try { - timestamp = Stopwatch.GetTimestamp(); - BootTime = now.AddTicks(-timestamp.Value + StartupTimestamp = Stopwatch.GetTimestamp(); + BootTime = now.AddTicks(-StartupTimestamp / (Stopwatch.Frequency / TimeSpan.TicksPerSecond)); } @@ -79,7 +84,7 @@ internal ProcessInfo( options.LogError(e, "Failed to find BootTime: Now {0}, GetTimestamp {1}, Frequency {2}, TicksPerSecond: {3}", now, - timestamp, + StartupTimestamp, Stopwatch.Frequency, TimeSpan.TicksPerSecond); } diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index e51f68ee6b..505bf7bb1b 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -15,13 +15,6 @@ namespace Sentry; /// public static partial class SentrySdk { - /// - /// The starting ticks - /// - public static long AppStartTicks => _appStartTicks ??= Stopwatch.GetTimestamp(); - private static long? _appStartTicks; - - internal static IHub CurrentHub = DisabledHub.Instance; internal static SentryOptions? CurrentOptions => CurrentHub.GetSentryOptions(); From 3b2116811c7a1947959571a62f780d64a695b753 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Thu, 13 Mar 2025 10:40:59 -0400 Subject: [PATCH 03/10] WIP --- .../Sentry.Samples.Maui.csproj | 6 +- .../Internal/IMauiPageEventHandler.cs | 31 ++++++ .../Internal/MauiButtonEventsBinder.cs | 11 ++- src/Sentry.Maui/Internal/MauiEventsBinder.cs | 97 ++++--------------- .../Internal/TtdMauiPageEventHandler.cs | 73 ++++++++++++++ .../SentryMauiAppBuilderExtensions.cs | 6 +- 6 files changed, 140 insertions(+), 84 deletions(-) create mode 100644 src/Sentry.Maui/Internal/IMauiPageEventHandler.cs create mode 100644 src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs diff --git a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj index 28c0f0d502..bf92b361fa 100644 --- a/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj +++ b/samples/Sentry.Samples.Maui/Sentry.Samples.Maui.csproj @@ -6,9 +6,9 @@ On Mac, we'll also build for iOS and MacCatalyst. On Windows, we'll also build for Windows 10. --> - $(TargetFrameworks);net9.0-android35.0 - $(TargetFrameworks);net9.0-windows10.0.19041.0;net9.0-ios18.0;net9.0-maccatalyst18.0 - $(TargetFrameworks);net9.0-ios18.0;net9.0-maccatalyst18.0 + $(TargetFrameworks);net9.0-android + $(TargetFrameworks);net9.0-windows;net9.0-ios;net9.0-maccatalyst + $(TargetFrameworks);net9.0-ios;net9.0-maccatalyst Exe Sentry.Samples.Maui true diff --git a/src/Sentry.Maui/Internal/IMauiPageEventHandler.cs b/src/Sentry.Maui/Internal/IMauiPageEventHandler.cs new file mode 100644 index 0000000000..d6e951d683 --- /dev/null +++ b/src/Sentry.Maui/Internal/IMauiPageEventHandler.cs @@ -0,0 +1,31 @@ +namespace Sentry.Maui.Internal; + +/// +/// Allows you to receive MAUI page level events without hooking (this list is NOT exhaustive at this time) +/// +public interface IMauiPageEventHandler +{ + /// + /// Page.OnAppearing + /// + /// + void OnAppearing(Page page); + + /// + /// Page.OnDisappearing + /// + /// + void OnDisappearing(Page page); + + /// + /// Page.OnNavigatedTo + /// + /// + void OnNavigatedTo(Page page); + + /// + /// Page.OnNavigatedFrom + /// + /// + void OnNavigatedFrom(Page page); +} diff --git a/src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs b/src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs index f9b1dcbcb6..e9cdda5ca1 100644 --- a/src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiButtonEventsBinder.cs @@ -1,7 +1,7 @@ namespace Sentry.Maui.Internal; /// -public class MauiButtonEventsBinder : IMauiElementEventBinder +public class MauiButtonEventsBinder(IHub hub) : IMauiElementEventBinder { private Action? addBreadcrumbCallback; @@ -31,7 +31,14 @@ public void UnBind(VisualElement element) private void OnButtonOnClicked(object? sender, EventArgs _) - => addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Clicked))); + { + hub.ConfigureScope(scope => + { + // scope.Transaction.SetMeasurement(); + }); + addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Clicked))); + } + private void OnButtonOnPressed(object? sender, EventArgs _) => addBreadcrumbCallback?.Invoke(new(sender, nameof(Button.Pressed))); diff --git a/src/Sentry.Maui/Internal/MauiEventsBinder.cs b/src/Sentry.Maui/Internal/MauiEventsBinder.cs index 2448169b60..c1e7b48d0a 100644 --- a/src/Sentry.Maui/Internal/MauiEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiEventsBinder.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Options; using Microsoft.Maui.Controls; using Sentry.Internal; +using Sentry.Protocol; namespace Sentry.Maui.Internal; @@ -15,6 +16,7 @@ internal class MauiEventsBinder : IMauiEventsBinder private readonly IHub _hub; private readonly SentryMauiOptions _options; private readonly IEnumerable _elementEventBinders; + private readonly IEnumerable _pageEventHandlers; // https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types // https://github.com/getsentry/sentry/blob/master/static/app/types/breadcrumbs.tsx @@ -26,11 +28,13 @@ internal class MauiEventsBinder : IMauiEventsBinder internal const string RenderingCategory = "ui.rendering"; internal const string UserActionCategory = "ui.useraction"; - public MauiEventsBinder(IHub hub, IOptions options, IEnumerable elementEventBinders) + + public MauiEventsBinder(IHub hub, IOptions options, IEnumerable elementEventBinders, IEnumerable pageEventHandlers) { _hub = hub; _options = options.Value; _elementEventBinders = elementEventBinders; + _pageEventHandlers = pageEventHandlers; } public void HandleApplicationEvents(Application application, bool bind = true) @@ -271,75 +275,6 @@ internal void HandleShellEvents(Shell shell, bool bind = true) } } - - /* -class NativeAppStartHandler { - NativeAppStartHandler(this._native); - - final SentryNativeBinding _native; - - late final Hub _hub; - late final SentryFlutterOptions _options; - - /// We filter out App starts more than 60s - static const _maxAppStartMillis = 60000; - - Future call(Hub hub, SentryFlutterOptions options, - {required DateTime? appStartEnd}) async { - _hub = hub; - _options = options; - - final nativeAppStart = await _native.fetchNativeAppStart(); - if (nativeAppStart == null) { - return; - } - final appStartInfo = _infoNativeAppStart(nativeAppStart, appStartEnd); - if (appStartInfo == null) { - return; - } - - // Create Transaction & Span - - const screenName = 'root /'; - final transaction = _hub.startTransaction( - screenName, - SentrySpanOperations.uiLoad, - startTimestamp: appStartInfo.start, - ); - final ttidSpan = transaction.startChild( - SentrySpanOperations.uiTimeToInitialDisplay, - description: '$screenName initial display', - startTimestamp: appStartInfo.start, - ); - - // Enrich Transaction - - SentryTracer sentryTracer; - if (transaction is SentryTracer) { - sentryTracer = transaction; - } else { - return; - } - - SentryMeasurement? measurement; - if (options.autoAppStart) { - measurement = appStartInfo.toMeasurement(); - } else if (appStartEnd != null) { - appStartInfo.end = appStartEnd; - measurement = appStartInfo.toMeasurement(); - } - - if (measurement != null) { - sentryTracer.measurements[measurement.name] = measurement; - await _attachAppStartSpans(appStartInfo, sentryTracer); - } - - // Finish Transaction & Span - - await ttidSpan.finish(endTimestamp: appStartInfo.end); - await transaction.finish(endTimestamp: appStartInfo.end); - */ - internal void HandlePageEvents(Page page, bool bind = true) { if (bind) @@ -349,11 +284,6 @@ internal void HandlePageEvents(Page page, bool bind = true) page.Appearing += OnPageOnAppearing; page.Disappearing += OnPageOnDisappearing; - - // TODO: if I haven't already done the TTID, perform it here and end it in OnPageOnNavigatedTo - var timestamp = ProcessInfo.Instance!.StartupTimestamp; - - // Navigation events // https://github.com/dotnet/docs-maui/issues/583 page.NavigatedTo += OnPageOnNavigatedTo; @@ -381,10 +311,18 @@ internal void HandlePageEvents(Page page, bool bind = true) // Application Events - private void OnApplicationOnPageAppearing(object? sender, Page page) => + private void OnApplicationOnPageAppearing(object? sender, Page page) + { _hub.AddBreadcrumbForEvent(_options, sender, nameof(Application.PageAppearing), NavigationType, NavigationCategory, data => data.AddElementInfo(_options, page, nameof(Page))); - private void OnApplicationOnPageDisappearing(object? sender, Page page) => + RunPageEventHandlers(handler => handler.OnAppearing(page)); + } + + private void OnApplicationOnPageDisappearing(object? sender, Page page) + { _hub.AddBreadcrumbForEvent(_options, sender, nameof(Application.PageDisappearing), NavigationType, NavigationCategory, data => data.AddElementInfo(_options, page, nameof(Page))); + RunPageEventHandlers(handler => handler.OnDisappearing(page)); + } + private void OnApplicationOnModalPushed(object? sender, ModalPushedEventArgs e) => _hub.AddBreadcrumbForEvent(_options, sender, nameof(Application.ModalPushed), NavigationType, NavigationCategory, data => data.AddElementInfo(_options, e.Modal, nameof(e.Modal))); private void OnApplicationOnModalPopped(object? sender, ModalPoppedEventArgs e) => @@ -508,4 +446,9 @@ private void OnPageOnNavigatedTo(object? sender, NavigatedToEventArgs e) => private void OnPageOnLayoutChanged(object? sender, EventArgs _) => _hub.AddBreadcrumbForEvent(_options, sender, nameof(Page.LayoutChanged), SystemType, RenderingCategory); + + private void RunPageEventHandlers(Action action) + { + foreach (var handler in _pageEventHandlers) action(handler); // TODO: try/catch in case of user code? + } } diff --git a/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs new file mode 100644 index 0000000000..6714a1122d --- /dev/null +++ b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs @@ -0,0 +1,73 @@ +using CoreFoundation; +using Sentry.Internal; + +namespace Sentry.Maui.Internal; + +/// +/// Time-to-(initial/full)-display page event handler +/// https://docs.sentry.io/product/insights/mobile/mobile-vitals/ +/// +internal class TtdMauiPageEventHandler(IHub hub) : IMauiPageEventHandler +{ + internal static long? StartupTimestamp { get; set; } + + // [MobileVital.AppStartCold]: 'duration', + // [MobileVital.AppStartWarm]: 'duration', + // [MobileVital.FramesTotal]: 'integer', + // [MobileVital.FramesSlow]: 'integer', + // [MobileVital.FramesFrozen]: 'integer', + // [MobileVital.FramesSlowRate]: 'percentage', + // [MobileVital.FramesFrozenRate]: 'percentage', + // [MobileVital.StallCount]: 'integer', + // [MobileVital.StallTotalTime]: 'duration', + // [MobileVital.StallLongestTime]: 'duration', + // [MobileVital.StallPercentage]: 'percentage', + + internal const string LoadCategory = "ui.load"; + internal const string InitialDisplayType = "initial_display"; + internal const string FullDisplayType = "full_display"; + private bool _ttidRan = false; // this should require thread safety + private ISpan? _timeToInitialDisplaySpan; + private ITransactionTracer? _transaction; + + /// + public void OnAppearing(Page page) + { + if (_ttidRan && StartupTimestamp != null) + return; + + DispatchTime.Now.Nanoseconds + _ttidRan = true; + var timestamp = ProcessInfo.Instance!.StartupTimestamp; + var screenName = page.GetType().FullName ?? "root /"; + _transaction = hub.StartTransaction( + LoadCategory, + "start" + ); + _timeToInitialDisplaySpan = _transaction.StartChild(InitialDisplayType, $"{screenName} initial display"); + // _timeToInitialDisplaySpan.SetMeasurement("", MeasurementUnit.Parse("")); + // appStartSpan.SetMeasurement("", MeasurementUnit.Parse("ms")); + } + + /// + public void OnDisappearing(Page page) + { + } + + public void OnNavigatedTo(Page page) + { + if (_transaction != null) + { + + _timeToInitialDisplaySpan?.Finish(); // timestamp for now + _transaction?.Finish(); + + _timeToInitialDisplaySpan = null; + _transaction = null; + } + } + + public void OnNavigatedFrom(Page page) + { + } +} diff --git a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs index 93292759ab..710c312b6a 100644 --- a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs @@ -5,6 +5,7 @@ using Sentry; using Sentry.Extensibility; using Sentry.Extensions.Logging.Extensions.DependencyInjection; +using Sentry.Internal; using Sentry.Maui; using Sentry.Maui.Internal; @@ -43,8 +44,8 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, string dsn) public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, Action? configureOptions) { - _ = SentrySdk.AppStartTicks; // ensure start ticks is hit, this is almost immediately in range of startup - + // we set this as early as possible and during the DI phase + TtdMauiPageEventHandler.StartupTimestamp = Stopwatch.GetTimestamp(); var services = builder.Services; if (configureOptions != null) @@ -62,6 +63,7 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, services.AddSingleton(); services.TryAddSingleton(); + services.AddSingleton(); services.AddSentry(); builder.RegisterMauiEventsBinder(); From 37cc19ffe602ecd9951f1e13c69143d1a3ad0a50 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 31 Mar 2025 14:53:33 -0400 Subject: [PATCH 04/10] Start/Finish timestamps on spans --- .../Internal/TtdMauiPageEventHandler.cs | 16 +++++------ src/Sentry/ISentryClient.cs | 4 +-- src/Sentry/ISpan.cs | 20 +++++++------- src/Sentry/Internal/NoOpSpan.cs | 10 +++---- src/Sentry/SentrySdk.cs | 4 +-- src/Sentry/SpanTracer.cs | 27 +++++++++++-------- src/Sentry/TransactionTracer.cs | 21 ++++++++------- 7 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs index 6714a1122d..86b2166bdb 100644 --- a/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs +++ b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs @@ -1,4 +1,3 @@ -using CoreFoundation; using Sentry.Internal; namespace Sentry.Maui.Internal; @@ -36,17 +35,19 @@ public void OnAppearing(Page page) if (_ttidRan && StartupTimestamp != null) return; - DispatchTime.Now.Nanoseconds + //DispatchTime.Now.Nanoseconds _ttidRan = true; - var timestamp = ProcessInfo.Instance!.StartupTimestamp; + var startupTimestamp = ProcessInfo.Instance!.StartupTimestamp; var screenName = page.GetType().FullName ?? "root /"; _transaction = hub.StartTransaction( LoadCategory, "start" ); + var elapsedTime = Stopwatch.GetElapsedTime(startupTimestamp); + _timeToInitialDisplaySpan = _transaction.StartChild(InitialDisplayType, $"{screenName} initial display"); - // _timeToInitialDisplaySpan.SetMeasurement("", MeasurementUnit.Parse("")); - // appStartSpan.SetMeasurement("", MeasurementUnit.Parse("ms")); + _timeToInitialDisplaySpan.SetMeasurement("test", elapsedTime.TotalMilliseconds, MeasurementUnit.Parse("ms")); + _timeToInitialDisplaySpan.Finish(); } /// @@ -56,10 +57,9 @@ public void OnDisappearing(Page page) public void OnNavigatedTo(Page page) { - if (_transaction != null) + if (_transaction is { IsFinished: false }) { - - _timeToInitialDisplaySpan?.Finish(); // timestamp for now + // TODO: wait for all spans _transaction?.Finish(); _timeToInitialDisplaySpan = null; diff --git a/src/Sentry/ISentryClient.cs b/src/Sentry/ISentryClient.cs index 8229d57780..c2adbe8065 100644 --- a/src/Sentry/ISentryClient.cs +++ b/src/Sentry/ISentryClient.cs @@ -48,7 +48,7 @@ public interface ISentryClient /// /// /// Note: this method is NOT meant to be called from user code! - /// Instead, call on the transaction. + /// Instead, call on the transaction. /// /// The transaction. [EditorBrowsable(EditorBrowsableState.Never)] @@ -59,7 +59,7 @@ public interface ISentryClient /// /// /// Note: this method is NOT meant to be called from user code! - /// Instead, call on the transaction. + /// Instead, call on the transaction. /// /// The transaction. /// The scope to be applied to the transaction diff --git a/src/Sentry/ISpan.cs b/src/Sentry/ISpan.cs index 0259ac02c0..4f1b27e748 100644 --- a/src/Sentry/ISpan.cs +++ b/src/Sentry/ISpan.cs @@ -28,27 +28,27 @@ public interface ISpan : ISpanData /// /// Starts a child span. /// - public ISpan StartChild(string operation); + public ISpan StartChild(string operation, DateTimeOffset? startTime = null); /// /// Finishes the span. - /// - public void Finish(); + /// ` + public void Finish(DateTimeOffset? timestamp = null); /// /// Finishes the span with the specified status. /// - public void Finish(SpanStatus status); + public void Finish(SpanStatus status, DateTimeOffset? timestamp = null); /// /// Finishes the span with the specified exception and status. /// - public void Finish(Exception exception, SpanStatus status); + public void Finish(Exception exception, SpanStatus status, DateTimeOffset? timestamp = null); /// /// Finishes the span with the specified exception and automatically inferred status. /// - public void Finish(Exception exception); + public void Finish(Exception exception, DateTimeOffset? timestamp = null); } /// @@ -60,18 +60,18 @@ public static class SpanExtensions /// /// Starts a child span. /// - public static ISpan StartChild(this ISpan span, string operation, string? description) + public static ISpan StartChild(this ISpan span, string operation, string? description, DateTimeOffset? startTime = null) { - var child = span.StartChild(operation); + var child = span.StartChild(operation, startTime); child.Description = description; return child; } - internal static ISpan StartChild(this ISpan span, SpanContext context) + internal static ISpan StartChild(this ISpan span, SpanContext context, DateTimeOffset? startTime = null) { var transaction = span.GetTransaction() as TransactionTracer; - if (transaction?.StartChild(context.SpanId, span.SpanId, context.Operation, context.Instrumenter) + if (transaction?.StartChild(context.SpanId, span.SpanId, context.Operation, context.Instrumenter, startTime) is not SpanTracer childSpan) { return NoOpSpan.Instance; diff --git a/src/Sentry/Internal/NoOpSpan.cs b/src/Sentry/Internal/NoOpSpan.cs index f6e49e5a2c..c284c306a7 100644 --- a/src/Sentry/Internal/NoOpSpan.cs +++ b/src/Sentry/Internal/NoOpSpan.cs @@ -42,21 +42,21 @@ public SpanStatus? Status set { } } - public ISpan StartChild(string operation) => this; + public ISpan StartChild(string operation, DateTimeOffset? startTime) => this; - public void Finish() + public void Finish(DateTimeOffset? timestamp = null) { } - public void Finish(SpanStatus status) + public void Finish(SpanStatus status, DateTimeOffset? timestamp = null) { } - public void Finish(Exception exception, SpanStatus status) + public void Finish(Exception exception, SpanStatus status, DateTimeOffset? timestamp = null) { } - public void Finish(Exception exception) + public void Finish(Exception exception, DateTimeOffset? timestamp = null) { } diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index 401a0fa6f0..65aadfdd89 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -524,7 +524,7 @@ public static void CaptureUserFeedback(SentryId eventId, string email, string co /// /// /// Note: this method is NOT meant to be called from user code! - /// Instead, call on the transaction. + /// Instead, call on the transaction. /// [DebuggerStepThrough] [EditorBrowsable(EditorBrowsableState.Never)] @@ -536,7 +536,7 @@ public static void CaptureTransaction(SentryTransaction transaction) /// /// /// Note: this method is NOT meant to be called from user code! - /// Instead, call on the transaction. + /// Instead, call on the transaction. /// [DebuggerStepThrough] [EditorBrowsable(EditorBrowsableState.Never)] diff --git a/src/Sentry/SpanTracer.cs b/src/Sentry/SpanTracer.cs index ed51d48eaa..64924ff893 100644 --- a/src/Sentry/SpanTracer.cs +++ b/src/Sentry/SpanTracer.cs @@ -107,7 +107,8 @@ public SpanTracer( TransactionTracer transaction, SpanId? parentSpanId, SentryId traceId, - string operation) + string operation, + DateTimeOffset? startTimestamp = null) { _hub = hub; Transaction = transaction; @@ -115,7 +116,7 @@ public SpanTracer( ParentSpanId = parentSpanId; TraceId = traceId; Operation = operation; - StartTimestamp = _stopwatch.StartDateTimeOffset; + StartTimestamp = startTimestamp ?? _stopwatch.StartDateTimeOffset; } internal SpanTracer( @@ -125,7 +126,8 @@ internal SpanTracer( SpanId? parentSpanId, SentryId traceId, string operation, - Instrumenter instrumenter = Instrumenter.Sentry) + Instrumenter instrumenter = Instrumenter.Sentry, + DateTimeOffset? startTimestamp = null) { _hub = hub; _instrumenter = instrumenter; @@ -134,11 +136,11 @@ internal SpanTracer( ParentSpanId = parentSpanId; TraceId = traceId; Operation = operation; - StartTimestamp = _stopwatch.StartDateTimeOffset; + StartTimestamp = startTimestamp ?? _stopwatch.StartDateTimeOffset; } /// - public ISpan StartChild(string operation) => Transaction.StartChild(null, parentSpanId: SpanId, operation: operation); + public ISpan StartChild(string operation, DateTimeOffset? timestamp = null) => Transaction.StartChild(null, parentSpanId: SpanId, operation: operation, timestamp: timestamp); /// /// Used to mark a span as unfinished when it was previously marked as finished. This allows us to reuse spans for @@ -151,28 +153,31 @@ internal void Unfinish() } /// - public void Finish() + public void Finish(DateTimeOffset? timestamp = null) { Status ??= SpanStatus.Ok; + if (timestamp is not null) + EndTimestamp = timestamp; + EndTimestamp ??= _stopwatch.CurrentDateTimeOffset; } /// - public void Finish(SpanStatus status) + public void Finish(SpanStatus status, DateTimeOffset? timestamp = null) { Status = status; - Finish(); + Finish(timestamp); } /// - public void Finish(Exception exception, SpanStatus status) + public void Finish(Exception exception, SpanStatus status, DateTimeOffset? timestamp = null) { _hub.BindException(exception, this); - Finish(status); + Finish(status, timestamp); } /// - public void Finish(Exception exception) => Finish(exception, SpanStatusConverter.FromException(exception)); + public void Finish(Exception exception, DateTimeOffset? timestamp = null) => Finish(exception, SpanStatusConverter.FromException(exception), timestamp); /// public SentryTraceHeader GetTraceHeader() => new(TraceId, SpanId, IsSampled); diff --git a/src/Sentry/TransactionTracer.cs b/src/Sentry/TransactionTracer.cs index c4da3d5933..f6b3b4cf43 100644 --- a/src/Sentry/TransactionTracer.cs +++ b/src/Sentry/TransactionTracer.cs @@ -293,10 +293,10 @@ internal TransactionTracer(IHub hub, ITransactionContext context, TimeSpan? idle public void SetMeasurement(string name, Measurement measurement) => _measurements[name] = measurement; /// - public ISpan StartChild(string operation) => StartChild(spanId: null, parentSpanId: SpanId, operation); + public ISpan StartChild(string operation, DateTimeOffset? timestamp = null) => StartChild(spanId: null, parentSpanId: SpanId, operation, timestamp: timestamp); internal ISpan StartChild(SpanId? spanId, SpanId parentSpanId, string operation, - Instrumenter instrumenter = Instrumenter.Sentry) + Instrumenter instrumenter = Instrumenter.Sentry, DateTimeOffset? timestamp = null) { var span = new SpanTracer(_hub, this, SpanId.Create(), parentSpanId, TraceId, operation, instrumenter: instrumenter); if (spanId is { } id) @@ -368,7 +368,7 @@ public void Clear() public ISpan? GetLastActiveSpan() => _activeSpanTracker.PeekActive(); /// - public void Finish() + public void Finish(DateTimeOffset? timestamp = null) { _options?.LogDebug("Attempting to finish Transaction {0}.", SpanId); if (Interlocked.Exchange(ref _cancelIdleTimeout, 0) == 1) @@ -389,6 +389,9 @@ public void Finish() TransactionProfiler?.Finish(); Status ??= SpanStatus.Ok; + if (timestamp != null) + EndTimestamp = timestamp.Value; + EndTimestamp ??= _stopwatch.CurrentDateTimeOffset; _options?.LogDebug("Finished Transaction {0}.", SpanId); @@ -403,22 +406,22 @@ public void Finish() } /// - public void Finish(SpanStatus status) + public void Finish(SpanStatus status, DateTimeOffset? timestamp = null) { Status = status; - Finish(); + Finish(timestamp); } /// - public void Finish(Exception exception, SpanStatus status) + public void Finish(Exception exception, SpanStatus status, DateTimeOffset? timestamp = null) { _hub.BindException(exception, this); - Finish(status); + Finish(status, timestamp); } /// - public void Finish(Exception exception) => - Finish(exception, SpanStatusConverter.FromException(exception)); + public void Finish(Exception exception, DateTimeOffset? timestamp = null) => + Finish(exception, SpanStatusConverter.FromException(exception), timestamp); /// public SentryTraceHeader GetTraceHeader() => new(TraceId, SpanId, IsSampled); From 0cab1fec17f9802c99e516696a59913e222f11c0 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 31 Mar 2025 14:54:30 -0400 Subject: [PATCH 05/10] Compiling tests again --- test/Sentry.Maui.Tests/MauiEventsBinderTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs index 6d2c7ce1ef..265211cfca 100644 --- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs @@ -23,9 +23,10 @@ public Fixture() hub, options, [ - new MauiButtonEventsBinder(), + new MauiButtonEventsBinder(hub), new MauiImageButtonEventsBinder() - ] + ], + [] ); } } From 19430a362abb165b7b41b53f3e1f2a2212ab1b86 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Fri, 4 Apr 2025 22:19:02 -0400 Subject: [PATCH 06/10] StatusChanged for spans --- src/Sentry/ISpan.cs | 5 +++++ src/Sentry/Internal/NoOpSpan.cs | 6 ++++++ src/Sentry/SpanTracer.cs | 17 ++++++++++++++++- src/Sentry/TransactionTracer.cs | 12 +++++++++++- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/Sentry/ISpan.cs b/src/Sentry/ISpan.cs index 4f1b27e748..05ebb2475d 100644 --- a/src/Sentry/ISpan.cs +++ b/src/Sentry/ISpan.cs @@ -7,6 +7,11 @@ namespace Sentry; /// public interface ISpan : ISpanData { + /// + /// When the status of the span changes, this event will fire + /// + public event EventHandler? StatusChanged; + /// /// Span description. /// diff --git a/src/Sentry/Internal/NoOpSpan.cs b/src/Sentry/Internal/NoOpSpan.cs index c284c306a7..b946c9cacf 100644 --- a/src/Sentry/Internal/NoOpSpan.cs +++ b/src/Sentry/Internal/NoOpSpan.cs @@ -30,6 +30,12 @@ public string Operation set { } } + public event EventHandler? StatusChanged + { + add {} + remove {} + } + public string? Description { get => default; diff --git a/src/Sentry/SpanTracer.cs b/src/Sentry/SpanTracer.cs index 64924ff893..0bf06411c0 100644 --- a/src/Sentry/SpanTracer.cs +++ b/src/Sentry/SpanTracer.cs @@ -52,11 +52,26 @@ public void SetMeasurement(string name, Measurement measurement) => /// public string Operation { get; set; } + /// + public event EventHandler? StatusChanged; + /// public string? Description { get; set; } + private SpanStatus? _status; /// - public SpanStatus? Status { get; set; } + public SpanStatus? Status + { + get => _status; + set + { + if (_status == value) + return; + + _status = value; + StatusChanged?.Invoke(this, _status); + } + } /// /// Used by the Sentry.OpenTelemetry.SentrySpanProcessor to mark a span as a Sentry request. Ideally we wouldn't diff --git a/src/Sentry/TransactionTracer.cs b/src/Sentry/TransactionTracer.cs index f6b3b4cf43..802f406071 100644 --- a/src/Sentry/TransactionTracer.cs +++ b/src/Sentry/TransactionTracer.cs @@ -74,6 +74,9 @@ public string Operation set => Contexts.Trace.Operation = value; } + /// + public event EventHandler? StatusChanged; + /// public string? Description { get; set; } @@ -81,7 +84,14 @@ public string Operation public SpanStatus? Status { get => Contexts.Trace.Status; - set => Contexts.Trace.Status = value; + set + { + if (Contexts.Trace.Status == value) + return; + + Contexts.Trace.Status = value; + StatusChanged?.Invoke(this, value); + } } /// From 2590ffc4f2c6e4a6f4cbe5539a71d9a3a38f7a6c Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Fri, 4 Apr 2025 22:19:13 -0400 Subject: [PATCH 07/10] WIP --- .../Internal/TtdMauiPageEventHandler.cs | 125 ++++++++++++++++-- 1 file changed, 111 insertions(+), 14 deletions(-) diff --git a/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs index 86b2166bdb..4793a275be 100644 --- a/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs +++ b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs @@ -1,4 +1,5 @@ using Sentry.Internal; +using Sentry.Internal.Extensions; namespace Sentry.Maui.Internal; @@ -30,11 +31,14 @@ internal class TtdMauiPageEventHandler(IHub hub) : IMauiPageEventHandler private ITransactionTracer? _transaction; /// - public void OnAppearing(Page page) + public async void OnAppearing(Page page) { if (_ttidRan && StartupTimestamp != null) return; + // if (Interlocked.Exchange(ref _ttidRan, true)) + // return; + //DispatchTime.Now.Nanoseconds _ttidRan = true; var startupTimestamp = ProcessInfo.Instance!.StartupTimestamp; @@ -45,29 +49,122 @@ public void OnAppearing(Page page) ); var elapsedTime = Stopwatch.GetElapsedTime(startupTimestamp); - _timeToInitialDisplaySpan = _transaction.StartChild(InitialDisplayType, $"{screenName} initial display"); + _timeToInitialDisplaySpan = _transaction.StartChild(InitialDisplayType, $"{screenName} initial display", ProcessInfo.Instance!.StartupTime); _timeToInitialDisplaySpan.SetMeasurement("test", elapsedTime.TotalMilliseconds, MeasurementUnit.Parse("ms")); _timeToInitialDisplaySpan.Finish(); - } - /// - public void OnDisappearing(Page page) - { + // we allow 200ms for the user to start any async tasks with spans + await Task.Delay(200).ConfigureAwait(false); + + try + { + using var cts = new CancellationTokenSource(); + cts.CancelAfterSafe(TimeSpan.FromSeconds(30)); + + // TODO: what about time to full display - it should happen WHEN this finishes + await _transaction.WaitForLastSpanToFinishAsync(cts.Token).ConfigureAwait(false); + } + catch (Exception ex) + { + // TODO: what to do? + Console.WriteLine(ex); + } } - public void OnNavigatedTo(Page page) + public void OnDisappearing(Page page) { } + public void OnNavigatedTo(Page page) { } + public void OnNavigatedFrom(Page page) { } +} + + +/// +/// TDOO +/// +public static class Tester +{ + /// + /// TODO + /// + /// + /// + /// + public static async ValueTask WaitForLastSpanToFinishAsync(this ITransactionTracer transaction, CancellationToken cancellationToken = default) { - if (_transaction is { IsFinished: false }) + if (transaction.IsAllSpansFinished()) { - // TODO: wait for all spans - _transaction?.Finish(); - - _timeToInitialDisplaySpan = null; - _transaction = null; + var span = transaction.GetLastFinishedSpan(); + if (span != null) + transaction.Finish(span.EndTimestamp); + } + else + { + var span = await transaction.GetLastSpanWhenFinishedAsync(cancellationToken).ConfigureAwait(false); + if (span != null) + transaction.Finish(span.EndTimestamp); } } - public void OnNavigatedFrom(Page page) + /// + /// TODO + /// + /// + /// + public static bool IsAllSpansFinished(this ITransactionTracer transaction) + => transaction.Spans.All(x => x.IsFinished); + + + /// + /// TODO + /// + /// + /// + public static ISpan? GetLastFinishedSpan(this ITransactionTracer transaction) + => transaction.Spans + .ToList() + .Where(x => x.IsFinished) + .OrderByDescending(x => x.EndTimestamp) + .LastOrDefault(x => x.IsFinished); + + /// + /// TODO + /// + /// + /// + /// + public static async Task GetLastSpanWhenFinishedAsync(this ITransactionTracer transaction, CancellationToken cancellationToken = default) { + // what if no spans + if (transaction.IsAllSpansFinished()) + return transaction.GetLastFinishedSpan(); + + var tcs = new TaskCompletionSource(); + var handler = new EventHandler((_, _) => + { + if (transaction.IsAllSpansFinished()) + { + var lastSpan = transaction.GetLastFinishedSpan(); + tcs.SetResult(lastSpan); + } + }); + + try + { + foreach (var span in transaction.Spans) + { + if (!span.IsFinished) + { + span.StatusChanged += handler; + } + } + + return await tcs.Task.ConfigureAwait(false); + } + finally + { + foreach (var span in transaction.Spans) + { + span.StatusChanged -= handler; + } + } } } From c89380be5aee7d8e1947a3879882866a71df6378 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 7 Apr 2025 10:34:42 -0400 Subject: [PATCH 08/10] Many extensions and assumptions for time-to-full-display --- .../Internal/TtdMauiPageEventHandler.cs | 96 +------------------ src/Sentry/TransactionTracerExtensions.cs | 94 ++++++++++++++++++ 2 files changed, 96 insertions(+), 94 deletions(-) create mode 100644 src/Sentry/TransactionTracerExtensions.cs diff --git a/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs index 4793a275be..c06f652e09 100644 --- a/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs +++ b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs @@ -61,8 +61,8 @@ public async void OnAppearing(Page page) using var cts = new CancellationTokenSource(); cts.CancelAfterSafe(TimeSpan.FromSeconds(30)); - // TODO: what about time to full display - it should happen WHEN this finishes - await _transaction.WaitForLastSpanToFinishAsync(cts.Token).ConfigureAwait(false); + // we're assuming that the user starts any spans around data calls, we wait for those before marking the transaction as finished + await _transaction.FinishWithLastSpanAsync(cts.Token).ConfigureAwait(false); } catch (Exception ex) { @@ -76,95 +76,3 @@ public void OnNavigatedTo(Page page) { } public void OnNavigatedFrom(Page page) { } } - -/// -/// TDOO -/// -public static class Tester -{ - /// - /// TODO - /// - /// - /// - /// - public static async ValueTask WaitForLastSpanToFinishAsync(this ITransactionTracer transaction, CancellationToken cancellationToken = default) - { - if (transaction.IsAllSpansFinished()) - { - var span = transaction.GetLastFinishedSpan(); - if (span != null) - transaction.Finish(span.EndTimestamp); - } - else - { - var span = await transaction.GetLastSpanWhenFinishedAsync(cancellationToken).ConfigureAwait(false); - if (span != null) - transaction.Finish(span.EndTimestamp); - } - } - - /// - /// TODO - /// - /// - /// - public static bool IsAllSpansFinished(this ITransactionTracer transaction) - => transaction.Spans.All(x => x.IsFinished); - - - /// - /// TODO - /// - /// - /// - public static ISpan? GetLastFinishedSpan(this ITransactionTracer transaction) - => transaction.Spans - .ToList() - .Where(x => x.IsFinished) - .OrderByDescending(x => x.EndTimestamp) - .LastOrDefault(x => x.IsFinished); - - /// - /// TODO - /// - /// - /// - /// - public static async Task GetLastSpanWhenFinishedAsync(this ITransactionTracer transaction, CancellationToken cancellationToken = default) - { - // what if no spans - if (transaction.IsAllSpansFinished()) - return transaction.GetLastFinishedSpan(); - - var tcs = new TaskCompletionSource(); - var handler = new EventHandler((_, _) => - { - if (transaction.IsAllSpansFinished()) - { - var lastSpan = transaction.GetLastFinishedSpan(); - tcs.SetResult(lastSpan); - } - }); - - try - { - foreach (var span in transaction.Spans) - { - if (!span.IsFinished) - { - span.StatusChanged += handler; - } - } - - return await tcs.Task.ConfigureAwait(false); - } - finally - { - foreach (var span in transaction.Spans) - { - span.StatusChanged -= handler; - } - } - } -} diff --git a/src/Sentry/TransactionTracerExtensions.cs b/src/Sentry/TransactionTracerExtensions.cs new file mode 100644 index 0000000000..961c526608 --- /dev/null +++ b/src/Sentry/TransactionTracerExtensions.cs @@ -0,0 +1,94 @@ +namespace Sentry; + + +/// +/// Extensions for ITransactionTracer +/// +public static class TransactionTracerExtensions +{ + /// + /// Waits for the last span to finish + /// + /// + /// + /// + public static async ValueTask FinishWithLastSpanAsync(this ITransactionTracer transaction, CancellationToken cancellationToken = default) + { + if (transaction.IsAllSpansFinished()) + { + var span = transaction.GetLastFinishedSpan(); + if (span != null) + transaction.Finish(span.EndTimestamp); + } + else + { + var span = await transaction.GetLastSpanWhenFinishedAsync(cancellationToken).ConfigureAwait(false); + if (span != null) + transaction.Finish(span.EndTimestamp); + } + } + + /// + /// Checks if all spans are finished within a trnasaction + /// + /// + /// + public static bool IsAllSpansFinished(this ITransactionTracer transaction) + => transaction.Spans.All(x => x.IsFinished); + + + /// + /// Sorts all spans within a transaction by on end timestamp and returns the last span + /// + /// + /// + public static ISpan? GetLastFinishedSpan(this ITransactionTracer transaction) + => transaction.Spans + .ToList() + .Where(x => x.IsFinished) + .OrderByDescending(x => x.EndTimestamp) + .LastOrDefault(x => x.IsFinished); + + /// + /// Gets the last span (if one), when all spans mark themselves as IsFinished: true + /// + /// + /// + /// + public static async Task GetLastSpanWhenFinishedAsync(this ITransactionTracer transaction, CancellationToken cancellationToken = default) + { + // what if no spans + if (transaction.IsAllSpansFinished()) + return transaction.GetLastFinishedSpan(); + + var tcs = new TaskCompletionSource(); + var handler = new EventHandler((_, _) => + { + if (transaction.IsAllSpansFinished()) + { + var lastSpan = transaction.GetLastFinishedSpan(); + tcs.SetResult(lastSpan); + } + }); + + try + { + foreach (var span in transaction.Spans) + { + if (!span.IsFinished) + { + span.StatusChanged += handler; + } + } + + return await tcs.Task.ConfigureAwait(false); + } + finally + { + foreach (var span in transaction.Spans) + { + span.StatusChanged -= handler; + } + } + } +} From 9bfed02cd6265aa2083d878900d85c9a95eaeb15 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 7 Apr 2025 10:39:13 -0400 Subject: [PATCH 09/10] Update TtdMauiPageEventHandler.cs --- src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs index c06f652e09..6b72d1eda3 100644 --- a/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs +++ b/src/Sentry.Maui/Internal/TtdMauiPageEventHandler.cs @@ -61,6 +61,7 @@ public async void OnAppearing(Page page) using var cts = new CancellationTokenSource(); cts.CancelAfterSafe(TimeSpan.FromSeconds(30)); + // TODO: grab last span and add ttfd measurement // we're assuming that the user starts any spans around data calls, we wait for those before marking the transaction as finished await _transaction.FinishWithLastSpanAsync(cts.Token).ConfigureAwait(false); } From bc68fa28c581fa80a92e2582cc8238c328fa9486 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Mon, 7 Apr 2025 14:58:40 +0000 Subject: [PATCH 10/10] Format code --- modules/sentry-native | 2 +- src/Sentry.Maui/Internal/IMauiPageEventHandler.cs | 8 ++++---- src/Sentry.Maui/Internal/MauiEventsBinder.cs | 3 ++- src/Sentry/Internal/NoOpSpan.cs | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/sentry-native b/modules/sentry-native index ccef7125b3..77dbf6a600 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit ccef7125b3783210f44ee6b100df7278e6ba3eff +Subproject commit 77dbf6a6006f0be24af30d056bdcac98a7bf5877 diff --git a/src/Sentry.Maui/Internal/IMauiPageEventHandler.cs b/src/Sentry.Maui/Internal/IMauiPageEventHandler.cs index d6e951d683..a15a457d1a 100644 --- a/src/Sentry.Maui/Internal/IMauiPageEventHandler.cs +++ b/src/Sentry.Maui/Internal/IMauiPageEventHandler.cs @@ -9,23 +9,23 @@ public interface IMauiPageEventHandler /// Page.OnAppearing /// /// - void OnAppearing(Page page); + public void OnAppearing(Page page); /// /// Page.OnDisappearing /// /// - void OnDisappearing(Page page); + public void OnDisappearing(Page page); /// /// Page.OnNavigatedTo /// /// - void OnNavigatedTo(Page page); + public void OnNavigatedTo(Page page); /// /// Page.OnNavigatedFrom /// /// - void OnNavigatedFrom(Page page); + public void OnNavigatedFrom(Page page); } diff --git a/src/Sentry.Maui/Internal/MauiEventsBinder.cs b/src/Sentry.Maui/Internal/MauiEventsBinder.cs index 3311b6e78c..cd97bccb3d 100644 --- a/src/Sentry.Maui/Internal/MauiEventsBinder.cs +++ b/src/Sentry.Maui/Internal/MauiEventsBinder.cs @@ -458,6 +458,7 @@ private void OnPageOnLayoutChanged(object? sender, EventArgs _) => private void RunPageEventHandlers(Action action) { - foreach (var handler in _pageEventHandlers) action(handler); // TODO: try/catch in case of user code? + foreach (var handler in _pageEventHandlers) + action(handler); // TODO: try/catch in case of user code? } } diff --git a/src/Sentry/Internal/NoOpSpan.cs b/src/Sentry/Internal/NoOpSpan.cs index b946c9cacf..f7d5fada65 100644 --- a/src/Sentry/Internal/NoOpSpan.cs +++ b/src/Sentry/Internal/NoOpSpan.cs @@ -32,8 +32,8 @@ public string Operation public event EventHandler? StatusChanged { - add {} - remove {} + add { } + remove { } } public string? Description