Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(MAUI): Automatically captured breadcrumbs #2900

Merged
merged 25 commits into from
Dec 1, 2023

Conversation

bitsandfoxes
Copy link
Contributor

@bitsandfoxes bitsandfoxes commented Nov 24, 2023

Fixes #2804
Resolves #2827

This PR does a couple of things:

  1. It completely removes the binding to events via reflection. It would not work with AOT anyway. But the red flags are the unintended side-effects (see Rendering UI issue when Sentry SDK is enabled  #2804) where the SDK seems to consume third-party events.
  2. Breadcrumbs are incredibly noisy right now and do not seem to offer any value. Our bare sample app created 94 breadcrumbs. I trimmed them down to the most essential ones - Navigation, Buttons, Lifecycle - and opted to stick with the finalizing ones for the "duplicates. (i.e. Pushing vs Pushed).
  3. It also sets up unbinding upon element removal to prevent memory leaks

New:

"message": "App.ChildAdded",
"message": "AppShell.Appearing"
"message": "App.PageAppearing",
"message": "Window.Created"
"message": "App.PageAppearing",
"message": "AppShell.Navigated",
"message": "ShellContent.ChildAdded",
"message": "ShellContent.ChildAdded",
"message": "MainPage.LayoutChanged"
"message": "Window.Activated"
"message": "Button.Pressed",
"message": "Button.Released",
"message": "The button has been clicked 1 times"
"message": "Button.Clicked",
"message": "Button.Pressed",
"message": "Button.Released",

Old:

"App.HandlerChanging",
"App.HandlerChanged",
"App.ChildAdded",
"AppShell.Appearing"
"App.PageAppearing",
"Window.HandlerChanging",
"AppShell.HandlerChanging",
"AppShell.HandlerChanged",
"AppShell.MeasureInvalidated"
"Window.HandlerChanged",
"Window.Created"
"AppShell.Loaded"
"AppShell.SizeChanged"
"ShellContent.Appearing"
"App.PageAppearing",
"AppShell.Navigated",
"ShellContent.ChildAdded",
"MainPage.HandlerChanging",
"ScrollView.HandlerChanging",
"VerticalStackLayout.HandlerChanging",
"Image.HandlerChanging",
"Image.HandlerChanged",
"Image.MeasureInvalidated"
"Label.HandlerChanging",
"Label.HandlerChanged",
"Label.MeasureInvalidated"
"Label.HandlerChanging",
"Label.HandlerChanged",
"Label.MeasureInvalidated"
"Button.HandlerChanging",
"Button.HandlerChanged",
"Button.MeasureInvalidated",
"Button.HandlerChanging",
"Button.HandlerChanged",
"Button.MeasureInvalidated",
"Button.HandlerChanging",
"Button.HandlerChanged",
"Button.MeasureInvalidated",
"Button.HandlerChanging",
"Button.HandlerChanged",
"Button.MeasureInvalidated",
"Button.HandlerChanging",
"Button.HandlerChanged",
"Button.MeasureInvalidated",
"Button.HandlerChanging",
"Button.HandlerChanged",
"Button.MeasureInvalidated",
"VerticalStackLayout.HandlerChanged",
"ScrollView.MeasureInvalidated"
"VerticalStackLayout.MeasureInvalidated"
"ScrollView.HandlerChanged",
"MainPage.MeasureInvalidated"
"ScrollView.MeasureInvalidated"
"MainPage.HandlerChanged",
"MainPage.MeasureInvalidated"
"Image.Loaded"
"Label.Loaded"
"Label.Loaded"
"Button.Loaded",
"Button.Loaded",
"Button.Loaded",
"Button.Loaded",
"Button.Loaded",
"Button.Loaded",
"VerticalStackLayout.Loaded"
"ScrollView.Loaded"
"MainPage.Loaded"
"ShellContent.ChildAdded",
"Image.Loaded"
"Label.Loaded"
"Grid.Loaded"
"Grid.SizeChanged"
"Image.SizeChanged"
"Label.SizeChanged"
"ScrollView.SizeChanged"
"MainPage.LayoutChanged"
"MainPage.SizeChanged"
"VerticalStackLayout.SizeChanged"
"Image.SizeChanged"
"Label.SizeChanged"
"Label.SizeChanged"
"Button.SizeChanged",
"Button.SizeChanged",
"Button.SizeChanged",
"Button.SizeChanged",
"Button.SizeChanged",
"Window.Activated"
"Button.Pressed",
"Button.Released",
"Button.MeasureInvalidated",
"The button has been clicked 1 times"
"Button.Clicked",
"Button.SizeChanged",
"Button.Pressed",
"Button.Released",

@bitsandfoxes bitsandfoxes self-assigned this Nov 28, 2023
@bitsandfoxes bitsandfoxes marked this pull request as ready for review November 28, 2023 17:06
@bitsandfoxes bitsandfoxes changed the title fix(MAUI): Removed reflection based breadcrumb creation fix(MAUI): Automatically captured breadcrumbs Nov 28, 2023
CHANGELOG.md Outdated Show resolved Hide resolved
samples/Sentry.Samples.Maui/MainPage.xaml.cs Outdated Show resolved Hide resolved
@jamescrosswell
Copy link
Collaborator

Delete fest! 🎉

@Redth
Copy link

Redth commented Nov 28, 2023

Really good start to helping performance.

I'd like to suggest also looking at either removing, or making opt-in, everything in BindElementEvents. Profiling my virtual listview control usage in an app, it looks like a lot of time is spent dealing with adding breadcrumbs in the element.BindingContextChanged handler. When you're scrolling through a list quickly and views are being recycled, the binding context is constantly being swapped out as you scroll. This adds a lot of unnessary churn/overhead for what I tend to think is little value from a logging/breadcrumb perspective.

Could these be options in the extension configuration to opt-out (or opt-in if they are default opt-out) to wiring up Element events? Or, perhaps another delegate similar to the BeforeBreadcrumb one to help decide if a particular element should be wired up or not?

{
if (visualElement is Element element)
{
element.HandlerChanged += (sender, _) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're subscribing to events here but not unsubscribing anywhere. This is often a memory leak source.

Here's some tips on looking for leaks: https://github.com/dotnet/maui/wiki/Memory-Leaks

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var type = element.Handler?.GetType();
var handlerName = type is not null ? type.Name : string.Empty;

SentrySdk.AddBreadcrumb($"'{elementName}' handler changed to '{handlerName}'", "system", "ui.handlers");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we're accessing SentrySdk (static method) so not really testable (usually we'd take IHub ?)

@bitsandfoxes
Copy link
Contributor Author

Could these be options in the extension configuration to opt-out (or opt-in if they are default opt-out) to wiring up Element events?

Definitely! Thanks for the feedback. I was just looking for even more reasons to trim them down!

Copy link

@Redth Redth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for incorporating my feedback :)

public static void OnWindowOnBackgrounding(object? sender, BackgroundingEventArgs e) =>
Hub.AddBreadcrumbForEvent(Options, sender, nameof(Window.Backgrounding), SystemType, LifecycleCategory, data =>
{
if (!Options?.IncludeBackgroundingStateInBreadcrumbs ?? true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default is null so we're actually defaulting to true here? that'd be nice

samples/Sentry.Samples.Maui/MainPage.xaml.cs Outdated Show resolved Hide resolved
src/Sentry.Maui/BindableSentryMauiOptions.cs Outdated Show resolved Hide resolved
src/Sentry.Maui/Internal/MauiEvents.cs Outdated Show resolved Hide resolved
src/Sentry.Maui/Internal/MauiEventsBinder.cs Outdated Show resolved Hide resolved
src/Sentry.Maui/Internal/MauiEventsBinder.cs Outdated Show resolved Hide resolved
}

public void BindApplicationEvents(Application application)
public void HandleApplicationEvents(Application application, bool bind = true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do we call this with bind=false? Looking at when do we unregister events. I imagine when the object we subscribed from is done with we need to do that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two places:

  1. We hook in the lifecycle events in src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs and remove ourselves on shutdown/terminate
  2. The application has application.DescendantRemoved += OnApplicationOnDescendantRemoved; and we crawl through all the events we bound to and remove ourselves.

Copy link
Member

@bruno-garcia bruno-garcia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! With a feature of no leaks! :)

@bruno-garcia
Copy link
Member

This seems ready to go but CI stuck on some xharness error?

@bruno-garcia
Copy link
Member

/usr/bin/sh -c pwsh scripts/device-test.ps1 android -Run
INFO    | Boot completed in 28075 ms
INFO    | Increasing screen off timeout, logcat buffer size to 2M.
Skipping NuGet package signature verification.
You can invoke the tool using the following command: xharness
Tool 'microsoft.dotnet.xharness.cli' (version '1.0.0-prerelease.23252.4') was successfully installed.
[1.0.0-prerelease.23252.4+d4d9f33ac28fe4680c3c4d12ce69a1a44d6f6b20] XHarness command issued: android test --app bin/io.sentry.dotnet.maui.device.testapp-Signed.apk --package-name io.sentry.dotnet.maui.device.testapp --output-directory=test_output
info: Will attempt to find device supporting architectures: 'x86_64'
info: Finding attached devices/emulators...
info: Active Android device set to serial 'emulator-5554'
info: Waiting for device to be available (max 5 minutes)
info: Attempting to remove apk 'io.sentry.dotnet.maui.device.testapp'..
info: APK 'io.sentry.dotnet.maui.device.testapp' was not on device
info: Attempting to install /home/runner/work/sentry-dotnet/sentry-dotnet/bin/io.sentry.dotnet.maui.device.testapp-Signed.apk
info: Successfully installed /home/runner/work/sentry-dotnet/sentry-dotnet/bin/io.sentry.dotnet.maui.device.testapp-Signed.apk
info: Killing all running processes for 'io.sentry.dotnet.maui.device.testapp': 
info: Starting default instrumentation class on io.sentry.dotnet.maui.device.testapp (exit code 0 == success)
info: Running instrumentation class {default} took 14.5516671 seconds
info: Found XML result file: '/storage/emulated/0/Download/io.sentry.dotnet.maui.device.testapp/1a5516b1b5a14c779257c121851d6501/TestResults.xml'(key: test-results-path)
info: Attempting to pull contents of /storage/emulated/0/Download/io.sentry.dotnet.maui.device.testapp/1a5516b1b5a14c779257c121851d6501/TestResults.xml to /home/runner/work/sentry-dotnet/sentry-dotnet/test_output
E1201 17:45:49.910855    2939 FrameBuffer.cpp:3765] Failed to find ColorBuffer:60
info: Test execution summary:
      Tests run: 1590 Passed: [158](https://github.com/getsentry/sentry-dotnet/actions/runs/7063255444/job/19228957446?pr=2900#step:8:162)0 Inconclusive: 0 Failed: 1 Ignored: 9
info: Instrumentation finished normally with exit code 1
info: Wrote current ADB log to /home/runner/work/sentry-dotnet/sentry-dotnet/test_output/adb-logcat-io.sentry.dotnet.maui.device.testapp-default.log
fail: Non-success instrumentation exit code: 1, expected: 0
info: Attempting to remove apk 'io.sentry.dotnet.maui.device.testapp'..
info: Successfully uninstalled io.sentry.dotnet.maui.device.testapp
XHarness exit code: 1 (TESTS_FAILED)
Exception: /home/runner/work/sentry-dotnet/sentry-dotnet/scripts/device-test.ps1:73
Line |
  73 |                  throw "xharness run failed with non-zero exit code"
     |                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | xharness run failed with non-zero exit code

Error: The process '/usr/bin/sh' failed with exit code 1
Terminate Emulator
  /usr/local/lib/android/sdk/platform-tools/adb -s emulator-5554 emu kill
  OK: killing emulator, bye bye
  OK
  INFO    | Wait for emulator (pid 2785) 20 seconds to shutdown gracefully before kill;you can set environment variable ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL(in seconds) to change the default value (20 seconds)
INFO    | Discarding the changed state: command-line flag
WARNING | Discarding the changed state (command-line flag).
ERROR   | stop: Not implemented

@vaind is this something you've seen before?

@vaind
Copy link
Collaborator

vaind commented Dec 1, 2023

@vaind is this something you've seen before?

nothing wrong with xharness, it's a test failure, says so in the log:

Tests run: 1590 Passed: 1580 Inconclusive: 0 Failed: 1 Ignored: 9

Specifically Sentry.Maui.Tests.SentryMauiAppBuilderExtensionsTests.UseSentry_BindsToApplicationStartupEvent_Android https://github.com/getsentry/sentry-dotnet/actions/runs/7063255444?pr=2900

@bitsandfoxes
Copy link
Contributor Author

Yes. I'm crawling through the logs and fixing the tests.

@bitsandfoxes bitsandfoxes merged commit 610f5fa into main Dec 1, 2023
19 checks passed
@bitsandfoxes bitsandfoxes deleted the fix/maui-event-binding branch December 1, 2023 20:58
@bruno-garcia
Copy link
Member

Oh sorry Ivan! Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants