Skip to content

Commit

Permalink
Add new page with overall components statistics aggregated by type
Browse files Browse the repository at this point in the history
  • Loading branch information
lahmatiy committed Aug 17, 2023
1 parent 3319191 commit 644503e
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 2 deletions.
10 changes: 10 additions & 0 deletions src/common/consumer-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ export type FiberTypeDef = {
contexts: TransferFiberContext[] | null;
hooks: FiberTypeHook[];
};
export type FiberTypeStat = {
typeId: number;
typeDef: FiberTypeDef;
displayName: string;
mounts: number;
mountTime: number;
unmounts: number;
updates: number;
updateTime: number;
};
export type FiberChanges = {
props?: TransferPropChange[] | null;
context: FiberContextChange[] | null;
Expand Down
3 changes: 3 additions & 0 deletions src/data/fiber-dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Message } from "common-types";
import {
Commit,
FiberTypeDef,
FiberTypeStat,
LinkedEvent,
MessageFiber,
} from "../common/consumer-types";
Expand All @@ -15,6 +16,7 @@ export function createFiberDataset(events: Message[] = []) {
const commitById = new SubscribeMap<number, Commit>();
const fiberById = new SubscribeMap<number, MessageFiber>();
const fiberTypeDefById = new SubscribeMap<number, FiberTypeDef>();
const fiberTypeStat = new SubscribeMap<number, FiberTypeStat>();
const fibersByTypeId = new SubsetSplit<number, number>();
const fibersByProviderId = new SubsetSplit<number, number>();
const leakedFibers = new Subset<number>();
Expand All @@ -29,6 +31,7 @@ export function createFiberDataset(events: Message[] = []) {
commitById,
fiberById,
fiberTypeDefById,
fiberTypeStat,
fibersByTypeId,
fibersByProviderId,
leakedFibers,
Expand Down
41 changes: 40 additions & 1 deletion src/data/process-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export function processEvents(
commitById,
fiberById,
fiberTypeDefById,
fiberTypeStat,
fibersByTypeId,
fibersByProviderId,
leakedFibers,
Expand Down Expand Up @@ -128,7 +129,7 @@ export function processEvents(

switch (event.op) {
case "fiber-type-def": {
fiberTypeDefById.set(event.typeId, {
const typeDef = {
...event.definition,
hooks: event.definition.hooks.map((hook, index) => ({
index,
Expand All @@ -138,6 +139,18 @@ export function processEvents(
? event.definition.contexts?.[hook.context] || null
: null,
})),
};

fiberTypeDefById.set(event.typeId, typeDef);
fiberTypeStat.set(event.typeId, {
typeId: event.typeId,
typeDef,
displayName: event.displayName || "[render root]",
mounts: 0,
mountTime: 0,
unmounts: 0,
updates: 0,
updateTime: 0,
});
continue;
}
Expand Down Expand Up @@ -182,6 +195,15 @@ export function processEvents(
}
}

const typeStat = fiberTypeStat.get(fiber.typeId);
if (typeStat) {
fiberTypeStat.set(fiber.typeId, {
...typeStat,
mounts: typeStat.mounts + 1,
mountTime: typeStat.mountTime + event.selfTime,
});
}

break;
}

Expand All @@ -197,6 +219,14 @@ export function processEvents(
parentTree.delete(fiber.id);
ownerTree.delete(fiber.id);

const typeStat = fiberTypeStat.get(fiber.typeId);
if (typeStat) {
fiberTypeStat.set(fiber.typeId, {
...typeStat,
unmounts: typeStat.unmounts + 1,
});
}

break;
}

Expand All @@ -213,6 +243,15 @@ export function processEvents(
warnings: fiber.warnings + (changes?.warnings?.size || 0),
};

const typeStat = fiberTypeStat.get(fiber.typeId);
if (typeStat) {
fiberTypeStat.set(fiber.typeId, {
...typeStat,
updates: typeStat.updates + 1,
updateTime: typeStat.updateTime + event.selfTime,
});
}

break;

case "update-bailout-state":
Expand Down
3 changes: 3 additions & 0 deletions src/ui/images/table-sort-asc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/ui/images/table-sort-desc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/ui/images/table-sortable.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/ui/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
@import url("./components/toolbar/SelectionHistoryNavigation.css");
@import url("./components/toolbar/Toolbar.css");
@import url("./pages/Commits.css");
@import url("./pages/Components.css");
@import url("./pages/ComponentsTree.css");
@import url("./pages/MaybeLeaks.css");
@import url("./pages/maybe-leaks/Fiber.css");
Expand Down
63 changes: 63 additions & 0 deletions src/ui/pages/Components.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.app-page-components {
overflow-y: scroll;
}

.app-page-components {
position: relative;
}
.app-page-stat-table {
border-collapse: collapse;
font-size: 12px;
}

.app-page-stat-table thead {
position: sticky;
top: 0px;
}
.app-page-stat-table thead tr {
border-bottom: 1px solid white;
}
.app-page-stat-table th {
background: #eee no-repeat right 1px center;
background-size: 16px;
background-clip: padding-box;
border: none;
font-weight: normal;
font-size: 11px;
text-align: left;
padding: 0 4px;
cursor: pointer;
}
.app-page-stat-table th.sortable {
padding-right: 18px;
background-image: url("../images/table-sortable.svg");
cursor: pointer;
}
.app-page-stat-table th.sortable:not(.asc):not(.desc) {
background-size: 14px;
background-position: right 2px center;
}
.app-page-stat-table th.sortable.asc {
background-image: url("../images/table-sort-asc.svg");
}
.app-page-stat-table th.sortable.desc {
background-image: url("../images/table-sort-desc.svg");
}
.app-page-stat-table th.sortable:hover {
background-color: #ddd;
}

.app-page-stat-table td {
border: 1px solid #eee;
padding: 0 4px;
}
.app-page-stat-table td.time::after {
content: "ms";
color: #aaa;
font-size: 10px;
padding-left: 3px;
}
.app-page-stat-table tbody td:not(:first-child) {
text-align: right;
min-width: 32px;
}
159 changes: 159 additions & 0 deletions src/ui/pages/Components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import * as React from "react";
import { useSelectedId } from "../utils/selection";
import { useFiberTypeStat } from "../utils/fiber-maps";
import { FiberTypeStat } from "../types";

type Sorting =
| "displayName"
| "count"
| "time"
| "mounted"
| "mountTime"
| "unmounted"
| "updates"
| "avgUpdates"
| "updateTime"
| "hooks";
type OrderedSorting = `+${Sorting}` | `-${Sorting}`;
const sortings: Record<
Sorting,
(a: FiberTypeStat, b: FiberTypeStat) => number
> = {
displayName: (a, b) => (a.displayName < b.displayName ? -1 : 1),
count: (a, b) => a.mounts - b.mounts,
time: (a, b) => a.mountTime + a.updateTime - (b.mountTime + b.updateTime),
mounted: (a, b) => a.mounts - a.unmounts - (b.mounts - b.unmounts),
mountTime: (a, b) => a.mountTime / a.mounts - b.mountTime / b.mounts,
unmounted: (a, b) => a.unmounts - b.unmounts,
updates: (a, b) => a.updates - b.updates,
avgUpdates: (a, b) => a.updates / a.mounts - b.updates / b.mounts,
updateTime: (a, b) =>
(a.updates ? a.updateTime / a.updates : 0) -
(b.updates ? b.updateTime / b.updates : 0),
hooks: (a, b) => a.typeDef.hooks.length - b.typeDef.hooks.length,
};

function ComponentsPageBadge() {
const types = useFiberTypeStat();
return <span>{types.length}</span>;
}

function ComponentsPage() {
const { selectedId } = useSelectedId();
const fiberTypeStat = useFiberTypeStat();

const [sorting, setSorting] = React.useReducer(
(prevState: OrderedSorting, nextState: OrderedSorting) =>
nextState === prevState
? (prevState.replace(/^./, m =>
m === "-" ? "+" : "-"
) as OrderedSorting)
: nextState,
"+displayName"
);
const sortingFn = React.useMemo(() => {
const compare = sortings[sorting.slice(1) as Sorting];
return sorting[0] === "+"
? compare
: (a: FiberTypeStat, b: FiberTypeStat) => compare(b, a);
}, [sorting]);
const sortedFiberTypeStat = React.useMemo(
() => fiberTypeStat.slice().sort(sortingFn),
[fiberTypeStat, sortingFn]
);
const createTh = (caption: string, colSorting: OrderedSorting) => (
<Th sorting={colSorting} currentSorting={sorting} setSorting={setSorting}>
{caption}
</Th>
);

return (
<div
className="app-page app-page-components"
data-has-selected={selectedId !== null || undefined}
>
<table className="app-page-stat-table">
<thead>
<tr>
{createTh("Type", "+displayName")}
{createTh("Count", "-count")}
{createTh("Time", "-time")}
{createTh("Mount", "-mounted")}
{createTh("Mount", "-mountTime")}
{createTh("Unmount", "-unmounted")}
{createTh("Updates", "-updates")}
{createTh("Avg(U)", "-avgUpdates")}
{createTh("Update", "-updateTime")}
{createTh("Hooks", "-hooks")}
</tr>
</thead>
<tbody>
{sortedFiberTypeStat.map(stat => (
<ComponentRow key={stat.typeId} stat={stat} />
))}
</tbody>
</table>
</div>
);
}

function Th({
children,
sorting,
currentSorting,
setSorting,
}: {
children: string;
sorting: OrderedSorting;
currentSorting: OrderedSorting;
setSorting: (nextSorting: OrderedSorting) => void;
}) {
return (
<th
{...(sorting
? {
className:
sorting.slice(1) === currentSorting.slice(1)
? `sortable ${currentSorting[0] === "+" ? "asc" : "desc"}`
: "sortable",
onClick: () => setSorting(sorting),
}
: null)}
>
{children}
</th>
);
}

function ComponentRow({ stat }: { stat: FiberTypeStat }) {
const totalTime = stat.mountTime + stat.updateTime;
const avgUpdates = stat.updates / stat.mounts;
const mountTime = stat.mountTime / stat.mounts;
const updateTime = stat.updates ? stat.updateTime / stat.updates : 0;

return (
<tr>
<td>{stat.displayName}</td>
<td>{stat.mounts}</td>
<td className="time">{totalTime.toFixed(1)}</td>
<td>{stat.mounts - stat.unmounts || ""}</td>
<td className="time">{mountTime.toFixed(1)}</td>
<td>{stat.unmounts || ""}</td>
<td>{stat.updates || ""}</td>
<td>{avgUpdates ? avgUpdates.toFixed(1) : ""}</td>
<td className="time">{updateTime ? updateTime.toFixed(1) : ""}</td>
<td>{stat.typeDef.hooks.length || "–"}</td>
</tr>
);
}

const ComponentsPageBadgeMemo = React.memo(ComponentsPageBadge);
ComponentsPageBadgeMemo.displayName = "ComponentsPageBadge";

const ComponentsPageMemo = React.memo(ComponentsPage);
ComponentsPageMemo.displayName = "ComponentsPage";

export {
ComponentsPageMemo as ComponentsPage,
ComponentsPageBadgeMemo as ComponentsPageBadge,
};
8 changes: 8 additions & 0 deletions src/ui/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { CommitsPage, CommitsPageBadge } from "./Commits";
import { FeatureCommits, FeatureMemLeaks } from "../../common/constants";
import { ComponentsTreePage } from "./ComponentsTree";
import { MaybeLeaksPage, MaybeLeaksPageBadge } from "./MaybeLeaks";
import { ComponentsPage, ComponentsPageBadge } from "./Components";

export const enum AppPage {
ComponentTree = "component-tree",
Components = "components",
Commits = "commits",
MaybeLeaks = "maybe-leaks",
}
Expand All @@ -23,6 +25,12 @@ export const pages: Record<AppPage, AppPageConfig> = {
title: "Component tree",
content: ComponentsTreePage,
},
[AppPage.Components]: {
id: AppPage.Components,
title: "Components",
content: ComponentsPage,
badge: ComponentsPageBadge,
},
[AppPage.Commits]: {
id: AppPage.Commits,
title: "Commits",
Expand Down
Loading

0 comments on commit 644503e

Please sign in to comment.