performanceForSentry.tsx 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. import {Fragment, Profiler, ReactNode, useEffect, useRef} from 'react';
  2. import {timestampWithMs} from '@sentry/utils';
  3. import getCurrentSentryReactTransaction from './getCurrentSentryReactTransaction';
  4. const MIN_UPDATE_SPAN_TIME = 16; // Frame boundary @ 60fps
  5. /**
  6. * Callback for React Profiler https://reactjs.org/docs/profiler.html
  7. */
  8. export function onRenderCallback(
  9. id: string,
  10. phase: 'mount' | 'update',
  11. actualDuration: number
  12. ) {
  13. try {
  14. const transaction = getCurrentSentryReactTransaction();
  15. if (transaction && actualDuration > MIN_UPDATE_SPAN_TIME) {
  16. const now = timestampWithMs();
  17. transaction.startChild({
  18. description: `<${id}>`,
  19. op: `ui.react.${phase}`,
  20. startTimestamp: now - actualDuration / 1000,
  21. endTimestamp: now,
  22. });
  23. }
  24. } catch (_) {
  25. // Add defensive catch since this wraps all of App
  26. }
  27. }
  28. export const VisuallyCompleteWithData = ({
  29. id,
  30. hasData,
  31. children,
  32. }: {
  33. children: ReactNode;
  34. hasData: boolean;
  35. id: string;
  36. }) => {
  37. const isVisuallyCompleteSet = useRef(false);
  38. const isDataCompleteSet = useRef(false);
  39. useEffect(() => {
  40. try {
  41. const transaction: any = getCurrentSentryReactTransaction(); // Using any to override types for private api.
  42. const now = timestampWithMs();
  43. const transactionStart = transaction.startTimestamp;
  44. const normalizedValue = Math.abs((now - transactionStart) * 1000);
  45. if (!isVisuallyCompleteSet.current) {
  46. transaction.registerBeforeFinishCallback((t, _) => {
  47. // Should be called after performance entries finish callback.
  48. t.setMeasurements({
  49. ...t._measurements,
  50. visuallyComplete: {value: normalizedValue},
  51. });
  52. });
  53. isVisuallyCompleteSet.current = true;
  54. }
  55. if (!isDataCompleteSet.current && hasData) {
  56. transaction.registerBeforeFinishCallback((t, _) => {
  57. // Should be called after performance entries finish callback.
  58. t.setMeasurements({
  59. ...t._measurements,
  60. visuallyCompleteData: {value: normalizedValue},
  61. });
  62. });
  63. isDataCompleteSet.current = true;
  64. }
  65. } catch (_) {
  66. // Defensive catch since this code is auxiliary.
  67. }
  68. }, [hasData]);
  69. return (
  70. <Profiler id={id} onRender={onRenderCallback}>
  71. <Fragment>{children}</Fragment>
  72. </Profiler>
  73. );
  74. };