@ogygia/devtools 0.0.0-stage → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -2
- package/dist/client.d.ts +3 -0
- package/dist/client.js +24 -0
- package/dist/devtools/BoundaryOverlay.svelte +396 -0
- package/dist/devtools/BoundaryOverlay.svelte.d.ts +19 -0
- package/dist/devtools/Devtools.svelte +663 -0
- package/dist/devtools/Devtools.svelte.d.ts +15 -0
- package/dist/devtools/HubTab.svelte +206 -0
- package/dist/devtools/HubTab.svelte.d.ts +11 -0
- package/dist/devtools/HydrationTab.svelte +237 -0
- package/dist/devtools/HydrationTab.svelte.d.ts +13 -0
- package/dist/devtools/IslandDetail.svelte +615 -0
- package/dist/devtools/IslandDetail.svelte.d.ts +15 -0
- package/dist/devtools/LedgerTab.svelte +287 -0
- package/dist/devtools/LedgerTab.svelte.d.ts +11 -0
- package/dist/devtools/LensTab.svelte +290 -0
- package/dist/devtools/LensTab.svelte.d.ts +19 -0
- package/dist/devtools/NavTab.svelte +229 -0
- package/dist/devtools/NavTab.svelte.d.ts +11 -0
- package/dist/devtools/PageTab.svelte +1122 -0
- package/dist/devtools/PageTab.svelte.d.ts +15 -0
- package/dist/devtools/ProfilerTab.svelte +755 -0
- package/dist/devtools/ProfilerTab.svelte.d.ts +15 -0
- package/dist/devtools/PropsTree.svelte +155 -0
- package/dist/devtools/PropsTree.svelte.d.ts +15 -0
- package/dist/devtools/SessionTab.svelte +796 -0
- package/dist/devtools/SessionTab.svelte.d.ts +11 -0
- package/dist/devtools/TimelineTab.svelte +267 -0
- package/dist/devtools/TimelineTab.svelte.d.ts +11 -0
- package/dist/devtools/WireTab.svelte +149 -0
- package/dist/devtools/WireTab.svelte.d.ts +11 -0
- package/dist/devtools/bytes.d.ts +70 -0
- package/dist/devtools/bytes.js +82 -0
- package/dist/devtools/cache-headers.d.ts +20 -0
- package/dist/devtools/cache-headers.js +81 -0
- package/dist/devtools/highlight.d.ts +7 -0
- package/dist/devtools/highlight.js +83 -0
- package/dist/devtools/hydration.d.ts +48 -0
- package/dist/devtools/hydration.js +93 -0
- package/dist/devtools/index.d.ts +3 -0
- package/dist/devtools/index.js +3 -0
- package/dist/devtools/ledger-dom.d.ts +7 -0
- package/dist/devtools/ledger-dom.js +151 -0
- package/dist/devtools/leftovers.d.ts +77 -0
- package/dist/devtools/leftovers.js +319 -0
- package/dist/devtools/names.d.ts +1 -0
- package/dist/devtools/names.js +34 -0
- package/dist/devtools/page.d.ts +96 -0
- package/dist/devtools/page.js +957 -0
- package/dist/devtools/profile-anchors.d.ts +22 -0
- package/dist/devtools/profile-anchors.js +135 -0
- package/dist/devtools/profile-store.d.ts +175 -0
- package/dist/devtools/profile-store.js +239 -0
- package/dist/devtools/regions.d.ts +77 -0
- package/dist/devtools/regions.js +132 -0
- package/dist/devtools/session.d.ts +31 -0
- package/dist/devtools/session.js +455 -0
- package/dist/devtools/since-load.d.ts +50 -0
- package/dist/devtools/since-load.js +89 -0
- package/dist/devtools/source-lines.d.ts +24 -0
- package/dist/devtools/source-lines.js +106 -0
- package/dist/devtools/styles.d.ts +52 -0
- package/dist/devtools/styles.js +248 -0
- package/dist/devtools/testing-engine.d.ts +83 -0
- package/dist/devtools/testing-engine.js +220 -0
- package/dist/devtools/ui-app.d.ts +5 -0
- package/dist/devtools/ui-app.js +69 -0
- package/dist/devtools/ui.d.ts +50 -0
- package/dist/devtools/ui.js +175 -0
- package/dist/profiler/alloc.d.ts +75 -0
- package/dist/profiler/alloc.js +76 -0
- package/dist/profiler/analyze.d.ts +437 -0
- package/dist/profiler/analyze.js +1941 -0
- package/dist/profiler/app-path.d.ts +7 -0
- package/dist/profiler/app-path.js +26 -0
- package/dist/profiler/async-io.d.ts +58 -0
- package/dist/profiler/async-io.js +187 -0
- package/dist/profiler/beacon-standalone.d.ts +4 -0
- package/dist/profiler/beacon-standalone.js +409 -0
- package/dist/profiler/browser-findings.d.ts +25 -0
- package/dist/profiler/browser-findings.js +260 -0
- package/dist/profiler/byte-strip.d.ts +58 -0
- package/dist/profiler/byte-strip.js +228 -0
- package/dist/profiler/client-files.d.ts +16 -0
- package/dist/profiler/client-files.js +60 -0
- package/dist/profiler/client-windows.d.ts +49 -0
- package/dist/profiler/client-windows.js +95 -0
- package/dist/profiler/client.d.ts +40 -0
- package/dist/profiler/client.js +82 -0
- package/dist/profiler/compare.d.ts +308 -0
- package/dist/profiler/compare.js +733 -0
- package/dist/profiler/compress.d.ts +10 -0
- package/dist/profiler/compress.js +36 -0
- package/dist/profiler/contention.d.ts +60 -0
- package/dist/profiler/contention.js +86 -0
- package/dist/profiler/crypto.d.ts +19 -0
- package/dist/profiler/crypto.js +101 -0
- package/dist/profiler/doc-diff.d.ts +47 -0
- package/dist/profiler/doc-diff.js +214 -0
- package/dist/profiler/dom-diff.d.ts +43 -0
- package/dist/profiler/dom-diff.js +121 -0
- package/dist/profiler/drill.d.ts +89 -0
- package/dist/profiler/drill.js +553 -0
- package/dist/profiler/embedded-maps.d.ts +37 -0
- package/dist/profiler/embedded-maps.js +135 -0
- package/dist/profiler/env.d.ts +6 -0
- package/dist/profiler/env.js +10 -0
- package/dist/profiler/forecast.d.ts +76 -0
- package/dist/profiler/forecast.js +225 -0
- package/dist/profiler/frames.d.ts +11 -0
- package/dist/profiler/frames.js +42 -0
- package/dist/profiler/gc.d.ts +144 -0
- package/dist/profiler/gc.js +271 -0
- package/dist/profiler/hole-slots.d.ts +31 -0
- package/dist/profiler/hole-slots.js +90 -0
- package/dist/profiler/index.d.ts +288 -0
- package/dist/profiler/index.js +4921 -0
- package/dist/profiler/insights.d.ts +96 -0
- package/dist/profiler/insights.js +239 -0
- package/dist/profiler/ledger.d.ts +121 -0
- package/dist/profiler/ledger.js +440 -0
- package/dist/profiler/lineage.d.ts +81 -0
- package/dist/profiler/lineage.js +359 -0
- package/dist/profiler/module-map.d.ts +12 -0
- package/dist/profiler/module-map.js +94 -0
- package/dist/profiler/net.d.ts +169 -0
- package/dist/profiler/net.js +586 -0
- package/dist/profiler/on-demand.d.ts +11 -0
- package/dist/profiler/on-demand.js +15 -0
- package/dist/profiler/own-requests.d.ts +9 -0
- package/dist/profiler/own-requests.js +17 -0
- package/dist/profiler/patterns.d.ts +476 -0
- package/dist/profiler/patterns.js +2203 -0
- package/dist/profiler/profiler-router.d.ts +245 -0
- package/dist/profiler/profiler-router.js +113 -0
- package/dist/profiler/refresh-stub.d.ts +2 -0
- package/dist/profiler/refresh-stub.js +37 -0
- package/dist/profiler/report.d.ts +1404 -0
- package/dist/profiler/report.js +2638 -0
- package/dist/profiler/river.d.ts +65 -0
- package/dist/profiler/river.js +298 -0
- package/dist/profiler/route-files.d.ts +22 -0
- package/dist/profiler/route-files.js +55 -0
- package/dist/profiler/route-imports.d.ts +14 -0
- package/dist/profiler/route-imports.js +83 -0
- package/dist/profiler/saving.d.ts +22 -0
- package/dist/profiler/saving.js +42 -0
- package/dist/profiler/score.d.ts +109 -0
- package/dist/profiler/score.js +261 -0
- package/dist/profiler/session-cookie.d.ts +28 -0
- package/dist/profiler/session-cookie.js +45 -0
- package/dist/profiler/sink.d.ts +81 -0
- package/dist/profiler/sink.js +124 -0
- package/dist/profiler/site-fixes.d.ts +25 -0
- package/dist/profiler/site-fixes.js +62 -0
- package/dist/profiler/source-scan.d.ts +210 -0
- package/dist/profiler/source-scan.js +1724 -0
- package/dist/profiler/span.d.ts +90 -0
- package/dist/profiler/span.js +109 -0
- package/dist/profiler/stacks.d.ts +43 -0
- package/dist/profiler/stacks.js +107 -0
- package/dist/profiler/standalone.d.ts +27 -0
- package/dist/profiler/standalone.js +98 -0
- package/dist/profiler/steps.d.ts +39 -0
- package/dist/profiler/steps.js +74 -0
- package/dist/profiler/storage/index.d.ts +87 -0
- package/dist/profiler/storage/index.js +76 -0
- package/dist/profiler/storage/postgres.d.ts +12 -0
- package/dist/profiler/storage/postgres.js +94 -0
- package/dist/profiler/storage/redis.d.ts +19 -0
- package/dist/profiler/storage/redis.js +148 -0
- package/dist/profiler/storage/sqlite.d.ts +2 -0
- package/dist/profiler/storage/sqlite.js +85 -0
- package/dist/profiler/storage/visits.d.ts +4 -0
- package/dist/profiler/storage/visits.js +6 -0
- package/dist/profiler/stream-tail.d.ts +26 -0
- package/dist/profiler/stream-tail.js +74 -0
- package/dist/profiler/timeline.d.ts +279 -0
- package/dist/profiler/timeline.js +690 -0
- package/dist/profiler/ui/AllocStrip.svelte +146 -0
- package/dist/profiler/ui/AllocStrip.svelte.d.ts +17 -0
- package/dist/profiler/ui/Awaits.svelte +363 -0
- package/dist/profiler/ui/Awaits.svelte.d.ts +16 -0
- package/dist/profiler/ui/ByteStrip.svelte +182 -0
- package/dist/profiler/ui/ByteStrip.svelte.d.ts +18 -0
- package/dist/profiler/ui/Compare.svelte +24 -0
- package/dist/profiler/ui/Compare.svelte.d.ts +16 -0
- package/dist/profiler/ui/CompareBody.svelte +429 -0
- package/dist/profiler/ui/CompareBody.svelte.d.ts +8 -0
- package/dist/profiler/ui/ComponentsTable.svelte +265 -0
- package/dist/profiler/ui/ComponentsTable.svelte.d.ts +20 -0
- package/dist/profiler/ui/Dashboard.svelte +492 -0
- package/dist/profiler/ui/Dashboard.svelte.d.ts +31 -0
- package/dist/profiler/ui/DataRiver.svelte +206 -0
- package/dist/profiler/ui/DataRiver.svelte.d.ts +13 -0
- package/dist/profiler/ui/DomTravel.svelte +207 -0
- package/dist/profiler/ui/DomTravel.svelte.d.ts +9 -0
- package/dist/profiler/ui/DownloadHtml.svelte +52 -0
- package/dist/profiler/ui/DownloadHtml.svelte.d.ts +10 -0
- package/dist/profiler/ui/Drill.svelte +276 -0
- package/dist/profiler/ui/Drill.svelte.d.ts +12 -0
- package/dist/profiler/ui/ExportButton.svelte +59 -0
- package/dist/profiler/ui/ExportButton.svelte.d.ts +8 -0
- package/dist/profiler/ui/Flame.svelte +222 -0
- package/dist/profiler/ui/Flame.svelte.d.ts +12 -0
- package/dist/profiler/ui/FrameDetails.svelte +308 -0
- package/dist/profiler/ui/FrameDetails.svelte.d.ts +16 -0
- package/dist/profiler/ui/FunctionsTable.svelte +156 -0
- package/dist/profiler/ui/FunctionsTable.svelte.d.ts +17 -0
- package/dist/profiler/ui/IslandHeatmap.svelte +151 -0
- package/dist/profiler/ui/IslandHeatmap.svelte.d.ts +16 -0
- package/dist/profiler/ui/IslandsTable.svelte +448 -0
- package/dist/profiler/ui/IslandsTable.svelte.d.ts +16 -0
- package/dist/profiler/ui/KeepLocal.svelte +46 -0
- package/dist/profiler/ui/KeepLocal.svelte.d.ts +8 -0
- package/dist/profiler/ui/Ledger.svelte +196 -0
- package/dist/profiler/ui/Ledger.svelte.d.ts +18 -0
- package/dist/profiler/ui/LocalCompare.svelte +48 -0
- package/dist/profiler/ui/LocalCompare.svelte.d.ts +8 -0
- package/dist/profiler/ui/LocalReports.svelte +68 -0
- package/dist/profiler/ui/LocalReports.svelte.d.ts +7 -0
- package/dist/profiler/ui/Login.svelte +13 -0
- package/dist/profiler/ui/Login.svelte.d.ts +12 -0
- package/dist/profiler/ui/LoginForm.svelte +42 -0
- package/dist/profiler/ui/LoginForm.svelte.d.ts +7 -0
- package/dist/profiler/ui/Message.svelte +10 -0
- package/dist/profiler/ui/Message.svelte.d.ts +12 -0
- package/dist/profiler/ui/OnThisPage.svelte +137 -0
- package/dist/profiler/ui/OnThisPage.svelte.d.ts +3 -0
- package/dist/profiler/ui/OneClock.svelte +297 -0
- package/dist/profiler/ui/OneClock.svelte.d.ts +27 -0
- package/dist/profiler/ui/Panel.svelte +10 -0
- package/dist/profiler/ui/Panel.svelte.d.ts +16 -0
- package/dist/profiler/ui/Paths.svelte +269 -0
- package/dist/profiler/ui/Paths.svelte.d.ts +15 -0
- package/dist/profiler/ui/Patterns.svelte +364 -0
- package/dist/profiler/ui/Patterns.svelte.d.ts +15 -0
- package/dist/profiler/ui/PermalinkGate.svelte +143 -0
- package/dist/profiler/ui/PermalinkGate.svelte.d.ts +10 -0
- package/dist/profiler/ui/RenderSteps.svelte +218 -0
- package/dist/profiler/ui/RenderSteps.svelte.d.ts +13 -0
- package/dist/profiler/ui/Report.svelte +28 -0
- package/dist/profiler/ui/Report.svelte.d.ts +20 -0
- package/dist/profiler/ui/ReportBody.svelte +2592 -0
- package/dist/profiler/ui/ReportBody.svelte.d.ts +21 -0
- package/dist/profiler/ui/Run.svelte +17 -0
- package/dist/profiler/ui/Run.svelte.d.ts +14 -0
- package/dist/profiler/ui/RunView.svelte +146 -0
- package/dist/profiler/ui/RunView.svelte.d.ts +10 -0
- package/dist/profiler/ui/ScoreCard.svelte +197 -0
- package/dist/profiler/ui/ScoreCard.svelte.d.ts +7 -0
- package/dist/profiler/ui/Scrub.svelte +293 -0
- package/dist/profiler/ui/Scrub.svelte.d.ts +18 -0
- package/dist/profiler/ui/SeedExplainer.svelte +193 -0
- package/dist/profiler/ui/SeedExplainer.svelte.d.ts +16 -0
- package/dist/profiler/ui/ShareLink.svelte +148 -0
- package/dist/profiler/ui/ShareLink.svelte.d.ts +8 -0
- package/dist/profiler/ui/Shell.svelte +50 -0
- package/dist/profiler/ui/Shell.svelte.d.ts +27 -0
- package/dist/profiler/ui/Sidebar.svelte +312 -0
- package/dist/profiler/ui/Sidebar.svelte.d.ts +7 -0
- package/dist/profiler/ui/Site.svelte +20 -0
- package/dist/profiler/ui/Site.svelte.d.ts +23 -0
- package/dist/profiler/ui/SiteView.svelte +385 -0
- package/dist/profiler/ui/SiteView.svelte.d.ts +27 -0
- package/dist/profiler/ui/ThemeSwitch.svelte +78 -0
- package/dist/profiler/ui/ThemeSwitch.svelte.d.ts +3 -0
- package/dist/profiler/ui/Timeline.svelte +672 -0
- package/dist/profiler/ui/Timeline.svelte.d.ts +22 -0
- package/dist/profiler/ui/Treemap.svelte +278 -0
- package/dist/profiler/ui/Treemap.svelte.d.ts +20 -0
- package/dist/profiler/ui/Upload.svelte +19 -0
- package/dist/profiler/ui/Upload.svelte.d.ts +10 -0
- package/dist/profiler/ui/UploadForm.svelte +59 -0
- package/dist/profiler/ui/UploadForm.svelte.d.ts +6 -0
- package/dist/profiler/ui/WaitingTable.svelte +55 -0
- package/dist/profiler/ui/WaitingTable.svelte.d.ts +14 -0
- package/dist/profiler/ui/Waterfall.svelte +371 -0
- package/dist/profiler/ui/Waterfall.svelte.d.ts +7 -0
- package/dist/profiler/ui/WhatIf.svelte +87 -0
- package/dist/profiler/ui/WhatIf.svelte.d.ts +18 -0
- package/dist/profiler/ui/fake-progress.svelte.d.ts +15 -0
- package/dist/profiler/ui/fake-progress.svelte.js +38 -0
- package/dist/profiler/ui/format.d.ts +20 -0
- package/dist/profiler/ui/format.js +71 -0
- package/dist/profiler/ui/permalink.d.ts +9 -0
- package/dist/profiler/ui/permalink.js +108 -0
- package/dist/profiler/ui/press.d.ts +5 -0
- package/dist/profiler/ui/press.js +19 -0
- package/dist/profiler/ui/profiler.css +650 -0
- package/dist/profiler/ui/report-data.d.ts +189 -0
- package/dist/profiler/ui/report-data.js +393 -0
- package/dist/profiler/ui/report-request.d.ts +6 -0
- package/dist/profiler/ui/report-request.js +50 -0
- package/dist/profiler/ui/row-anchor.svelte.d.ts +12 -0
- package/dist/profiler/ui/row-anchor.svelte.js +44 -0
- package/dist/profiler/ui/sort.svelte.d.ts +14 -0
- package/dist/profiler/ui/sort.svelte.js +37 -0
- package/dist/profiler/ui/store.d.ts +45 -0
- package/dist/profiler/ui/store.js +92 -0
- package/dist/profiler/visit.d.ts +389 -0
- package/dist/profiler/visit.js +1002 -0
- package/dist/report.d.ts +5 -0
- package/dist/report.js +4 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +38 -0
- package/dist/vite.d.ts +12 -0
- package/dist/vite.js +78 -0
- package/package.json +103 -3
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<script>/**
|
|
2
|
+
* THE DOCUMENT AS A BYTE STRIP — one bar, every byte coloured by what it is (byte-strip.ts),
|
|
3
|
+
* with the time each part left the server (the strip is in write order) and, when this
|
|
4
|
+
* browser's visit is known, the time it arrived. A `wake:'load'` island: hover a segment, click
|
|
5
|
+
* a kind in the legend to light only that kind.
|
|
6
|
+
*/
|
|
7
|
+
import { arrival_ms, left_at } from "../byte-strip.js";
|
|
8
|
+
import { fmt_bytes, fmt_ms } from "./format.js";
|
|
9
|
+
let { strip, names = {}, nav = null } = $props();
|
|
10
|
+
const COLOR = {
|
|
11
|
+
head: "#374151",
|
|
12
|
+
style: "#d9a03d",
|
|
13
|
+
"css-link": "#b8860b",
|
|
14
|
+
runtime: "#8b5cf6",
|
|
15
|
+
script: "#f0883e",
|
|
16
|
+
seed: "#58a6ff",
|
|
17
|
+
"remote-seed": "#1f6feb",
|
|
18
|
+
props: "#79c0ff",
|
|
19
|
+
holes: "#a371f7",
|
|
20
|
+
island: "#2ea043",
|
|
21
|
+
lake: "#3fb950",
|
|
22
|
+
hole: "#56d364",
|
|
23
|
+
shadow: "#ff7b72",
|
|
24
|
+
markup: "#4b5563"
|
|
25
|
+
};
|
|
26
|
+
const LABEL = {
|
|
27
|
+
head: "head markup",
|
|
28
|
+
style: "inline styles",
|
|
29
|
+
"css-link": "stylesheet links",
|
|
30
|
+
runtime: "runtime bootstrap",
|
|
31
|
+
script: "other scripts",
|
|
32
|
+
seed: "page seed",
|
|
33
|
+
"remote-seed": "remote seed",
|
|
34
|
+
props: "island props",
|
|
35
|
+
holes: "holes record",
|
|
36
|
+
island: "island markup",
|
|
37
|
+
lake: "lakes",
|
|
38
|
+
hole: "holes",
|
|
39
|
+
shadow: "shadow DOM",
|
|
40
|
+
markup: "markup"
|
|
41
|
+
};
|
|
42
|
+
const kinds = $derived(Object.keys(strip.by_kind).filter((k) => (strip.by_kind[k] ?? 0) > 0 && k !== "shadow").sort((a, b) => (strip.by_kind[b] ?? 0) - (strip.by_kind[a] ?? 0)));
|
|
43
|
+
let lit = $state(null);
|
|
44
|
+
let tip = $state(null);
|
|
45
|
+
const pct = (n) => strip.total ? n / strip.total * 100 : 0;
|
|
46
|
+
const name_of = (s) => s.kind === "island" && s.label ? names[s.label] ?? s.label.slice(0, 8) : s.label ?? "";
|
|
47
|
+
const islands = $derived(strip.segments.filter((s) => s.kind === "island"));
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<div
|
|
51
|
+
class="strip"
|
|
52
|
+
role="img"
|
|
53
|
+
aria-label="the document, byte by byte"
|
|
54
|
+
onmouseleave={() => (tip = null)}
|
|
55
|
+
>
|
|
56
|
+
{#each strip.segments as s, i (i)}
|
|
57
|
+
<div
|
|
58
|
+
class="seg"
|
|
59
|
+
class:dim={lit && s.kind !== lit}
|
|
60
|
+
style="width:{Math.max(pct(s.end - s.start), 0.15)}%;background:{COLOR[s.kind]}"
|
|
61
|
+
role="presentation"
|
|
62
|
+
onmouseenter={(e) => (tip = { s, x: e.clientX, y: e.clientY })}
|
|
63
|
+
onmousemove={(e) => (tip = { s, x: e.clientX, y: e.clientY })}
|
|
64
|
+
></div>
|
|
65
|
+
{/each}
|
|
66
|
+
</div>
|
|
67
|
+
{#if tip}
|
|
68
|
+
<div class="tip" style="left:{tip.x + 12}px;top:{tip.y + 12}px">
|
|
69
|
+
<b>{LABEL[tip.s.kind]}{name_of(tip.s) ? ` · ${name_of(tip.s)}` : ''}</b><br />
|
|
70
|
+
{fmt_bytes(tip.s.end - tip.s.start)} · {pct(tip.s.end - tip.s.start).toFixed(1)}% of the
|
|
71
|
+
document · bytes {tip.s.start.toLocaleString()}–{tip.s.end.toLocaleString()}
|
|
72
|
+
{#if strip.chunks}{@const l0 = left_at(tip.s.start, strip.chunks)}{@const l1 = left_at(
|
|
73
|
+
Math.max(tip.s.start, tip.s.end - 1),
|
|
74
|
+
strip.chunks
|
|
75
|
+
)}{#if l0 !== undefined && l1 !== undefined}<br />left the server {fmt_ms(l0)}{l1 !== l0
|
|
76
|
+
? ` → ${fmt_ms(l1)}`
|
|
77
|
+
: ''} ms into the render{/if}{/if}
|
|
78
|
+
{#if nav}{@const a = arrival_ms(tip.s, strip.total, nav.res_start, nav.res_end)}<br />arrives {fmt_ms(
|
|
79
|
+
a.first
|
|
80
|
+
)} → {fmt_ms(a.last)} ms after the click (steady download assumed){/if}
|
|
81
|
+
</div>
|
|
82
|
+
{/if}
|
|
83
|
+
{#if strip.chunks}
|
|
84
|
+
<p class="hint">
|
|
85
|
+
Streamed in {strip.chunks.length} chunks: the first left the server {fmt_ms(strip.chunks[0].t)} ms
|
|
86
|
+
into the render, the last {fmt_ms(strip.chunks[strip.chunks.length - 1].t)} ms. Hover a segment for
|
|
87
|
+
when its bytes left.
|
|
88
|
+
</p>
|
|
89
|
+
{/if}
|
|
90
|
+
<div class="legend">
|
|
91
|
+
{#each kinds as k (k)}
|
|
92
|
+
<button
|
|
93
|
+
class="key"
|
|
94
|
+
class:on={lit === k}
|
|
95
|
+
onclick={() => (lit = lit === k ? null : k)}
|
|
96
|
+
title="click to light only this kind"
|
|
97
|
+
>
|
|
98
|
+
<i style="background:{COLOR[k]}"></i>{LABEL[k]} <b>{fmt_bytes(strip.by_kind[k] ?? 0)}</b>
|
|
99
|
+
<span class="hint">{pct(strip.by_kind[k] ?? 0).toFixed(0)}%</span>
|
|
100
|
+
</button>
|
|
101
|
+
{/each}
|
|
102
|
+
{#if strip.shadow_count}
|
|
103
|
+
<span class="key static"
|
|
104
|
+
><i style="background:{COLOR.shadow}"></i>shadow DOM inside the above
|
|
105
|
+
<b>{fmt_bytes(strip.shadow_bytes)}</b>
|
|
106
|
+
<span class="hint">{strip.shadow_count} roots</span></span
|
|
107
|
+
>
|
|
108
|
+
{/if}
|
|
109
|
+
<span class="hint">total {fmt_bytes(strip.total)}</span>
|
|
110
|
+
</div>
|
|
111
|
+
{#if islands.length}
|
|
112
|
+
<p class="hint">
|
|
113
|
+
Islands in document order: {#each islands as s, i (i)}{#if i > 0},
|
|
114
|
+
{/if}<b>{name_of(s)}</b>
|
|
115
|
+
{fmt_bytes(s.end - s.start)}{/each}.
|
|
116
|
+
{#if nav}The last byte arrives {fmt_ms(nav.res_end)} ms after the click; everything after the seed
|
|
117
|
+
and the props tail cannot paint before they do.{/if}
|
|
118
|
+
</p>
|
|
119
|
+
{/if}
|
|
120
|
+
|
|
121
|
+
<style>
|
|
122
|
+
.strip {
|
|
123
|
+
display: flex;
|
|
124
|
+
height: 26px;
|
|
125
|
+
border-radius: 4px;
|
|
126
|
+
overflow: hidden;
|
|
127
|
+
border: 1px solid var(--line);
|
|
128
|
+
background: var(--bg-sunken);
|
|
129
|
+
}
|
|
130
|
+
.seg {
|
|
131
|
+
height: 100%;
|
|
132
|
+
min-width: 1px;
|
|
133
|
+
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.35);
|
|
134
|
+
transition: opacity 0.12s;
|
|
135
|
+
}
|
|
136
|
+
.seg.dim {
|
|
137
|
+
opacity: 0.15;
|
|
138
|
+
}
|
|
139
|
+
.legend {
|
|
140
|
+
display: flex;
|
|
141
|
+
flex-wrap: wrap;
|
|
142
|
+
gap: 6px 14px;
|
|
143
|
+
margin: 8px 0 4px;
|
|
144
|
+
font-size: 12px;
|
|
145
|
+
align-items: center;
|
|
146
|
+
}
|
|
147
|
+
.key {
|
|
148
|
+
background: none;
|
|
149
|
+
border: 0;
|
|
150
|
+
color: inherit;
|
|
151
|
+
font: inherit;
|
|
152
|
+
padding: 0;
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
display: inline-flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
gap: 5px;
|
|
157
|
+
}
|
|
158
|
+
.key.on {
|
|
159
|
+
text-decoration: underline;
|
|
160
|
+
}
|
|
161
|
+
/* a legend line that is not a filter: no hand, nothing to press */
|
|
162
|
+
.key.static {
|
|
163
|
+
cursor: default;
|
|
164
|
+
}
|
|
165
|
+
.key i {
|
|
166
|
+
width: 10px;
|
|
167
|
+
height: 10px;
|
|
168
|
+
border-radius: 2px;
|
|
169
|
+
display: inline-block;
|
|
170
|
+
}
|
|
171
|
+
.tip {
|
|
172
|
+
position: fixed;
|
|
173
|
+
z-index: 20;
|
|
174
|
+
background: var(--bg-raised);
|
|
175
|
+
border: 1px solid var(--line-strong);
|
|
176
|
+
border-radius: 6px;
|
|
177
|
+
padding: 6px 8px;
|
|
178
|
+
font-size: 12px;
|
|
179
|
+
max-width: 380px;
|
|
180
|
+
pointer-events: none;
|
|
181
|
+
}
|
|
182
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE DOCUMENT AS A BYTE STRIP — one bar, every byte coloured by what it is (byte-strip.ts),
|
|
3
|
+
* with the time each part left the server (the strip is in write order) and, when this
|
|
4
|
+
* browser's visit is known, the time it arrived. A `wake:'load'` island: hover a segment, click
|
|
5
|
+
* a kind in the legend to light only that kind.
|
|
6
|
+
*/
|
|
7
|
+
import { type ByteStrip } from '../byte-strip.js';
|
|
8
|
+
type $$ComponentProps = {
|
|
9
|
+
strip: ByteStrip;
|
|
10
|
+
names?: Record<string, string>;
|
|
11
|
+
nav?: {
|
|
12
|
+
res_start: number;
|
|
13
|
+
res_end: number;
|
|
14
|
+
} | null;
|
|
15
|
+
};
|
|
16
|
+
declare const ByteStrip: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
17
|
+
type ByteStrip = ReturnType<typeof ByteStrip>;
|
|
18
|
+
export default ByteStrip;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script>/**
|
|
2
|
+
* COMPARE two reports (`/compare/[a]/[b]`). The server's comparison when it holds both reports
|
|
3
|
+
* (static render, CompareBody); otherwise the LocalCompare island builds the same comparison
|
|
4
|
+
* from this browser's store — a restarted server or an ephemeral host forgets, the browser does
|
|
5
|
+
* not.
|
|
6
|
+
*/
|
|
7
|
+
import Shell from "./Shell.svelte";
|
|
8
|
+
import CompareBody from "./CompareBody.svelte";
|
|
9
|
+
import LocalCompare from "./LocalCompare.svelte" with { wake: "load" };
|
|
10
|
+
let { data } = $props();
|
|
11
|
+
const { base, cmp, a, b } = $derived(data);
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<svelte:head
|
|
15
|
+
><title>compare — {cmp ? `${cmp.a.label} vs ${cmp.b.label}` : `${a} vs ${b}`}</title></svelte:head
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
<Shell {base}>
|
|
19
|
+
{#if cmp}
|
|
20
|
+
<CompareBody {base} {cmp} />
|
|
21
|
+
{:else}
|
|
22
|
+
<LocalCompare {base} {a} {b} />
|
|
23
|
+
{/if}
|
|
24
|
+
</Shell>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
declare const Compare: import("svelte").Component<{
|
|
2
|
+
data: {
|
|
3
|
+
base: string;
|
|
4
|
+
cmp: import("../compare.js").Comparison | null;
|
|
5
|
+
a: string;
|
|
6
|
+
b: string;
|
|
7
|
+
};
|
|
8
|
+
form: null;
|
|
9
|
+
params: {
|
|
10
|
+
a: string;
|
|
11
|
+
b: string;
|
|
12
|
+
};
|
|
13
|
+
search: {};
|
|
14
|
+
}, {}, "">;
|
|
15
|
+
type Compare = ReturnType<typeof Compare>;
|
|
16
|
+
export default Compare;
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
<script>/**
|
|
2
|
+
* THE COMPARISON BODY: summary deltas, phases, components and functions side by side, findings
|
|
3
|
+
* that appeared or went away. One renderer, two sources — the server's comparison (Compare
|
|
4
|
+
* page) or one computed in the browser from the store (LocalCompare island). A positive delta
|
|
5
|
+
* is a regression (b slower than a) and reads red.
|
|
6
|
+
*/
|
|
7
|
+
import { fmt_ms } from "./format.js";
|
|
8
|
+
let { base, cmp } = $props();
|
|
9
|
+
const when = (ms) => new Date(ms).toLocaleString();
|
|
10
|
+
const fmt = (n, unit) => unit === "ms" ? fmt_ms(n) + " ms" : unit === "KB" ? Math.round(n) + " KB" : String(Math.round(n));
|
|
11
|
+
const sign = (d, unit) => (d > 0 ? "+" : "") + (unit === "ms" ? fmt_ms(d) : Math.round(d));
|
|
12
|
+
const pct = (a, d) => a > 0 ? ` (${d > 0 ? "+" : ""}${(d / a * 100).toFixed(0)}%)` : "";
|
|
13
|
+
const cls = (d, floor = .5) => d > floor ? "worse" : d < -floor ? "better" : "";
|
|
14
|
+
const comps = $derived(cmp.components.filter((r) => Math.abs(r.d_self) >= .05 || Math.abs(r.d_total) >= .5).slice(0, 40));
|
|
15
|
+
const fns = $derived(cmp.functions.filter((r) => Math.abs(r.d_self) >= .05).slice(0, 40));
|
|
16
|
+
const mb = (b) => !b ? "—" : b >= 1048576 ? (b / 1048576).toFixed(1) + " MB" : Math.round(b / 1024) + " KB";
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<h1>Compare <small>{cmp.a.label} → {cmp.b.label}</small></h1>
|
|
20
|
+
<p class="hint">
|
|
21
|
+
<a href={base}>← dashboard</a> ·
|
|
22
|
+
<b>A</b> <a href="{base}/report/{cmp.a.id}">{cmp.a.label}</a> ({when(cmp.a.created)}) ·
|
|
23
|
+
<b>B</b> <a href="{base}/report/{cmp.b.id}">{cmp.b.label}</a> ({when(cmp.b.created)}) ·
|
|
24
|
+
<a href="{base}/compare/{cmp.b.id}/{cmp.a.id}">swap</a>. A positive delta means B is slower.
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
<h2>Summary</h2>
|
|
28
|
+
<table>
|
|
29
|
+
<thead
|
|
30
|
+
><tr
|
|
31
|
+
><th><span class="sr-only">what</span></th><th class="num">A</th><th class="num">B</th><th
|
|
32
|
+
class="num">Δ</th
|
|
33
|
+
></tr
|
|
34
|
+
></thead
|
|
35
|
+
>
|
|
36
|
+
<tbody>
|
|
37
|
+
{#each cmp.summary as r (r.label)}
|
|
38
|
+
<tr>
|
|
39
|
+
<td class="fn">{r.label}</td>
|
|
40
|
+
<td class="num">{fmt(r.a, r.unit)}</td>
|
|
41
|
+
<td class="num">{fmt(r.b, r.unit)}</td>
|
|
42
|
+
<td class="num {cls(r.d, r.unit === 'ms' ? 0.5 : 0)}"
|
|
43
|
+
><b>{sign(r.d, r.unit)}</b>{pct(r.a, r.d)}</td
|
|
44
|
+
>
|
|
45
|
+
</tr>
|
|
46
|
+
{/each}
|
|
47
|
+
</tbody>
|
|
48
|
+
</table>
|
|
49
|
+
|
|
50
|
+
{#if cmp.order}
|
|
51
|
+
{@const o = cmp.order}
|
|
52
|
+
<!-- the run order trap: the later report ran on the heap the earlier one's renders filled -->
|
|
53
|
+
<p class="same-render">
|
|
54
|
+
<b>Run order changes this result.</b> Both ran on one server, and {o.earlier.toUpperCase()} kept {o.kept_mb}
|
|
55
|
+
MB alive on every render.
|
|
56
|
+
{o.earlier === 'a' ? 'B' : 'A'} ran {o.requests_between} requests later on that fuller heap (more
|
|
57
|
+
garbage collection, slower allocation), so it reads slower than its code is. Restart the server between
|
|
58
|
+
the two, or profile them in the other order, before trusting the difference.
|
|
59
|
+
</p>
|
|
60
|
+
{/if}
|
|
61
|
+
|
|
62
|
+
{#if cmp.render_same}
|
|
63
|
+
<!-- the ground truth: did the render get faster? When not, per-line moves are attribution -->
|
|
64
|
+
<p class="same-render">
|
|
65
|
+
<b>No real change in render time</b> ({fmt_ms(cmp.render_same.a_ms)} ms → {fmt_ms(
|
|
66
|
+
cmp.render_same.b_ms
|
|
67
|
+
)} ms, within the runs' own spread of {fmt_ms(cmp.render_same.noise_ms)} ms). V8 decides what to inline
|
|
68
|
+
differently from run to run, so the same CPU time can be charged to a different line: the moves below
|
|
69
|
+
are marked <i>shifted</i>, not fixed or worse. Waiting and memory keep their verdicts.
|
|
70
|
+
</p>
|
|
71
|
+
{/if}
|
|
72
|
+
|
|
73
|
+
{#if cmp.fix_check && !cmp.render_same}
|
|
74
|
+
{@const f = cmp.fix_check}
|
|
75
|
+
<!-- did the fix pay: what the fixed patterns promised against the render's measured change -->
|
|
76
|
+
<p class="fix-check {f.verdict}">
|
|
77
|
+
<b>
|
|
78
|
+
{#if f.verdict === 'as-expected'}The fix paid off as expected{:else if f.verdict === 'less'}The
|
|
79
|
+
fix saved less than expected{:else}The fix saved more than expected{/if}
|
|
80
|
+
</b>: the patterns that went away or shrank were expected to save about {fmt_ms(f.predicted_ms)} ms
|
|
81
|
+
per render, and the median render got {f.measured_ms >= 0
|
|
82
|
+
? `${fmt_ms(f.measured_ms)} ms faster`
|
|
83
|
+
: `${fmt_ms(-f.measured_ms)} ms slower`}.
|
|
84
|
+
{#if f.verdict === 'less' && f.skewed}
|
|
85
|
+
Most likely the run order above, not the fix: B ran on the heap A filled, so it reads slower
|
|
86
|
+
than its code is. Restart the server and profile B again first.
|
|
87
|
+
{:else if f.skewed}
|
|
88
|
+
B ran on the heap A filled (see above), so the real saving is likely larger still.
|
|
89
|
+
{:else if f.verdict === 'less'}
|
|
90
|
+
The time most likely moved: look for lines and patterns that got worse below, or a wait that
|
|
91
|
+
grew. If nothing did, profile both again with more renders; the runs may just be noisy.
|
|
92
|
+
{:else if f.verdict === 'more'}
|
|
93
|
+
The change removed more than the patterns saw: a cost that sat under the reporting floor, or
|
|
94
|
+
less garbage to collect.
|
|
95
|
+
{/if}
|
|
96
|
+
</p>
|
|
97
|
+
{/if}
|
|
98
|
+
|
|
99
|
+
{#if cmp.patterns?.length}
|
|
100
|
+
<h2>
|
|
101
|
+
Slow patterns <span class="hint" style="font-weight:400"
|
|
102
|
+
>(what the change fixed, and what it brought in)</span
|
|
103
|
+
>
|
|
104
|
+
</h2>
|
|
105
|
+
<table>
|
|
106
|
+
<thead
|
|
107
|
+
><tr
|
|
108
|
+
><th><span class="sr-only">what</span></th><th>pattern</th><th class="num">A</th><th
|
|
109
|
+
class="num">B</th
|
|
110
|
+
><th class="num">Δ</th></tr
|
|
111
|
+
></thead
|
|
112
|
+
>
|
|
113
|
+
<tbody>
|
|
114
|
+
{#each cmp.patterns as p (p.kind + '\0' + p.title)}
|
|
115
|
+
<tr>
|
|
116
|
+
<td class="status {p.status}">{p.status}</td>
|
|
117
|
+
<td class="fn">{p.title}</td>
|
|
118
|
+
<td class="num">{p.a_ms ? fmt_ms(p.a_ms) + ' ms' : '—'}</td>
|
|
119
|
+
<td class="num">{p.b_ms ? fmt_ms(p.b_ms) + ' ms' : '—'}</td>
|
|
120
|
+
<td class="num {cls(p.d_ms)}"
|
|
121
|
+
><b>{sign(p.d_ms, 'ms')}</b>{#if p.wait} waiting{/if}</td
|
|
122
|
+
>
|
|
123
|
+
</tr>
|
|
124
|
+
{/each}
|
|
125
|
+
</tbody>
|
|
126
|
+
</table>
|
|
127
|
+
{/if}
|
|
128
|
+
|
|
129
|
+
{#if cmp.drill?.length}
|
|
130
|
+
<h2>
|
|
131
|
+
Where one render's time moved <span class="hint" style="font-weight:400"
|
|
132
|
+
>(the rows of the drill-down that changed, down to the call or line that did it; one render
|
|
133
|
+
each side)</span
|
|
134
|
+
>
|
|
135
|
+
</h2>
|
|
136
|
+
<table>
|
|
137
|
+
<thead
|
|
138
|
+
><tr
|
|
139
|
+
><th><span class="sr-only">what</span></th><th>where</th><th class="num">A</th><th
|
|
140
|
+
class="num">B</th
|
|
141
|
+
><th class="num">Δ</th></tr
|
|
142
|
+
></thead
|
|
143
|
+
>
|
|
144
|
+
<tbody>
|
|
145
|
+
{#each cmp.drill as r, i (r.path.join('\0') + '\0' + i)}
|
|
146
|
+
<tr>
|
|
147
|
+
<td class="status {r.status}">{r.status}</td>
|
|
148
|
+
<td class="fn">
|
|
149
|
+
<span class="hint"
|
|
150
|
+
>{r.path.slice(0, -1).join(' › ')}{r.path.length > 1 ? ' › ' : ''}</span
|
|
151
|
+
><b
|
|
152
|
+
>{r.kind === 'line'
|
|
153
|
+
? (r.at ?? r.path[r.path.length - 1])
|
|
154
|
+
: r.path[r.path.length - 1]}</b
|
|
155
|
+
>
|
|
156
|
+
{#if r.at && r.kind !== 'line' && r.kind !== 'lane'}<div class="code">{r.at}</div>{/if}
|
|
157
|
+
</td>
|
|
158
|
+
<!-- ≤: that side only had an "N more" fold, so the row is at most that -->
|
|
159
|
+
<td class="num">{r.upto === 'a' ? '≤ ' : ''}{r.a_ms ? fmt_ms(r.a_ms) + ' ms' : '—'}</td>
|
|
160
|
+
<td class="num">{r.upto === 'b' ? '≤ ' : ''}{r.b_ms ? fmt_ms(r.b_ms) + ' ms' : '—'}</td>
|
|
161
|
+
<td class="num {cls(r.d_ms)}"><b>{r.upto ? 'at least ' : ''}{sign(r.d_ms, 'ms')}</b></td>
|
|
162
|
+
</tr>
|
|
163
|
+
{/each}
|
|
164
|
+
</tbody>
|
|
165
|
+
</table>
|
|
166
|
+
{/if}
|
|
167
|
+
|
|
168
|
+
{#if cmp.lines?.filter((l) => l.status !== 'same').length}
|
|
169
|
+
<h2>
|
|
170
|
+
The exact lines <span class="hint" style="font-weight:400"
|
|
171
|
+
>(your costliest lines, per render, matched by their code so an edit above them does not lose
|
|
172
|
+
them)</span
|
|
173
|
+
>
|
|
174
|
+
</h2>
|
|
175
|
+
<table>
|
|
176
|
+
<thead
|
|
177
|
+
><tr
|
|
178
|
+
><th><span class="sr-only">what</span></th><th>line</th><th class="num">A</th><th
|
|
179
|
+
class="num">B</th
|
|
180
|
+
><th class="num">Δ</th><th class="num">made A → B</th><th class="num">kept A → B</th></tr
|
|
181
|
+
></thead
|
|
182
|
+
>
|
|
183
|
+
<tbody>
|
|
184
|
+
{#each cmp.lines.filter((l) => l.status !== 'same') as l (l.file + '\0' + l.code + '\0' + l.line)}
|
|
185
|
+
<tr>
|
|
186
|
+
<td class="status {l.status}">{l.status}</td>
|
|
187
|
+
<td class="fn"
|
|
188
|
+
><code>{l.file}:{l.line}</code>
|
|
189
|
+
<div class="code">{l.code}</div></td
|
|
190
|
+
>
|
|
191
|
+
<td class="num">{l.a_ms ? fmt_ms(l.a_ms) + ' ms' : '—'}</td>
|
|
192
|
+
<td class="num">{l.b_ms ? fmt_ms(l.b_ms) + ' ms' : '—'}</td>
|
|
193
|
+
<td class="num {cls(l.d_ms)}"><b>{sign(l.d_ms, 'ms')}</b></td>
|
|
194
|
+
<td class="num">{mb(l.a_bytes)} → {mb(l.b_bytes)}</td>
|
|
195
|
+
<td class="num">{mb(l.a_kept)} → {mb(l.b_kept)}</td>
|
|
196
|
+
</tr>
|
|
197
|
+
{/each}
|
|
198
|
+
</tbody>
|
|
199
|
+
</table>
|
|
200
|
+
{/if}
|
|
201
|
+
|
|
202
|
+
{#if cmp.phases.length}
|
|
203
|
+
<h2>Phases</h2>
|
|
204
|
+
<table>
|
|
205
|
+
<thead
|
|
206
|
+
><tr><th>phase</th><th class="num">A</th><th class="num">B</th><th class="num">Δ</th></tr
|
|
207
|
+
></thead
|
|
208
|
+
>
|
|
209
|
+
<tbody>
|
|
210
|
+
{#each cmp.phases as p (p.phase)}
|
|
211
|
+
<tr>
|
|
212
|
+
<td class="fn">{p.label}</td>
|
|
213
|
+
<td class="num">{fmt_ms(p.a)} ms</td>
|
|
214
|
+
<td class="num">{fmt_ms(p.b)} ms</td>
|
|
215
|
+
<td class="num {cls(p.d)}"><b>{sign(p.d, 'ms')}</b>{pct(p.a, p.d)}</td>
|
|
216
|
+
</tr>
|
|
217
|
+
{/each}
|
|
218
|
+
</tbody>
|
|
219
|
+
</table>
|
|
220
|
+
{/if}
|
|
221
|
+
|
|
222
|
+
{#if cmp.findings.added.length || cmp.findings.gone.length}
|
|
223
|
+
<h2>Findings</h2>
|
|
224
|
+
{#each cmp.findings.added as f (f)}<p class="finding worse">+ {f}</p>{/each}
|
|
225
|
+
{#each cmp.findings.gone as f (f)}<p class="finding better">− {f}</p>{/each}
|
|
226
|
+
{/if}
|
|
227
|
+
|
|
228
|
+
{#if cmp.paths.length}
|
|
229
|
+
<h2>Paths to fix <span class="hint" style="font-weight:400">(by change)</span></h2>
|
|
230
|
+
<p class="hint">
|
|
231
|
+
Each path is the hot functions under one caller. A path that shrank is a fix that landed; one
|
|
232
|
+
that appeared is new work under that caller.
|
|
233
|
+
</p>
|
|
234
|
+
<table>
|
|
235
|
+
<thead
|
|
236
|
+
><tr
|
|
237
|
+
><th>caller</th><th>where</th><th class="num">A</th><th class="num">B</th><th class="num"
|
|
238
|
+
>Δ</th
|
|
239
|
+
><th>functions</th></tr
|
|
240
|
+
></thead
|
|
241
|
+
>
|
|
242
|
+
<tbody>
|
|
243
|
+
{#each cmp.paths as p (p.owner)}
|
|
244
|
+
<tr>
|
|
245
|
+
<td class="fn"
|
|
246
|
+
><b>{p.owner}</b>{#if p.only}<span class="hint">
|
|
247
|
+
(only in {p.only.toUpperCase()})</span
|
|
248
|
+
>{/if}</td
|
|
249
|
+
>
|
|
250
|
+
<td class="file"
|
|
251
|
+
>{p.file}{#if p.line > 0}:{p.line}{/if}</td
|
|
252
|
+
>
|
|
253
|
+
<td class="num">{p.a_ms ? fmt_ms(p.a_ms) : '—'}</td>
|
|
254
|
+
<td class="num">{p.b_ms ? fmt_ms(p.b_ms) : '—'}</td>
|
|
255
|
+
<td class="num {cls(p.d_ms, 1)}"><b>{sign(p.d_ms, 'ms')}</b>{pct(p.a_ms, p.d_ms)}</td>
|
|
256
|
+
<td class="hint">{(p.b_fns.length ? p.b_fns : p.a_fns).slice(0, 6).join(', ')}</td>
|
|
257
|
+
</tr>
|
|
258
|
+
{/each}
|
|
259
|
+
</tbody>
|
|
260
|
+
</table>
|
|
261
|
+
{/if}
|
|
262
|
+
|
|
263
|
+
{#if cmp.gc_makers.length}
|
|
264
|
+
<h2>
|
|
265
|
+
Garbage makers <span class="hint" style="font-weight:400"
|
|
266
|
+
>(what each line allocated and the GC it caused, by change)</span
|
|
267
|
+
>
|
|
268
|
+
</h2>
|
|
269
|
+
<table>
|
|
270
|
+
<thead
|
|
271
|
+
><tr
|
|
272
|
+
><th>line</th><th>in</th><th class="num">A allocated</th><th class="num">B allocated</th><th
|
|
273
|
+
class="num">Δ</th
|
|
274
|
+
><th class="num">A GC ms</th><th class="num">B GC ms</th><th class="num">Δ</th></tr
|
|
275
|
+
></thead
|
|
276
|
+
>
|
|
277
|
+
<tbody>
|
|
278
|
+
{#each cmp.gc_makers.slice(0, 25) as m (m.name + m.caller)}
|
|
279
|
+
<tr>
|
|
280
|
+
<td class="fn"
|
|
281
|
+
><b>{m.name}</b>{#if m.caller}<span class="hint">
|
|
282
|
+
← {m.caller}</span
|
|
283
|
+
>{/if}{#if m.only}<span class="hint"> (only in {m.only.toUpperCase()})</span>{/if}</td
|
|
284
|
+
>
|
|
285
|
+
<td class="fn">{m.component ?? '—'}</td>
|
|
286
|
+
<td class="num">{m.a_mb} MB</td>
|
|
287
|
+
<td class="num">{m.b_mb} MB</td>
|
|
288
|
+
<td class="num {cls(m.d_mb, 0.5)}"
|
|
289
|
+
><b>{m.d_mb > 0 ? '+' : ''}{m.d_mb} MB</b>{pct(m.a_mb, m.d_mb)}</td
|
|
290
|
+
>
|
|
291
|
+
<td class="num">{fmt_ms(m.a_gc_ms)}</td>
|
|
292
|
+
<td class="num">{fmt_ms(m.b_gc_ms)}</td>
|
|
293
|
+
<td class="num {cls(m.d_gc_ms, 0.5)}"><b>{sign(m.d_gc_ms, 'ms')}</b></td>
|
|
294
|
+
</tr>
|
|
295
|
+
{/each}
|
|
296
|
+
</tbody>
|
|
297
|
+
</table>
|
|
298
|
+
{/if}
|
|
299
|
+
|
|
300
|
+
<h2>Components <span class="hint" style="font-weight:400">(by change in self time)</span></h2>
|
|
301
|
+
{#if comps.length}
|
|
302
|
+
<table>
|
|
303
|
+
<thead>
|
|
304
|
+
<tr
|
|
305
|
+
><th>component</th><th class="num">self A</th><th class="num">self B</th><th class="num"
|
|
306
|
+
>Δ self</th
|
|
307
|
+
><th class="num">total A</th><th class="num">total B</th><th class="num">Δ total</th><th
|
|
308
|
+
class="num">renders</th
|
|
309
|
+
></tr
|
|
310
|
+
>
|
|
311
|
+
</thead>
|
|
312
|
+
<tbody>
|
|
313
|
+
{#each comps as r (r.name)}
|
|
314
|
+
<tr>
|
|
315
|
+
<td class="fn"
|
|
316
|
+
><b>{r.name}</b>{#if r.only}<span class="hint">
|
|
317
|
+
only in {r.only.toUpperCase()}</span
|
|
318
|
+
>{/if}</td
|
|
319
|
+
>
|
|
320
|
+
<td class="num">{fmt_ms(r.a_self)}</td>
|
|
321
|
+
<td class="num">{fmt_ms(r.b_self)}</td>
|
|
322
|
+
<td class="num {cls(r.d_self, 0.05)}"><b>{sign(r.d_self, 'ms')}</b></td>
|
|
323
|
+
<td class="num">{fmt_ms(r.a_total)}</td>
|
|
324
|
+
<td class="num">{fmt_ms(r.b_total)}</td>
|
|
325
|
+
<td class="num {cls(r.d_total)}">{sign(r.d_total, 'ms')}</td>
|
|
326
|
+
<td class="num">{r.a_calls ?? '—'} → {r.b_calls ?? '—'}</td>
|
|
327
|
+
</tr>
|
|
328
|
+
{/each}
|
|
329
|
+
</tbody>
|
|
330
|
+
</table>
|
|
331
|
+
{:else}
|
|
332
|
+
<p class="hint">No component changed by more than 0.05 ms.</p>
|
|
333
|
+
{/if}
|
|
334
|
+
|
|
335
|
+
<h2>Functions <span class="hint" style="font-weight:400">(by change in self time)</span></h2>
|
|
336
|
+
{#if fns.length}
|
|
337
|
+
<table>
|
|
338
|
+
<thead>
|
|
339
|
+
<tr
|
|
340
|
+
><th>function</th><th>where</th><th class="num">self A</th><th class="num">self B</th><th
|
|
341
|
+
class="num">Δ</th
|
|
342
|
+
><th class="num">calls</th></tr
|
|
343
|
+
>
|
|
344
|
+
</thead>
|
|
345
|
+
<tbody>
|
|
346
|
+
{#each fns as r (r.key ?? r.name + r.file + ':' + r.line)}
|
|
347
|
+
<tr>
|
|
348
|
+
<td class="fn"
|
|
349
|
+
><b>{r.name}</b>{#if r.only}<span class="hint">
|
|
350
|
+
only in {r.only.toUpperCase()}</span
|
|
351
|
+
>{/if}</td
|
|
352
|
+
>
|
|
353
|
+
<td class="file"
|
|
354
|
+
>{r.file}{#if r.line > 0}:{r.line}{/if}</td
|
|
355
|
+
>
|
|
356
|
+
<td class="num">{fmt_ms(r.a_self)}</td>
|
|
357
|
+
<td class="num">{fmt_ms(r.b_self)}</td>
|
|
358
|
+
<td class="num {cls(r.d_self, 0.05)}"
|
|
359
|
+
><b>{sign(r.d_self, 'ms')}</b>{pct(r.a_self, r.d_self)}</td
|
|
360
|
+
>
|
|
361
|
+
<td class="num">{r.a_calls ?? '—'} → {r.b_calls ?? '—'}</td>
|
|
362
|
+
</tr>
|
|
363
|
+
{/each}
|
|
364
|
+
</tbody>
|
|
365
|
+
</table>
|
|
366
|
+
{:else}
|
|
367
|
+
<p class="hint">No function changed by more than 0.05 ms.</p>
|
|
368
|
+
{/if}
|
|
369
|
+
|
|
370
|
+
<style>
|
|
371
|
+
.status {
|
|
372
|
+
font-size: 12px;
|
|
373
|
+
font-weight: 600;
|
|
374
|
+
white-space: nowrap;
|
|
375
|
+
color: var(--text-faint);
|
|
376
|
+
}
|
|
377
|
+
.status.fixed,
|
|
378
|
+
.status.better {
|
|
379
|
+
color: var(--good);
|
|
380
|
+
}
|
|
381
|
+
.status.new,
|
|
382
|
+
.status.worse {
|
|
383
|
+
color: var(--bad);
|
|
384
|
+
}
|
|
385
|
+
.status.shifted {
|
|
386
|
+
color: var(--text-faint);
|
|
387
|
+
font-style: italic;
|
|
388
|
+
}
|
|
389
|
+
.fix-check {
|
|
390
|
+
margin: 12px 0;
|
|
391
|
+
padding: 10px 14px;
|
|
392
|
+
border: 1px solid var(--line);
|
|
393
|
+
border-left: 3px solid var(--c-green, #4a9d6e);
|
|
394
|
+
border-radius: 8px;
|
|
395
|
+
background: var(--bg-panel);
|
|
396
|
+
font-size: 13.5px;
|
|
397
|
+
line-height: 1.5;
|
|
398
|
+
}
|
|
399
|
+
.fix-check.less {
|
|
400
|
+
border-left-color: var(--c-amber, #d4a017);
|
|
401
|
+
}
|
|
402
|
+
.same-render {
|
|
403
|
+
margin: 12px 0;
|
|
404
|
+
padding: 10px 14px;
|
|
405
|
+
border: 1px solid var(--line);
|
|
406
|
+
border-radius: 8px;
|
|
407
|
+
background: var(--bg-panel);
|
|
408
|
+
font-size: 13.5px;
|
|
409
|
+
line-height: 1.5;
|
|
410
|
+
}
|
|
411
|
+
.code {
|
|
412
|
+
font-family: ui-monospace, monospace;
|
|
413
|
+
font-size: 11.5px;
|
|
414
|
+
color: var(--text-faint);
|
|
415
|
+
white-space: pre-wrap;
|
|
416
|
+
word-break: break-word;
|
|
417
|
+
max-width: 520px;
|
|
418
|
+
}
|
|
419
|
+
.worse {
|
|
420
|
+
color: var(--bad);
|
|
421
|
+
}
|
|
422
|
+
.better {
|
|
423
|
+
color: var(--good);
|
|
424
|
+
}
|
|
425
|
+
.finding {
|
|
426
|
+
margin: 4px 0;
|
|
427
|
+
font-size: 13px;
|
|
428
|
+
}
|
|
429
|
+
</style>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Comparison } from '../compare.js';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
base: string;
|
|
4
|
+
cmp: Comparison;
|
|
5
|
+
};
|
|
6
|
+
declare const CompareBody: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
7
|
+
type CompareBody = ReturnType<typeof CompareBody>;
|
|
8
|
+
export default CompareBody;
|