packages/react-reconciler/src/ReactFiberPerformanceTrack.js JAVASCRIPT 1,725 lines View on github.com → Search inside
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.