feat: improve performance panel
All checks were successful
Deploy to GitHub Pages / build_site (push) Successful in 1m54s

This commit is contained in:
max_richter 2024-04-25 18:40:45 +02:00
parent 66ae9e6c72
commit bd359fbaf7
10 changed files with 185 additions and 88 deletions

View File

@ -17,9 +17,6 @@
<div class="wrapper"> <div class="wrapper">
{#if !activeUser} {#if !activeUser}
<div class="header">
<h3>Users</h3>
</div>
{#await registry.fetchUsers()} {#await registry.fetchUsers()}
<div>Loading...</div> <div>Loading...</div>
{:then users} {:then users}
@ -37,15 +34,6 @@
{#await registry.fetchUser(activeUser)} {#await registry.fetchUser(activeUser)}
<div>Loading...</div> <div>Loading...</div>
{:then user} {:then user}
<div class="header">
<button
on:click={() => {
$activeId = "";
}}
class="i-tabler-arrow-back"
></button>
<h3>Collections</h3>
</div>
{#each user.collections as collection} {#each user.collections as collection}
<button <button
on:click={() => { on:click={() => {
@ -59,15 +47,6 @@
<div>{error.message}</div> <div>{error.message}</div>
{/await} {/await}
{:else if !activeNode} {:else if !activeNode}
<div class="header">
<button
on:click={() => {
$activeId = activeUser;
}}
class="i-tabler-arrow-back"
></button>
<h3>Nodes</h3>
</div>
{#await registry.fetchCollection(`${activeUser}/${activeCollection}`)} {#await registry.fetchCollection(`${activeUser}/${activeCollection}`)}
<div>Loading...</div> <div>Loading...</div>
{:then collection} {:then collection}

View File

@ -2,6 +2,7 @@
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import Monitor from "./Monitor.svelte"; import Monitor from "./Monitor.svelte";
import { humanizeNumber } from "$lib/helpers"; import { humanizeNumber } from "$lib/helpers";
import { Checkbox } from "@nodes/ui";
type PerformanceData = { type PerformanceData = {
total: Record<string, number>; total: Record<string, number>;
@ -10,44 +11,105 @@
export let data: PerformanceData; export let data: PerformanceData;
export let viewer: PerformanceData; export let viewer: PerformanceData;
let lastRunOnly = true;
function getPerformanceData() { function getTotalPerformance(onlyLast = false) {
return Object.entries(data.total) if (onlyLast) {
.sort((a, b) => b[1] - a[1]) return (
.filter(([key]) => !key.startsWith("node/")); data.runs.at(-1).runtime[0] + viewer.runs.at(-1)["create-geometries"][0]
);
}
return data.total.runtime + viewer.total["create-geometries"];
} }
function getNodePerformanceData() { function count(input?: number | number[]) {
if (!input) return 0;
if (Array.isArray(input))
return input.reduce((acc, val) => acc + val, 0) / input.length;
return input;
}
function getCacheRatio(onlyLast = false) {
let ratio = onlyLast
? count(data.runs.at(-1)?.["cache-hit"])
: count(data.total["cache-hit"]);
return `${Math.floor(ratio * 100)}%`;
}
function getPerformanceData(onlyLast: boolean = false) {
if (onlyLast) {
return Object.entries(data.runs.at(-1))
.map(([key, value]) => [key, value[0]])
.filter(
([key]) =>
!key.startsWith("node/") &&
key !== "total" &&
!key.includes("cache"),
)
.sort((a, b) => b[1] - a[1]);
}
return Object.entries(data.total)
.sort((a, b) => b[1] - a[1])
.filter(
([key]) =>
!key.startsWith("node/") && key !== "total" && !key.includes("cache"),
);
}
function getNodePerformanceData(onlyLast: boolean = false) {
if (onlyLast) {
return Object.entries(data.runs.at(-1))
.map(([key, value]) => [key, value[0]])
.filter(([key]) => key.startsWith("node/"))
.sort((a, b) => b[1] - a[1]);
}
return Object.entries(data.total) return Object.entries(data.total)
.filter(([key]) => key.startsWith("node/")) .filter(([key]) => key.startsWith("node/"))
.sort((a, b) => b[1] - a[1]); .sort((a, b) => b[1] - a[1]);
} }
function getViewerPerformanceData() { function getViewerPerformanceData(onlyLast: boolean = false) {
if (onlyLast) {
return Object.entries(viewer.runs.at(-1))
.map(([key, value]) => [key, value[0]])
.filter(([key]) => key !== "total-vertices" && key !== "total-faces")
.sort((a, b) => b[1] - a[1]);
}
return Object.entries(viewer.total) return Object.entries(viewer.total)
.filter(([key]) => key !== "total-vertices" && key !== "total-faces") .filter(([key]) => key !== "total-vertices" && key !== "total-faces")
.sort((a, b) => b[1] - a[1]); .sort((a, b) => b[1] - a[1]);
} }
function constructPoints(key: keyof (typeof data.runs)[0]) { function constructPoints(key: keyof (typeof data.runs)[0]) {
return data.runs return data.runs.map((run, i) => {
.map((run, i) => { return run[key][0];
return run[key][0]; });
})
.slice(-100);
} }
</script> </script>
{#key data} {#key data}
{#if browser} {#if browser}
<Monitor points={constructPoints("total")} /> <Monitor points={constructPoints("runtime")} />
{/if} {/if}
<div class="px-4"> <div class="p-4">
<div class="flex items-center gap-2">
<Checkbox id="show-total" bind:value={lastRunOnly} />
<label for="show-total">Show Average</label>
</div>
{#if data.runs.length !== 0} {#if data.runs.length !== 0}
<h3>General</h3> <h3>General</h3>
<table> <table>
{#each getPerformanceData() as [key, value]} <tr>
<td>
{Math.floor(getTotalPerformance(!lastRunOnly) * 100) / 100}<span
>ms</span
>
</td>
<td>total</td>
</tr>
{#each getPerformanceData(!lastRunOnly) as [key, value]}
<tr> <tr>
<td> <td>
{Math.floor(value * 100) / 100}<span>ms</span> {Math.floor(value * 100) / 100}<span>ms</span>
@ -58,8 +120,18 @@
</tr> </tr>
{/each} {/each}
<tr>
<td> {getCacheRatio(!lastRunOnly)} </td>
<td>cache hit</td>
</tr>
<tr>
<td>{data.runs.length}</td>
<td>Samples</td>
</tr>
<h3>Nodes</h3> <h3>Nodes</h3>
{#each getNodePerformanceData() as [key, value]} {#each getNodePerformanceData(!lastRunOnly) as [key, value]}
<tr> <tr>
<td> <td>
{Math.floor(value * 100) / 100}<span>ms</span> {Math.floor(value * 100) / 100}<span>ms</span>
@ -80,7 +152,7 @@
<td>{humanizeNumber(viewer.runs.at(-1)?.["total-faces"])}</td> <td>{humanizeNumber(viewer.runs.at(-1)?.["total-faces"])}</td>
<td>Faces</td> <td>Faces</td>
</tr> </tr>
{#each getViewerPerformanceData() as [key, value]} {#each getViewerPerformanceData(!lastRunOnly) as [key, value]}
<tr> <tr>
<td> <td>
{Math.floor(value * 100) / 100}<span>ms</span> {Math.floor(value * 100) / 100}<span>ms</span>

View File

@ -42,6 +42,7 @@ export function createPerformanceStore(): PerformanceStore {
}); });
data.runs.push(currentRun); data.runs.push(currentRun);
data.runs = data.runs.slice(-100);
currentRun = undefined; currentRun = undefined;
if (set) set(data); if (set) set(data);
} }

View File

@ -154,8 +154,6 @@
} }
$: if (result) { $: if (result) {
perf?.startRun();
let a = performance.now(); let a = performance.now();
const inputs = parse_args(result); const inputs = parse_args(result);
let b = performance.now(); let b = performance.now();
@ -194,8 +192,6 @@
perf?.addPoint("total-vertices", totalVertices); perf?.addPoint("total-vertices", totalVertices);
perf?.addPoint("total-faces", totalFaces); perf?.addPoint("total-faces", totalFaces);
perf?.stopRun();
} }
</script> </script>

View File

@ -213,15 +213,22 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
let cachedValue = this.cache?.get(inputHash); let cachedValue = this.cache?.get(inputHash);
if (cachedValue !== undefined) { if (cachedValue !== undefined) {
log.log(`Using cached value for ${node_type.id || node.id}`); log.log(`Using cached value for ${node_type.id || node.id}`);
this.perf?.addPoint("cache-hit", 1);
results[node.id] = cachedValue as Int32Array; results[node.id] = cachedValue as Int32Array;
continue; continue;
} }
this.perf?.addPoint("cache-hit", 0);
log.group(`executing ${node_type.id || node.id}`); log.group(`executing ${node_type.id || node.id}`);
log.log(`Inputs:`, inputs); log.log(`Inputs:`, inputs);
a = performance.now(); a = performance.now();
results[node.id] = node_type.execute(encoded_inputs); results[node.id] = node_type.execute(encoded_inputs);
b = performance.now(); b = performance.now();
if (this.cache) {
this.cache.set(inputHash, results[node.id]);
}
this.perf?.addPoint("node/" + node_type.id, b - a); this.perf?.addPoint("node/" + node_type.id, b - a);
log.log("Result:", results[node.id]); log.log("Result:", results[node.id]);
log.groupEnd(); log.groupEnd();
@ -237,7 +244,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor {
// return the result of the parent of the output node // return the result of the parent of the output node
const res = results[outputNode.id]; const res = results[outputNode.id];
this.perf?.addPoint("total", performance.now() - a0); this.perf?.addPoint("runtime", performance.now() - a0);
this.perf?.stopRun(); this.perf?.stopRun();

View File

@ -5,17 +5,27 @@
export let id: string; export let id: string;
export let icon: string = ""; export let icon: string = "";
export let title = ""; export let title = "";
export let classes = "";
export let hidden: boolean;
const setVisibility =
getContext<(id: string, visible: boolean) => void>("setVisibility");
$: if (typeof hidden === "boolean") {
setVisibility(id, !hidden);
}
const registerPanel = const registerPanel =
getContext<(id: string, icon: string) => Readable<boolean>>( getContext<
"registerPanel", (id: string, icon: string, classes: string) => Readable<boolean>
); >("registerPanel");
let visible = registerPanel(id, icon); let visible = registerPanel(id, icon, classes);
console.log(id, $visible, hidden);
</script> </script>
{#if $visible} {#if $visible}
<div class="wrapper"> <div class="wrapper" class:hidden>
{#if title} {#if title}
<header> <header>
<h3>{title}</h3> <h3>{title}</h3>

View File

@ -8,6 +8,8 @@
{ {
icon: string; icon: string;
id: string; id: string;
classes: string;
visible?: boolean;
} }
> = {}; > = {};
@ -22,8 +24,13 @@
) )
: []; : [];
setContext("registerPanel", (id: string, icon: string) => { setContext("setVisibility", (id: string, visible: boolean) => {
panels[id] = { id, icon }; panels[id].visible = visible;
panels = { ...panels };
});
setContext("registerPanel", (id: string, icon: string, classes: string) => {
panels[id] = { id, icon, classes };
return derived(activePanel, ($activePanel) => { return derived(activePanel, ($activePanel) => {
return $activePanel === id; return $activePanel === id;
}); });
@ -50,13 +57,15 @@
<span class="absolute i-tabler-chevron-left w-6 h-6 block"></span> <span class="absolute i-tabler-chevron-left w-6 h-6 block"></span>
</button> </button>
{#each keys as panel (panels[panel].id)} {#each keys as panel (panels[panel].id)}
<button {#if panels[panel].visible !== false}
class="tab" <button
class:active={panel === $activePanel} class="tab {panels[panel].classes}"
on:click={() => setActivePanel(panel)} class:active={panel === $activePanel}
> on:click={() => setActivePanel(panel)}
<i class={`block w-6 h-6 ${panels[panel].icon}`} /> >
</button> <span class={`block w-6 h-6 ${panels[panel].icon}`} />
</button>
{/if}
{/each} {/each}
</div> </div>
<div class="content"> <div class="content">
@ -81,12 +90,6 @@
min-width: 350px; min-width: 350px;
} }
h1 {
border-bottom: solid thin var(--outline);
box-sizing: border-box;
height: 70px;
}
.content { .content {
background: var(--layer-1); background: var(--layer-1);
position: relative; position: relative;
@ -103,35 +106,35 @@
padding: 5px; padding: 5px;
border-radius: 0px; border-radius: 0px;
background: none; background: none;
color: var(--outline);
border: none; border: none;
display: flex; display: flex;
align-items: center; align-items: center;
border-bottom: solid thin var(--outline); border-bottom: solid thin var(--outline);
border-left: solid thin var(--outline); border-left: solid thin var(--outline);
background: var(--layer-0);
}
.tabs > button > span {
opacity: 0.5;
} }
.tabs > button.active { .tabs > button.active {
color: var(--layer-3);
background: var(--layer-1); background: var(--layer-1);
} }
.visible .tabs > button { .tabs > button.active span {
border-left: none; opacity: 1;
} }
.visible .tabs { .visible .tabs {
margin-left: -1px; margin-left: -1px;
border-left: solid thin var(--outline);
} }
.visible > .tabs button:first-child { .visible > .tabs button:first-child > span {
transform: scaleX(-1); transform: scaleX(-1);
} }
.visible { .visible {
transform: translateX(0); transform: translateX(0);
border-left: solid thin var(--outline);
background: var(--layer-0);
} }
</style> </style>

View File

@ -8,6 +8,7 @@ export const AppSettings = localStore("node-settings", {
wireframe: false, wireframe: false,
showIndices: false, showIndices: false,
showVertices: false, showVertices: false,
showPerformancePanel: false,
centerCamera: true, centerCamera: true,
showStemLines: false, showStemLines: false,
amount: 5 amount: 5
@ -67,6 +68,11 @@ export const AppSettingTypes = {
label: "Show Indices", label: "Show Indices",
value: false, value: false,
}, },
showPerformancePanel: {
type: "boolean",
label: "Show Performance Panel",
value: false,
},
showVertices: { showVertices: {
type: "boolean", type: "boolean",
label: "Show Vertices", label: "Show Vertices",

View File

@ -1,10 +1,11 @@
import { MemoryRuntimeExecutor } from "./runtime-executor"; import { MemoryRuntimeExecutor, MemoryRuntimeCache } from "./runtime-executor";
import { RemoteNodeRegistry } from "./node-registry-client"; import { RemoteNodeRegistry } from "./node-registry-client";
import type { Graph } from "@nodes/types"; import type { Graph } from "@nodes/types";
import { createPerformanceStore } from "./performance/store"; import { createPerformanceStore } from "./performance/store";
const cache = new MemoryRuntimeCache();
const nodeRegistry = new RemoteNodeRegistry(""); const nodeRegistry = new RemoteNodeRegistry("");
const executor = new MemoryRuntimeExecutor(nodeRegistry); const executor = new MemoryRuntimeExecutor(nodeRegistry, cache);
const performanceStore = createPerformanceStore(); const performanceStore = createPerformanceStore();
executor.perf = performanceStore; executor.perf = performanceStore;

View File

@ -74,13 +74,24 @@
} }
isWorking = true; isWorking = true;
try { try {
let a = performance.now();
res = await workerRuntime.execute(_graph, _settings); res = await workerRuntime.execute(_graph, _settings);
performanceData = await workerRuntime.getPerformanceData(); let b = performance.now();
let perfData = await workerRuntime.getPerformanceData();
let lastRun = perfData.runs?.at(-1);
if (lastRun) {
perfData.total["worker-transfer"] = b - a - lastRun.runtime[0];
lastRun["worker-transfer"] = [b - a - lastRun.runtime[0]];
}
performanceData = perfData;
isWorking = false; isWorking = false;
} catch (error) { } catch (error) {
console.log("errors", error); console.log("errors", error);
} }
viewerPerformance.stopRun();
viewerPerformance.startRun();
if (unfinished) { if (unfinished) {
let d = unfinished; let d = unfinished;
unfinished = undefined; unfinished = undefined;
@ -137,21 +148,6 @@
settings={AppSettingTypes} settings={AppSettingTypes}
/> />
</Panel> </Panel>
<Panel id="node-store" title="Node Store" icon="i-tabler-database">
<NodeStore registry={nodeRegistry} />
</Panel>
<Panel
id="performance"
title="Performance"
icon="i-tabler-brand-speedtest"
>
{#if performanceData}
<PerformanceViewer
data={performanceData}
viewer={$viewerPerformance}
/>
{/if}
</Panel>
<Panel <Panel
id="shortcuts" id="shortcuts"
title="Keyboard Shortcuts" title="Keyboard Shortcuts"
@ -161,9 +157,32 @@
<Keymap {keymap} /> <Keymap {keymap} />
{/if} {/if}
</Panel> </Panel>
<Panel
id="node-store"
classes="text-green-400"
title="Node Store"
icon="i-tabler-database"
>
<NodeStore registry={nodeRegistry} />
</Panel>
<Panel
id="performance"
title="Performance"
classes="text-red-400"
hidden={!$AppSettings.showPerformancePanel}
icon="i-tabler-brand-speedtest"
>
{#if performanceData}
<PerformanceViewer
data={performanceData}
viewer={$viewerPerformance}
/>
{/if}
</Panel>
<Panel <Panel
id="graph-settings" id="graph-settings"
title="Graph Settings" title="Graph Settings"
classes="text-blue-400"
icon="i-tabler-brand-git" icon="i-tabler-brand-git"
> >
{#if Object.keys(graphSettingTypes).length > 0} {#if Object.keys(graphSettingTypes).length > 0}
@ -173,6 +192,7 @@
<Panel <Panel
id="active-node" id="active-node"
title="Node Settings" title="Node Settings"
classes="text-blue-400"
icon="i-tabler-adjustments" icon="i-tabler-adjustments"
> >
<ActiveNodeSettings {manager} node={activeNode} /> <ActiveNodeSettings {manager} node={activeNode} />
@ -183,6 +203,8 @@
</Grid.Row> </Grid.Row>
</div> </div>
<span class="font-red" />
<style> <style>
header { header {
/* border-bottom: solid thin var(--outline); */ /* border-bottom: solid thin var(--outline); */