Added Ranked chart data generation; fixed some logic errors in backend tree base duration tracking

This commit is contained in:
Brian Vaughn
2019-03-16 10:52:08 -07:00
parent a93dab7f83
commit 734e4146e7
10 changed files with 255 additions and 93 deletions

View File

@@ -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);

View File

@@ -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,
};
}

View File

@@ -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,
|};

View File

@@ -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]

View File

@@ -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();
};
}

View File

@@ -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),
};
};

View File

@@ -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();
}
};

View File

@@ -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);

View File

@@ -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 = {|

View File

@@ -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;