Skip to content

Commit e3049bb

Browse files
author
Brian Vaughn
authored
DevTools scheduling profiler: Add React component measures (#22013)
1 parent b54f36f commit e3049bb

24 files changed

+603
-39
lines changed

packages/react-devtools-scheduling-profiler/src/CanvasPage.js

+53
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
zeroPoint,
4545
} from './view-base';
4646
import {
47+
ComponentMeasuresView,
4748
FlamechartView,
4849
NativeEventsView,
4950
ReactMeasuresView,
@@ -132,6 +133,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
132133
const nativeEventsViewRef = useRef(null);
133134
const schedulingEventsViewRef = useRef(null);
134135
const suspenseEventsViewRef = useRef(null);
136+
const componentMeasuresViewRef = useRef(null);
135137
const reactMeasuresViewRef = useRef(null);
136138
const flamechartViewRef = useRef(null);
137139
const syncedHorizontalPanAndZoomViewsRef = useRef<HorizontalPanAndZoomView[]>(
@@ -259,6 +261,17 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
259261
true,
260262
);
261263

264+
let componentMeasuresViewWrapper = null;
265+
if (data.componentMeasures.length > 0) {
266+
const componentMeasuresView = new ComponentMeasuresView(
267+
surface,
268+
defaultFrame,
269+
data,
270+
);
271+
componentMeasuresViewRef.current = componentMeasuresView;
272+
componentMeasuresViewWrapper = createViewHelper(componentMeasuresView);
273+
}
274+
262275
const flamechartView = new FlamechartView(
263276
surface,
264277
defaultFrame,
@@ -293,6 +306,9 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
293306
rootView.addSubview(suspenseEventsViewWrapper);
294307
}
295308
rootView.addSubview(reactMeasuresViewWrapper);
309+
if (componentMeasuresViewWrapper !== null) {
310+
rootView.addSubview(componentMeasuresViewWrapper);
311+
}
296312
rootView.addSubview(flamechartViewWrapper);
297313

298314
// 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) {
323339
if (prevHoverEvent === null) {
324340
return prevHoverEvent;
325341
} else if (
342+
prevHoverEvent.componentMeasure !== null ||
326343
prevHoverEvent.flamechartStackFrame !== null ||
327344
prevHoverEvent.measure !== null ||
328345
prevHoverEvent.nativeEvent !== null ||
@@ -331,6 +348,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
331348
prevHoverEvent.userTimingMark !== null
332349
) {
333350
return {
351+
componentMeasure: null,
334352
data: prevHoverEvent.data,
335353
flamechartStackFrame: null,
336354
measure: null,
@@ -378,6 +396,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
378396
userTimingMarksView.onHover = userTimingMark => {
379397
if (!hoveredEvent || hoveredEvent.userTimingMark !== userTimingMark) {
380398
setHoveredEvent({
399+
componentMeasure: null,
381400
data,
382401
flamechartStackFrame: null,
383402
measure: null,
@@ -395,6 +414,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
395414
nativeEventsView.onHover = nativeEvent => {
396415
if (!hoveredEvent || hoveredEvent.nativeEvent !== nativeEvent) {
397416
setHoveredEvent({
417+
componentMeasure: null,
398418
data,
399419
flamechartStackFrame: null,
400420
measure: null,
@@ -412,6 +432,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
412432
schedulingEventsView.onHover = schedulingEvent => {
413433
if (!hoveredEvent || hoveredEvent.schedulingEvent !== schedulingEvent) {
414434
setHoveredEvent({
435+
componentMeasure: null,
415436
data,
416437
flamechartStackFrame: null,
417438
measure: null,
@@ -429,6 +450,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
429450
suspenseEventsView.onHover = suspenseEvent => {
430451
if (!hoveredEvent || hoveredEvent.suspenseEvent !== suspenseEvent) {
431452
setHoveredEvent({
453+
componentMeasure: null,
432454
data,
433455
flamechartStackFrame: null,
434456
measure: null,
@@ -446,6 +468,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
446468
reactMeasuresView.onHover = measure => {
447469
if (!hoveredEvent || hoveredEvent.measure !== measure) {
448470
setHoveredEvent({
471+
componentMeasure: null,
449472
data,
450473
flamechartStackFrame: null,
451474
measure,
@@ -458,6 +481,27 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
458481
};
459482
}
460483

484+
const {current: componentMeasuresView} = componentMeasuresViewRef;
485+
if (componentMeasuresView) {
486+
componentMeasuresView.onHover = componentMeasure => {
487+
if (
488+
!hoveredEvent ||
489+
hoveredEvent.componentMeasure !== componentMeasure
490+
) {
491+
setHoveredEvent({
492+
componentMeasure,
493+
data,
494+
flamechartStackFrame: null,
495+
measure: null,
496+
nativeEvent: null,
497+
schedulingEvent: null,
498+
suspenseEvent: null,
499+
userTimingMark: null,
500+
});
501+
}
502+
};
503+
}
504+
461505
const {current: flamechartView} = flamechartViewRef;
462506
if (flamechartView) {
463507
flamechartView.setOnHover(flamechartStackFrame => {
@@ -466,6 +510,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
466510
hoveredEvent.flamechartStackFrame !== flamechartStackFrame
467511
) {
468512
setHoveredEvent({
513+
componentMeasure: null,
469514
data,
470515
flamechartStackFrame,
471516
measure: null,
@@ -540,13 +585,21 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
540585
return null;
541586
}
542587
const {
588+
componentMeasure,
543589
flamechartStackFrame,
544590
measure,
545591
schedulingEvent,
546592
suspenseEvent,
547593
} = contextData.hoveredEvent;
548594
return (
549595
<Fragment>
596+
{componentMeasure !== null && (
597+
<ContextMenuItem
598+
onClick={() => copy(componentMeasure.componentName)}
599+
title="Copy component name">
600+
Copy component name
601+
</ContextMenuItem>
602+
)}
550603
{schedulingEvent !== null && (
551604
<ContextMenuItem
552605
onClick={() => copy(schedulingEvent.componentName)}

packages/react-devtools-scheduling-profiler/src/EventTooltip.js

+42-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {Point} from './view-base';
1111
import type {
1212
FlamechartStackFrame,
1313
NativeEvent,
14+
ReactComponentMeasure,
1415
ReactHoverContextInfo,
1516
ReactMeasure,
1617
ReactProfilerData,
@@ -81,6 +82,7 @@ export default function EventTooltip({
8182
}
8283

8384
const {
85+
componentMeasure,
8486
flamechartStackFrame,
8587
measure,
8688
nativeEvent,
@@ -89,7 +91,14 @@ export default function EventTooltip({
8991
userTimingMark,
9092
} = hoveredEvent;
9193

92-
if (nativeEvent !== null) {
94+
if (componentMeasure !== null) {
95+
return (
96+
<TooltipReactComponentMeasure
97+
componentMeasure={componentMeasure}
98+
tooltipRef={tooltipRef}
99+
/>
100+
);
101+
} else if (nativeEvent !== null) {
93102
return (
94103
<TooltipNativeEvent nativeEvent={nativeEvent} tooltipRef={tooltipRef} />
95104
);
@@ -130,6 +139,38 @@ export default function EventTooltip({
130139
return null;
131140
}
132141

142+
const TooltipReactComponentMeasure = ({
143+
componentMeasure,
144+
tooltipRef,
145+
}: {
146+
componentMeasure: ReactComponentMeasure,
147+
tooltipRef: Return<typeof useRef>,
148+
}) => {
149+
const {componentName, duration, timestamp, warning} = componentMeasure;
150+
151+
const label = `${componentName} rendered`;
152+
153+
return (
154+
<div className={styles.Tooltip} ref={tooltipRef}>
155+
<div className={styles.TooltipSection}>
156+
{trimString(label, 768)}
157+
<div className={styles.Divider} />
158+
<div className={styles.DetailsGrid}>
159+
<div className={styles.DetailsGridLabel}>Timestamp:</div>
160+
<div>{formatTimestamp(timestamp)}</div>
161+
<div className={styles.DetailsGridLabel}>Duration:</div>
162+
<div>{formatDuration(duration)}</div>
163+
</div>
164+
</div>
165+
{warning !== null && (
166+
<div className={styles.TooltipWarningSection}>
167+
<div className={styles.WarningText}>{warning}</div>
168+
</div>
169+
)}
170+
</div>
171+
);
172+
};
173+
133174
const TooltipFlamechartNode = ({
134175
stackFrame,
135176
tooltipRef,

packages/react-devtools-scheduling-profiler/src/SchedulingProfilerContext.js

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import createDataResourceFromImportedFile from './createDataResourceFromImported
1414
import type {DataResource} from './createDataResourceFromImportedFile';
1515

1616
export type Context = {|
17+
clearSchedulingProfilerData: () => void,
1718
importSchedulingProfilerData: (file: File) => void,
1819
schedulingProfilerData: DataResource | null,
1920
|};
@@ -33,6 +34,10 @@ function SchedulingProfilerContextController({children}: Props) {
3334
setSchedulingProfilerData,
3435
] = useState<DataResource | null>(null);
3536

37+
const clearSchedulingProfilerData = useCallback(() => {
38+
setSchedulingProfilerData(null);
39+
}, []);
40+
3641
const importSchedulingProfilerData = useCallback((file: File) => {
3742
setSchedulingProfilerData(createDataResourceFromImportedFile(file));
3843
}, []);
@@ -41,11 +46,13 @@ function SchedulingProfilerContextController({children}: Props) {
4146

4247
const value = useMemo(
4348
() => ({
49+
clearSchedulingProfilerData,
4450
importSchedulingProfilerData,
4551
schedulingProfilerData,
4652
// TODO (scheduling profiler)
4753
}),
4854
[
55+
clearSchedulingProfilerData,
4956
importSchedulingProfilerData,
5057
schedulingProfilerData,
5158
// TODO (scheduling profiler)

0 commit comments

Comments
 (0)