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: Implement fallback system to screens that aren't reporting on the native layer the time to display. #4042

Draft
wants to merge 28 commits into
base: main
Choose a base branch
from

Conversation

lucas-zimerman
Copy link
Collaborator

@lucas-zimerman lucas-zimerman commented Aug 22, 2024

📢 Type of change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring

📜 Description

This change introduces a new event emitter that uses the function requestAnimationFrame during a navigation, requestAnimationFrame is useful in this case because the callback is only invoked when all the screen frames are flushed, allowing us to have a good value for the time to display.

The way this implementation works is quite simple:

  • When we have the information from the native layer about the time to display the page, we rely on this information.
  • When the native layer didn't respond after 3 seconds that requestAnimationFrame received the callback, we use the time from requestAnimationFrame and simulate a native event.

This way we can patch SentryEventEmitter to receive the correct value when the Native part doesn't have any value to return.

💡 Motivation and Context

Close #3934

💚 How did you test it?

The playground tab on our sample is a good case for testing, since it doesn't use navigation stack on it, the events on this condition are limited, not generating and required event by us in order to track the time to display.

I used it and other tabs to compare the difference between the original implementation and also requestAnimationFrame, and the time difference between each other was quite low:

1St Tab
JS: requestAnimationFrame, "newFrameTimestampInSeconds" is 1724293664.8300002
Original: InitAsync Event received {"newFrameTimestampInSeconds":1724293664.8179998}

2Nd Tab
JS: requestAnimationFrame, "newFrameTimestampInSeconds" is 1724293761.688
Original: InitAsync Event received {"newFrameTimestampInSeconds":1724293761.689}

3Rd Tab
JS: requestAnimationFrame, "newFrameTimestampInSeconds" is 1724293824.887
Original: Android didn't emit an event for this page so it wasn't measured

Before this change on the playground screen:
https://sentry-sdks.sentry.io/performance/trace/10bd4bf28052404592f408f3cf58175a/?dataset=transactions&field=title&field=event.type&field=project&field=user.display&field=timestamp&field=replayId&fov=0%2C15046.88916015625&id=21705&name=&node=trace-root&project=5428561&query=&queryDataset=transaction-like&sort=-timestamp&source=discover&statsPeriod=1h&timestamp=1724358725&topEvents=5&yAxis=count%28%29

After this change on the playground screen:
https://sentry-sdks.sentry.io/performance/trace/8cb478b4370e444fa4a7b9be778d51ab/?dataset=transactions&field=title&field=event.type&field=project&field=user.display&field=timestamp&field=replayId&fov=0%2C235&id=21705&name=&node=txn-b5a8ba18aa254fe79743133baf9071b2&project=5428561&query=&queryDataset=transaction-like&sort=-timestamp&source=discover&statsPeriod=1h&timestamp=1724359145&topEvents=5&yAxis=count%28%29

📝 Checklist

  • I reviewed submitted code
  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled
  • All tests passing
  • No breaking changes

🔮 Next steps

Copy link
Contributor

github-actions bot commented Aug 22, 2024

Fails
🚫 Please consider adding a changelog entry for the next release.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

- Implement fallback system to screens that aren't reporting on the native layer the time to display ([#4042](https://github.com/getsentry/sentry-react-native/pull/4042))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description.

Generated by 🚫 dangerJS against 7e5f764

Copy link
Contributor

github-actions bot commented Aug 22, 2024

