performanceForSentry.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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. const num = useRef(1);
  40. const isVCDSet = useRef(false);
  41. if (isVCDSet && hasData && performance && performance.mark) {
  42. performance.mark(`${id}-vcsd-start`);
  43. isVCDSet.current = true;
  44. }
  45. useEffect(() => {
  46. try {
  47. const transaction: any = getCurrentSentryReactTransaction(); // Using any to override types for private api.
  48. if (!transaction) {
  49. return;
  50. }
  51. if (!isVisuallyCompleteSet.current) {
  52. const time = performance.now();
  53. transaction.registerBeforeFinishCallback((t, _) => {
  54. // Should be called after performance entries finish callback.
  55. t.setMeasurements({
  56. ...t._measurements,
  57. visuallyComplete: {value: time},
  58. });
  59. });
  60. isVisuallyCompleteSet.current = true;
  61. }
  62. if (!isDataCompleteSet.current && hasData) {
  63. isDataCompleteSet.current = true;
  64. setTimeout(() => {
  65. performance.mark(`${id}-vcsd-end`);
  66. performance.measure(
  67. `VCD [${id}] #${num.current}`,
  68. `${id}-vcsd-start`,
  69. `${id}-vcsd-end`
  70. );
  71. num.current = num.current++;
  72. const time = performance.now();
  73. transaction.registerBeforeFinishCallback(t => {
  74. // Should be called after performance entries finish callback.
  75. const lcp = t._measurements.lcp?.value;
  76. const newMeasurements = {
  77. ...t._measurements,
  78. visuallyCompleteData: {value: time},
  79. };
  80. if (lcp) {
  81. newMeasurements.lcpDiffVCD = {value: lcp - time};
  82. }
  83. t.setMeasurements(newMeasurements);
  84. });
  85. }, 0);
  86. }
  87. } catch (_) {
  88. // Defensive catch since this code is auxiliary.
  89. }
  90. }, [hasData]);
  91. return (
  92. <Profiler id={id} onRender={onRenderCallback}>
  93. <Fragment>{children}</Fragment>
  94. </Profiler>
  95. );
  96. };