DevTools scheduling profiler: Add React component measures (#22013)
This commit is contained in:
parent
b54f36f2b6
commit
e3049bb850
|
@ -44,6 +44,7 @@ import {
|
|||
zeroPoint,
|
||||
} from './view-base';
|
||||
import {
|
||||
ComponentMeasuresView,
|
||||
FlamechartView,
|
||||
NativeEventsView,
|
||||
ReactMeasuresView,
|
||||
|
@ -132,6 +133,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
const nativeEventsViewRef = useRef(null);
|
||||
const schedulingEventsViewRef = useRef(null);
|
||||
const suspenseEventsViewRef = useRef(null);
|
||||
const componentMeasuresViewRef = useRef(null);
|
||||
const reactMeasuresViewRef = useRef(null);
|
||||
const flamechartViewRef = useRef(null);
|
||||
const syncedHorizontalPanAndZoomViewsRef = useRef<HorizontalPanAndZoomView[]>(
|
||||
|
@ -259,6 +261,17 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
true,
|
||||
);
|
||||
|
||||
let componentMeasuresViewWrapper = null;
|
||||
if (data.componentMeasures.length > 0) {
|
||||
const componentMeasuresView = new ComponentMeasuresView(
|
||||
surface,
|
||||
defaultFrame,
|
||||
data,
|
||||
);
|
||||
componentMeasuresViewRef.current = componentMeasuresView;
|
||||
componentMeasuresViewWrapper = createViewHelper(componentMeasuresView);
|
||||
}
|
||||
|
||||
const flamechartView = new FlamechartView(
|
||||
surface,
|
||||
defaultFrame,
|
||||
|
@ -293,6 +306,9 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
rootView.addSubview(suspenseEventsViewWrapper);
|
||||
}
|
||||
rootView.addSubview(reactMeasuresViewWrapper);
|
||||
if (componentMeasuresViewWrapper !== null) {
|
||||
rootView.addSubview(componentMeasuresViewWrapper);
|
||||
}
|
||||
rootView.addSubview(flamechartViewWrapper);
|
||||
|
||||
// If subviews are less than the available height, fill remaining height with a solid color.
|
||||
|
@ -323,6 +339,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
if (prevHoverEvent === null) {
|
||||
return prevHoverEvent;
|
||||
} else if (
|
||||
prevHoverEvent.componentMeasure !== null ||
|
||||
prevHoverEvent.flamechartStackFrame !== null ||
|
||||
prevHoverEvent.measure !== null ||
|
||||
prevHoverEvent.nativeEvent !== null ||
|
||||
|
@ -331,6 +348,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
prevHoverEvent.userTimingMark !== null
|
||||
) {
|
||||
return {
|
||||
componentMeasure: null,
|
||||
data: prevHoverEvent.data,
|
||||
flamechartStackFrame: null,
|
||||
measure: null,
|
||||
|
@ -378,6 +396,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
userTimingMarksView.onHover = userTimingMark => {
|
||||
if (!hoveredEvent || hoveredEvent.userTimingMark !== userTimingMark) {
|
||||
setHoveredEvent({
|
||||
componentMeasure: null,
|
||||
data,
|
||||
flamechartStackFrame: null,
|
||||
measure: null,
|
||||
|
@ -395,6 +414,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
nativeEventsView.onHover = nativeEvent => {
|
||||
if (!hoveredEvent || hoveredEvent.nativeEvent !== nativeEvent) {
|
||||
setHoveredEvent({
|
||||
componentMeasure: null,
|
||||
data,
|
||||
flamechartStackFrame: null,
|
||||
measure: null,
|
||||
|
@ -412,6 +432,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
schedulingEventsView.onHover = schedulingEvent => {
|
||||
if (!hoveredEvent || hoveredEvent.schedulingEvent !== schedulingEvent) {
|
||||
setHoveredEvent({
|
||||
componentMeasure: null,
|
||||
data,
|
||||
flamechartStackFrame: null,
|
||||
measure: null,
|
||||
|
@ -429,6 +450,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
suspenseEventsView.onHover = suspenseEvent => {
|
||||
if (!hoveredEvent || hoveredEvent.suspenseEvent !== suspenseEvent) {
|
||||
setHoveredEvent({
|
||||
componentMeasure: null,
|
||||
data,
|
||||
flamechartStackFrame: null,
|
||||
measure: null,
|
||||
|
@ -446,6 +468,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
reactMeasuresView.onHover = measure => {
|
||||
if (!hoveredEvent || hoveredEvent.measure !== measure) {
|
||||
setHoveredEvent({
|
||||
componentMeasure: null,
|
||||
data,
|
||||
flamechartStackFrame: null,
|
||||
measure,
|
||||
|
@ -458,6 +481,27 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
};
|
||||
}
|
||||
|
||||
const {current: componentMeasuresView} = componentMeasuresViewRef;
|
||||
if (componentMeasuresView) {
|
||||
componentMeasuresView.onHover = componentMeasure => {
|
||||
if (
|
||||
!hoveredEvent ||
|
||||
hoveredEvent.componentMeasure !== componentMeasure
|
||||
) {
|
||||
setHoveredEvent({
|
||||
componentMeasure,
|
||||
data,
|
||||
flamechartStackFrame: null,
|
||||
measure: null,
|
||||
nativeEvent: null,
|
||||
schedulingEvent: null,
|
||||
suspenseEvent: null,
|
||||
userTimingMark: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const {current: flamechartView} = flamechartViewRef;
|
||||
if (flamechartView) {
|
||||
flamechartView.setOnHover(flamechartStackFrame => {
|
||||
|
@ -466,6 +510,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
hoveredEvent.flamechartStackFrame !== flamechartStackFrame
|
||||
) {
|
||||
setHoveredEvent({
|
||||
componentMeasure: null,
|
||||
data,
|
||||
flamechartStackFrame,
|
||||
measure: null,
|
||||
|
@ -540,6 +585,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
return null;
|
||||
}
|
||||
const {
|
||||
componentMeasure,
|
||||
flamechartStackFrame,
|
||||
measure,
|
||||
schedulingEvent,
|
||||
|
@ -547,6 +593,13 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
|
|||
} = contextData.hoveredEvent;
|
||||
return (
|
||||
<Fragment>
|
||||
{componentMeasure !== null && (
|
||||
<ContextMenuItem
|
||||
onClick={() => copy(componentMeasure.componentName)}
|
||||
title="Copy component name">
|
||||
Copy component name
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
{schedulingEvent !== null && (
|
||||
<ContextMenuItem
|
||||
onClick={() => copy(schedulingEvent.componentName)}
|
||||
|
|
|
@ -11,6 +11,7 @@ import type {Point} from './view-base';
|
|||
import type {
|
||||
FlamechartStackFrame,
|
||||
NativeEvent,
|
||||
ReactComponentMeasure,
|
||||
ReactHoverContextInfo,
|
||||
ReactMeasure,
|
||||
ReactProfilerData,
|
||||
|
@ -81,6 +82,7 @@ export default function EventTooltip({
|
|||
}
|
||||
|
||||
const {
|
||||
componentMeasure,
|
||||
flamechartStackFrame,
|
||||
measure,
|
||||
nativeEvent,
|
||||
|
@ -89,7 +91,14 @@ export default function EventTooltip({
|
|||
userTimingMark,
|
||||
} = hoveredEvent;
|
||||
|
||||
if (nativeEvent !== null) {
|
||||
if (componentMeasure !== null) {
|
||||
return (
|
||||
<TooltipReactComponentMeasure
|
||||
componentMeasure={componentMeasure}
|
||||
tooltipRef={tooltipRef}
|
||||
/>
|
||||
);
|
||||
} else if (nativeEvent !== null) {
|
||||
return (
|
||||
<TooltipNativeEvent nativeEvent={nativeEvent} tooltipRef={tooltipRef} />
|
||||
);
|
||||
|
@ -130,6 +139,38 @@ export default function EventTooltip({
|
|||
return null;
|
||||
}
|
||||
|
||||
const TooltipReactComponentMeasure = ({
|
||||
componentMeasure,
|
||||
tooltipRef,
|
||||
}: {
|
||||
componentMeasure: ReactComponentMeasure,
|
||||
tooltipRef: Return<typeof useRef>,
|
||||
}) => {
|
||||
const {componentName, duration, timestamp, warning} = componentMeasure;
|
||||
|
||||
const label = `${componentName} rendered`;
|
||||
|
||||
return (
|
||||
<div className={styles.Tooltip} ref={tooltipRef}>
|
||||
<div className={styles.TooltipSection}>
|
||||
{trimString(label, 768)}
|
||||
<div className={styles.Divider} />
|
||||
<div className={styles.DetailsGrid}>
|
||||
<div className={styles.DetailsGridLabel}>Timestamp:</div>
|
||||
<div>{formatTimestamp(timestamp)}</div>
|
||||
<div className={styles.DetailsGridLabel}>Duration:</div>
|
||||
<div>{formatDuration(duration)}</div>
|
||||
</div>
|
||||
</div>
|
||||
{warning !== null && (
|
||||
<div className={styles.TooltipWarningSection}>
|
||||
<div className={styles.WarningText}>{warning}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TooltipFlamechartNode = ({
|
||||
stackFrame,
|
||||
tooltipRef,
|
||||
|
|
|
@ -14,6 +14,7 @@ import createDataResourceFromImportedFile from './createDataResourceFromImported
|
|||
import type {DataResource} from './createDataResourceFromImportedFile';
|
||||
|
||||
export type Context = {|
|
||||
clearSchedulingProfilerData: () => void,
|
||||
importSchedulingProfilerData: (file: File) => void,
|
||||
schedulingProfilerData: DataResource | null,
|
||||
|};
|
||||
|
@ -33,6 +34,10 @@ function SchedulingProfilerContextController({children}: Props) {
|
|||
setSchedulingProfilerData,
|
||||
] = useState<DataResource | null>(null);
|
||||
|
||||
const clearSchedulingProfilerData = useCallback(() => {
|
||||
setSchedulingProfilerData(null);
|
||||
}, []);
|
||||
|
||||
const importSchedulingProfilerData = useCallback((file: File) => {
|
||||
setSchedulingProfilerData(createDataResourceFromImportedFile(file));
|
||||
}, []);
|
||||
|
@ -41,11 +46,13 @@ function SchedulingProfilerContextController({children}: Props) {
|
|||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
clearSchedulingProfilerData,
|
||||
importSchedulingProfilerData,
|
||||
schedulingProfilerData,
|
||||
// TODO (scheduling profiler)
|
||||
}),
|
||||
[
|
||||
clearSchedulingProfilerData,
|
||||
importSchedulingProfilerData,
|
||||
schedulingProfilerData,
|
||||
// TODO (scheduling profiler)
|
||||
|
|
222
packages/react-devtools-scheduling-profiler/src/content-views/ComponentMeasuresView.js
vendored
Normal file
222
packages/react-devtools-scheduling-profiler/src/content-views/ComponentMeasuresView.js
vendored
Normal file
|
@ -0,0 +1,222 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactComponentMeasure, ReactProfilerData} from '../types';
|
||||
import type {
|
||||
Interaction,
|
||||
IntrinsicSize,
|
||||
MouseMoveInteraction,
|
||||
Rect,
|
||||
ViewRefs,
|
||||
} from '../view-base';
|
||||
|
||||
import {
|
||||
durationToWidth,
|
||||
positioningScaleFactor,
|
||||
positionToTimestamp,
|
||||
timestampToPosition,
|
||||
} from './utils/positioning';
|
||||
import {drawText} from './utils/text';
|
||||
import {formatDuration} from '../utils/formatting';
|
||||
import {
|
||||
View,
|
||||
Surface,
|
||||
rectContainsPoint,
|
||||
rectIntersectsRect,
|
||||
intersectionOfRects,
|
||||
} from '../view-base';
|
||||
import {COLORS, NATIVE_EVENT_HEIGHT, BORDER_SIZE} from './constants';
|
||||
|
||||
const ROW_WITH_BORDER_HEIGHT = NATIVE_EVENT_HEIGHT + BORDER_SIZE;
|
||||
|
||||
export class ComponentMeasuresView extends View {
|
||||
_hoveredComponentMeasure: ReactComponentMeasure | null = null;
|
||||
_intrinsicSize: IntrinsicSize;
|
||||
_profilerData: ReactProfilerData;
|
||||
|
||||
onHover: ((event: ReactComponentMeasure | null) => void) | null = null;
|
||||
|
||||
constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
|
||||
super(surface, frame);
|
||||
|
||||
this._profilerData = profilerData;
|
||||
|
||||
this._intrinsicSize = {
|
||||
width: profilerData.duration,
|
||||
height: ROW_WITH_BORDER_HEIGHT,
|
||||
};
|
||||
}
|
||||
|
||||
desiredSize() {
|
||||
return this._intrinsicSize;
|
||||
}
|
||||
|
||||
setHoveredEvent(hoveredEvent: ReactComponentMeasure | null) {
|
||||
if (this._hoveredComponentMeasure === hoveredEvent) {
|
||||
return;
|
||||
}
|
||||
this._hoveredComponentMeasure = hoveredEvent;
|
||||
this.setNeedsDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a single `ReactComponentMeasure` as a box/span with text inside of it.
|
||||
*/
|
||||
_drawSingleReactComponentMeasure(
|
||||
context: CanvasRenderingContext2D,
|
||||
rect: Rect,
|
||||
componentMeasure: ReactComponentMeasure,
|
||||
scaleFactor: number,
|
||||
showHoverHighlight: boolean,
|
||||
): boolean {
|
||||
const {frame} = this;
|
||||
const {componentName, duration, timestamp, warning} = componentMeasure;
|
||||
|
||||
const xStart = timestampToPosition(timestamp, scaleFactor, frame);
|
||||
const xStop = timestampToPosition(timestamp + duration, scaleFactor, frame);
|
||||
const componentMeasureRect: Rect = {
|
||||
origin: {
|
||||
x: xStart,
|
||||
y: frame.origin.y,
|
||||
},
|
||||
size: {width: xStop - xStart, height: NATIVE_EVENT_HEIGHT},
|
||||
};
|
||||
if (!rectIntersectsRect(componentMeasureRect, rect)) {
|
||||
return false; // Not in view
|
||||
}
|
||||
|
||||
const width = durationToWidth(duration, scaleFactor);
|
||||
if (width < 1) {
|
||||
return false; // Too small to render at this zoom level
|
||||
}
|
||||
|
||||
const drawableRect = intersectionOfRects(componentMeasureRect, rect);
|
||||
context.beginPath();
|
||||
if (warning !== null) {
|
||||
context.fillStyle = showHoverHighlight
|
||||
? COLORS.WARNING_BACKGROUND_HOVER
|
||||
: COLORS.WARNING_BACKGROUND;
|
||||
} else {
|
||||
context.fillStyle = showHoverHighlight
|
||||
? COLORS.REACT_COMPONENT_MEASURE_HOVER
|
||||
: COLORS.REACT_COMPONENT_MEASURE;
|
||||
}
|
||||
context.fillRect(
|
||||
drawableRect.origin.x,
|
||||
drawableRect.origin.y,
|
||||
drawableRect.size.width,
|
||||
drawableRect.size.height,
|
||||
);
|
||||
|
||||
const label = `${componentName} rendered - ${formatDuration(duration)}`;
|
||||
|
||||
drawText(label, context, componentMeasureRect, drawableRect);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
draw(context: CanvasRenderingContext2D) {
|
||||
const {
|
||||
frame,
|
||||
_profilerData: {componentMeasures},
|
||||
_hoveredComponentMeasure,
|
||||
visibleArea,
|
||||
} = this;
|
||||
|
||||
context.fillStyle = COLORS.BACKGROUND;
|
||||
context.fillRect(
|
||||
visibleArea.origin.x,
|
||||
visibleArea.origin.y,
|
||||
visibleArea.size.width,
|
||||
visibleArea.size.height,
|
||||
);
|
||||
|
||||
// Draw events
|
||||
const scaleFactor = positioningScaleFactor(
|
||||
this._intrinsicSize.width,
|
||||
frame,
|
||||
);
|
||||
|
||||
let didDrawMeasure = false;
|
||||
componentMeasures.forEach(componentMeasure => {
|
||||
didDrawMeasure =
|
||||
this._drawSingleReactComponentMeasure(
|
||||
context,
|
||||
visibleArea,
|
||||
componentMeasure,
|
||||
scaleFactor,
|
||||
componentMeasure === _hoveredComponentMeasure,
|
||||
) || didDrawMeasure;
|
||||
});
|
||||
|
||||
if (!didDrawMeasure) {
|
||||
drawText(
|
||||
'(zoom or pan to see React components)',
|
||||
context,
|
||||
visibleArea,
|
||||
visibleArea,
|
||||
'center',
|
||||
COLORS.TEXT_DIM_COLOR,
|
||||
);
|
||||
}
|
||||
|
||||
context.fillStyle = COLORS.PRIORITY_BORDER;
|
||||
context.fillRect(
|
||||
visibleArea.origin.x,
|
||||
visibleArea.origin.y + ROW_WITH_BORDER_HEIGHT - BORDER_SIZE,
|
||||
visibleArea.size.width,
|
||||
BORDER_SIZE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_handleMouseMove(interaction: MouseMoveInteraction, viewRefs: ViewRefs) {
|
||||
const {frame, _intrinsicSize, onHover, visibleArea} = this;
|
||||
if (!onHover) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {location} = interaction.payload;
|
||||
if (!rectContainsPoint(location, visibleArea)) {
|
||||
onHover(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
|
||||
const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
|
||||
|
||||
const componentMeasures = this._profilerData.componentMeasures;
|
||||
for (let index = componentMeasures.length - 1; index >= 0; index--) {
|
||||
const componentMeasure = componentMeasures[index];
|
||||
const {duration, timestamp} = componentMeasure;
|
||||
|
||||
if (
|
||||
hoverTimestamp >= timestamp &&
|
||||
hoverTimestamp <= timestamp + duration
|
||||
) {
|
||||
this.currentCursor = 'context-menu';
|
||||
viewRefs.hoveredView = this;
|
||||
onHover(componentMeasure);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onHover(null);
|
||||
}
|
||||
|
||||
handleInteraction(interaction: Interaction, viewRefs: ViewRefs) {
|
||||
switch (interaction.type) {
|
||||
case 'mousemove':
|
||||
this._handleMouseMove(interaction, viewRefs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -128,7 +128,7 @@ class FlamechartStackLayerView extends View {
|
|||
visibleArea,
|
||||
} = this;
|
||||
|
||||
context.fillStyle = COLORS.BACKGROUND;
|
||||
context.fillStyle = COLORS.PRIORITY_BACKGROUND;
|
||||
context.fillRect(
|
||||
visibleArea.origin.x,
|
||||
visibleArea.origin.y,
|
||||
|
@ -172,7 +172,29 @@ class FlamechartStackLayerView extends View {
|
|||
drawableRect.size.height,
|
||||
);
|
||||
|
||||
drawText(name, context, nodeRect, drawableRect, width);
|
||||
drawText(name, context, nodeRect, drawableRect);
|
||||
}
|
||||
|
||||
// Render bottom border.
|
||||
const borderFrame: Rect = {
|
||||
origin: {
|
||||
x: frame.origin.x,
|
||||
y: frame.origin.y + FLAMECHART_FRAME_HEIGHT - BORDER_SIZE,
|
||||
},
|
||||
size: {
|
||||
width: frame.size.width,
|
||||
height: BORDER_SIZE,
|
||||
},
|
||||
};
|
||||
if (rectIntersectsRect(borderFrame, visibleArea)) {
|
||||
const borderDrawableRect = intersectionOfRects(borderFrame, visibleArea);
|
||||
context.fillStyle = COLORS.PRIORITY_BORDER;
|
||||
context.fillRect(
|
||||
borderDrawableRect.origin.x,
|
||||
borderDrawableRect.origin.y,
|
||||
borderDrawableRect.size.width,
|
||||
borderDrawableRect.size.height,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ export class NativeEventsView extends View {
|
|||
|
||||
const label = `${type} - ${formatDuration(duration)}`;
|
||||
|
||||
drawText(label, context, eventRect, drawableRect, width);
|
||||
drawText(label, context, eventRect, drawableRect);
|
||||
}
|
||||
|
||||
draw(context: CanvasRenderingContext2D) {
|
||||
|
|
|
@ -231,7 +231,7 @@ export class SuspenseEventsView extends View {
|
|||
label += ` - ${formatDuration(duration)}`;
|
||||
}
|
||||
|
||||
drawText(label, context, eventRect, drawableRect, width);
|
||||
drawText(label, context, eventRect, drawableRect);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,8 @@ export let COLORS = {
|
|||
PRIORITY_LABEL: '',
|
||||
USER_TIMING: '',
|
||||
USER_TIMING_HOVER: '',
|
||||
REACT_COMPONENT_MEASURE: '',
|
||||
REACT_COMPONENT_MEASURE_HOVER: '',
|
||||
REACT_IDLE: '',
|
||||
REACT_IDLE_HOVER: '',
|
||||
REACT_RENDER: '',
|
||||
|
@ -75,6 +77,7 @@ export let COLORS = {
|
|||
REACT_WORK_BORDER: '',
|
||||
SCROLL_CARET: '',
|
||||
TEXT_COLOR: '',
|
||||
TEXT_DIM_COLOR: '',
|
||||
TIME_MARKER_LABEL: '',
|
||||
WARNING_BACKGROUND: '',
|
||||
WARNING_BACKGROUND_HOVER: '',
|
||||
|
@ -111,6 +114,12 @@ export function updateColorsToMatchTheme(element: Element): boolean {
|
|||
USER_TIMING_HOVER: computedStyle.getPropertyValue(
|
||||
'--color-scheduling-profiler-user-timing-hover',
|
||||
),
|
||||
REACT_COMPONENT_MEASURE: computedStyle.getPropertyValue(
|
||||
'--color-scheduling-profiler-react-render',
|
||||
),
|
||||
REACT_COMPONENT_MEASURE_HOVER: computedStyle.getPropertyValue(
|
||||
'--color-scheduling-profiler-react-render-hover',
|
||||
),
|
||||
REACT_IDLE: computedStyle.getPropertyValue(
|
||||
'--color-scheduling-profiler-react-idle',
|
||||
),
|
||||
|
@ -182,6 +191,9 @@ export function updateColorsToMatchTheme(element: Element): boolean {
|
|||
TEXT_COLOR: computedStyle.getPropertyValue(
|
||||
'--color-scheduling-profiler-text-color',
|
||||
),
|
||||
TEXT_DIM_COLOR: computedStyle.getPropertyValue(
|
||||
'--color-scheduling-profiler-text-dim-color',
|
||||
),
|
||||
TIME_MARKER_LABEL: computedStyle.getPropertyValue('--color-text'),
|
||||
WARNING_BACKGROUND: computedStyle.getPropertyValue(
|
||||
'--color-warning-background',
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
export * from './ComponentMeasuresView';
|
||||
export * from './FlamechartView';
|
||||
export * from './NativeEventsView';
|
||||
export * from './ReactMeasuresView';
|
||||
|
|
|
@ -41,11 +41,10 @@ export function drawText(
|
|||
context: CanvasRenderingContext2D,
|
||||
fullRect: Rect,
|
||||
drawableRect: Rect,
|
||||
availableWidth: number,
|
||||
textAlign: 'left' | 'center' = 'left',
|
||||
fillStyle: string = COLORS.TEXT_COLOR,
|
||||
): void {
|
||||
if (availableWidth > TEXT_PADDING * 2) {
|
||||
if (fullRect.size.width > TEXT_PADDING * 2) {
|
||||
context.textAlign = textAlign;
|
||||
context.textBaseline = 'middle';
|
||||
context.font = `${FONT_SIZE}px sans-serif`;
|
||||
|
@ -55,7 +54,7 @@ export function drawText(
|
|||
const trimmedName = trimText(
|
||||
context,
|
||||
text,
|
||||
availableWidth - TEXT_PADDING * 2 + (x < 0 ? x : 0),
|
||||
fullRect.size.width - TEXT_PADDING * 2 + (x < 0 ? x : 0),
|
||||
);
|
||||
|
||||
if (trimmedName !== null) {
|
||||
|
@ -81,7 +80,7 @@ export function drawText(
|
|||
|
||||
let textX;
|
||||
if (textAlign === 'center') {
|
||||
textX = x + availableWidth / 2 + TEXT_PADDING - (x < 0 ? x : 0);
|
||||
textX = x + fullRect.size.width / 2 + TEXT_PADDING - (x < 0 ? x : 0);
|
||||
} else {
|
||||
textX = x + TEXT_PADDING - (x < 0 ? x : 0);
|
||||
}
|
||||
|
|
|
@ -203,6 +203,7 @@ describe(preprocessData, () => {
|
|||
});
|
||||
|
||||
expect(preprocessData([cpuProfilerSample, randomSample])).toStrictEqual({
|
||||
componentMeasures: [],
|
||||
duration: 0.002,
|
||||
flamechart: [],
|
||||
measures: [],
|
||||
|
@ -259,6 +260,7 @@ describe(preprocessData, () => {
|
|||
}),
|
||||
]),
|
||||
).toStrictEqual({
|
||||
componentMeasures: [],
|
||||
duration: 0.008,
|
||||
flamechart: [],
|
||||
measures: [
|
||||
|
@ -323,6 +325,7 @@ describe(preprocessData, () => {
|
|||
|
||||
const userTimingData = createUserTimingData(clearedMarks);
|
||||
expect(preprocessData(userTimingData)).toStrictEqual({
|
||||
componentMeasures: [],
|
||||
duration: 0.011,
|
||||
flamechart: [],
|
||||
measures: [
|
||||
|
@ -405,13 +408,27 @@ describe(preprocessData, () => {
|
|||
|
||||
const userTimingData = createUserTimingData(clearedMarks);
|
||||
expect(preprocessData(userTimingData)).toStrictEqual({
|
||||
duration: 0.022,
|
||||
componentMeasures: [
|
||||
{
|
||||
componentName: 'App',
|
||||
duration: 0.001,
|
||||
timestamp: 0.007,
|
||||
warning: null,
|
||||
},
|
||||
{
|
||||
componentName: 'App',
|
||||
duration: 0.0010000000000000009,
|
||||
timestamp: 0.018,
|
||||
warning: null,
|
||||
},
|
||||
],
|
||||
duration: 0.026,
|
||||
flamechart: [],
|
||||
measures: [
|
||||
{
|
||||
batchUID: 0,
|
||||
depth: 0,
|
||||
duration: 0.004999999999999999,
|
||||
duration: 0.006999999999999999,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.006,
|
||||
|
@ -420,7 +437,7 @@ describe(preprocessData, () => {
|
|||
{
|
||||
batchUID: 0,
|
||||
depth: 0,
|
||||
duration: 0.001,
|
||||
duration: 0.002999999999999999,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.006,
|
||||
|
@ -432,7 +449,7 @@ describe(preprocessData, () => {
|
|||
duration: 0.002999999999999999,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.008,
|
||||
timestamp: 0.01,
|
||||
type: 'commit',
|
||||
},
|
||||
{
|
||||
|
@ -441,7 +458,7 @@ describe(preprocessData, () => {
|
|||
duration: 0.0010000000000000009,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.009,
|
||||
timestamp: 0.011,
|
||||
type: 'layout-effects',
|
||||
},
|
||||
{
|
||||
|
@ -450,27 +467,18 @@ describe(preprocessData, () => {
|
|||
duration: 0.002,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.012,
|
||||
timestamp: 0.014,
|
||||
type: 'passive-effects',
|
||||
},
|
||||
{
|
||||
batchUID: 1,
|
||||
depth: 0,
|
||||
duration: 0.005000000000000001,
|
||||
duration: 0.006999999999999999,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.015,
|
||||
timestamp: 0.017,
|
||||
type: 'render-idle',
|
||||
},
|
||||
{
|
||||
batchUID: 1,
|
||||
depth: 0,
|
||||
duration: 0.0010000000000000009,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.015,
|
||||
type: 'render',
|
||||
},
|
||||
{
|
||||
batchUID: 1,
|
||||
depth: 0,
|
||||
|
@ -478,6 +486,15 @@ describe(preprocessData, () => {
|
|||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.017,
|
||||
type: 'render',
|
||||
},
|
||||
{
|
||||
batchUID: 1,
|
||||
depth: 0,
|
||||
duration: 0.002999999999999999,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.021,
|
||||
type: 'commit',
|
||||
},
|
||||
{
|
||||
|
@ -486,7 +503,7 @@ describe(preprocessData, () => {
|
|||
duration: 0.0010000000000000009,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.018,
|
||||
timestamp: 0.022,
|
||||
type: 'layout-effects',
|
||||
},
|
||||
{
|
||||
|
@ -495,7 +512,7 @@ describe(preprocessData, () => {
|
|||
duration: 0.0009999999999999974,
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.021,
|
||||
timestamp: 0.025,
|
||||
type: 'passive-effects',
|
||||
},
|
||||
],
|
||||
|
@ -522,7 +539,7 @@ describe(preprocessData, () => {
|
|||
componentName: 'App',
|
||||
laneLabels: ['Default'],
|
||||
lanes: [4],
|
||||
timestamp: 0.013,
|
||||
timestamp: 0.015,
|
||||
type: 'schedule-state-update',
|
||||
warning: null,
|
||||
},
|
||||
|
|
|
@ -18,6 +18,7 @@ import type {
|
|||
Flamechart,
|
||||
NativeEvent,
|
||||
ReactLane,
|
||||
ReactComponentMeasure,
|
||||
ReactMeasureType,
|
||||
ReactProfilerData,
|
||||
SuspenseEvent,
|
||||
|
@ -36,6 +37,7 @@ type MeasureStackElement = {|
|
|||
|
||||
type ProcessorState = {|
|
||||
batchUID: BatchUID,
|
||||
currentReactComponentMeasure: ReactComponentMeasure | null,
|
||||
measureStack: MeasureStackElement[],
|
||||
nativeEventStack: NativeEvent[],
|
||||
nextRenderShouldGenerateNewBatchID: boolean,
|
||||
|
@ -240,8 +242,32 @@ function processTimelineEvent(
|
|||
case 'blink.user_timing':
|
||||
const startTime = (ts - currentProfilerData.startTime) / 1000;
|
||||
|
||||
// React Events - schedule
|
||||
if (name.startsWith('--schedule-render-')) {
|
||||
if (name.startsWith('--component-render-start-')) {
|
||||
const [componentName] = name.substr(25).split('-');
|
||||
|
||||
if (state.currentReactComponentMeasure !== null) {
|
||||
console.error(
|
||||
'Render started while another render in progress:',
|
||||
state.currentReactComponentMeasure,
|
||||
);
|
||||
}
|
||||
|
||||
state.currentReactComponentMeasure = {
|
||||
componentName,
|
||||
timestamp: startTime,
|
||||
duration: 0,
|
||||
warning: null,
|
||||
};
|
||||
} else if (name === '--component-render-stop') {
|
||||
if (state.currentReactComponentMeasure !== null) {
|
||||
const componentMeasure = state.currentReactComponentMeasure;
|
||||
componentMeasure.duration = startTime - componentMeasure.timestamp;
|
||||
|
||||
state.currentReactComponentMeasure = null;
|
||||
|
||||
currentProfilerData.componentMeasures.push(componentMeasure);
|
||||
}
|
||||
} else if (name.startsWith('--schedule-render-')) {
|
||||
const [laneBitmaskString, laneLabels] = name.substr(18).split('-');
|
||||
currentProfilerData.schedulingEvents.push({
|
||||
type: 'schedule-render',
|
||||
|
@ -581,6 +607,7 @@ export default function preprocessData(
|
|||
const flamechart = preprocessFlamechart(timeline);
|
||||
|
||||
const profilerData: ReactProfilerData = {
|
||||
componentMeasures: [],
|
||||
duration: 0,
|
||||
flamechart,
|
||||
measures: [],
|
||||
|
@ -619,6 +646,7 @@ export default function preprocessData(
|
|||
|
||||
const state: ProcessorState = {
|
||||
batchUID: 0,
|
||||
currentReactComponentMeasure: null,
|
||||
measureStack: [],
|
||||
nativeEventStack: [],
|
||||
nextRenderShouldGenerateNewBatchID: true,
|
||||
|
|
|
@ -91,6 +91,13 @@ export type ReactMeasure = {|
|
|||
+depth: number,
|
||||
|};
|
||||
|
||||
export type ReactComponentMeasure = {|
|
||||
+componentName: string,
|
||||
duration: Milliseconds,
|
||||
+timestamp: Milliseconds,
|
||||
warning: string | null,
|
||||
|};
|
||||
|
||||
/**
|
||||
* A flamechart stack frame belonging to a stack trace.
|
||||
*/
|
||||
|
@ -117,6 +124,7 @@ export type FlamechartStackLayer = FlamechartStackFrame[];
|
|||
export type Flamechart = FlamechartStackLayer[];
|
||||
|
||||
export type ReactProfilerData = {|
|
||||
componentMeasures: ReactComponentMeasure[],
|
||||
duration: number,
|
||||
flamechart: Flamechart,
|
||||
measures: ReactMeasure[],
|
||||
|
@ -128,6 +136,7 @@ export type ReactProfilerData = {|
|
|||
|};
|
||||
|
||||
export type ReactHoverContextInfo = {|
|
||||
componentMeasure: ReactComponentMeasure | null,
|
||||
data: $ReadOnly<ReactProfilerData> | null,
|
||||
flamechartStackFrame: FlamechartStackFrame | null,
|
||||
measure: ReactMeasure | null,
|
||||
|
|
|
@ -110,7 +110,6 @@ class ResizeBar extends View {
|
|||
context,
|
||||
labelRect,
|
||||
drawableRect,
|
||||
visibleArea.size.width,
|
||||
'center',
|
||||
COLORS.REACT_RESIZE_BAR_DOT,
|
||||
);
|
||||
|
|
|
@ -164,6 +164,7 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
|
|||
'--color-scheduling-profiler-react-suspense-unresolved': '#c9cacd',
|
||||
'--color-scheduling-profiler-react-suspense-unresolved-hover': '#93959a',
|
||||
'--color-scheduling-profiler-text-color': '#000000',
|
||||
'--color-scheduling-profiler-text-dim-color': '#ccc',
|
||||
'--color-scheduling-profiler-react-work-border': '#ffffff',
|
||||
'--color-search-match': 'yellow',
|
||||
'--color-search-match-current': '#f7923b',
|
||||
|
@ -293,7 +294,8 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
|
|||
'--color-scheduling-profiler-react-suspense-resolved-hover': '#89d281',
|
||||
'--color-scheduling-profiler-react-suspense-unresolved': '#c9cacd',
|
||||
'--color-scheduling-profiler-react-suspense-unresolved-hover': '#93959a',
|
||||
'--color-scheduling-profiler-text-color': '#000000',
|
||||
'--color-scheduling-profiler-text-color': '#282c34',
|
||||
'--color-scheduling-profiler-text-dim-color': '#555b66',
|
||||
'--color-scheduling-profiler-react-work-border': '#ffffff',
|
||||
'--color-search-match': 'yellow',
|
||||
'--color-search-match-current': '#f7923b',
|
||||
|
|
|
@ -8,22 +8,41 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {useCallback, useContext} from 'react';
|
||||
import {useContext} from 'react';
|
||||
import {ProfilerContext} from './ProfilerContext';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import {StoreContext} from '../context';
|
||||
import {SchedulingProfilerContext} from 'react-devtools-scheduling-profiler/src/SchedulingProfilerContext';
|
||||
|
||||
export default function ClearProfilingDataButton() {
|
||||
const store = useContext(StoreContext);
|
||||
const {didRecordCommits, isProfiling} = useContext(ProfilerContext);
|
||||
const {didRecordCommits, isProfiling, selectedTabID} = useContext(
|
||||
ProfilerContext,
|
||||
);
|
||||
const {clearSchedulingProfilerData, schedulingProfilerData} = useContext(
|
||||
SchedulingProfilerContext,
|
||||
);
|
||||
const {profilerStore} = store;
|
||||
|
||||
const clear = useCallback(() => profilerStore.clear(), [profilerStore]);
|
||||
let doesHaveData = false;
|
||||
if (selectedTabID === 'scheduling-profiler') {
|
||||
doesHaveData = schedulingProfilerData !== null;
|
||||
} else {
|
||||
doesHaveData = didRecordCommits;
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
if (selectedTabID === 'scheduling-profiler') {
|
||||
clearSchedulingProfilerData();
|
||||
} else {
|
||||
profilerStore.clear();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
disabled={isProfiling || !didRecordCommits}
|
||||
disabled={isProfiling || !doesHaveData}
|
||||
onClick={clear}
|
||||
title="Clear profiling data">
|
||||
<ButtonIcon type="clear" />
|
||||
|
|
|
@ -103,8 +103,16 @@ function Profiler(_: {||}) {
|
|||
<div className={styles.Profiler}>
|
||||
<div className={styles.LeftColumn}>
|
||||
<div className={styles.Toolbar}>
|
||||
<RecordToggle disabled={!supportsProfiling} />
|
||||
<ReloadAndProfileButton />
|
||||
<RecordToggle
|
||||
disabled={
|
||||
!supportsProfiling || selectedTabID === 'scheduling-profiler'
|
||||
}
|
||||
/>
|
||||
<ReloadAndProfileButton
|
||||
disabled={
|
||||
selectedTabID === 'scheduling-profiler' || !supportsProfiling
|
||||
}
|
||||
/>
|
||||
<ClearProfilingDataButton />
|
||||
<ProfilingImportExportButtons />
|
||||
<div className={styles.VRule} />
|
||||
|
|
|
@ -19,7 +19,11 @@ type SubscriptionData = {|
|
|||
supportsReloadAndProfile: boolean,
|
||||
|};
|
||||
|
||||
export default function ReloadAndProfileButton() {
|
||||
export default function ReloadAndProfileButton({
|
||||
disabled,
|
||||
}: {|
|
||||
disabled: boolean,
|
||||
|}) {
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
|
||||
|
@ -61,7 +65,7 @@ export default function ReloadAndProfileButton() {
|
|||
|
||||
return (
|
||||
<Button
|
||||
disabled={!store.supportsProfiling}
|
||||
disabled={disabled}
|
||||
onClick={reloadAndProfile}
|
||||
title="Reload and start profiling">
|
||||
<ButtonIcon type="reload" />
|
||||
|
|
|
@ -31,7 +31,10 @@ import type {
|
|||
import type {UpdateQueue} from './ReactUpdateQueue.new';
|
||||
|
||||
import checkPropTypes from 'shared/checkPropTypes';
|
||||
|
||||
import {
|
||||
markComponentRenderStarted,
|
||||
markComponentRenderStopped,
|
||||
} from './SchedulingProfiler';
|
||||
import {
|
||||
IndeterminateComponent,
|
||||
FunctionComponent,
|
||||
|
@ -85,6 +88,7 @@ import {
|
|||
enableCache,
|
||||
enableLazyContextPropagation,
|
||||
enableSuspenseLayoutEffectSemantics,
|
||||
enableSchedulingProfiler,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import invariant from 'shared/invariant';
|
||||
import isArray from 'shared/isArray';
|
||||
|
@ -357,6 +361,9 @@ function updateForwardRef(
|
|||
// The rest is a fork of updateFunctionComponent
|
||||
let nextChildren;
|
||||
prepareToReadContext(workInProgress, renderLanes);
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
setIsRendering(true);
|
||||
|
@ -397,6 +404,9 @@ function updateForwardRef(
|
|||
renderLanes,
|
||||
);
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
|
||||
if (current !== null && !didReceiveUpdate) {
|
||||
bailoutHooks(current, workInProgress, renderLanes);
|
||||
|
@ -958,6 +968,9 @@ function updateFunctionComponent(
|
|||
|
||||
let nextChildren;
|
||||
prepareToReadContext(workInProgress, renderLanes);
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
setIsRendering(true);
|
||||
|
@ -998,6 +1011,9 @@ function updateFunctionComponent(
|
|||
renderLanes,
|
||||
);
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
|
||||
if (current !== null && !didReceiveUpdate) {
|
||||
bailoutHooks(current, workInProgress, renderLanes);
|
||||
|
@ -1177,6 +1193,9 @@ function finishClassComponent(
|
|||
stopProfilerTimerIfRunning(workInProgress);
|
||||
}
|
||||
} else {
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
if (__DEV__) {
|
||||
setIsRendering(true);
|
||||
nextChildren = instance.render();
|
||||
|
@ -1195,6 +1214,9 @@ function finishClassComponent(
|
|||
} else {
|
||||
nextChildren = instance.render();
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
}
|
||||
|
||||
// React DevTools reads this flag.
|
||||
|
@ -1567,6 +1589,9 @@ function mountIndeterminateComponent(
|
|||
prepareToReadContext(workInProgress, renderLanes);
|
||||
let value;
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
if (__DEV__) {
|
||||
if (
|
||||
Component.prototype &&
|
||||
|
@ -1610,6 +1635,10 @@ function mountIndeterminateComponent(
|
|||
renderLanes,
|
||||
);
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.flags |= PerformedWork;
|
||||
|
||||
|
@ -3195,6 +3224,9 @@ function updateContextConsumer(
|
|||
|
||||
prepareToReadContext(workInProgress, renderLanes);
|
||||
const newValue = readContext(context);
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
let newChildren;
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
|
@ -3204,6 +3236,9 @@ function updateContextConsumer(
|
|||
} else {
|
||||
newChildren = render(newValue);
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.flags |= PerformedWork;
|
||||
|
|
|
@ -31,7 +31,10 @@ import type {
|
|||
import type {UpdateQueue} from './ReactUpdateQueue.old';
|
||||
|
||||
import checkPropTypes from 'shared/checkPropTypes';
|
||||
|
||||
import {
|
||||
markComponentRenderStarted,
|
||||
markComponentRenderStopped,
|
||||
} from './SchedulingProfiler';
|
||||
import {
|
||||
IndeterminateComponent,
|
||||
FunctionComponent,
|
||||
|
@ -85,6 +88,7 @@ import {
|
|||
enableCache,
|
||||
enableLazyContextPropagation,
|
||||
enableSuspenseLayoutEffectSemantics,
|
||||
enableSchedulingProfiler,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import invariant from 'shared/invariant';
|
||||
import isArray from 'shared/isArray';
|
||||
|
@ -357,6 +361,9 @@ function updateForwardRef(
|
|||
// The rest is a fork of updateFunctionComponent
|
||||
let nextChildren;
|
||||
prepareToReadContext(workInProgress, renderLanes);
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
setIsRendering(true);
|
||||
|
@ -397,6 +404,9 @@ function updateForwardRef(
|
|||
renderLanes,
|
||||
);
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
|
||||
if (current !== null && !didReceiveUpdate) {
|
||||
bailoutHooks(current, workInProgress, renderLanes);
|
||||
|
@ -958,6 +968,9 @@ function updateFunctionComponent(
|
|||
|
||||
let nextChildren;
|
||||
prepareToReadContext(workInProgress, renderLanes);
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
setIsRendering(true);
|
||||
|
@ -998,6 +1011,9 @@ function updateFunctionComponent(
|
|||
renderLanes,
|
||||
);
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
|
||||
if (current !== null && !didReceiveUpdate) {
|
||||
bailoutHooks(current, workInProgress, renderLanes);
|
||||
|
@ -1177,6 +1193,9 @@ function finishClassComponent(
|
|||
stopProfilerTimerIfRunning(workInProgress);
|
||||
}
|
||||
} else {
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
if (__DEV__) {
|
||||
setIsRendering(true);
|
||||
nextChildren = instance.render();
|
||||
|
@ -1195,6 +1214,9 @@ function finishClassComponent(
|
|||
} else {
|
||||
nextChildren = instance.render();
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
}
|
||||
|
||||
// React DevTools reads this flag.
|
||||
|
@ -1567,6 +1589,9 @@ function mountIndeterminateComponent(
|
|||
prepareToReadContext(workInProgress, renderLanes);
|
||||
let value;
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
if (__DEV__) {
|
||||
if (
|
||||
Component.prototype &&
|
||||
|
@ -1610,6 +1635,10 @@ function mountIndeterminateComponent(
|
|||
renderLanes,
|
||||
);
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.flags |= PerformedWork;
|
||||
|
||||
|
@ -3195,6 +3224,9 @@ function updateContextConsumer(
|
|||
|
||||
prepareToReadContext(workInProgress, renderLanes);
|
||||
const newValue = readContext(context);
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStarted(workInProgress);
|
||||
}
|
||||
let newChildren;
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = workInProgress;
|
||||
|
@ -3204,6 +3236,9 @@ function updateContextConsumer(
|
|||
} else {
|
||||
newChildren = render(newValue);
|
||||
}
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
}
|
||||
|
||||
// React DevTools reads this flag.
|
||||
workInProgress.flags |= PerformedWork;
|
||||
|
|
|
@ -70,7 +70,10 @@ import {
|
|||
import {propagateParentContextChangesToDeferredTree} from './ReactFiberNewContext.new';
|
||||
import {logCapturedError} from './ReactFiberErrorLogger';
|
||||
import {logComponentSuspended} from './DebugTracing';
|
||||
import {markComponentSuspended} from './SchedulingProfiler';
|
||||
import {
|
||||
markComponentRenderStopped,
|
||||
markComponentSuspended,
|
||||
} from './SchedulingProfiler';
|
||||
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
|
||||
import {
|
||||
SyncLane,
|
||||
|
@ -244,6 +247,7 @@ function throwException(
|
|||
}
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
markComponentSuspended(sourceFiber, wakeable, rootRenderLanes);
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,10 @@ import {
|
|||
import {propagateParentContextChangesToDeferredTree} from './ReactFiberNewContext.old';
|
||||
import {logCapturedError} from './ReactFiberErrorLogger';
|
||||
import {logComponentSuspended} from './DebugTracing';
|
||||
import {markComponentSuspended} from './SchedulingProfiler';
|
||||
import {
|
||||
markComponentRenderStopped,
|
||||
markComponentSuspended,
|
||||
} from './SchedulingProfiler';
|
||||
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
|
||||
import {
|
||||
SyncLane,
|
||||
|
@ -244,6 +247,7 @@ function throwException(
|
|||
}
|
||||
|
||||
if (enableSchedulingProfiler) {
|
||||
markComponentRenderStopped();
|
||||
markComponentSuspended(sourceFiber, wakeable, rootRenderLanes);
|
||||
}
|
||||
|
||||
|
|
|
@ -97,6 +97,23 @@ export function markCommitStopped(): void {
|
|||
}
|
||||
}
|
||||
|
||||
export function markComponentRenderStarted(fiber: Fiber): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (supportsUserTimingV3) {
|
||||
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
|
||||
markAndClear(`--component-render-start-${componentName}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markComponentRenderStopped(): void {
|
||||
if (enableSchedulingProfiler) {
|
||||
if (supportsUserTimingV3) {
|
||||
markAndClear('--component-render-stop');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
|
||||
|
||||
// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
|
||||
|
|
|
@ -174,6 +174,8 @@ describe('SchedulingProfiler', () => {
|
|||
`--react-init-${ReactVersion}`,
|
||||
`--schedule-render-${formatLanes(ReactFiberLane.TransitionLane1)}`,
|
||||
`--render-start-${formatLanes(ReactFiberLane.TransitionLane1)}`,
|
||||
'--component-render-start-Foo',
|
||||
'--component-render-stop',
|
||||
'--render-yield',
|
||||
]);
|
||||
} else {
|
||||
|
@ -208,6 +210,8 @@ describe('SchedulingProfiler', () => {
|
|||
`--react-init-${ReactVersion}`,
|
||||
`--schedule-render-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
`--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--suspense-suspend-0-Example-mount-1-Sync',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
|
@ -239,6 +243,8 @@ describe('SchedulingProfiler', () => {
|
|||
`--react-init-${ReactVersion}`,
|
||||
`--schedule-render-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
`--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--suspense-suspend-0-Example-mount-1-Sync',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
|
@ -278,6 +284,8 @@ describe('SchedulingProfiler', () => {
|
|||
|
||||
expectMarksToEqual([
|
||||
`--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--suspense-suspend-0-Example-mount-16-Default',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
|
@ -317,6 +325,8 @@ describe('SchedulingProfiler', () => {
|
|||
|
||||
expectMarksToEqual([
|
||||
`--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--suspense-suspend-0-Example-mount-16-Default',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
|
@ -356,12 +366,16 @@ describe('SchedulingProfiler', () => {
|
|||
|
||||
expectMarksToEqual([
|
||||
`--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
`--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
`--schedule-state-update-${formatLanes(ReactFiberLane.SyncLane)}-Example`,
|
||||
'--layout-effects-stop',
|
||||
`--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
'--commit-stop',
|
||||
|
@ -393,6 +407,8 @@ describe('SchedulingProfiler', () => {
|
|||
|
||||
expectMarksToEqual([
|
||||
`--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
`--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
|
@ -401,6 +417,8 @@ describe('SchedulingProfiler', () => {
|
|||
)}-Example`,
|
||||
'--layout-effects-stop',
|
||||
`--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
'--commit-stop',
|
||||
|
@ -495,12 +513,16 @@ describe('SchedulingProfiler', () => {
|
|||
|
||||
expectMarksToEqual([
|
||||
`--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
`--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
`--schedule-state-update-${formatLanes(ReactFiberLane.SyncLane)}-Example`,
|
||||
'--layout-effects-stop',
|
||||
`--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
|
||||
'--commit-stop',
|
||||
|
@ -528,6 +550,8 @@ describe('SchedulingProfiler', () => {
|
|||
`--react-init-${ReactVersion}`,
|
||||
`--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
`--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
`--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
|
@ -539,6 +563,8 @@ describe('SchedulingProfiler', () => {
|
|||
)}-Example`,
|
||||
'--passive-effects-stop',
|
||||
`--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
'--component-render-start-Example',
|
||||
'--component-render-stop',
|
||||
'--render-stop',
|
||||
`--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
|
||||
'--commit-stop',
|
||||
|
|
Loading…
Reference in New Issue