Android (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 467.43 ms 471.20 ms 3.77 ms
Size 17.73 MiB 20.07 MiB 2.33 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
31fcca2 391.22 ms 414.78 ms 23.56 ms
76d1baf+dirty 335.72 ms 355.52 ms 19.80 ms
d361d38 354.10 ms 381.69 ms 27.59 ms
62a750b 395.96 ms 423.36 ms 27.41 ms
5a22220 412.38 ms 447.35 ms 34.97 ms
700cbf4 425.56 ms 436.26 ms 10.70 ms
86d6d2c+dirty 332.90 ms 352.45 ms 19.55 ms
dadc233+dirty 333.78 ms 343.94 ms 10.16 ms
0677344 327.74 ms 337.14 ms 9.40 ms
1c65324 426.37 ms 460.36 ms 33.99 ms

App size

Revision Plain With Sentry Diff
31fcca2 17.73 MiB 19.90 MiB 2.17 MiB
76d1baf+dirty 17.73 MiB 20.04 MiB 2.31 MiB
d361d38 17.73 MiB 19.81 MiB 2.08 MiB
62a750b 17.73 MiB 19.93 MiB 2.20 MiB
5a22220 17.73 MiB 19.93 MiB 2.20 MiB
700cbf4 17.73 MiB 20.07 MiB 2.33 MiB
86d6d2c+dirty 17.73 MiB 20.04 MiB 2.31 MiB
dadc233+dirty 17.73 MiB 19.75 MiB 2.02 MiB
0677344 17.73 MiB 19.81 MiB 2.07 MiB
1c65324 17.73 MiB 19.95 MiB 2.21 MiB

Previous results on branch: test/deadline

Startup times

Revision Plain With Sentry Diff
993f0a4 486.06 ms 505.04 ms 18.98 ms

App size

Revision Plain With Sentry Diff
993f0a4 17.73 MiB 20.06 MiB 2.33 MiB

Copy link
Contributor

github-actions bot commented Aug 22, 2024

Android (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 418.18 ms 460.47 ms 42.29 ms
Size 7.15 MiB 8.34 MiB 1.19 MiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
f06c879+dirty 361.27 ms 407.88 ms 46.61 ms
2534337+dirty 597.14 ms 665.04 ms 67.90 ms
76d1baf+dirty 339.02 ms 408.65 ms 69.63 ms
1d86dd6+dirty 335.76 ms 371.22 ms 35.46 ms
1c65324+dirty 381.10 ms 427.26 ms 46.16 ms
5bb8d5f+dirty 356.71 ms 389.65 ms 32.94 ms
5a22220+dirty 384.61 ms 419.06 ms 34.45 ms
0db0c72+dirty 335.20 ms 351.06 ms 15.86 ms
5446992+dirty 371.61 ms 390.00 ms 18.39 ms
4a6664f+dirty 357.02 ms 394.91 ms 37.89 ms

App size

Revision Plain With Sentry Diff
f06c879+dirty 7.15 MiB 8.12 MiB 997.78 KiB
2534337+dirty 7.15 MiB 8.11 MiB 988.68 KiB
76d1baf+dirty 7.15 MiB 8.09 MiB 964.41 KiB
1d86dd6+dirty 7.15 MiB 8.13 MiB 1002.18 KiB
1c65324+dirty 7.15 MiB 8.22 MiB 1.07 MiB
5bb8d5f+dirty 7.15 MiB 8.21 MiB 1.06 MiB
5a22220+dirty 7.15 MiB 8.21 MiB 1.06 MiB
0db0c72+dirty 7.15 MiB 8.04 MiB 911.02 KiB
5446992+dirty 7.15 MiB 8.12 MiB 999.45 KiB
4a6664f+dirty 7.15 MiB 8.22 MiB 1.07 MiB

Previous results on branch: test/deadline

Startup times

Revision Plain With Sentry Diff
993f0a4+dirty 432.25 ms 491.56 ms 59.31 ms

App size

Revision Plain With Sentry Diff
993f0a4+dirty 7.15 MiB 8.34 MiB 1.19 MiB

Copy link
Contributor

github-actions bot commented Aug 22, 2024

iOS (legacy) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1234.54 ms 1235.31 ms 0.76 ms
Size 2.36 MiB 3.08 MiB 735.56 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
728164b+dirty 1256.10 ms 1259.08 ms 2.98 ms
8900e1a+dirty 1210.27 ms 1218.66 ms 8.39 ms
ad6c299+dirty 1244.76 ms 1260.10 ms 15.34 ms
15c80ab+dirty 1223.74 ms 1228.96 ms 5.22 ms
b1e8712+dirty 1256.02 ms 1265.14 ms 9.12 ms
e2b64fe+dirty 1232.22 ms 1255.20 ms 22.98 ms
baa882f+dirty 1218.00 ms 1227.04 ms 9.04 ms
70caa60+dirty 1218.27 ms 1230.30 ms 12.03 ms
70e6261+dirty 1220.09 ms 1230.04 ms 9.95 ms
4a6664f+dirty 1209.49 ms 1208.63 ms -0.86 ms

App size

Revision Plain With Sentry Diff
728164b+dirty 2.36 MiB 2.88 MiB 530.38 KiB
8900e1a+dirty 2.36 MiB 2.83 MiB 479.25 KiB
ad6c299+dirty 2.36 MiB 2.84 MiB 488.85 KiB
15c80ab+dirty 2.36 MiB 2.83 MiB 474.49 KiB
b1e8712+dirty 2.36 MiB 2.84 MiB 488.84 KiB
e2b64fe+dirty 2.36 MiB 2.85 MiB 495.80 KiB
baa882f+dirty 2.36 MiB 3.08 MiB 731.91 KiB
70caa60+dirty 2.36 MiB 2.83 MiB 479.27 KiB
70e6261+dirty 2.36 MiB 3.03 MiB 680.42 KiB
4a6664f+dirty 2.36 MiB 3.04 MiB 696.39 KiB

Previous results on branch: test/deadline

Startup times

Revision Plain With Sentry Diff
993f0a4+dirty 1238.52 ms 1241.51 ms 2.99 ms

App size

Revision Plain With Sentry Diff
993f0a4+dirty 2.36 MiB 3.08 MiB 732.19 KiB

Copy link
Contributor

github-actions bot commented Aug 22, 2024

iOS (new) Performance metrics 🚀

  Plain With Sentry Diff
Startup time 1228.69 ms 1218.22 ms -10.47 ms
Size 2.92 MiB 3.64 MiB 741.52 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
728164b+dirty 1280.06 ms 1285.26 ms 5.20 ms
8900e1a+dirty 1268.36 ms 1273.04 ms 4.68 ms
ad6c299+dirty 1248.50 ms 1248.88 ms 0.38 ms
15c80ab+dirty 1248.41 ms 1251.24 ms 2.83 ms
b1e8712+dirty 1284.11 ms 1297.82 ms 13.71 ms
e2b64fe+dirty 1285.78 ms 1297.56 ms 11.78 ms
baa882f+dirty 1235.48 ms 1229.02 ms -6.46 ms
70caa60+dirty 1279.08 ms 1281.54 ms 2.46 ms
70e6261+dirty 1224.90 ms 1231.02 ms 6.12 ms
4a6664f+dirty 1218.77 ms 1221.07 ms 2.30 ms

App size

Revision Plain With Sentry Diff
728164b+dirty 2.92 MiB 3.44 MiB 533.26 KiB
8900e1a+dirty 2.92 MiB 3.39 MiB 485.96 KiB
ad6c299+dirty 2.92 MiB 3.40 MiB 494.12 KiB
15c80ab+dirty 2.92 MiB 3.39 MiB 481.56 KiB
b1e8712+dirty 2.92 MiB 3.40 MiB 494.15 KiB
e2b64fe+dirty 2.92 MiB 3.41 MiB 499.97 KiB
baa882f+dirty 2.92 MiB 3.64 MiB 738.56 KiB
70caa60+dirty 2.92 MiB 3.39 MiB 486.04 KiB
70e6261+dirty 2.92 MiB 3.59 MiB 686.11 KiB
4a6664f+dirty 2.92 MiB 3.60 MiB 702.09 KiB

Previous results on branch: test/deadline

Startup times

Revision Plain With Sentry Diff
993f0a4+dirty 1241.51 ms 1235.85 ms -5.66 ms

App size

Revision Plain With Sentry Diff
993f0a4+dirty 2.92 MiB 3.64 MiB 738.79 KiB

@kahest kahest mentioned this pull request Aug 26, 2024
7 tasks
@krystofwoldrich
Copy link
Member

krystofwoldrich commented Aug 26, 2024

Hi @lucas-zimerman,
thank you for the detailed description, and opening the draft PR, this makes validation and discussing the ideas much easier.

I think waiting for the next drawn frame is a good fallback. But there is one party breaker hidden in the RN implementation. Compared to browsers implementation, in RN requestAnimationFrame is the same as setTimeout(timeout, 0).

requestAnimationFrame -> CreateTimer -> Timer is hook to DisplayLink of JS Thread
https://github.com/facebook/react-native/blob/c30e35fb44affb179c9a208cf0a3e4575347e76f/packages/react-native/ReactCommon/react/runtime/TimerManager.cpp#L443

DisplayLink uses current thread -> JS Thread
https://github.com/facebook/react-native/blob/4e12c2e37cb168f09f8768941cb915e4024a1c34/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm#L354

This means the callback runs after the React Native Node Tree is assembled and passed to native. So unless the JS loop is doing some heavy task, it will generally be executed before the render.

We can fix that by implementing our own requestAnimationFrame which will emit an event from the native layer when the next frame is rendered.

onNavigationStateChanged() {
  // executed in useEffect of the root navigator 
  SENTRY_NATIVE.requestAnimationFrame((frameTimestamp) {
  	// this will actually be called on the next native frame (linked to main app thread)
  })
}
// NATIVE IMPL
SENTRY_NATIVE.requestAnimationFrame(callback) {
	// Similar to RNSentryOnDrawReporterView implementation
	// https://github.com/getsentry/sentry-react-native/blob/cc9666e872837955125bd0a80492e57625fc16a0/android/src/main/java/io/sentry/react/RNSentryOnDrawReporterManager.java#L121
	// https://github.com/getsentry/sentry-react-native/blob/2b83fc5d3de33c5eab1c8a08d762ac813e3a7c9b/ios/RNSentryOnDrawReporter.m#L44
	hookToNextFrame(() {
		callback(Date.now())
	})
}

The native implementation would the same method to hook into the render loop as RNSentryOnDrawReporterView but instead of component it would be native method of RNSentry.

I think we can still use the JS requestAnimationFrame to add support to react-native-web and maybe good fall back for Expo Go.

It should work well with the current draft, replacing requestAnimationFrame with custom method.

Let me know what you think, if my though process make sense, or I've missed something.

@lucas-zimerman
Copy link
Collaborator Author

The Android integration is done, we will now finish the iOS integration

@lucas-zimerman
Copy link
Collaborator Author

iOS integration should also be done and tests are also completed.

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

Successfully merging this pull request may close these issues.

Manual TTID/TTFD API times out
2 participants