1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910/* eslint-disable react-internal/no-production-logging */1112import type {Fiber} from './ReactInternalTypes';1314import type {Lanes} from './ReactFiberLane';1516import type {CapturedValue} from './ReactCapturedValue';1718import {SuspenseComponent} from './ReactWorkTags';1920import getComponentNameFromFiber from './getComponentNameFromFiber';2122import {23 getGroupNameOfHighestPriorityLane,24 includesOnlyHydrationLanes,25 includesOnlyOffscreenLanes,26 includesOnlyHydrationOrOffscreenLanes,27 includesSomeLane,28} from './ReactFiberLane';2930import {31 addValueToProperties,32 addObjectToProperties,33 addObjectDiffToProperties,34} from 'shared/ReactPerformanceTrackProperties';3536import {37 enableProfilerTimer,38 enableGestureTransition,39 enablePerformanceIssueReporting,40} from 'shared/ReactFeatureFlags';4142const supportsUserTiming =43 enableProfilerTimer &&44 typeof console !== 'undefined' &&45 typeof console.timeStamp === 'function' &&46 (!__DEV__ ||47 // In DEV we also rely on performance.measure48 (typeof performance !== 'undefined' &&49 // $FlowFixMe[method-unbinding]50 typeof performance.measure === 'function'));5152const COMPONENTS_TRACK = 'Components ⚛';53const LANES_TRACK_GROUP = 'Scheduler ⚛';5455let currentTrack: string = 'Blocking'; // Lane5657export function setCurrentTrackFromLanes(lanes: Lanes): void {58 currentTrack = getGroupNameOfHighestPriorityLane(lanes);59}6061export function markAllLanesInOrder() {62 if (supportsUserTiming) {63 // Ensure we create all tracks in priority order. Currently performance.mark() are in64 // first insertion order but performance.measure() are in the reverse order. We can65 // always add the 0 time slot even if it's in the past. That's still considered for66 // ordering.67 console.timeStamp(68 'Blocking Track',69 0.003,70 0.003,71 'Blocking',72 LANES_TRACK_GROUP,73 'primary-light',74 );75 if (enableGestureTransition) {76 console.timeStamp(77 'Gesture Track',78 0.003,79 0.003,80 'Gesture',81 LANES_TRACK_GROUP,82 'primary-light',83 );84 }85 console.timeStamp(86 'Transition Track',87 0.003,88 0.003,89 'Transition',90 LANES_TRACK_GROUP,91 'primary-light',92 );93 console.timeStamp(94 'Suspense Track',95 0.003,96 0.003,97 'Suspense',98 LANES_TRACK_GROUP,99 'primary-light',100 );101 console.timeStamp(102 'Idle Track',103 0.003,104 0.003,105 'Idle',106 LANES_TRACK_GROUP,107 'primary-light',108 );109 }110}111112function logComponentTrigger(113 fiber: Fiber,114 startTime: number,115 endTime: number,116 trigger: string,117) {118 if (supportsUserTiming) {119 reusableComponentOptions.start = startTime;120 reusableComponentOptions.end = endTime;121 reusableComponentDevToolDetails.color = 'warning';122 reusableComponentDevToolDetails.tooltipText = trigger;123 reusableComponentDevToolDetails.properties = null;124 const debugTask = fiber._debugTask;125 if (__DEV__ && debugTask) {126 debugTask.run(127 // $FlowFixMe[method-unbinding]128 performance.measure.bind(129 performance,130 trigger,131 reusableComponentOptions,132 ),133 );134 } else {135 performance.measure(trigger, reusableComponentOptions);136 }137 performance.clearMeasures(trigger);138 }139}140141export function logComponentMount(142 fiber: Fiber,143 startTime: number,144 endTime: number,145): void {146 logComponentTrigger(fiber, startTime, endTime, 'Mount');147}148149export function logComponentUnmount(150 fiber: Fiber,151 startTime: number,152 endTime: number,153): void {154 logComponentTrigger(fiber, startTime, endTime, 'Unmount');155}156157export function logComponentReappeared(158 fiber: Fiber,159 startTime: number,160 endTime: number,161): void {162 logComponentTrigger(fiber, startTime, endTime, 'Reconnect');163}164165export function logComponentDisappeared(166 fiber: Fiber,167 startTime: number,168 endTime: number,169): void {170 logComponentTrigger(fiber, startTime, endTime, 'Disconnect');171}172173let alreadyWarnedForDeepEquality = false;174175export function pushDeepEquality(): boolean {176 if (__DEV__) {177 // If this is true then we don't reset it to false because we're tracking if any178 // parent already warned about having deep equality props in this subtree.179 return alreadyWarnedForDeepEquality;180 }181 return false;182}183184export function popDeepEquality(prev: boolean): void {185 if (__DEV__) {186 alreadyWarnedForDeepEquality = prev;187 }188}189190const reusableComponentDevToolDetails = {191 color: 'primary',192 properties: null as null | Array<[string, string]>,193 tooltipText: '',194 track: COMPONENTS_TRACK,195};196197const reusableComponentOptions: PerformanceMeasureOptions = {198 start: -0,199 end: -0,200 detail: {201 devtools: reusableComponentDevToolDetails,202 },203};204205const reusableChangedPropsEntry = ['Changed Props', ''];206207const reusableCascadingUpdateIssue = {208 name: 'React: Cascading Update',209 severity: 'warning',210 description:211 'A cascading update is an update that is triggered during an ongoing render. This can lead to performance issues.',212 learnMoreUrl:213 'https://react.dev/reference/dev-tools/react-performance-tracks#cascading-updates',214};215216const DEEP_EQUALITY_WARNING =217 'This component received deeply equal props. It might benefit from useMemo or the React Compiler in its owner.';218219const reusableDeeplyEqualPropsEntry = ['Changed Props', DEEP_EQUALITY_WARNING];220221export function logComponentRender(222 fiber: Fiber,223 startTime: number,224 endTime: number,225 wasHydrated: boolean,226 committedLanes: Lanes,227): void {228 const name = getComponentNameFromFiber(fiber);229 if (name === null) {230 // Skip231 return;232 }233 if (supportsUserTiming) {234 const alternate = fiber.alternate;235 let selfTime: number = fiber.actualDuration as any;236 if (alternate === null || alternate.child !== fiber.child) {237 for (let child = fiber.child; child !== null; child = child.sibling) {238 selfTime -= child.actualDuration as any;239 }240 }241 const color =242 selfTime < 0.5243 ? wasHydrated244 ? 'tertiary-light'245 : 'primary-light'246 : selfTime < 10247 ? wasHydrated248 ? 'tertiary'249 : 'primary'250 : selfTime < 100251 ? wasHydrated252 ? 'tertiary-dark'253 : 'primary-dark'254 : 'error';255256 if (!__DEV__) {257 console.timeStamp(258 name,259 startTime,260 endTime,261 COMPONENTS_TRACK,262 undefined,263 color,264 );265 } else {266 const props = fiber.memoizedProps;267 const debugTask = fiber._debugTask;268269 if (270 props !== null &&271 alternate !== null &&272 alternate.memoizedProps !== props273 ) {274 // If this is an update, we'll diff the props and emit which ones changed.275 const properties: Array<[string, string]> = [reusableChangedPropsEntry];276 const isDeeplyEqual = addObjectDiffToProperties(277 alternate.memoizedProps,278 props,279 properties,280 0,281 );282 if (properties.length > 1) {283 if (284 isDeeplyEqual &&285 !alreadyWarnedForDeepEquality &&286 !includesSomeLane(alternate.lanes, committedLanes) &&287 (fiber.actualDuration as any) > 100288 ) {289 alreadyWarnedForDeepEquality = true;290 // This is the first component in a subtree which rerendered with deeply equal props291 // and didn't have its own work scheduled and took a non-trivial amount of time.292 // We highlight this for further inspection.293 // Note that we only consider this case if properties.length > 1 which it will only294 // be if we have emitted any diffs. We'd only emit diffs if there were any nested295 // equal objects. Therefore, we don't warn for simple shallow equality.296 properties[0] = reusableDeeplyEqualPropsEntry;297 reusableComponentDevToolDetails.color = 'warning';298 reusableComponentDevToolDetails.tooltipText = DEEP_EQUALITY_WARNING;299 } else {300 reusableComponentDevToolDetails.color = color;301 reusableComponentDevToolDetails.tooltipText = name;302 }303 reusableComponentDevToolDetails.properties = properties;304 reusableComponentOptions.start = startTime;305 reusableComponentOptions.end = endTime;306307 const measureName = '\u200b' + name;308 if (debugTask != null) {309 debugTask.run(310 // $FlowFixMe[method-unbinding]311 performance.measure.bind(312 performance,313 measureName,314 reusableComponentOptions,315 ),316 );317 } else {318 performance.measure(measureName, reusableComponentOptions);319 }320 performance.clearMeasures(measureName);321 } else {322 if (debugTask != null) {323 debugTask.run(324 // $FlowFixMe[method-unbinding]325 console.timeStamp.bind(326 console,327 name,328 startTime,329 endTime,330 COMPONENTS_TRACK,331 undefined,332 color,333 ),334 );335 } else {336 console.timeStamp(337 name,338 startTime,339 endTime,340 COMPONENTS_TRACK,341 undefined,342 color,343 );344 }345 }346 } else {347 if (debugTask != null) {348 debugTask.run(349 // $FlowFixMe[method-unbinding]350 console.timeStamp.bind(351 console,352 name,353 startTime,354 endTime,355 COMPONENTS_TRACK,356 undefined,357 color,358 ),359 );360 } else {361 console.timeStamp(362 name,363 startTime,364 endTime,365 COMPONENTS_TRACK,366 undefined,367 color,368 );369 }370 }371 }372 }373}374375export function logComponentErrored(376 fiber: Fiber,377 startTime: number,378 endTime: number,379 errors: Array<CapturedValue<mixed>>,380): void {381 if (supportsUserTiming) {382 const name = getComponentNameFromFiber(fiber);383 if (name === null) {384 // Skip385 return;386 }387 if (__DEV__) {388 let debugTask: ?ConsoleTask = null;389 const properties: Array<[string, string]> = [];390 for (let i = 0; i < errors.length; i++) {391 const capturedValue = errors[i];392 if (debugTask == null && capturedValue.source !== null) {393 // If the captured value has a source Fiber, use its debugTask for394 // the stack instead of the error boundary's stack. So you can find395 // which component errored since we don't show the errored render tree.396 // TODO: Ideally we should instead, store the failed fibers and log the397 // whole subtree including the component that errored.398 debugTask = capturedValue.source._debugTask;399 }400 const error = capturedValue.value;401 const message =402 typeof error === 'object' &&403 error !== null &&404 typeof error.message === 'string'405 ? // eslint-disable-next-line react-internal/safe-string-coercion406 String(error.message)407 : // eslint-disable-next-line react-internal/safe-string-coercion408 String(error);409 properties.push(['Error', message]);410 }411 if (fiber.key !== null) {412 addValueToProperties('key', fiber.key, properties, 0, '');413 }414 if (fiber.memoizedProps !== null) {415 addObjectToProperties(fiber.memoizedProps, properties, 0, '');416 }417 if (debugTask == null) {418 // If the captured values don't have a debug task, fallback to the419 // error boundary itself.420 debugTask = fiber._debugTask;421 }422 const options: PerformanceMeasureOptions = {423 start: startTime,424 end: endTime,425 detail: {426 devtools: {427 color: 'error',428 track: COMPONENTS_TRACK,429 tooltipText:430 fiber.tag === SuspenseComponent431 ? 'Hydration failed'432 : 'Error boundary caught an error',433 properties,434 },435 },436 };437438 const measureName = '\u200b' + name;439 if (__DEV__ && debugTask) {440 debugTask.run(441 // $FlowFixMe[method-unbinding]442 performance.measure.bind(performance, measureName, options),443 );444 } else {445 performance.measure(measureName, options);446 }447 performance.clearMeasures(measureName);448 } else {449 console.timeStamp(450 name,451 startTime,452 endTime,453 COMPONENTS_TRACK,454 undefined,455 'error',456 );457 }458 }459}460461function logComponentEffectErrored(462 fiber: Fiber,463 startTime: number,464 endTime: number,465 errors: Array<CapturedValue<mixed>>,466): void {467 if (supportsUserTiming) {468 const name = getComponentNameFromFiber(fiber);469 if (name === null) {470 // Skip471 return;472 }473 if (__DEV__) {474 const properties: Array<[string, string]> = [];475 for (let i = 0; i < errors.length; i++) {476 const capturedValue = errors[i];477 const error = capturedValue.value;478 const message =479 typeof error === 'object' &&480 error !== null &&481 typeof error.message === 'string'482 ? // eslint-disable-next-line react-internal/safe-string-coercion483 String(error.message)484 : // eslint-disable-next-line react-internal/safe-string-coercion485 String(error);486 properties.push(['Error', message]);487 }488 if (fiber.key !== null) {489 addValueToProperties('key', fiber.key, properties, 0, '');490 }491 if (fiber.memoizedProps !== null) {492 addObjectToProperties(fiber.memoizedProps, properties, 0, '');493 }494 const options = {495 start: startTime,496 end: endTime,497 detail: {498 devtools: {499 color: 'error',500 track: COMPONENTS_TRACK,501 tooltipText: 'A lifecycle or effect errored',502 properties,503 },504 },505 };506 const debugTask = fiber._debugTask;507 const measureName = '\u200b' + name;508 if (debugTask) {509 debugTask.run(510 // $FlowFixMe[method-unbinding]511 performance.measure.bind(performance, measureName, options),512 );513 } else {514 // $FlowFixMe[incompatible-type]515 performance.measure(measureName, options);516 }517 performance.clearMeasures(measureName);518 } else {519 console.timeStamp(520 name,521 startTime,522 endTime,523 COMPONENTS_TRACK,524 undefined,525 'error',526 );527 }528 }529}530531export function logComponentEffect(532 fiber: Fiber,533 startTime: number,534 endTime: number,535 selfTime: number,536 errors: null | Array<CapturedValue<mixed>>,537): void {538 if (errors !== null) {539 logComponentEffectErrored(fiber, startTime, endTime, errors);540 return;541 }542 const name = getComponentNameFromFiber(fiber);543 if (name === null) {544 // Skip545 return;546 }547 if (supportsUserTiming) {548 const color =549 selfTime < 1550 ? 'secondary-light'551 : selfTime < 100552 ? 'secondary'553 : selfTime < 500554 ? 'secondary-dark'555 : 'error';556 const debugTask = fiber._debugTask;557 if (__DEV__ && debugTask) {558 debugTask.run(559 // $FlowFixMe[method-unbinding]560 console.timeStamp.bind(561 console,562 name,563 startTime,564 endTime,565 COMPONENTS_TRACK,566 undefined,567 color,568 ),569 );570 } else {571 console.timeStamp(572 name,573 startTime,574 endTime,575 COMPONENTS_TRACK,576 undefined,577 color,578 );579 }580 }581}582583export function logYieldTime(startTime: number, endTime: number): void {584 if (supportsUserTiming) {585 const yieldDuration = endTime - startTime;586 if (yieldDuration < 3) {587 // Skip sub-millisecond yields. This happens all the time and is not interesting.588 return;589 }590 // Being blocked on CPU is potentially bad so we color it by how long it took.591 const color =592 yieldDuration < 5593 ? 'primary-light'594 : yieldDuration < 10595 ? 'primary'596 : yieldDuration < 100597 ? 'primary-dark'598 : 'error';599 // This get logged in the components track if we don't commit which leaves them600 // hanging by themselves without context. It's a useful indicator for why something601 // might be starving this render though.602 // TODO: Considering adding these to a queue and only logging them if we commit.603 console.timeStamp(604 'Blocked',605 startTime,606 endTime,607 COMPONENTS_TRACK,608 undefined,609 color,610 );611 }612}613614export function logSuspendedYieldTime(615 startTime: number,616 endTime: number,617 suspendedFiber: Fiber,618): void {619 if (supportsUserTiming) {620 const debugTask = suspendedFiber._debugTask;621 if (__DEV__ && debugTask) {622 debugTask.run(623 // $FlowFixMe[method-unbinding]624 console.timeStamp.bind(625 console,626 'Suspended',627 startTime,628 endTime,629 COMPONENTS_TRACK,630 undefined,631 'primary-light',632 ),633 );634 } else {635 console.timeStamp(636 'Suspended',637 startTime,638 endTime,639 COMPONENTS_TRACK,640 undefined,641 'primary-light',642 );643 }644 }645}646647export function logActionYieldTime(648 startTime: number,649 endTime: number,650 suspendedFiber: Fiber,651): void {652 if (supportsUserTiming) {653 const debugTask = suspendedFiber._debugTask;654 if (__DEV__ && debugTask) {655 debugTask.run(656 // $FlowFixMe[method-unbinding]657 console.timeStamp.bind(658 console,659 'Action',660 startTime,661 endTime,662 COMPONENTS_TRACK,663 undefined,664 'primary-light',665 ),666 );667 } else {668 console.timeStamp(669 'Action',670 startTime,671 endTime,672 COMPONENTS_TRACK,673 undefined,674 'primary-light',675 );676 }677 }678}679680export function logBlockingStart(681 updateTime: number,682 eventTime: number,683 eventType: null | string,684 eventIsRepeat: boolean,685 isSpawnedUpdate: boolean,686 isPingedUpdate: boolean,687 renderStartTime: number,688 lanes: Lanes,689 debugTask: null | ConsoleTask, // DEV-only690 updateMethodName: null | string,691 updateComponentName: null | string,692): void {693 if (supportsUserTiming) {694 currentTrack = 'Blocking';695 // Clamp start times696 if (updateTime > 0) {697 if (updateTime > renderStartTime) {698 updateTime = renderStartTime;699 }700 } else {701 updateTime = renderStartTime;702 }703 if (eventTime > 0) {704 if (eventTime > updateTime) {705 eventTime = updateTime;706 }707 } else {708 eventTime = updateTime;709 }710 // If a blocking update was spawned within render or an effect, that's considered a cascading render.711 // If you have a second blocking update within the same event, that suggests multiple flushSync or712 // setState in a microtask which is also considered a cascade.713 if (eventType !== null && updateTime > eventTime) {714 // Log the time from the event timeStamp until we called setState.715 const color = eventIsRepeat ? 'secondary-light' : 'warning';716 if (__DEV__ && debugTask) {717 debugTask.run(718 // $FlowFixMe[method-unbinding]719 console.timeStamp.bind(720 console,721 eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,722 eventTime,723 updateTime,724 currentTrack,725 LANES_TRACK_GROUP,726 color,727 ),728 );729 } else {730 console.timeStamp(731 eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,732 eventTime,733 updateTime,734 currentTrack,735 LANES_TRACK_GROUP,736 color,737 );738 }739 }740 if (renderStartTime > updateTime) {741 // Log the time from when we called setState until we started rendering.742 const color = isSpawnedUpdate743 ? 'error'744 : includesOnlyHydrationOrOffscreenLanes(lanes)745 ? 'tertiary-light'746 : 'primary-light';747 const label = isPingedUpdate748 ? 'Promise Resolved'749 : isSpawnedUpdate750 ? 'Cascading Update'751 : renderStartTime - updateTime > 5752 ? 'Update Blocked'753 : 'Update';754755 if (__DEV__) {756 const properties = [];757 if (updateComponentName != null) {758 properties.push(['Component name', updateComponentName]);759 }760 if (updateMethodName != null) {761 properties.push(['Method name', updateMethodName]);762 }763 const measureOptions = {764 start: updateTime,765 end: renderStartTime,766 detail: {767 devtools: {768 properties,769 track: currentTrack,770 trackGroup: LANES_TRACK_GROUP,771 color,772 },773 },774 };775 if (enablePerformanceIssueReporting && isSpawnedUpdate) {776 // $FlowFixMe[prop-missing] - detail is untyped777 measureOptions.detail.devtools.performanceIssue =778 reusableCascadingUpdateIssue;779 }780781 if (debugTask) {782 debugTask.run(783 // $FlowFixMe[method-unbinding]784 performance.measure.bind(performance, label, measureOptions),785 );786 } else {787 // $FlowFixMe[incompatible-type]788 performance.measure(label, measureOptions);789 }790 performance.clearMeasures(label);791 } else {792 console.timeStamp(793 label,794 updateTime,795 renderStartTime,796 currentTrack,797 LANES_TRACK_GROUP,798 color,799 );800 }801 }802 }803}804805export function logGestureStart(806 updateTime: number,807 eventTime: number,808 eventType: null | string,809 eventIsRepeat: boolean,810 isPingedUpdate: boolean,811 renderStartTime: number,812 debugTask: null | ConsoleTask, // DEV-only813 updateMethodName: null | string,814 updateComponentName: null | string,815): void {816 if (supportsUserTiming) {817 currentTrack = 'Gesture';818 // Clamp start times819 if (updateTime > 0) {820 if (updateTime > renderStartTime) {821 updateTime = renderStartTime;822 }823 } else {824 updateTime = renderStartTime;825 }826 if (eventTime > 0) {827 if (eventTime > updateTime) {828 eventTime = updateTime;829 }830 } else {831 eventTime = updateTime;832 }833834 if (updateTime > eventTime && eventType !== null) {835 // Log the time from the event timeStamp until we started a gesture.836 const color = eventIsRepeat ? 'secondary-light' : 'warning';837 if (__DEV__ && debugTask) {838 debugTask.run(839 console.timeStamp.bind(840 console,841 eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,842 eventTime,843 updateTime,844 currentTrack,845 LANES_TRACK_GROUP,846 color,847 ),848 );849 } else {850 console.timeStamp(851 eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,852 eventTime,853 updateTime,854 currentTrack,855 LANES_TRACK_GROUP,856 color,857 );858 }859 }860 if (renderStartTime > updateTime) {861 // Log the time from when we called setState until we started rendering.862 const label = isPingedUpdate863 ? 'Promise Resolved'864 : renderStartTime - updateTime > 5865 ? 'Gesture Blocked'866 : 'Gesture';867 if (__DEV__) {868 const properties = [];869 if (updateComponentName != null) {870 properties.push(['Component name', updateComponentName]);871 }872 if (updateMethodName != null) {873 properties.push(['Method name', updateMethodName]);874 }875 const measureOptions = {876 start: updateTime,877 end: renderStartTime,878 detail: {879 devtools: {880 properties,881 track: currentTrack,882 trackGroup: LANES_TRACK_GROUP,883 color: 'primary-light',884 },885 },886 };887888 if (debugTask) {889 debugTask.run(890 // $FlowFixMe[method-unbinding]891 performance.measure.bind(performance, label, measureOptions),892 );893 } else {894 // $FlowFixMe[incompatible-type]895 performance.measure(label, measureOptions);896 }897 performance.clearMeasures(label);898 } else {899 console.timeStamp(900 label,901 updateTime,902 renderStartTime,903 currentTrack,904 LANES_TRACK_GROUP,905 'primary-light',906 );907 }908 }909 }910}911912export function logTransitionStart(913 startTime: number,914 updateTime: number,915 eventTime: number,916 eventType: null | string,917 eventIsRepeat: boolean,918 isPingedUpdate: boolean,919 renderStartTime: number,920 debugTask: null | ConsoleTask, // DEV-only921 updateMethodName: null | string,922 updateComponentName: null | string,923): void {924 if (supportsUserTiming) {925 currentTrack = 'Transition';926 // Clamp start times927 if (updateTime > 0) {928 if (updateTime > renderStartTime) {929 updateTime = renderStartTime;930 }931 } else {932 updateTime = renderStartTime;933 }934 if (startTime > 0) {935 if (startTime > updateTime) {936 startTime = updateTime;937 }938 } else {939 startTime = updateTime;940 }941 if (eventTime > 0) {942 if (eventTime > startTime) {943 eventTime = startTime;944 }945 } else {946 eventTime = startTime;947 }948949 if (startTime > eventTime && eventType !== null) {950 // Log the time from the event timeStamp until we started a transition.951 const color = eventIsRepeat ? 'secondary-light' : 'warning';952 if (__DEV__ && debugTask) {953 debugTask.run(954 console.timeStamp.bind(955 console,956 eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,957 eventTime,958 startTime,959 currentTrack,960 LANES_TRACK_GROUP,961 color,962 ),963 );964 } else {965 console.timeStamp(966 eventIsRepeat ? 'Consecutive' : 'Event: ' + eventType,967 eventTime,968 startTime,969 currentTrack,970 LANES_TRACK_GROUP,971 color,972 );973 }974 }975 if (updateTime > startTime) {976 // Log the time from when we started an async transition until we called setState or started rendering.977 // TODO: Ideally this would use the debugTask of the startTransition call perhaps.978 if (__DEV__ && debugTask) {979 debugTask.run(980 // $FlowFixMe[method-unbinding]981 console.timeStamp.bind(982 console,983 'Action',984 startTime,985 updateTime,986 currentTrack,987 LANES_TRACK_GROUP,988 'primary-dark',989 ),990 );991 } else {992 console.timeStamp(993 'Action',994 startTime,995 updateTime,996 currentTrack,997 LANES_TRACK_GROUP,998 'primary-dark',999 );1000 }1001 }1002 if (renderStartTime > updateTime) {1003 // Log the time from when we called setState until we started rendering.1004 const label = isPingedUpdate1005 ? 'Promise Resolved'1006 : renderStartTime - updateTime > 51007 ? 'Update Blocked'1008 : 'Update';1009 if (__DEV__) {1010 const properties = [];1011 if (updateComponentName != null) {1012 properties.push(['Component name', updateComponentName]);1013 }1014 if (updateMethodName != null) {1015 properties.push(['Method name', updateMethodName]);1016 }1017 const measureOptions = {1018 start: updateTime,1019 end: renderStartTime,1020 detail: {1021 devtools: {1022 properties,1023 track: currentTrack,1024 trackGroup: LANES_TRACK_GROUP,1025 color: 'primary-light',1026 },1027 },1028 };10291030 if (debugTask) {1031 debugTask.run(1032 // $FlowFixMe[method-unbinding]1033 performance.measure.bind(performance, label, measureOptions),1034 );1035 } else {1036 // $FlowFixMe[incompatible-type]1037 performance.measure(label, measureOptions);1038 }1039 performance.clearMeasures(label);1040 } else {1041 console.timeStamp(1042 label,1043 updateTime,1044 renderStartTime,1045 currentTrack,1046 LANES_TRACK_GROUP,1047 'primary-light',1048 );1049 }1050 }1051 }1052}10531054export function logRenderPhase(1055 startTime: number,1056 endTime: number,1057 lanes: Lanes,1058 debugTask: null | ConsoleTask,1059): void {1060 if (supportsUserTiming) {1061 if (endTime <= startTime) {1062 return;1063 }1064 const color = includesOnlyHydrationOrOffscreenLanes(lanes)1065 ? 'tertiary-dark'1066 : 'primary-dark';1067 const label = includesOnlyOffscreenLanes(lanes)1068 ? 'Prepared'1069 : includesOnlyHydrationLanes(lanes)1070 ? 'Hydrated'1071 : 'Render';1072 if (__DEV__ && debugTask) {1073 debugTask.run(1074 // $FlowFixMe[method-unbinding]1075 console.timeStamp.bind(1076 console,1077 label,1078 startTime,1079 endTime,1080 currentTrack,1081 LANES_TRACK_GROUP,1082 color,1083 ),1084 );1085 } else {1086 console.timeStamp(1087 label,1088 startTime,1089 endTime,1090 currentTrack,1091 LANES_TRACK_GROUP,1092 color,1093 );1094 }1095 }1096}10971098export function logInterruptedRenderPhase(1099 startTime: number,1100 endTime: number,1101 lanes: Lanes,1102 debugTask: null | ConsoleTask,1103): void {1104 if (supportsUserTiming) {1105 if (endTime <= startTime) {1106 return;1107 }1108 const color = includesOnlyHydrationOrOffscreenLanes(lanes)1109 ? 'tertiary-dark'1110 : 'primary-dark';1111 const label = includesOnlyOffscreenLanes(lanes)1112 ? 'Prewarm'1113 : includesOnlyHydrationLanes(lanes)1114 ? 'Interrupted Hydration'1115 : 'Interrupted Render';1116 if (__DEV__ && debugTask) {1117 debugTask.run(1118 // $FlowFixMe[method-unbinding]1119 console.timeStamp.bind(1120 console,1121 label,1122 startTime,1123 endTime,1124 currentTrack,1125 LANES_TRACK_GROUP,1126 color,1127 ),1128 );1129 } else {1130 console.timeStamp(1131 label,1132 startTime,1133 endTime,1134 currentTrack,1135 LANES_TRACK_GROUP,1136 color,1137 );1138 }1139 }1140}11411142export function logSuspendedRenderPhase(1143 startTime: number,1144 endTime: number,1145 lanes: Lanes,1146 debugTask: null | ConsoleTask,1147): void {1148 if (supportsUserTiming) {1149 if (endTime <= startTime) {1150 return;1151 }1152 const color = includesOnlyHydrationOrOffscreenLanes(lanes)1153 ? 'tertiary-dark'1154 : 'primary-dark';1155 if (__DEV__ && debugTask) {1156 debugTask.run(1157 // $FlowFixMe[method-unbinding]1158 console.timeStamp.bind(1159 console,1160 'Prewarm',1161 startTime,1162 endTime,1163 currentTrack,1164 LANES_TRACK_GROUP,1165 color,1166 ),1167 );1168 } else {1169 console.timeStamp(1170 'Prewarm',1171 startTime,1172 endTime,1173 currentTrack,1174 LANES_TRACK_GROUP,1175 color,1176 );1177 }1178 }1179}11801181export function logSuspendedWithDelayPhase(1182 startTime: number,1183 endTime: number,1184 lanes: Lanes,1185 debugTask: null | ConsoleTask,1186): void {1187 // This means the render was suspended and cannot commit until it gets unblocked.1188 if (supportsUserTiming) {1189 if (endTime <= startTime) {1190 return;1191 }1192 const color = includesOnlyHydrationOrOffscreenLanes(lanes)1193 ? 'tertiary-dark'1194 : 'primary-dark';1195 if (__DEV__ && debugTask) {1196 debugTask.run(1197 // $FlowFixMe[method-unbinding]1198 console.timeStamp.bind(1199 console,1200 'Suspended',1201 startTime,1202 endTime,1203 currentTrack,1204 LANES_TRACK_GROUP,1205 color,1206 ),1207 );1208 } else {1209 console.timeStamp(1210 'Suspended',1211 startTime,1212 endTime,1213 currentTrack,1214 LANES_TRACK_GROUP,1215 color,1216 );1217 }1218 }1219}12201221export function logRecoveredRenderPhase(1222 startTime: number,1223 endTime: number,1224 lanes: Lanes,1225 recoverableErrors: Array<CapturedValue<mixed>>,1226 hydrationFailed: boolean,1227 debugTask: null | ConsoleTask,1228): void {1229 if (supportsUserTiming) {1230 if (endTime <= startTime) {1231 return;1232 }1233 if (__DEV__) {1234 const properties: Array<[string, string]> = [];1235 for (let i = 0; i < recoverableErrors.length; i++) {1236 const capturedValue = recoverableErrors[i];1237 const error = capturedValue.value;1238 const message =1239 typeof error === 'object' &&1240 error !== null &&1241 typeof error.message === 'string'1242 ? // eslint-disable-next-line react-internal/safe-string-coercion1243 String(error.message)1244 : // eslint-disable-next-line react-internal/safe-string-coercion1245 String(error);1246 properties.push(['Recoverable Error', message]);1247 }1248 const options: PerformanceMeasureOptions = {1249 start: startTime,1250 end: endTime,1251 detail: {1252 devtools: {1253 color: 'primary-dark',1254 track: currentTrack,1255 trackGroup: LANES_TRACK_GROUP,1256 tooltipText: hydrationFailed1257 ? 'Hydration Failed'1258 : 'Recovered after Error',1259 properties,1260 },1261 },1262 };1263 if (debugTask) {1264 debugTask.run(1265 // $FlowFixMe[method-unbinding]1266 performance.measure.bind(performance, 'Recovered', options),1267 );1268 } else {1269 performance.measure('Recovered', options);1270 }1271 performance.clearMeasures('Recovered');1272 } else {1273 console.timeStamp(1274 'Recovered',1275 startTime,1276 endTime,1277 currentTrack,1278 LANES_TRACK_GROUP,1279 'error',1280 );1281 }1282 }1283}12841285export function logErroredRenderPhase(1286 startTime: number,1287 endTime: number,1288 lanes: Lanes,1289 debugTask: null | ConsoleTask,1290): void {1291 if (supportsUserTiming) {1292 if (endTime <= startTime) {1293 return;1294 }1295 if (__DEV__ && debugTask) {1296 debugTask.run(1297 // $FlowFixMe[method-unbinding]1298 console.timeStamp.bind(1299 console,1300 'Errored',1301 startTime,1302 endTime,1303 currentTrack,1304 LANES_TRACK_GROUP,1305 'error',1306 ),1307 );1308 } else {1309 console.timeStamp(1310 'Errored',1311 startTime,1312 endTime,1313 currentTrack,1314 LANES_TRACK_GROUP,1315 'error',1316 );1317 }1318 }1319}13201321export function logInconsistentRender(1322 startTime: number,1323 endTime: number,1324 debugTask: null | ConsoleTask,1325): void {1326 if (supportsUserTiming) {1327 if (endTime <= startTime) {1328 return;1329 }1330 if (__DEV__ && debugTask) {1331 debugTask.run(1332 // $FlowFixMe[method-unbinding]1333 console.timeStamp.bind(1334 console,1335 'Teared Render',1336 startTime,1337 endTime,1338 currentTrack,1339 LANES_TRACK_GROUP,1340 'error',1341 ),1342 );1343 } else {1344 console.timeStamp(1345 'Teared Render',1346 startTime,1347 endTime,1348 currentTrack,1349 LANES_TRACK_GROUP,1350 'error',1351 );1352 }1353 }1354}13551356export function logSuspendedCommitPhase(1357 startTime: number,1358 endTime: number,1359 reason: string,1360 debugTask: null | ConsoleTask,1361): void {1362 // This means the commit was suspended on CSS or images.1363 if (supportsUserTiming) {1364 if (endTime <= startTime) {1365 return;1366 }1367 // TODO: Include the exact reason and URLs of what resources suspended.1368 // TODO: This might also be Suspended while waiting on a View Transition.1369 if (__DEV__ && debugTask) {1370 debugTask.run(1371 // $FlowFixMe[method-unbinding]1372 console.timeStamp.bind(1373 console,1374 reason,1375 startTime,1376 endTime,1377 currentTrack,1378 LANES_TRACK_GROUP,1379 'secondary-light',1380 ),1381 );1382 } else {1383 console.timeStamp(1384 reason,1385 startTime,1386 endTime,1387 currentTrack,1388 LANES_TRACK_GROUP,1389 'secondary-light',1390 );1391 }1392 }1393}13941395export function logSuspendedViewTransitionPhase(1396 startTime: number,1397 endTime: number,1398 reason: string,1399 debugTask: null | ConsoleTask,1400): void {1401 // This means the commit was suspended on CSS or images.1402 if (supportsUserTiming) {1403 if (endTime <= startTime) {1404 return;1405 }1406 // TODO: Include the exact reason and URLs of what resources suspended.1407 // TODO: This might also be Suspended while waiting on a View Transition.1408 if (__DEV__ && debugTask) {1409 debugTask.run(1410 // $FlowFixMe[method-unbinding]1411 console.timeStamp.bind(1412 console,1413 reason,1414 startTime,1415 endTime,1416 currentTrack,1417 LANES_TRACK_GROUP,1418 'secondary-light',1419 ),1420 );1421 } else {1422 console.timeStamp(1423 reason,1424 startTime,1425 endTime,1426 currentTrack,1427 LANES_TRACK_GROUP,1428 'secondary-light',1429 );1430 }1431 }1432}14331434export function logCommitErrored(1435 startTime: number,1436 endTime: number,1437 errors: Array<CapturedValue<mixed>>,1438 passive: boolean,1439 debugTask: null | ConsoleTask,1440): void {1441 if (supportsUserTiming) {1442 if (endTime <= startTime) {1443 return;1444 }1445 if (__DEV__) {1446 const properties: Array<[string, string]> = [];1447 for (let i = 0; i < errors.length; i++) {1448 const capturedValue = errors[i];1449 const error = capturedValue.value;1450 const message =1451 typeof error === 'object' &&1452 error !== null &&1453 typeof error.message === 'string'1454 ? // eslint-disable-next-line react-internal/safe-string-coercion1455 String(error.message)1456 : // eslint-disable-next-line react-internal/safe-string-coercion1457 String(error);1458 properties.push(['Error', message]);1459 }1460 const options: PerformanceMeasureOptions = {1461 start: startTime,1462 end: endTime,1463 detail: {1464 devtools: {1465 color: 'error',1466 track: currentTrack,1467 trackGroup: LANES_TRACK_GROUP,1468 tooltipText: passive1469 ? 'Remaining Effects Errored'1470 : 'Commit Errored',1471 properties,1472 },1473 },1474 };1475 if (debugTask) {1476 debugTask.run(1477 // $FlowFixMe[method-unbinding]1478 performance.measure.bind(performance, 'Errored', options),1479 );1480 } else {1481 performance.measure('Errored', options);1482 }1483 performance.clearMeasures('Errored');1484 } else {1485 console.timeStamp(1486 'Errored',1487 startTime,1488 endTime,1489 currentTrack,1490 LANES_TRACK_GROUP,1491 'error',1492 );1493 }1494 }1495}14961497export function logCommitPhase(1498 startTime: number,1499 endTime: number,1500 errors: null | Array<CapturedValue<mixed>>,1501 abortedViewTransition: boolean,1502 debugTask: null | ConsoleTask,1503): void {1504 if (errors !== null) {1505 logCommitErrored(startTime, endTime, errors, false, debugTask);1506 return;1507 }1508 if (supportsUserTiming) {1509 if (endTime <= startTime) {1510 return;1511 }1512 if (__DEV__ && debugTask) {1513 debugTask.run(1514 // $FlowFixMe[method-unbinding]1515 console.timeStamp.bind(1516 console,1517 abortedViewTransition1518 ? 'Commit Interrupted View Transition'1519 : 'Commit',1520 startTime,1521 endTime,1522 currentTrack,1523 LANES_TRACK_GROUP,1524 abortedViewTransition ? 'error' : 'secondary-dark',1525 ),1526 );1527 } else {1528 console.timeStamp(1529 abortedViewTransition ? 'Commit Interrupted View Transition' : 'Commit',1530 startTime,1531 endTime,1532 currentTrack,1533 LANES_TRACK_GROUP,1534 abortedViewTransition ? 'error' : 'secondary-dark',1535 );1536 }1537 }1538}15391540export function logPaintYieldPhase(1541 startTime: number,1542 endTime: number,1543 delayedUntilPaint: boolean,1544 debugTask: null | ConsoleTask,1545): void {1546 if (supportsUserTiming) {1547 if (endTime <= startTime) {1548 return;1549 }1550 if (__DEV__ && debugTask) {1551 debugTask.run(1552 // $FlowFixMe[method-unbinding]1553 console.timeStamp.bind(1554 console,1555 delayedUntilPaint ? 'Waiting for Paint' : 'Waiting',1556 startTime,1557 endTime,1558 currentTrack,1559 LANES_TRACK_GROUP,1560 'secondary-light',1561 ),1562 );1563 } else {1564 console.timeStamp(1565 delayedUntilPaint ? 'Waiting for Paint' : 'Waiting',1566 startTime,1567 endTime,1568 currentTrack,1569 LANES_TRACK_GROUP,1570 'secondary-light',1571 );1572 }1573 }1574}15751576export function logApplyGesturePhase(1577 startTime: number,1578 endTime: number,1579 debugTask: null | ConsoleTask,1580): void {1581 if (supportsUserTiming) {1582 if (endTime <= startTime) {1583 return;1584 }1585 if (__DEV__ && debugTask) {1586 debugTask.run(1587 // $FlowFixMe[method-unbinding]1588 console.timeStamp.bind(1589 console,1590 'Create Ghost Tree',1591 startTime,1592 endTime,1593 currentTrack,1594 LANES_TRACK_GROUP,1595 'secondary-dark',1596 ),1597 );1598 } else {1599 console.timeStamp(1600 'Create Ghost Tree',1601 startTime,1602 endTime,1603 currentTrack,1604 LANES_TRACK_GROUP,1605 'secondary-dark',1606 );1607 }1608 }1609}16101611export function logStartViewTransitionYieldPhase(1612 startTime: number,1613 endTime: number,1614 abortedViewTransition: boolean,1615 debugTask: null | ConsoleTask,1616): void {1617 if (supportsUserTiming) {1618 if (endTime <= startTime) {1619 return;1620 }1621 if (__DEV__ && debugTask) {1622 debugTask.run(1623 // $FlowFixMe[method-unbinding]1624 console.timeStamp.bind(1625 console,1626 abortedViewTransition1627 ? 'Interrupted View Transition'1628 : 'Starting Animation',1629 startTime,1630 endTime,1631 currentTrack,1632 LANES_TRACK_GROUP,1633 abortedViewTransition ? 'error' : 'secondary-light',1634 ),1635 );1636 } else {1637 console.timeStamp(1638 abortedViewTransition1639 ? 'Interrupted View Transition'1640 : 'Starting Animation',1641 startTime,1642 endTime,1643 currentTrack,1644 LANES_TRACK_GROUP,1645 abortedViewTransition ? ' error' : 'secondary-light',1646 );1647 }1648 }1649}16501651export function logAnimatingPhase(1652 startTime: number,1653 endTime: number,1654 debugTask: null | ConsoleTask,1655): void {1656 if (supportsUserTiming) {1657 if (endTime <= startTime) {1658 return;1659 }1660 if (__DEV__ && debugTask) {1661 debugTask.run(1662 // $FlowFixMe[method-unbinding]1663 console.timeStamp.bind(1664 console,1665 'Animating',1666 startTime,1667 endTime,1668 currentTrack,1669 LANES_TRACK_GROUP,1670 'secondary-dark',1671 ),1672 );1673 } else {1674 console.timeStamp(1675 'Animating',1676 startTime,1677 endTime,1678 currentTrack,1679 LANES_TRACK_GROUP,1680 'secondary-dark',1681 );1682 }1683 }1684}16851686export function logPassiveCommitPhase(1687 startTime: number,1688 endTime: number,1689 errors: null | Array<CapturedValue<mixed>>,1690 debugTask: null | ConsoleTask,1691): void {1692 if (errors !== null) {1693 logCommitErrored(startTime, endTime, errors, true, debugTask);1694 return;1695 }1696 if (supportsUserTiming) {1697 if (endTime <= startTime) {1698 return;1699 }1700 if (__DEV__ && debugTask) {1701 debugTask.run(1702 // $FlowFixMe[method-unbinding]1703 console.timeStamp.bind(1704 console,1705 'Remaining Effects',1706 startTime,1707 endTime,1708 currentTrack,1709 LANES_TRACK_GROUP,1710 'secondary-dark',1711 ),1712 );1713 } else {1714 console.timeStamp(1715 'Remaining Effects',1716 startTime,1717 endTime,1718 currentTrack,1719 LANES_TRACK_GROUP,1720 'secondary-dark',1721 );1722 }1723 }1724}
Findings
✓ No findings reported for this file.