-
-
Notifications
You must be signed in to change notification settings - Fork 277
Feat: Add tracing for routes #611
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
Closed
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,9 @@ import '../../sentry_flutter.dart'; | |
| /// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/ | ||
| const _navigationKey = 'navigation'; | ||
|
|
||
| /// Used as value for [SentrySpanContext.operation] | ||
| const _transactionOp = 'ui.load'; | ||
|
|
||
| /// This is a navigation observer to record navigational breadcrumbs. | ||
| /// For now it only records navigation events and no gestures. | ||
| /// | ||
|
|
@@ -35,10 +38,20 @@ const _navigationKey = 'navigation'; | |
| /// - [RouteObserver](https://api.flutter.dev/flutter/widgets/RouteObserver-class.html) | ||
| /// - [Navigating with arguments](https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments) | ||
| class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> { | ||
| SentryNavigatorObserver({Hub? hub}) : hub = hub ?? HubAdapter(); | ||
| SentryNavigatorObserver({Hub? hub, this.enableTracing = false}) | ||
| : hub = hub ?? HubAdapter(); | ||
|
|
||
| final Hub hub; | ||
|
|
||
| /// Create a new transaction, which gets bound to the scope, on each | ||
| /// navigation event. | ||
| /// [RouteSettings] are added as extras. The [RouteSettings.name] is used as | ||
| /// a name. | ||
| final bool enableTracing; | ||
|
|
||
| ISentrySpan? _currentTransaction; | ||
| ISentrySpan? _currentSpan; | ||
|
|
||
| @override | ||
| void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { | ||
| super.didPush(route, previousRoute); | ||
|
|
@@ -47,6 +60,11 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> { | |
| from: previousRoute?.settings, | ||
| to: route.settings, | ||
| ); | ||
| if (route is PopupRoute) { | ||
| _startSpan(route); | ||
| } else { | ||
| _startTrace(route); | ||
| } | ||
| } | ||
|
|
||
| @override | ||
|
|
@@ -58,6 +76,14 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> { | |
| from: oldRoute?.settings, | ||
| to: newRoute?.settings, | ||
| ); | ||
| if (oldRoute is ModalRoute) { | ||
| _currentSpan?.finish(); | ||
| } | ||
| if (newRoute is PopupRoute) { | ||
| _startSpan(newRoute); | ||
| } else { | ||
| _startTrace(newRoute); | ||
| } | ||
| } | ||
|
|
||
| @override | ||
|
|
@@ -69,6 +95,10 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> { | |
| from: route.settings, | ||
| to: previousRoute?.settings, | ||
| ); | ||
| if (previousRoute is ModalRoute) { | ||
| _currentSpan?.finish(); | ||
| } | ||
| _startTrace(previousRoute); | ||
| } | ||
|
|
||
| void _addBreadcrumb({ | ||
|
|
@@ -82,6 +112,42 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> { | |
| to: to, | ||
| )); | ||
| } | ||
|
|
||
| Future<void> _startTrace(Route? route) async { | ||
| if (!enableTracing) { | ||
| return; | ||
| } | ||
| await _currentTransaction?.finish(); | ||
|
|
||
| var span = hub.startTransaction( | ||
| route?.settings.name ?? 'unnamed page', | ||
| _transactionOp, | ||
| bindToScope: true, | ||
| ); | ||
|
|
||
| final arguments = route?.settings.arguments; | ||
| if (arguments != null) { | ||
| span.setData('route_settings_arguments', arguments); | ||
| } | ||
|
|
||
| _currentTransaction = span; | ||
| } | ||
|
|
||
| Future<void> _startSpan(PopupRoute? route) async { | ||
| if (!enableTracing) { | ||
| return; | ||
| } | ||
| await _currentSpan?.finish(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| final span = _currentTransaction?.startChild( | ||
| _transactionOp, | ||
| description: route?.settings.name ?? 'unnamed popup', | ||
| ); | ||
| final arguments = route?.settings.arguments; | ||
| if (arguments != null) { | ||
| span?.setData('route_settings_arguments', arguments); | ||
| } | ||
| _currentSpan = span; | ||
| } | ||
| } | ||
|
|
||
| /// This class makes it easier to record breadcrumbs for events of Flutters | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it mean that the current
_currentTransactiononly finishes when it's about to start a new one?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that's right. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really, a transaction should be finished within a lifecycle of an operation, if the goal of this transaction is tracking the rendering time of the screen, it should finish when it ends rendering, do we know when that happens? eg, on Android, we do that via the ActivityLifecycleCallbacks, starts on
onActivityCreatedand finishes ononActivityResumedwhich is the time that the screen is already responsive to the user.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe the
RouteObserverwould not be the right class to be instrumentedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I'm not sure if that's possible on Flutter. At least not with a Route Observer.
In Flutter there's no real distinction between widgets, routes and so on like there is in Android with Activities, Fragments and Views.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We even get a duration as a param to the callback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that would be awesome, we'd like to use the very same function for some other reason, see #576 (comment)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, we may be able to create a mixin that users can add to their pages/widgets, like here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're going down that way, you could also just use
addTimingsCallback. In release mode it's approximately called every 100 ms and also includes detailed information about the time spent on rasterization, building widgets, etc.100ms seems to be a more reasonable time duration to me to capture child spans for http requests and so on.
However I would still advise to use idle transactions, as Flutter has no real concept of a lifecycle nor of a page. It's much more similar to web in that regard than to iOS' ViewControllers or Androids Activities.
Each widget has its own lifecycle but it potentially goes through the complete lifecycle each widget tree build (which I think is once each frame but could also be multiple times each frame).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it's tricky. The flutter doc does compare routes to Activities/ViewControllers, so we could use those in the same way, but maybe also provide some manual hooks for users, becuase as you said, everything just a widget and we cant't just assume that widgets moving between navigation are the only main screens.
If
didPushis indeed always called after first render of the widget, we can't useaddPostFrameCallbackanyway if i understood this correctly. Need to play around with those three (Navigation, Post Frame Callback and Timing Callback) and see if this can be combined to reasonably go non-idling, but i suspect that you are right in this regard.