mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Added Ranked chart data generation; fixed some logic errors in backend tree base duration tracking
This commit is contained in:
@@ -46,6 +46,9 @@ export default class Agent extends EventEmitter {
|
||||
addBridge(bridge: Bridge) {
|
||||
this._bridge = bridge;
|
||||
|
||||
// TODO (profiling) Component commits
|
||||
// TODO (profiling) Interactions
|
||||
|
||||
bridge.addListener('getCommitDetails', this.getCommitDetails);
|
||||
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
|
||||
bridge.addListener('getProfilingSummary', this.getProfilingSummary);
|
||||
|
||||
@@ -693,30 +693,32 @@ export function attach(
|
||||
) {
|
||||
debug('enqueueUpdateIfNecessary()', fiber);
|
||||
|
||||
if (isProfiling) {
|
||||
if (haveProfilerTimesChanged(fiber.alternate, fiber)) {
|
||||
const id = getFiberID(getPrimaryFiber(fiber));
|
||||
const { actualDuration, treeBaseDuration } = fiber;
|
||||
const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
|
||||
if (isProfilingSupported) {
|
||||
const id = getFiberID(getPrimaryFiber(fiber));
|
||||
const { actualDuration, treeBaseDuration } = fiber;
|
||||
|
||||
const operation = new Uint32Array(3);
|
||||
operation[0] = TREE_OPERATION_UPDATE_TREE_BASE_DURATION;
|
||||
operation[1] = id;
|
||||
operation[2] = treeBaseDuration;
|
||||
addOperation(operation);
|
||||
idToTreeBaseDurationMap.set(id, fiber.treeBaseDuration);
|
||||
|
||||
idToTreeBaseDurationMap.set(id, treeBaseDuration);
|
||||
if (isProfiling) {
|
||||
if (treeBaseDuration !== fiber.alternate.treeBaseDuration) {
|
||||
const operation = new Uint32Array(3);
|
||||
operation[0] = TREE_OPERATION_UPDATE_TREE_BASE_DURATION;
|
||||
operation[1] = getFiberID(getPrimaryFiber(fiber));
|
||||
operation[2] = treeBaseDuration;
|
||||
addOperation(operation);
|
||||
}
|
||||
|
||||
if (actualDuration > 0) {
|
||||
// If profiling is active, store durations for elements that were rendered during the commit.
|
||||
const metadata = ((currentCommitProfilingMetadata: any): CommitProfilingData);
|
||||
metadata.committedFibers.push({
|
||||
id,
|
||||
actualDuration,
|
||||
});
|
||||
metadata.maxActualDuration = Math.max(
|
||||
metadata.maxActualDuration,
|
||||
actualDuration
|
||||
);
|
||||
if (haveProfilerTimesChanged(fiber.alternate, fiber)) {
|
||||
if (actualDuration > 0) {
|
||||
// If profiling is active, store durations for elements that were rendered during the commit.
|
||||
const metadata = ((currentCommitProfilingMetadata: any): CommitProfilingData);
|
||||
metadata.actualDurations.push(id, actualDuration);
|
||||
metadata.maxActualDuration = Math.max(
|
||||
metadata.maxActualDuration,
|
||||
actualDuration
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -880,7 +882,7 @@ export function attach(
|
||||
// If profiling is active, store commit time and duration, and the current interactions.
|
||||
// The frontend may request this information after profiling has stopped.
|
||||
currentCommitProfilingMetadata = {
|
||||
committedFibers: [],
|
||||
actualDurations: [],
|
||||
commitTime: performance.now() - profilingStartTime,
|
||||
interactions: Array.from(root.memoizedInteractions).map(
|
||||
(interaction: Interaction) => ({
|
||||
@@ -1377,13 +1379,8 @@ export function attach(
|
||||
}
|
||||
}
|
||||
|
||||
type CommittedFiber = {|
|
||||
actualDuration: number,
|
||||
id: number,
|
||||
|};
|
||||
|
||||
type CommitProfilingData = {|
|
||||
committedFibers: Array<CommittedFiber>,
|
||||
actualDurations: Array<number>,
|
||||
commitTime: number,
|
||||
interactions: Array<Interaction>,
|
||||
maxActualDuration: number,
|
||||
@@ -1410,7 +1407,7 @@ export function attach(
|
||||
return {
|
||||
commitIndex,
|
||||
interactions: commitProfilingData.interactions,
|
||||
committedFibers: commitProfilingData.committedFibers,
|
||||
actualDurations: commitProfilingData.actualDurations,
|
||||
rootID,
|
||||
};
|
||||
}
|
||||
@@ -1419,7 +1416,7 @@ export function attach(
|
||||
return {
|
||||
commitIndex,
|
||||
interactions: [],
|
||||
committedFibers: [],
|
||||
actualDurations: [],
|
||||
rootID,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -56,12 +56,9 @@ export type Interaction = {|
|
||||
|};
|
||||
|
||||
export type CommitDetails = {|
|
||||
actualDurations: Array<number>,
|
||||
commitIndex: number,
|
||||
interactions: Array<Interaction>,
|
||||
committedFibers: Array<{|
|
||||
actualDuration: number,
|
||||
id: number,
|
||||
|}>,
|
||||
rootID: number,
|
||||
|};
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export default class ProfilingCache {
|
||||
if (!store.profilingOperations.has(rootID)) {
|
||||
// If no profiling data was recorded for this root, skip the round trip.
|
||||
resolve({
|
||||
committedFibers: [],
|
||||
actualDurations: new Map(),
|
||||
interactions: [],
|
||||
});
|
||||
} else {
|
||||
@@ -139,7 +139,7 @@ export default class ProfilingCache {
|
||||
|
||||
onCommitDetails = ({
|
||||
commitIndex,
|
||||
committedFibers,
|
||||
actualDurations,
|
||||
interactions,
|
||||
rootID,
|
||||
}: CommitDetailsBackend) => {
|
||||
@@ -148,8 +148,13 @@ export default class ProfilingCache {
|
||||
if (resolve != null) {
|
||||
this._pendingCommitDetailsMap.delete(key);
|
||||
|
||||
const actualDurationsMap = new Map();
|
||||
for (let i = 0; i < actualDurations.length; i += 2) {
|
||||
actualDurationsMap.set(actualDurations[i], actualDurations[i + 1]);
|
||||
}
|
||||
|
||||
resolve({
|
||||
committedFibers,
|
||||
actualDurations: actualDurationsMap,
|
||||
interactions,
|
||||
});
|
||||
}
|
||||
@@ -166,7 +171,7 @@ export default class ProfilingCache {
|
||||
if (resolve != null) {
|
||||
this._pendingProfileSummaryMap.delete(rootID);
|
||||
const initialTreeBaseDurationsMap = new Map();
|
||||
for (let i = 0; i < initialTreeBaseDurations.length; i++) {
|
||||
for (let i = 0; i < initialTreeBaseDurations.length; i += 2) {
|
||||
initialTreeBaseDurationsMap.set(
|
||||
initialTreeBaseDurations[i],
|
||||
initialTreeBaseDurations[i + 1]
|
||||
|
||||
@@ -575,33 +575,37 @@ export default class Store extends EventEmitter {
|
||||
|
||||
// DEBUG
|
||||
__printTree = () => {
|
||||
console.group('__printTree()');
|
||||
this._roots.forEach((rootID: number) => {
|
||||
const printElement = (id: number) => {
|
||||
const element = ((this._idToElement.get(id): any): Element);
|
||||
console.log(
|
||||
`${'•'.repeat(element.depth)}${element.id}:${element.displayName ||
|
||||
''}${element.key ? `key:"${element.key}"` : ''} (${element.weight})`
|
||||
);
|
||||
element.children.forEach(printElement);
|
||||
};
|
||||
const root = ((this._idToElement.get(rootID): any): Element);
|
||||
console.group(`${rootID}:root (${root.weight})`);
|
||||
root.children.forEach(printElement);
|
||||
console.groupEnd();
|
||||
});
|
||||
console.group(`List of ${this.numElements} elements`);
|
||||
for (let i = 0; i < this.numElements; i++) {
|
||||
//if (i === 4) { debugger }
|
||||
const element = this.getElementAtIndex(i);
|
||||
if (element != null) {
|
||||
console.log(
|
||||
`${'•'.repeat(element.depth)}${i}: ${element.displayName ||
|
||||
'Unknown'}`
|
||||
);
|
||||
if (__DEBUG__) {
|
||||
console.group('__printTree()');
|
||||
this._roots.forEach((rootID: number) => {
|
||||
const printElement = (id: number) => {
|
||||
const element = ((this._idToElement.get(id): any): Element);
|
||||
console.log(
|
||||
`${'•'.repeat(element.depth)}${element.id}:${element.displayName ||
|
||||
''} ${element.key ? `key:"${element.key}"` : ''} (${
|
||||
element.weight
|
||||
})`
|
||||
);
|
||||
element.children.forEach(printElement);
|
||||
};
|
||||
const root = ((this._idToElement.get(rootID): any): Element);
|
||||
console.group(`${rootID}:root (${root.weight})`);
|
||||
root.children.forEach(printElement);
|
||||
console.groupEnd();
|
||||
});
|
||||
console.group(`List of ${this.numElements} elements`);
|
||||
for (let i = 0; i < this.numElements; i++) {
|
||||
//if (i === 4) { debugger }
|
||||
const element = this.getElementAtIndex(i);
|
||||
if (element != null) {
|
||||
console.log(
|
||||
`${'•'.repeat(element.depth)}${i}: ${element.displayName ||
|
||||
'Unknown'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
console.groupEnd();
|
||||
console.groupEnd();
|
||||
}
|
||||
console.groupEnd();
|
||||
console.groupEnd();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { ProfilerContext } from './ProfilerContext';
|
||||
import { calculateSelfDuration } from './utils';
|
||||
import { StoreContext } from '../context';
|
||||
|
||||
import styles from './CommitRanked.css';
|
||||
|
||||
import type { CommitDetails, CommitTree, Node } from './types';
|
||||
|
||||
export default function CommitRanked(_: {||}) {
|
||||
const { rendererID, rootID, selectedCommitIndex } = useContext(
|
||||
ProfilerContext
|
||||
@@ -31,5 +34,57 @@ export default function CommitRanked(_: {||}) {
|
||||
rootID: ((rootID: any): number),
|
||||
});
|
||||
|
||||
const chartData = generateChartData(commitTree, commitDetails);
|
||||
|
||||
return 'Coming soon: Ranked';
|
||||
}
|
||||
|
||||
type ChartNode = {|
|
||||
id: number,
|
||||
label: string,
|
||||
name: string,
|
||||
title: string,
|
||||
value: number,
|
||||
|};
|
||||
|
||||
type ChartData = {|
|
||||
maxValue: number,
|
||||
nodes: Array<ChartNode>,
|
||||
|};
|
||||
|
||||
const generateChartData = (
|
||||
commitTree: CommitTree,
|
||||
commitDetails: CommitDetails
|
||||
): ChartData => {
|
||||
const { nodes } = commitTree;
|
||||
|
||||
let maxSelfDuration = 0;
|
||||
|
||||
const chartNodes: Array<ChartNode> = [];
|
||||
commitDetails.actualDurations.forEach((actualDuration, id) => {
|
||||
const node = ((nodes.get(id): any): Node);
|
||||
|
||||
// Don't show the root node in this chart.
|
||||
if (node.parentID === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selfDuration = calculateSelfDuration(id, commitTree, commitDetails);
|
||||
maxSelfDuration = Math.max(maxSelfDuration, selfDuration);
|
||||
|
||||
const name = node.displayName || 'Unknown';
|
||||
const label = `${name} (${selfDuration.toFixed(1)}ms)`;
|
||||
chartNodes.push({
|
||||
id,
|
||||
label,
|
||||
name,
|
||||
title: label,
|
||||
value: selfDuration,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
maxValue: maxSelfDuration,
|
||||
nodes: chartNodes.sort((a, b) => b.value - a.value),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
__DEBUG__,
|
||||
TREE_OPERATION_ADD,
|
||||
TREE_OPERATION_REMOVE,
|
||||
TREE_OPERATION_RESET_CHILDREN,
|
||||
@@ -17,6 +18,17 @@ import type {
|
||||
ProfilingSummary as ProfilingSummaryFrontend,
|
||||
} from 'src/devtools/views/Profiler/types';
|
||||
|
||||
const debug = (methodName, ...args) => {
|
||||
if (__DEBUG__) {
|
||||
console.log(
|
||||
`%cCommitTreeBuilder %c${methodName}`,
|
||||
'color: pink; font-weight: bold;',
|
||||
'font-weight: bold;',
|
||||
...args
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const rootToCommitTreeMap: Map<number, Array<CommitTree>> = new Map();
|
||||
|
||||
export function getCommitTree({
|
||||
@@ -49,40 +61,29 @@ export function getCommitTree({
|
||||
// If this is the very first commit, start with the cached snapshot and apply the first mutation.
|
||||
// Otherwise load (or generate) the previous commit and append a mutation to it.
|
||||
if (commitIndex === 0) {
|
||||
const initialCommitTree = {
|
||||
nodes: new Map(),
|
||||
rootID,
|
||||
};
|
||||
const nodes = new Map();
|
||||
|
||||
// Construct the initial tree.
|
||||
const queue: Array<number> = [rootID];
|
||||
while (queue.length > 0) {
|
||||
const currentID = queue.pop();
|
||||
const currentNode = ((store.profilingSnapshot.get(
|
||||
currentID
|
||||
): any): Node);
|
||||
|
||||
initialCommitTree.nodes.set(currentID, {
|
||||
id: currentID,
|
||||
children: currentNode.children,
|
||||
displayName: currentNode.displayName,
|
||||
key: currentNode.key,
|
||||
parentID: 0,
|
||||
treeBaseDuration: ((profilingSummary.initialTreeBaseDurations.get(
|
||||
currentID
|
||||
): any): number),
|
||||
});
|
||||
|
||||
queue.push(...currentNode.children);
|
||||
}
|
||||
recursivelyIniitliazeTree(
|
||||
rootID,
|
||||
0,
|
||||
nodes,
|
||||
profilingSummary.initialTreeBaseDurations,
|
||||
store
|
||||
);
|
||||
|
||||
// Mutate the tree
|
||||
const commitOperations = store.profilingOperations.get(rootID);
|
||||
if (commitOperations != null && commitIndex < commitOperations.length) {
|
||||
const commitTree = updateTree(
|
||||
initialCommitTree,
|
||||
{ nodes, rootID },
|
||||
commitOperations[commitIndex]
|
||||
);
|
||||
|
||||
if (__DEBUG__) {
|
||||
__printTree(commitTree);
|
||||
}
|
||||
|
||||
commitTrees.push(commitTree);
|
||||
return commitTree;
|
||||
}
|
||||
@@ -100,6 +101,11 @@ export function getCommitTree({
|
||||
previousCommitTree,
|
||||
commitOperations[commitIndex]
|
||||
);
|
||||
|
||||
if (__DEBUG__) {
|
||||
__printTree(commitTree);
|
||||
}
|
||||
|
||||
commitTrees.push(commitTree);
|
||||
return commitTree;
|
||||
}
|
||||
@@ -113,6 +119,35 @@ export function getCommitTree({
|
||||
};
|
||||
}
|
||||
|
||||
function recursivelyIniitliazeTree(
|
||||
id: number,
|
||||
parentID: number,
|
||||
nodes: Map<number, Node>,
|
||||
initialTreeBaseDurations: Map<number, number>,
|
||||
store: Store
|
||||
): void {
|
||||
const node = ((store.profilingSnapshot.get(id): any): Node);
|
||||
|
||||
nodes.set(id, {
|
||||
id,
|
||||
children: node.children,
|
||||
displayName: node.displayName,
|
||||
key: node.key,
|
||||
parentID,
|
||||
treeBaseDuration: ((initialTreeBaseDurations.get(id): any): number),
|
||||
});
|
||||
|
||||
node.children.forEach(childID =>
|
||||
recursivelyIniitliazeTree(
|
||||
childID,
|
||||
id,
|
||||
nodes,
|
||||
initialTreeBaseDurations,
|
||||
store
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function updateTree(
|
||||
commitTree: CommitTree,
|
||||
operations: Uint32Array
|
||||
@@ -170,6 +205,11 @@ function updateTree(
|
||||
parentNode = ((nodes.get(parentID): any): Node);
|
||||
parentNode.children = parentNode.children.concat(id);
|
||||
|
||||
debug(
|
||||
'Add',
|
||||
`fiber ${id} (${displayName || 'null'}) as child of ${parentID}`
|
||||
);
|
||||
|
||||
const node: Node = {
|
||||
children: [],
|
||||
displayName,
|
||||
@@ -197,6 +237,8 @@ function updateTree(
|
||||
if (parentNode == null) {
|
||||
// No-op
|
||||
} else {
|
||||
debug('Remove', `fiber ${id} from parent ${parentID}`);
|
||||
|
||||
parentNode.children = parentNode.children.filter(
|
||||
childID => childID !== id
|
||||
);
|
||||
@@ -212,8 +254,11 @@ function updateTree(
|
||||
|
||||
i = i + 3 + numChildren;
|
||||
|
||||
debug('Re-order', `fiber ${id} children ${children.join(',')}`);
|
||||
|
||||
node = ((nodes.get(id): any): Node);
|
||||
node.children = Array.from(children);
|
||||
|
||||
break;
|
||||
case TREE_OPERATION_UPDATE_TREE_BASE_DURATION:
|
||||
id = operations[i + 1];
|
||||
@@ -221,6 +266,11 @@ function updateTree(
|
||||
node = ((nodes.get(id): any): Node);
|
||||
node.treeBaseDuration = operations[i + 2];
|
||||
|
||||
debug(
|
||||
'Update',
|
||||
`fiber ${id} treeBaseDuration to ${node.treeBaseDuration}`
|
||||
);
|
||||
|
||||
i = i + 3;
|
||||
break;
|
||||
default:
|
||||
@@ -237,3 +287,29 @@ function updateTree(
|
||||
export function invalidateCommitTrees(): void {
|
||||
rootToCommitTreeMap.clear();
|
||||
}
|
||||
|
||||
// DEBUG
|
||||
const __printTree = (commitTree: CommitTree) => {
|
||||
if (__DEBUG__) {
|
||||
const { nodes, rootID } = commitTree;
|
||||
console.group('__printTree()');
|
||||
const queue = [rootID, 0];
|
||||
while (queue.length > 0) {
|
||||
const id = queue.shift();
|
||||
const depth = queue.shift();
|
||||
|
||||
const node = ((nodes.get(id): any): Node);
|
||||
|
||||
console.log(
|
||||
`${'•'.repeat(depth)}${node.id}:${node.displayName || ''} ${
|
||||
node.key ? `key:"${node.key}"` : ''
|
||||
} (${node.treeBaseDuration})`
|
||||
);
|
||||
|
||||
node.children.forEach(childID => {
|
||||
queue.push(childID, depth + 1);
|
||||
});
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,6 +55,8 @@ export default function SnapshotSelector(_: Props) {
|
||||
return null;
|
||||
}, [filteredCommitIndices, selectedCommitIndex]);
|
||||
|
||||
// TODO (profiling) This should be managed by the context controller (reducer).
|
||||
// TODO (profiling) We should also reset the selected index to 0 between profiling sessions.
|
||||
if (selectedFilteredCommitIndex === null) {
|
||||
if (numFilteredCommits > 0) {
|
||||
setSelectedCommitIndex(0);
|
||||
|
||||
@@ -21,11 +21,8 @@ export type Interaction = {|
|
||||
|};
|
||||
|
||||
export type CommitDetails = {|
|
||||
actualDurations: Map<number, number>,
|
||||
interactions: Array<Interaction>,
|
||||
committedFibers: Array<{|
|
||||
actualDuration: number,
|
||||
id: number,
|
||||
|}>,
|
||||
|};
|
||||
|
||||
export type ProfilingSummary = {|
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import type { CommitDetails, CommitTree, Node } from './types';
|
||||
|
||||
const commitGradient = [
|
||||
'var(--color-commit-gradient-0)',
|
||||
'var(--color-commit-gradient-1)',
|
||||
@@ -13,6 +15,30 @@ const commitGradient = [
|
||||
'var(--color-commit-gradient-9)',
|
||||
];
|
||||
|
||||
export const calculateSelfDuration = (
|
||||
id: number,
|
||||
commitTree: CommitTree,
|
||||
commitDetails: CommitDetails
|
||||
): number => {
|
||||
const { actualDurations } = commitDetails;
|
||||
const { nodes } = commitTree;
|
||||
|
||||
if (!actualDurations.has(id)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let selfDuration = ((actualDurations.get(id): any): number);
|
||||
|
||||
const node = ((nodes.get(id): any): Node);
|
||||
node.children.forEach(childID => {
|
||||
if (actualDurations.has(childID)) {
|
||||
selfDuration -= ((actualDurations.get(childID): any): number);
|
||||
}
|
||||
});
|
||||
|
||||
return selfDuration;
|
||||
};
|
||||
|
||||
export const getGradientColor = (value: number) => {
|
||||
const maxIndex = commitGradient.length - 1;
|
||||
let index;
|
||||
|
||||
Reference in New Issue
Block a user