sentry-dart icon indicating copy to clipboard operation
sentry-dart copied to clipboard

TTID & TTFD for navigating between screens

Open buenaflor opened this issue 2 years ago • 4 comments

Description

Implement:

  • TTID (Time to Initial Display) - for a screen, the time it takes to load the minimally interactive state (background, navigation, fast loading local content)

  • TTFD (Time to Full Display) - for a screen, the time it takes to completely render the screen, including content from disk/network

TTID:

  • enabled by default

TTFD:

  • disabled by default
  • opt-in with 30 seconds timeout where the span finishes with deadline exceeded
  • manually reported by user with Sentry.reportFullyDisplayed()

buenaflor avatar Dec 11 '23 15:12 buenaflor

This TTID and TTID metric collection is a bit tricky with Flutter since it doesn't have similar concept to Androids Activity or iOS' ViewController. In Flutter it's just widget. Changing "screen"s is just rebuilding a widget, but widgets don't work like Activities or ViewControllers and you basically can't do heavy work in them. Since it's all widgets you don't really have a starting point from where to start measuring TTID and TTFD.

Therefore, the only time those metrics are actually helpful is during the app startup, not during screen changes.

The first frame is drawn when the first endOfFrame future finished. (Related https://api.flutter.dev/flutter/scheduler/SchedulerBinding/scheduleWarmUpFrame.html)

However, you can manually defer drawing of the first frame via https://api.flutter.dev/flutter/rendering/RendererBinding/deferFirstFrame.html together with https://api.flutter.dev/flutter/rendering/RendererBinding/allowFirstFrame.html

Hooking into those methods is possible via custom widgetsbinding (I did play around with that for measuring methodchannel, so I can give you some pointers if interested). Ultimately, I kinda question whether the metrics are actually useful for Flutter since they're probably virtually identical to the app start metrics, or at least very similar.

ueman avatar Dec 11 '23 15:12 ueman

If it's done right, it still makes sense IMO, eg when a new route is called, it's technically a new widget/screen.

The question is if it's possible to determine the very first frame drawn on that route for properly measuring TTID (without a delay, will need a callback that is executed right away after the frame is drawn).

The other thing to figure out is that the routing methods are non-async methods and you can't await the calls nor wrap the methods in a Future in the observer, so the timing isn't always accurate because when you request a new route, most of the time is executed right away if the event loop isn't busy, but if it is, it might execute a few milliseconds later, and the ttid/ttfd is already off by a bit, today this problem already exists with the current transactions in the sentry observer IIRC.

marandaneto avatar Dec 12 '23 07:12 marandaneto

Would using WidgetsBinding.instance.addPostFrameCallback in didPush be able to approximate a measurement?

afaik didPush is called after the page is pushed into the navigation stack so we might lose a couple milliseconds if we start the measurement in didPush.

Doesn't solve the potential event loop delays but it might be a direction we can look into for the callback.

buenaflor avatar Dec 12 '23 12:12 buenaflor

Would using WidgetsBinding.instance.addPostFrameCallback in didPush be able to approximate a measurement?

@denrase and I played a bit with that, we also used it for app start, but it's a global callback, and not per screen, so you might get the wrong callback and assume it's the one you want (just a hunch).

afaik didPush is called after the page is pushed into the navigation stack so we might lose a couple milliseconds if we start the measurement in didPush.

didPush is a sync method, but Sentry's start transaction isn't (maybe it can be now?), so you have to go around that too.

marandaneto avatar Dec 12 '23 13:12 marandaneto