Skip to content

Commit

Permalink
[Optimize flamechart][1/4] Add flamechart to ReactProfilerData
Browse files Browse the repository at this point in the history
This commit begins a stack of PRs that optimizes our flamechart.
Broadly, we need to replace the use of Speedscope's flamechart types
with our own types that are optimized for our rendering code.

As flamechart was always moved around together with ReactProfilerData,
it makes sense to move flamechart into it. That also necessitates moving
preprocessFlamechart into preprocessData.

This PR works towards #50.

* `yarn flow`: errors present, but no errors in affected code.
* `yarn lint`
* `yarn start`
  • Loading branch information
taneliang committed Jul 28, 2020
1 parent 14920c7 commit b9f310c
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 81 deletions.
23 changes: 5 additions & 18 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// @flow

import type {FlamechartData, ReactProfilerData} from './types';
import type {ReactProfilerData} from './types';

import React, {useState, useCallback} from 'react';
import {unstable_batchedUpdates} from 'react-dom';
import React, {useState} from 'react';

import ImportPage from './ImportPage';
import CanvasPage from './CanvasPage';
Expand All @@ -12,22 +11,10 @@ export default function App() {
const [profilerData, setProfilerData] = useState<ReactProfilerData | null>(
null,
);
const [flamechart, setFlamechart] = useState<FlamechartData | null>(null);

const handleDataImported = useCallback(
(
importedProfilerData: ReactProfilerData,
importedFlamechart: FlamechartData,
) => {
unstable_batchedUpdates(() => {
setProfilerData(importedProfilerData);
setFlamechart(importedFlamechart);
});
},
);
if (profilerData && flamechart) {
return <CanvasPage profilerData={profilerData} flamechart={flamechart} />;
if (profilerData) {
return <CanvasPage profilerData={profilerData} />;
} else {
return <ImportPage onDataImported={handleDataImported} />;
return <ImportPage onDataImported={setProfilerData} />;
}
}
30 changes: 6 additions & 24 deletions src/CanvasPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@ import {ContextMenu, ContextMenuItem, useContextMenu} from './context';

const CONTEXT_MENU_ID = 'canvas';

import type {
FlamechartData,
ReactHoverContextInfo,
ReactProfilerData,
} from './types';
import type {ReactHoverContextInfo, ReactProfilerData} from './types';
import {useCanvasInteraction} from './useCanvasInteraction';
import {
FlamegraphView,
Expand All @@ -48,28 +44,21 @@ import {

type ContextMenuContextData = {|
data: ReactProfilerData,
flamechart: FlamechartData,
hoveredEvent: ReactHoverContextInfo | null,
|};

type Props = {|
profilerData: ReactProfilerData,
flamechart: FlamechartData,
|};

function CanvasPage({profilerData, flamechart}: Props) {
function CanvasPage({profilerData}: Props) {
return (
<div
className={styles.CanvasPage}
style={{backgroundColor: COLORS.BACKGROUND}}>
<AutoSizer>
{({height, width}: {height: number, width: number}) => (
<AutoSizedCanvas
data={profilerData}
flamechart={flamechart}
height={height}
width={width}
/>
<AutoSizedCanvas data={profilerData} height={height} width={width} />
)}
</AutoSizer>
</div>
Expand Down Expand Up @@ -104,17 +93,11 @@ const copySummary = (data, measure) => {

type AutoSizedCanvasProps = {|
data: ReactProfilerData,
flamechart: FlamechartData,
height: number,
width: number,
|};

function AutoSizedCanvas({
data,
flamechart,
height,
width,
}: AutoSizedCanvasProps) {
function AutoSizedCanvas({data, height, width}: AutoSizedCanvasProps) {
const canvasRef = useRef<HTMLCanvasElement | null>(null);

const [isContextMenuShown, setIsContextMenuShown] = useState<boolean>(false);
Expand Down Expand Up @@ -156,7 +139,7 @@ function AutoSizedCanvas({
const flamegraphView = new FlamegraphView(
surfaceRef.current,
{origin: zeroPoint, size: {width, height}},
flamechart,
data.flamechart,
data,
);
flamegraphViewRef.current = flamegraphView;
Expand Down Expand Up @@ -194,7 +177,7 @@ function AutoSizedCanvas({
);

surfaceRef.current.rootView = rootViewRef.current;
}, [data, flamechart, setHoveredEvent]);
}, [data, setHoveredEvent]);

useLayoutEffect(() => {
if (canvasRef.current) {
Expand Down Expand Up @@ -229,7 +212,6 @@ function AutoSizedCanvas({
useContextMenu<ContextMenuContextData>({
data: {
data,
flamechart,
hoveredEvent,
},
id: CONTEXT_MENU_ID,
Expand Down
17 changes: 3 additions & 14 deletions src/ImportPage.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,27 @@
// @flow

import type {TimelineEvent} from '@elg/speedscope';
import type {FlamechartData, ReactProfilerData} from './types';
import type {ReactProfilerData} from './types';

import React, {useEffect, useCallback, useRef} from 'react';
import profilerBrowser from './assets/profilerBrowser.png';
import style from './ImportPage.css';

import preprocessData from './util/preprocessData';
import preprocessFlamechart from './util/preprocessFlamechart';
import {readInputData} from './util/readInputData';

// TODO: Use for dev only, switch to import file after
import JSON_PATH from 'url:../static/perfprofilev2.json';

type Props = {|
onDataImported: (
profilerData: ReactProfilerData,
flamechart: FlamechartData,
) => void,
onDataImported: (profilerData: ReactProfilerData) => void,
|};

export default function ImportPage({onDataImported}: Props) {
const processTimeline = useCallback(
(events: TimelineEvent[]) => {
// Filter null entries and sort by timestamp.
// I would not expect to have to do either of this,
// but some of the data being passed in requires it.
events = events.filter(Boolean).sort((a, b) => (a.ts > b.ts ? 1 : -1));

if (events.length > 0) {
const processedData = preprocessData(events);
const processedFlamechart = preprocessFlamechart(events);
onDataImported(processedData, processedFlamechart);
onDataImported(preprocessData(events));
}
},
[onDataImported],
Expand Down
5 changes: 3 additions & 2 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,14 @@ export type ReactMeasure = {|
+depth: number,
|};

export type FlamechartData = Flamechart;

export type ReactProfilerData = {|
startTime: number,
duration: number,
events: ReactEvent[],
measures: ReactMeasure[],
flamechart: FlamechartData,
|};

export type ReactHoverContextInfo = {|
Expand All @@ -100,5 +103,3 @@ export type ReactHoverContextInfo = {|
data: $ReadOnly<ReactProfilerData> | null,
flamechartNode: FlamechartFrame | null,
|};

export type FlamechartData = Flamechart;
22 changes: 20 additions & 2 deletions src/util/preprocessData.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// @flow

import {importFromChromeTimeline, Flamechart} from '@elg/speedscope';
import type {TimelineEvent} from '@elg/speedscope';
import type {
Milliseconds,
BatchUID,
FlamechartData,
ReactLane,
ReactMeasureType,
ReactProfilerData,
Expand Down Expand Up @@ -345,18 +347,34 @@ function processTimelineEvent(
}
}

function preprocessFlamechart(rawData: TimelineEvent[]): FlamechartData {
const parsedData = importFromChromeTimeline(rawData, 'react-devtools');
const profile = parsedData.profiles[0]; // TODO: Choose the main CPU thread only
const flamechart = new Flamechart({
getTotalWeight: profile.getTotalWeight.bind(profile),
forEachCall: profile.forEachCall.bind(profile),
formatValue: profile.formatValue.bind(profile),
getColorBucketForFrame: () => 0,
});
return flamechart;
}

export default function preprocessData(
timeline: TimelineEvent[],
): ReactProfilerData {
const profilerData = {
const flamechart = preprocessFlamechart(timeline);

const profilerData: ReactProfilerData = {
startTime: 0,
duration: 0,
events: [],
measures: [],
flamechart,
};

// TODO: Sort `timeline`. JSON Array Format trace events need not be ordered. See:
// Sort `timeline`. JSON Array Format trace events need not be ordered. See:
// https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.f2f0yd51wi15
timeline = timeline.filter(Boolean).sort((a, b) => (a.ts > b.ts ? 1 : -1));

// Events displayed in flamechart have timestamps relative to the profile
// event's startTime. Source: https://github.com/v8/v8/blob/44bd8fd7/src/inspector/js_protocol.json#L1486
Expand Down
21 changes: 0 additions & 21 deletions src/util/preprocessFlamechart.js

This file was deleted.

1 comment on commit b9f310c

@vercel
Copy link

@vercel vercel bot commented on b9f310c Jul 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.