[DevTools] Add native events to the scheduling profiler (#21947)

This commit is contained in:
Brian Vaughn
2021-07-26 10:36:16 -04:00
committed by GitHub
parent 4758e4533e
commit b1811ebf01
12 changed files with 671 additions and 308 deletions
+47 -12
View File
@@ -44,6 +44,7 @@ import {
} from './view-base';
import {
FlamechartView,
NativeEventsView,
ReactEventsView,
ReactMeasuresView,
TimeAxisMarkersView,
@@ -126,6 +127,7 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
const surfaceRef = useRef(new Surface());
const userTimingMarksViewRef = useRef(null);
const nativeEventsViewRef = useRef(null);
const reactEventsViewRef = useRef(null);
const reactMeasuresViewRef = useRef(null);
const flamechartViewRef = useRef(null);
@@ -176,6 +178,10 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
topContentStack.addSubview(userTimingMarksView);
}
const nativeEventsView = new NativeEventsView(surface, defaultFrame, data);
nativeEventsViewRef.current = nativeEventsView;
topContentStack.addSubview(nativeEventsView);
const reactEventsView = new ReactEventsView(surface, defaultFrame, data);
reactEventsViewRef.current = reactEventsView;
topContentStack.addSubview(reactEventsView);
@@ -299,7 +305,24 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
if (!hoveredEvent || hoveredEvent.userTimingMark !== userTimingMark) {
setHoveredEvent({
userTimingMark,
event: null,
nativeEvent: null,
reactEvent: null,
flamechartStackFrame: null,
measure: null,
data,
});
}
};
}
const {current: nativeEventsView} = nativeEventsViewRef;
if (nativeEventsView) {
nativeEventsView.onHover = nativeEvent => {
if (!hoveredEvent || hoveredEvent.nativeEvent !== nativeEvent) {
setHoveredEvent({
userTimingMark: null,
nativeEvent,
reactEvent: null,
flamechartStackFrame: null,
measure: null,
data,
@@ -310,11 +333,12 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
const {current: reactEventsView} = reactEventsViewRef;
if (reactEventsView) {
reactEventsView.onHover = event => {
if (!hoveredEvent || hoveredEvent.event !== event) {
reactEventsView.onHover = reactEvent => {
if (!hoveredEvent || hoveredEvent.reactEvent !== reactEvent) {
setHoveredEvent({
userTimingMark: null,
event,
nativeEvent: null,
reactEvent,
flamechartStackFrame: null,
measure: null,
data,
@@ -329,7 +353,8 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
if (!hoveredEvent || hoveredEvent.measure !== measure) {
setHoveredEvent({
userTimingMark: null,
event: null,
nativeEvent: null,
reactEvent: null,
flamechartStackFrame: null,
measure,
data,
@@ -347,7 +372,8 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
) {
setHoveredEvent({
userTimingMark: null,
event: null,
nativeEvent: null,
reactEvent: null,
flamechartStackFrame,
measure: null,
data,
@@ -368,9 +394,18 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
);
}
const {current: nativeEventsView} = nativeEventsViewRef;
if (nativeEventsView) {
nativeEventsView.setHoveredEvent(
hoveredEvent ? hoveredEvent.nativeEvent : null,
);
}
const {current: reactEventsView} = reactEventsViewRef;
if (reactEventsView) {
reactEventsView.setHoveredEvent(hoveredEvent ? hoveredEvent.event : null);
reactEventsView.setHoveredEvent(
hoveredEvent ? hoveredEvent.reactEvent : null,
);
}
const {current: reactMeasuresView} = reactMeasuresViewRef;
@@ -402,22 +437,22 @@ function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
return null;
}
const {
event,
reactEvent,
flamechartStackFrame,
measure,
} = contextData.hoveredEvent;
return (
<Fragment>
{event !== null && (
{reactEvent !== null && (
<ContextMenuItem
onClick={() => copy(event.componentName)}
onClick={() => copy(reactEvent.componentName)}
title="Copy component name">
Copy component name
</ContextMenuItem>
)}
{event !== null && event.componentStack && (
{reactEvent !== null && reactEvent.componentStack && (
<ContextMenuItem
onClick={() => copy(event.componentStack)}
onClick={() => copy(reactEvent.componentStack)}
title="Copy component stack">
Copy component stack
</ContextMenuItem>
@@ -29,6 +29,7 @@
.DetailsGridLabel {
color: var(--color-dim);
text-align: right;
white-space: nowrap;
}
.DetailsGridURL {
@@ -45,7 +46,7 @@
.ComponentName {
font-weight: bold;
word-break: break-word;
margin-right: 0.4rem;
margin-right: 0.25rem;
}
.ComponentStack {
@@ -10,6 +10,7 @@
import type {Point} from './view-base';
import type {
FlamechartStackFrame,
NativeEvent,
ReactEvent,
ReactHoverContextInfo,
ReactMeasure,
@@ -47,8 +48,8 @@ function trimmedString(string: string, length: number): string {
return string;
}
function getReactEventLabel(type): string | null {
switch (type) {
function getReactEventLabel(event: ReactEvent): string | null {
switch (event.type) {
case 'schedule-render':
return 'render scheduled';
case 'schedule-state-update':
@@ -111,10 +112,22 @@ export default function EventTooltip({data, hoveredEvent, origin}: Props) {
return null;
}
const {event, measure, flamechartStackFrame, userTimingMark} = hoveredEvent;
const {
nativeEvent,
reactEvent,
measure,
flamechartStackFrame,
userTimingMark,
} = hoveredEvent;
if (event !== null) {
return <TooltipReactEvent event={event} tooltipRef={tooltipRef} />;
if (nativeEvent !== null) {
return (
<TooltipNativeEvent nativeEvent={nativeEvent} tooltipRef={tooltipRef} />
);
} else if (reactEvent !== null) {
return (
<TooltipReactEvent reactEvent={reactEvent} tooltipRef={tooltipRef} />
);
} else if (measure !== null) {
return (
<TooltipReactMeasure
@@ -189,23 +202,47 @@ const TooltipFlamechartNode = ({
);
};
const TooltipReactEvent = ({
event,
const TooltipNativeEvent = ({
nativeEvent,
tooltipRef,
}: {
event: ReactEvent,
nativeEvent: NativeEvent,
tooltipRef: Return<typeof useRef>,
}) => {
const label = getReactEventLabel(event.type);
const color = getReactEventColor(event);
const {duration, timestamp, type} = nativeEvent;
return (
<div className={styles.Tooltip} ref={tooltipRef}>
<span className={styles.ComponentName}>{trimmedString(type, 768)}</span>
event
<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>
);
};
const TooltipReactEvent = ({
reactEvent,
tooltipRef,
}: {
reactEvent: ReactEvent,
tooltipRef: Return<typeof useRef>,
}) => {
const label = getReactEventLabel(reactEvent);
const color = getReactEventColor(reactEvent);
if (!label || !color) {
if (__DEV__) {
console.warn('Unexpected event type "%s"', event.type);
console.warn('Unexpected reactEvent type "%s"', reactEvent.type);
}
return null;
}
const {componentName, componentStack, timestamp} = event;
const {componentName, componentStack, timestamp} = reactEvent;
return (
<div className={styles.Tooltip} ref={tooltipRef}>
@@ -0,0 +1,224 @@
/**
* 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 {NativeEvent, ReactProfilerData} from '../types';
import type {Interaction, MouseMoveInteraction, Rect, Size} from '../view-base';
import {
positioningScaleFactor,
timestampToPosition,
positionToTimestamp,
} from './utils/positioning';
import {
View,
Surface,
rectContainsPoint,
rectIntersectsRect,
intersectionOfRects,
} from '../view-base';
import {
COLORS,
EVENT_ROW_PADDING,
EVENT_DIAMETER,
BORDER_SIZE,
} from './constants';
const EVENT_ROW_HEIGHT_FIXED =
EVENT_ROW_PADDING + EVENT_DIAMETER + EVENT_ROW_PADDING;
export class NativeEventsView extends View {
_profilerData: ReactProfilerData;
_intrinsicSize: Size;
_hoveredEvent: NativeEvent | null = null;
onHover: ((event: NativeEvent | null) => void) | null = null;
constructor(surface: Surface, frame: Rect, profilerData: ReactProfilerData) {
super(surface, frame);
this._profilerData = profilerData;
this._intrinsicSize = {
width: this._profilerData.duration,
height: EVENT_ROW_HEIGHT_FIXED,
};
}
desiredSize() {
return this._intrinsicSize;
}
setHoveredEvent(hoveredEvent: NativeEvent | null) {
if (this._hoveredEvent === hoveredEvent) {
return;
}
this._hoveredEvent = hoveredEvent;
this.setNeedsDisplay();
}
/**
* Draw a single `NativeEvent` as a circle in the canvas.
*/
_drawSingleNativeEvent(
context: CanvasRenderingContext2D,
rect: Rect,
event: NativeEvent,
baseY: number,
scaleFactor: number,
showHoverHighlight: boolean,
) {
const {frame} = this;
const {duration, timestamp} = event;
const xStart = timestampToPosition(timestamp, scaleFactor, frame);
const xStop = timestampToPosition(timestamp + duration, scaleFactor, frame);
const eventRect: Rect = {
origin: {
x: xStart,
y: baseY,
},
size: {width: xStop - xStart, height: EVENT_DIAMETER},
};
if (!rectIntersectsRect(eventRect, rect)) {
return; // Not in view
}
const fillStyle = showHoverHighlight
? COLORS.NATIVE_EVENT_HOVER
: COLORS.NATIVE_EVENT;
const drawableRect = intersectionOfRects(eventRect, rect);
context.beginPath();
context.fillStyle = fillStyle;
context.fillRect(
drawableRect.origin.x,
drawableRect.origin.y,
drawableRect.size.width,
drawableRect.size.height,
);
}
draw(context: CanvasRenderingContext2D) {
const {
frame,
_profilerData: {nativeEvents},
_hoveredEvent,
visibleArea,
} = this;
context.fillStyle = COLORS.BACKGROUND;
context.fillRect(
visibleArea.origin.x,
visibleArea.origin.y,
visibleArea.size.width,
visibleArea.size.height,
);
// Draw events
const baseY = frame.origin.y + EVENT_ROW_PADDING;
const scaleFactor = positioningScaleFactor(
this._intrinsicSize.width,
frame,
);
nativeEvents.forEach(event => {
if (event === _hoveredEvent) {
// Draw the highlighted items on top so they stand out.
// This is helpful if there are multiple (overlapping) items close to each other.
this._drawSingleNativeEvent(
context,
visibleArea,
event,
baseY,
scaleFactor,
true,
);
} else {
this._drawSingleNativeEvent(
context,
visibleArea,
event,
baseY,
scaleFactor,
false,
);
}
});
// Render bottom border.
// Propose border rect, check if intersects with `rect`, draw intersection.
const borderFrame: Rect = {
origin: {
x: frame.origin.x,
y: frame.origin.y + EVENT_ROW_HEIGHT_FIXED - 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,
);
}
}
/**
* @private
*/
_handleMouseMove(interaction: MouseMoveInteraction) {
const {frame, _intrinsicSize, onHover, visibleArea} = this;
if (!onHover) {
return;
}
const {location} = interaction.payload;
if (!rectContainsPoint(location, visibleArea)) {
onHover(null);
return;
}
const {nativeEvents} = this._profilerData;
const scaleFactor = positioningScaleFactor(_intrinsicSize.width, frame);
const hoverTimestamp = positionToTimestamp(location.x, scaleFactor, frame);
// Find the event being hovered over.
//
// Because data ranges may overlap, we want to find the last intersecting item.
// This will always be the one on "top" (the one the user is hovering over).
for (let index = nativeEvents.length - 1; index >= 0; index--) {
const nativeEvent = nativeEvents[index];
const {duration, timestamp} = nativeEvent;
if (
hoverTimestamp >= timestamp &&
hoverTimestamp <= timestamp + duration
) {
onHover(nativeEvent);
return;
}
}
onHover(null);
}
handleInteraction(interaction: Interaction) {
switch (interaction.type) {
case 'mousemove':
this._handleMouseMove(interaction);
break;
}
}
}
@@ -100,6 +100,8 @@ export class ReactEventsView extends View {
let fillStyle = null;
switch (type) {
case 'native-event':
return;
case 'schedule-render':
case 'schedule-state-update':
case 'schedule-force-update':
@@ -140,7 +142,7 @@ export class ReactEventsView extends View {
draw(context: CanvasRenderingContext2D) {
const {
frame,
_profilerData: {events},
_profilerData: {reactEvents},
_hoveredEvent,
visibleArea,
} = this;
@@ -162,7 +164,7 @@ export class ReactEventsView extends View {
const highlightedEvents: ReactEvent[] = [];
events.forEach(event => {
reactEvents.forEach(event => {
if (
event === _hoveredEvent ||
(_hoveredEvent &&
@@ -236,7 +238,7 @@ export class ReactEventsView extends View {
}
const {
_profilerData: {events},
_profilerData: {reactEvents},
} = this;
const scaleFactor = positioningScaleFactor(
this._intrinsicSize.width,
@@ -250,8 +252,8 @@ export class ReactEventsView extends View {
// Because data ranges may overlap, we want to find the last intersecting item.
// This will always be the one on "top" (the one the user is hovering over).
for (let index = events.length - 1; index >= 0; index--) {
const event = events[index];
for (let index = reactEvents.length - 1; index >= 0; index--) {
const event = reactEvents[index];
const {timestamp} = event;
if (
@@ -44,10 +44,12 @@ export const FLAMECHART_TEXT_PADDING = 3;
// TODO Replace this with "export let" vars
export let COLORS = {
BACKGROUND: '',
FLAME_GRAPH_LABEL: '',
NATIVE_EVENT: '',
NATIVE_EVENT_HOVER: '',
PRIORITY_BACKGROUND: '',
PRIORITY_BORDER: '',
PRIORITY_LABEL: '',
FLAME_GRAPH_LABEL: '',
USER_TIMING: '',
USER_TIMING_HOVER: '',
REACT_IDLE: '',
@@ -81,6 +83,15 @@ export function updateColorsToMatchTheme(): void {
COLORS = {
BACKGROUND: computedStyle.getPropertyValue('--color-background'),
FLAME_GRAPH_LABEL: computedStyle.getPropertyValue(
'--color-scheduling-profiler-flame-graph-label',
),
NATIVE_EVENT: computedStyle.getPropertyValue(
'--color-scheduling-profiler-native-event',
),
NATIVE_EVENT_HOVER: computedStyle.getPropertyValue(
'--color-scheduling-profiler-native-event-hover',
),
PRIORITY_BACKGROUND: computedStyle.getPropertyValue(
'--color-scheduling-profiler-priority-background',
),
@@ -88,9 +99,6 @@ export function updateColorsToMatchTheme(): void {
'--color-scheduling-profiler-priority-border',
),
PRIORITY_LABEL: computedStyle.getPropertyValue('--color-text'),
FLAME_GRAPH_LABEL: computedStyle.getPropertyValue(
'--color-scheduling-profiler-flame-graph-label',
),
USER_TIMING: computedStyle.getPropertyValue(
'--color-scheduling-profiler-user-timing',
),
@@ -8,6 +8,7 @@
*/
export * from './FlamechartView';
export * from './NativeEventsView';
export * from './ReactEventsView';
export * from './ReactMeasuresView';
export * from './TimeAxisMarkersView';
@@ -204,10 +204,11 @@ describe(preprocessData, () => {
expect(preprocessData([cpuProfilerSample, randomSample])).toStrictEqual({
duration: 0.002,
events: [],
flamechart: [],
measures: [],
nativeEvents: [],
otherUserTimingMarks: [],
reactEvents: [],
startTime: 1,
});
});
@@ -258,15 +259,6 @@ describe(preprocessData, () => {
]),
).toStrictEqual({
duration: 0.008,
events: [
{
componentStack: '',
laneLabels: [],
lanes: [9],
timestamp: 0.002,
type: 'schedule-render',
},
],
flamechart: [],
measures: [
{
@@ -306,7 +298,17 @@ describe(preprocessData, () => {
type: 'layout-effects',
},
],
nativeEvents: [],
otherUserTimingMarks: [],
reactEvents: [
{
componentStack: '',
laneLabels: [],
lanes: [9],
timestamp: 0.002,
type: 'schedule-render',
},
],
startTime: 1,
});
});
@@ -320,15 +322,6 @@ describe(preprocessData, () => {
const userTimingData = createUserTimingData(clearedMarks);
expect(preprocessData(userTimingData)).toStrictEqual({
duration: 0.011,
events: [
{
componentStack: '',
laneLabels: ['Sync'],
lanes: [0],
timestamp: 0.005,
type: 'schedule-render',
},
],
flamechart: [],
measures: [
{
@@ -368,6 +361,7 @@ describe(preprocessData, () => {
type: 'layout-effects',
},
],
nativeEvents: [],
otherUserTimingMarks: [
{
name: '__v3',
@@ -378,6 +372,15 @@ describe(preprocessData, () => {
timestamp: 0.004,
},
],
reactEvents: [
{
componentStack: '',
laneLabels: ['Sync'],
lanes: [0],
timestamp: 0.005,
type: 'schedule-render',
},
],
startTime: 1,
});
});
@@ -400,24 +403,6 @@ describe(preprocessData, () => {
const userTimingData = createUserTimingData(clearedMarks);
expect(preprocessData(userTimingData)).toStrictEqual({
duration: 0.022,
events: [
{
componentStack: '',
laneLabels: ['Default'],
lanes: [4],
timestamp: 0.005,
type: 'schedule-render',
},
{
componentName: 'App',
componentStack: '',
isCascading: false,
laneLabels: ['Default'],
lanes: [4],
timestamp: 0.013,
type: 'schedule-state-update',
},
],
flamechart: [],
measures: [
{
@@ -511,6 +496,7 @@ describe(preprocessData, () => {
type: 'passive-effects',
},
],
nativeEvents: [],
otherUserTimingMarks: [
{
name: '__v3',
@@ -521,6 +507,24 @@ describe(preprocessData, () => {
timestamp: 0.004,
},
],
reactEvents: [
{
componentStack: '',
laneLabels: ['Default'],
lanes: [4],
timestamp: 0.005,
type: 'schedule-render',
},
{
componentName: 'App',
componentStack: '',
isCascading: false,
laneLabels: ['Default'],
lanes: [4],
timestamp: 0.013,
type: 'schedule-state-update',
},
],
startTime: 1,
});
});
@@ -161,247 +161,275 @@ function processTimelineEvent(
/** Intermediate processor state. May be mutated. */
state: ProcessorState,
) {
const {cat, name, ts, ph} = event;
if (cat !== 'blink.user_timing') {
return;
}
const {args, cat, name, ts, ph} = event;
switch (cat) {
case 'devtools.timeline':
if (name === 'EventDispatch') {
const type = args.data.type;
const startTime = (ts - currentProfilerData.startTime) / 1000;
if (type.startsWith('react-')) {
const stackTrace = args.data.stackTrace;
if (stackTrace) {
const topFrame = stackTrace[stackTrace.length - 1];
if (topFrame.url.includes('node_modules/react-dom')) {
// Filter out fake React events dispatched by invokeGuardedCallbackDev.
return;
}
}
}
// React Events - schedule
if (name.startsWith('--schedule-render-')) {
const [laneBitmaskString, laneLabels, ...splitComponentStack] = name
.substr(18)
.split('-');
currentProfilerData.events.push({
type: 'schedule-render',
lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
laneLabels: laneLabels ? laneLabels.split(',') : [],
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
});
} else if (name.startsWith('--schedule-forced-update-')) {
const [
laneBitmaskString,
laneLabels,
componentName,
...splitComponentStack
] = name.substr(25).split('-');
const isCascading = !!state.measureStack.find(
({type}) => type === 'commit',
);
currentProfilerData.events.push({
type: 'schedule-force-update',
lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
laneLabels: laneLabels ? laneLabels.split(',') : [],
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
isCascading,
});
} else if (name.startsWith('--schedule-state-update-')) {
const [
laneBitmaskString,
laneLabels,
componentName,
...splitComponentStack
] = name.substr(24).split('-');
const isCascading = !!state.measureStack.find(
({type}) => type === 'commit',
);
currentProfilerData.events.push({
type: 'schedule-state-update',
lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
laneLabels: laneLabels ? laneLabels.split(',') : [],
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
isCascading,
});
} // eslint-disable-line brace-style
const startTime = (ts - currentProfilerData.startTime) / 1000;
const duration = event.dur / 1000;
// React Events - suspense
else if (name.startsWith('--suspense-suspend-')) {
const [id, componentName, ...splitComponentStack] = name
.substr(19)
.split('-');
currentProfilerData.events.push({
type: 'suspense-suspend',
id,
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
});
} else if (name.startsWith('--suspense-resolved-')) {
const [id, componentName, ...splitComponentStack] = name
.substr(20)
.split('-');
currentProfilerData.events.push({
type: 'suspense-resolved',
id,
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
});
} else if (name.startsWith('--suspense-rejected-')) {
const [id, componentName, ...splitComponentStack] = name
.substr(20)
.split('-');
currentProfilerData.events.push({
type: 'suspense-rejected',
id,
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
});
} // eslint-disable-line brace-style
// TODO (scheduling profiler) Should we filter out certain event types?
currentProfilerData.nativeEvents.push({
duration,
timestamp: startTime,
type,
});
}
break;
case 'blink.user_timing':
const startTime = (ts - currentProfilerData.startTime) / 1000;
// React Measures - render
else if (name.startsWith('--render-start-')) {
if (state.nextRenderShouldGenerateNewBatchID) {
state.nextRenderShouldGenerateNewBatchID = false;
state.batchUID = ((state.uidCounter++: any): BatchUID);
}
const [laneBitmaskString, laneLabels] = name.substr(15).split('-');
const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
throwIfIncomplete('render', state.measureStack);
if (getLastType(state.measureStack) !== 'render-idle') {
markWorkStarted(
'render-idle',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
}
markWorkStarted(
'render',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
} else if (
name.startsWith('--render-stop') ||
name.startsWith('--render-yield')
) {
markWorkCompleted(
'render',
startTime,
currentProfilerData,
state.measureStack,
);
} else if (name.startsWith('--render-cancel')) {
state.nextRenderShouldGenerateNewBatchID = true;
markWorkCompleted(
'render',
startTime,
currentProfilerData,
state.measureStack,
);
markWorkCompleted(
'render-idle',
startTime,
currentProfilerData,
state.measureStack,
);
} // eslint-disable-line brace-style
// React Events - schedule
if (name.startsWith('--schedule-render-')) {
const [
laneBitmaskString,
laneLabels,
...splitComponentStack
] = name.substr(18).split('-');
currentProfilerData.reactEvents.push({
type: 'schedule-render',
lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
laneLabels: laneLabels ? laneLabels.split(',') : [],
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
});
} else if (name.startsWith('--schedule-forced-update-')) {
const [
laneBitmaskString,
laneLabels,
componentName,
...splitComponentStack
] = name.substr(25).split('-');
const isCascading = !!state.measureStack.find(
({type}) => type === 'commit',
);
currentProfilerData.reactEvents.push({
type: 'schedule-force-update',
lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
laneLabels: laneLabels ? laneLabels.split(',') : [],
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
isCascading,
});
} else if (name.startsWith('--schedule-state-update-')) {
const [
laneBitmaskString,
laneLabels,
componentName,
...splitComponentStack
] = name.substr(24).split('-');
const isCascading = !!state.measureStack.find(
({type}) => type === 'commit',
);
currentProfilerData.reactEvents.push({
type: 'schedule-state-update',
lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
laneLabels: laneLabels ? laneLabels.split(',') : [],
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
isCascading,
});
} // eslint-disable-line brace-style
// React Measures - commits
else if (name.startsWith('--commit-start-')) {
state.nextRenderShouldGenerateNewBatchID = true;
const [laneBitmaskString, laneLabels] = name.substr(15).split('-');
const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
markWorkStarted(
'commit',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
} else if (name.startsWith('--commit-stop')) {
markWorkCompleted(
'commit',
startTime,
currentProfilerData,
state.measureStack,
);
markWorkCompleted(
'render-idle',
startTime,
currentProfilerData,
state.measureStack,
);
} // eslint-disable-line brace-style
// React Events - suspense
else if (name.startsWith('--suspense-suspend-')) {
const [id, componentName, ...splitComponentStack] = name
.substr(19)
.split('-');
currentProfilerData.reactEvents.push({
type: 'suspense-suspend',
id,
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
});
} else if (name.startsWith('--suspense-resolved-')) {
const [id, componentName, ...splitComponentStack] = name
.substr(20)
.split('-');
currentProfilerData.reactEvents.push({
type: 'suspense-resolved',
id,
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
});
} else if (name.startsWith('--suspense-rejected-')) {
const [id, componentName, ...splitComponentStack] = name
.substr(20)
.split('-');
currentProfilerData.reactEvents.push({
type: 'suspense-rejected',
id,
componentName,
componentStack: splitComponentStack.join('-'),
timestamp: startTime,
});
} // eslint-disable-line brace-style
// React Measures - layout effects
else if (name.startsWith('--layout-effects-start-')) {
const [laneBitmaskString, laneLabels] = name.substr(23).split('-');
const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
markWorkStarted(
'layout-effects',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
} else if (name.startsWith('--layout-effects-stop')) {
markWorkCompleted(
'layout-effects',
startTime,
currentProfilerData,
state.measureStack,
);
} // eslint-disable-line brace-style
// React Measures - render
else if (name.startsWith('--render-start-')) {
if (state.nextRenderShouldGenerateNewBatchID) {
state.nextRenderShouldGenerateNewBatchID = false;
state.batchUID = ((state.uidCounter++: any): BatchUID);
}
const [laneBitmaskString, laneLabels] = name.substr(15).split('-');
const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
throwIfIncomplete('render', state.measureStack);
if (getLastType(state.measureStack) !== 'render-idle') {
markWorkStarted(
'render-idle',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
}
markWorkStarted(
'render',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
} else if (
name.startsWith('--render-stop') ||
name.startsWith('--render-yield')
) {
markWorkCompleted(
'render',
startTime,
currentProfilerData,
state.measureStack,
);
} else if (name.startsWith('--render-cancel')) {
state.nextRenderShouldGenerateNewBatchID = true;
markWorkCompleted(
'render',
startTime,
currentProfilerData,
state.measureStack,
);
markWorkCompleted(
'render-idle',
startTime,
currentProfilerData,
state.measureStack,
);
} // eslint-disable-line brace-style
// React Measures - passive effects
else if (name.startsWith('--passive-effects-start-')) {
const [laneBitmaskString, laneLabels] = name.substr(24).split('-');
const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
markWorkStarted(
'passive-effects',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
} else if (name.startsWith('--passive-effects-stop')) {
markWorkCompleted(
'passive-effects',
startTime,
currentProfilerData,
state.measureStack,
);
} // eslint-disable-line brace-style
// React Measures - commits
else if (name.startsWith('--commit-start-')) {
state.nextRenderShouldGenerateNewBatchID = true;
const [laneBitmaskString, laneLabels] = name.substr(15).split('-');
const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
markWorkStarted(
'commit',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
} else if (name.startsWith('--commit-stop')) {
markWorkCompleted(
'commit',
startTime,
currentProfilerData,
state.measureStack,
);
markWorkCompleted(
'render-idle',
startTime,
currentProfilerData,
state.measureStack,
);
} // eslint-disable-line brace-style
// Other user timing marks/measures
else if (ph === 'R' || ph === 'n') {
// User Timing mark
currentProfilerData.otherUserTimingMarks.push({
name,
timestamp: startTime,
});
} else if (ph === 'b') {
// TODO: Begin user timing measure
} else if (ph === 'e') {
// TODO: End user timing measure
} else if (ph === 'i' || ph === 'I') {
// Instant events.
// Note that the capital "I" is a deprecated value that exists in Chrome Canary traces.
} // eslint-disable-line brace-style
// React Measures - layout effects
else if (name.startsWith('--layout-effects-start-')) {
const [laneBitmaskString, laneLabels] = name.substr(23).split('-');
const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
markWorkStarted(
'layout-effects',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
} else if (name.startsWith('--layout-effects-stop')) {
markWorkCompleted(
'layout-effects',
startTime,
currentProfilerData,
state.measureStack,
);
} // eslint-disable-line brace-style
// Unrecognized event
else {
throw new InvalidProfileError(
`Unrecognized event ${JSON.stringify(
event,
)}! This is likely a bug in this profiler tool.`,
);
// React Measures - passive effects
else if (name.startsWith('--passive-effects-start-')) {
const [laneBitmaskString, laneLabels] = name.substr(24).split('-');
const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
markWorkStarted(
'passive-effects',
startTime,
lanes,
laneLabels ? laneLabels.split(',') : [],
currentProfilerData,
state,
);
} else if (name.startsWith('--passive-effects-stop')) {
markWorkCompleted(
'passive-effects',
startTime,
currentProfilerData,
state.measureStack,
);
} // eslint-disable-line brace-style
// Other user timing marks/measures
else if (ph === 'R' || ph === 'n') {
// User Timing mark
currentProfilerData.otherUserTimingMarks.push({
name,
timestamp: startTime,
});
} else if (ph === 'b') {
// TODO: Begin user timing measure
} else if (ph === 'e') {
// TODO: End user timing measure
} else if (ph === 'i' || ph === 'I') {
// Instant events.
// Note that the capital "I" is a deprecated value that exists in Chrome Canary traces.
} // eslint-disable-line brace-style
// Unrecognized event
else {
throw new InvalidProfileError(
`Unrecognized event ${JSON.stringify(
event,
)}! This is likely a bug in this profiler tool.`,
);
}
break;
}
}
@@ -447,7 +475,8 @@ export default function preprocessData(
const profilerData: ReactProfilerData = {
startTime: 0,
duration: 0,
events: [],
nativeEvents: [],
reactEvents: [],
measures: [],
flamechart,
otherUserTimingMarks: [],
+10 -2
View File
@@ -21,6 +21,12 @@ export type Milliseconds = number;
export type ReactLane = number;
export type NativeEvent = {|
+duration: Milliseconds,
+timestamp: Milliseconds,
+type: string,
|};
type BaseReactEvent = {|
+componentName?: string,
+componentStack?: string,
@@ -122,14 +128,16 @@ export type Flamechart = FlamechartStackLayer[];
export type ReactProfilerData = {|
startTime: number,
duration: number,
events: ReactEvent[],
nativeEvents: NativeEvent[],
reactEvents: ReactEvent[],
measures: ReactMeasure[],
flamechart: Flamechart,
otherUserTimingMarks: UserTimingMark[],
|};
export type ReactHoverContextInfo = {|
event: ReactEvent | null,
nativeEvent: NativeEvent | null,
reactEvent: ReactEvent | null,
measure: ReactMeasure | null,
data: $ReadOnly<ReactProfilerData> | null,
flamechartStackFrame: FlamechartStackFrame | null,
@@ -421,6 +421,16 @@ export function updateThemeVariables(
'color-scheduling-profiler-flame-graph-label',
documentElements,
);
updateStyleHelper(
theme,
'color-scheduling-profiler-native-event',
documentElements,
);
updateStyleHelper(
theme,
'color-scheduling-profiler-native-event-hover',
documentElements,
);
updateStyleHelper(
theme,
'color-selected-tree-highlight-active',
@@ -79,8 +79,10 @@
--light-color-record-inactive: #0088fa;
--light-color-resize-bar: #cccccc;
--light-color-scheduling-profiler-flame-graph-label: #000000;
--light-color-scheduling-profiler-priority-background: #ededf0;
--light-color-scheduling-profiler-priority-border: #d7d7db;
--light-color-scheduling-profiler-native-event: #aaaaaa;
--light-color-scheduling-profiler-native-event-hover: #888888;
--light-color-scheduling-profiler-priority-background: #f6f6f6;
--light-color-scheduling-profiler-priority-border: #eeeeee;
--light-color-scheduling-profiler-user-timing: #c9cacd;
--light-color-scheduling-profiler-user-timing-hover:#93959a;
--light-color-scheduling-profiler-react-idle: #edf6ff;
@@ -199,6 +201,8 @@
--dark-color-record-inactive: #61dafb;
--dark-color-resize-bar: #3d424a;
--dark-color-scheduling-profiler-flame-graph-label: #000000;
--dark-color-scheduling-profiler-native-event: #aaaaaa;
--dark-color-scheduling-profiler-native-event-hover: #888888;
--dark-color-scheduling-profiler-priority-background: #1d2129;
--dark-color-scheduling-profiler-priority-border: #282c34;
--dark-color-scheduling-profiler-user-timing: #c9cacd;