@ogygia/devtools 0.0.0 → 0.11.1
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 +24 -1
- 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 +99 -6
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
<script>/**
|
|
2
|
+
* THE WHOLE SITE — two pictures from the sink's rows (sink.ts), or this instance's own ring and
|
|
3
|
+
* sampler windows: the REQUEST CLOUD (every request as a dot: time across, duration up, colour
|
|
4
|
+
* by route; drag a range to get the hot functions of the sampler windows inside it) and the
|
|
5
|
+
* LAYER CAKE (per route, where the time went: CPU, waiting on calls, the rest). A `wake:'load'`
|
|
6
|
+
* island; `from` is a URL the rows are read from in this browser (the sink you own).
|
|
7
|
+
*/
|
|
8
|
+
import { parse_sink, cloud_points, layer_cake, hot_for_range } from "../sink.js";
|
|
9
|
+
import { fmt_ms } from "./format.js";
|
|
10
|
+
let { base, rows: initial, from = null, sink = null, ephemeral = false } = $props();
|
|
11
|
+
// the rows and the URL field are seeds: a `from` URL is read once, at setup, and replaces the rows
|
|
12
|
+
// svelte-ignore state_referenced_locally
|
|
13
|
+
let rows = $state(initial);
|
|
14
|
+
// svelte-ignore state_referenced_locally
|
|
15
|
+
let loading = $state(!!from);
|
|
16
|
+
let load_error = $state("");
|
|
17
|
+
// svelte-ignore state_referenced_locally
|
|
18
|
+
let from_input = $state(from ?? sink?.url ?? "");
|
|
19
|
+
// svelte-ignore state_referenced_locally
|
|
20
|
+
if (from) {
|
|
21
|
+
void fetch(from, { credentials: "omit" }).then(async (r) => {
|
|
22
|
+
if (!r.ok) throw new Error(`${r.status}`);
|
|
23
|
+
rows = parse_sink(await r.text());
|
|
24
|
+
}).catch((e) => load_error = `Could not read ${from}: ${e instanceof Error ? e.message : e}. The URL must be readable by this browser (CORS).`).finally(() => loading = false);
|
|
25
|
+
}
|
|
26
|
+
const points = $derived(cloud_points(rows));
|
|
27
|
+
const cake = $derived(layer_cake(rows));
|
|
28
|
+
const traps = $derived(rows.filter((r) => r.k === "trap"));
|
|
29
|
+
const windows = $derived(rows.filter((r) => r.k === "win").length);
|
|
30
|
+
// the cloud
|
|
31
|
+
const W = 1e3;
|
|
32
|
+
const H = 260;
|
|
33
|
+
const L = 48;
|
|
34
|
+
const B = 22;
|
|
35
|
+
const t0 = $derived(points.length ? points[0].t : 0);
|
|
36
|
+
const t1 = $derived(points.length ? Math.max(points[points.length - 1].t, t0 + 1e3) : 1);
|
|
37
|
+
const max_ms = $derived(Math.max(...points.map((p) => p.ms), 10));
|
|
38
|
+
const x = (t) => L + (t - t0) / (t1 - t0) * (W - L - 8);
|
|
39
|
+
const y = (ms) => H - B - Math.log10(Math.max(ms, 1)) / Math.log10(max_ms) * (H - B - 12);
|
|
40
|
+
const routes = $derived([...new Set(points.map((p) => p.route))]);
|
|
41
|
+
const PALETTE = [
|
|
42
|
+
"#58a6ff",
|
|
43
|
+
"#3fb950",
|
|
44
|
+
"#d9a03d",
|
|
45
|
+
"#a371f7",
|
|
46
|
+
"#ff7b72",
|
|
47
|
+
"#79c0ff",
|
|
48
|
+
"#f0883e",
|
|
49
|
+
"#56d364",
|
|
50
|
+
"#ffd166",
|
|
51
|
+
"#8b949e"
|
|
52
|
+
];
|
|
53
|
+
const color = (route) => PALETTE[routes.indexOf(route) % PALETTE.length];
|
|
54
|
+
const y_ticks = $derived([
|
|
55
|
+
1,
|
|
56
|
+
10,
|
|
57
|
+
100,
|
|
58
|
+
1e3,
|
|
59
|
+
1e4
|
|
60
|
+
].filter((v) => v <= max_ms * 1.5));
|
|
61
|
+
let brush = $state(null);
|
|
62
|
+
let drag = $state(null);
|
|
63
|
+
let svg_el = $state(null);
|
|
64
|
+
const t_at = (clientX) => {
|
|
65
|
+
if (!svg_el) return t0;
|
|
66
|
+
const r = svg_el.getBoundingClientRect();
|
|
67
|
+
const px = (clientX - r.left) / r.width * W;
|
|
68
|
+
return t0 + (px - L) / (W - L - 8) * (t1 - t0);
|
|
69
|
+
};
|
|
70
|
+
const range = $derived(brush ? {
|
|
71
|
+
lo: Math.min(brush.a, brush.b),
|
|
72
|
+
hi: Math.max(brush.a, brush.b)
|
|
73
|
+
} : null);
|
|
74
|
+
const selected = $derived(range ? points.filter((p) => p.t >= range.lo && p.t <= range.hi) : []);
|
|
75
|
+
const hot = $derived(range ? hot_for_range(rows, range.lo, range.hi) : []);
|
|
76
|
+
const p50 = (xs) => xs.length ? [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)] : 0;
|
|
77
|
+
let hover = $state(null);
|
|
78
|
+
const when = (ms) => new Date(ms).toLocaleString();
|
|
79
|
+
const cake_max = $derived(Math.max(...cake.map((c) => c.total), 1));
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<form
|
|
83
|
+
class="from"
|
|
84
|
+
onsubmit={(e) => {
|
|
85
|
+
e.preventDefault();
|
|
86
|
+
location.href = `${base}/site${from_input ? `?from=${encodeURIComponent(from_input)}` : ''}`;
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<label
|
|
90
|
+
>read rows from <input
|
|
91
|
+
type="url"
|
|
92
|
+
bind:value={from_input}
|
|
93
|
+
placeholder="https://…/profiler-rows.ndjson (a sink you serve)"
|
|
94
|
+
size="60"
|
|
95
|
+
/></label
|
|
96
|
+
>
|
|
97
|
+
<button class="btn" type="submit">load</button>
|
|
98
|
+
{#if sink}
|
|
99
|
+
<span class="hint"
|
|
100
|
+
>sink: <code>{sink.url}</code>{#if sink.last}
|
|
101
|
+
· last post {sink.last.ok ? 'ok' : 'failed'} ({sink.last.rows} rows{sink.last.error
|
|
102
|
+
? `: ${sink.last.error}`
|
|
103
|
+
: ''}){/if}{#if sink.buffered}
|
|
104
|
+
· {sink.buffered} buffered{/if}</span
|
|
105
|
+
>
|
|
106
|
+
{/if}
|
|
107
|
+
</form>
|
|
108
|
+
{#if load_error}<p class="verdict">{load_error}</p>{/if}
|
|
109
|
+
{#if loading}
|
|
110
|
+
<p class="hint">Reading rows…</p>
|
|
111
|
+
{:else if !points.length}
|
|
112
|
+
<p class="hint">
|
|
113
|
+
No requests to draw yet.{#if ephemeral}
|
|
114
|
+
This instance is ephemeral: it forgets its requests within seconds. Configure <code
|
|
115
|
+
>profiler.sink</code
|
|
116
|
+
> so every instance posts its rows somewhere you serve, then load that URL above.{:else}
|
|
117
|
+
Requests to the site show up here as they happen; the always-on sampler's windows make the
|
|
118
|
+
brushed selection answer with hot functions.{/if}
|
|
119
|
+
</p>
|
|
120
|
+
{:else}
|
|
121
|
+
<h2>
|
|
122
|
+
The request cloud <span class="hint" style="font-weight:400"
|
|
123
|
+
>({points.length} requests · {routes.length} routes · {windows} sampler windows{traps.length
|
|
124
|
+
? ` · ${traps.length} caught`
|
|
125
|
+
: ''} · from {when(t0)} to {when(t1)})</span
|
|
126
|
+
>
|
|
127
|
+
</h2>
|
|
128
|
+
<p class="hint">
|
|
129
|
+
Every request is a dot: time across, duration up (log scale), colour by route. Drag across a
|
|
130
|
+
range to select it; the hot functions of the sampler windows inside it appear below.
|
|
131
|
+
</p>
|
|
132
|
+
<!-- a picture to a reader; the brush is a pointer extra over it (no widget role fits a drag) -->
|
|
133
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
134
|
+
<svg
|
|
135
|
+
bind:this={svg_el}
|
|
136
|
+
viewBox="0 0 {W} {H}"
|
|
137
|
+
class="cloud"
|
|
138
|
+
role="img"
|
|
139
|
+
aria-label="every request as a dot"
|
|
140
|
+
onmousedown={(e) => {
|
|
141
|
+
drag = t_at(e.clientX);
|
|
142
|
+
brush = { a: drag, b: drag };
|
|
143
|
+
}}
|
|
144
|
+
onmousemove={(e) => {
|
|
145
|
+
if (drag !== null && brush) brush = { a: drag, b: t_at(e.clientX) };
|
|
146
|
+
}}
|
|
147
|
+
onmouseup={() => {
|
|
148
|
+
if (brush && Math.abs(brush.a - brush.b) < (t1 - t0) / 500) brush = null;
|
|
149
|
+
drag = null;
|
|
150
|
+
}}
|
|
151
|
+
onmouseleave={() => {
|
|
152
|
+
drag = null;
|
|
153
|
+
hover = null;
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{#each y_ticks as v (v)}
|
|
157
|
+
<line x1={L} x2={W - 8} y1={y(v)} y2={y(v)} class="grid" />
|
|
158
|
+
<text x={L - 4} y={y(v) + 3} class="tick" text-anchor="end">{v} ms</text>
|
|
159
|
+
{/each}
|
|
160
|
+
{#if brush}
|
|
161
|
+
<rect
|
|
162
|
+
x={x(Math.min(brush.a, brush.b))}
|
|
163
|
+
y="6"
|
|
164
|
+
width={Math.max(x(Math.max(brush.a, brush.b)) - x(Math.min(brush.a, brush.b)), 1)}
|
|
165
|
+
height={H - B - 6}
|
|
166
|
+
class="brush"
|
|
167
|
+
/>
|
|
168
|
+
{/if}
|
|
169
|
+
{#each points as p, i (i)}
|
|
170
|
+
<circle
|
|
171
|
+
cx={x(p.t)}
|
|
172
|
+
cy={y(p.ms)}
|
|
173
|
+
r={p.status >= 500 ? 4 : 2.6}
|
|
174
|
+
fill={color(p.route)}
|
|
175
|
+
class="dot"
|
|
176
|
+
class:err={p.status >= 500}
|
|
177
|
+
role="presentation"
|
|
178
|
+
onmouseenter={(e) => (hover = { p, x: e.clientX, y: e.clientY })}
|
|
179
|
+
/>
|
|
180
|
+
{/each}
|
|
181
|
+
{#each traps as t, i (i)}
|
|
182
|
+
{#if t.k === 'trap'}<text x={x(t.t)} y="12" class="trap">▼ caught</text>{/if}
|
|
183
|
+
{/each}
|
|
184
|
+
<text x={L} y={H - 6} class="tick">{when(t0)}</text>
|
|
185
|
+
<text x={W - 8} y={H - 6} class="tick" text-anchor="end">{when(t1)}</text>
|
|
186
|
+
</svg>
|
|
187
|
+
{#if hover}
|
|
188
|
+
<div class="tip" style="left:{hover.x + 12}px;top:{hover.y + 12}px">
|
|
189
|
+
<b>{hover.p.path}</b> · {fmt_ms(hover.p.ms)} ms · {hover.p.status}<br />{fmt_ms(hover.p.cpu)} ms
|
|
190
|
+
CPU · {fmt_ms(hover.p.wait)} ms waiting · {when(hover.p.t)}
|
|
191
|
+
</div>
|
|
192
|
+
{/if}
|
|
193
|
+
<div class="legend">
|
|
194
|
+
{#each routes.slice(0, 12) as r (r)}<span><i style="background:{color(r)}"></i>{r}</span>{/each}
|
|
195
|
+
</div>
|
|
196
|
+
{#if brush}
|
|
197
|
+
<div class="sel">
|
|
198
|
+
<p>
|
|
199
|
+
<b>{selected.length} requests selected</b> · p50 {fmt_ms(p50(selected.map((p) => p.ms)))} ms ·
|
|
200
|
+
max {fmt_ms(Math.max(...selected.map((p) => p.ms), 0))} ms · {[
|
|
201
|
+
...new Set(selected.map((p) => p.route))
|
|
202
|
+
]
|
|
203
|
+
.slice(0, 5)
|
|
204
|
+
.join(', ')} <button class="link" onclick={() => (brush = null)}>clear</button>
|
|
205
|
+
</p>
|
|
206
|
+
{#if hot.length}
|
|
207
|
+
<table>
|
|
208
|
+
<thead
|
|
209
|
+
><tr
|
|
210
|
+
><th>function</th><th>where</th><th class="num">self ms</th><th class="num"
|
|
211
|
+
>windows</th
|
|
212
|
+
></tr
|
|
213
|
+
></thead
|
|
214
|
+
>
|
|
215
|
+
<tbody>
|
|
216
|
+
{#each hot.slice(0, 20) as f, i (f.name + '\0' + f.file + '\0' + i)}
|
|
217
|
+
<tr
|
|
218
|
+
><td class="fn"><b>{f.name}</b></td><td class="file"
|
|
219
|
+
>{f.file} <span class="hint">{f.category}</span></td
|
|
220
|
+
><td class="num">{fmt_ms(f.self_ms)}</td><td class="num">{f.windows}</td></tr
|
|
221
|
+
>
|
|
222
|
+
{/each}
|
|
223
|
+
</tbody>
|
|
224
|
+
</table>
|
|
225
|
+
{:else}
|
|
226
|
+
<p class="hint">
|
|
227
|
+
No sampler window overlaps this range. Turn on <code>profiler.sample</code> so the site samples
|
|
228
|
+
itself; the windows then say what was hot when these requests ran.
|
|
229
|
+
</p>
|
|
230
|
+
{/if}
|
|
231
|
+
</div>
|
|
232
|
+
{/if}
|
|
233
|
+
|
|
234
|
+
<h2>
|
|
235
|
+
The layer cake <span class="hint" style="font-weight:400"
|
|
236
|
+
>(where the site's time goes, per route)</span
|
|
237
|
+
>
|
|
238
|
+
</h2>
|
|
239
|
+
<div class="cake">
|
|
240
|
+
{#each cake.slice(0, 25) as c (c.route)}
|
|
241
|
+
<div class="row">
|
|
242
|
+
<div class="name">
|
|
243
|
+
<b>{c.route}</b>
|
|
244
|
+
<span class="hint">{c.n} req · p50 {fmt_ms(c.p50)} · p95 {fmt_ms(c.p95)} ms</span>
|
|
245
|
+
</div>
|
|
246
|
+
<div
|
|
247
|
+
class="bar"
|
|
248
|
+
title="{fmt_ms(c.cpu)} ms CPU · {fmt_ms(c.wait)} ms waiting · {fmt_ms(c.rest)} ms other"
|
|
249
|
+
>
|
|
250
|
+
<i class="cpu" style="width:{(c.cpu / cake_max) * 100}%"></i><i
|
|
251
|
+
class="wait"
|
|
252
|
+
style="width:{(c.wait / cake_max) * 100}%"
|
|
253
|
+
></i><i class="rest" style="width:{(c.rest / cake_max) * 100}%"></i>
|
|
254
|
+
</div>
|
|
255
|
+
<div class="tot">{fmt_ms(c.total)} ms</div>
|
|
256
|
+
</div>
|
|
257
|
+
{/each}
|
|
258
|
+
</div>
|
|
259
|
+
<div class="legend">
|
|
260
|
+
<span><i class="cpu"></i>CPU</span><span><i class="wait"></i>waiting on calls</span><span
|
|
261
|
+
><i class="rest"></i>the rest (event loop, other requests, the platform)</span
|
|
262
|
+
>
|
|
263
|
+
</div>
|
|
264
|
+
<p class="hint">
|
|
265
|
+
Bars are the sum over the route's requests, so a route that is slow AND busy sits on top. A
|
|
266
|
+
route mostly “waiting” wants parallel calls or a cache; one mostly CPU wants a profile of one of
|
|
267
|
+
its pages; a wide “rest” means the instance was doing something else at the time.
|
|
268
|
+
</p>
|
|
269
|
+
{/if}
|
|
270
|
+
|
|
271
|
+
<style>
|
|
272
|
+
.from {
|
|
273
|
+
display: flex;
|
|
274
|
+
gap: 10px;
|
|
275
|
+
align-items: center;
|
|
276
|
+
flex-wrap: wrap;
|
|
277
|
+
margin-bottom: 8px;
|
|
278
|
+
}
|
|
279
|
+
.cloud {
|
|
280
|
+
width: 100%;
|
|
281
|
+
height: auto;
|
|
282
|
+
display: block;
|
|
283
|
+
background: var(--bg-sunken);
|
|
284
|
+
border: 1px solid var(--line);
|
|
285
|
+
border-radius: 6px;
|
|
286
|
+
cursor: crosshair;
|
|
287
|
+
user-select: none;
|
|
288
|
+
}
|
|
289
|
+
.grid {
|
|
290
|
+
stroke: #1c2129;
|
|
291
|
+
}
|
|
292
|
+
.tick {
|
|
293
|
+
fill: #6b7280;
|
|
294
|
+
font-size: 9px;
|
|
295
|
+
}
|
|
296
|
+
.dot {
|
|
297
|
+
opacity: 0.85;
|
|
298
|
+
}
|
|
299
|
+
.dot.err {
|
|
300
|
+
stroke: var(--bad);
|
|
301
|
+
stroke-width: 1.5;
|
|
302
|
+
}
|
|
303
|
+
.brush {
|
|
304
|
+
fill: rgba(88, 166, 255, 0.15);
|
|
305
|
+
stroke: var(--c-blue);
|
|
306
|
+
}
|
|
307
|
+
.trap {
|
|
308
|
+
fill: var(--bad);
|
|
309
|
+
font-size: 9px;
|
|
310
|
+
}
|
|
311
|
+
.legend {
|
|
312
|
+
display: flex;
|
|
313
|
+
flex-wrap: wrap;
|
|
314
|
+
gap: 6px 14px;
|
|
315
|
+
font-size: 12px;
|
|
316
|
+
margin: 6px 0;
|
|
317
|
+
}
|
|
318
|
+
.legend i,
|
|
319
|
+
.bar i {
|
|
320
|
+
display: inline-block;
|
|
321
|
+
width: 10px;
|
|
322
|
+
height: 10px;
|
|
323
|
+
border-radius: 2px;
|
|
324
|
+
margin-right: 4px;
|
|
325
|
+
vertical-align: middle;
|
|
326
|
+
}
|
|
327
|
+
.sel {
|
|
328
|
+
border: 1px solid var(--line);
|
|
329
|
+
border-radius: 6px;
|
|
330
|
+
padding: 8px 12px;
|
|
331
|
+
margin: 8px 0;
|
|
332
|
+
}
|
|
333
|
+
.link {
|
|
334
|
+
background: none;
|
|
335
|
+
border: 0;
|
|
336
|
+
color: inherit;
|
|
337
|
+
text-decoration: underline;
|
|
338
|
+
cursor: pointer;
|
|
339
|
+
font: inherit;
|
|
340
|
+
}
|
|
341
|
+
.cake .row {
|
|
342
|
+
display: grid;
|
|
343
|
+
grid-template-columns: 300px 1fr 80px;
|
|
344
|
+
gap: 10px;
|
|
345
|
+
align-items: center;
|
|
346
|
+
margin: 4px 0;
|
|
347
|
+
font-size: 13px;
|
|
348
|
+
}
|
|
349
|
+
.cake .bar {
|
|
350
|
+
height: 14px;
|
|
351
|
+
background: var(--bg-sunken);
|
|
352
|
+
border-radius: 3px;
|
|
353
|
+
overflow: hidden;
|
|
354
|
+
display: flex;
|
|
355
|
+
}
|
|
356
|
+
.cake .bar i {
|
|
357
|
+
height: 100%;
|
|
358
|
+
border-radius: 0;
|
|
359
|
+
margin: 0;
|
|
360
|
+
display: block;
|
|
361
|
+
}
|
|
362
|
+
.cpu {
|
|
363
|
+
background: var(--c-orange);
|
|
364
|
+
}
|
|
365
|
+
.wait {
|
|
366
|
+
background: var(--c-blue);
|
|
367
|
+
}
|
|
368
|
+
.rest {
|
|
369
|
+
background: #374151;
|
|
370
|
+
}
|
|
371
|
+
.tot {
|
|
372
|
+
text-align: right;
|
|
373
|
+
font-variant-numeric: tabular-nums;
|
|
374
|
+
}
|
|
375
|
+
.tip {
|
|
376
|
+
position: fixed;
|
|
377
|
+
z-index: 20;
|
|
378
|
+
background: var(--bg-raised);
|
|
379
|
+
border: 1px solid var(--line-strong);
|
|
380
|
+
border-radius: 6px;
|
|
381
|
+
padding: 6px 8px;
|
|
382
|
+
font-size: 12px;
|
|
383
|
+
pointer-events: none;
|
|
384
|
+
}
|
|
385
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE WHOLE SITE — two pictures from the sink's rows (sink.ts), or this instance's own ring and
|
|
3
|
+
* sampler windows: the REQUEST CLOUD (every request as a dot: time across, duration up, colour
|
|
4
|
+
* by route; drag a range to get the hot functions of the sampler windows inside it) and the
|
|
5
|
+
* LAYER CAKE (per route, where the time went: CPU, waiting on calls, the rest). A `wake:'load'`
|
|
6
|
+
* island; `from` is a URL the rows are read from in this browser (the sink you own).
|
|
7
|
+
*/
|
|
8
|
+
import { type SinkRow } from '../sink.js';
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
base: string;
|
|
11
|
+
rows: SinkRow[];
|
|
12
|
+
from?: string | null;
|
|
13
|
+
sink?: {
|
|
14
|
+
url: string;
|
|
15
|
+
last: {
|
|
16
|
+
at: number;
|
|
17
|
+
ok: boolean;
|
|
18
|
+
rows: number;
|
|
19
|
+
error?: string;
|
|
20
|
+
} | null;
|
|
21
|
+
buffered: number;
|
|
22
|
+
} | null;
|
|
23
|
+
ephemeral?: boolean;
|
|
24
|
+
};
|
|
25
|
+
declare const SiteView: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
26
|
+
type SiteView = ReturnType<typeof SiteView>;
|
|
27
|
+
export default SiteView;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script>const KEY = "og-profiler-theme";
|
|
2
|
+
const NEXT = {
|
|
3
|
+
system: "light",
|
|
4
|
+
light: "dark",
|
|
5
|
+
dark: "system"
|
|
6
|
+
};
|
|
7
|
+
const ICON = {
|
|
8
|
+
system: "◐",
|
|
9
|
+
light: "☀",
|
|
10
|
+
dark: "☾"
|
|
11
|
+
};
|
|
12
|
+
let mode = $state("system");
|
|
13
|
+
if (typeof window !== "undefined") {
|
|
14
|
+
let saved_mode = "system";
|
|
15
|
+
try {
|
|
16
|
+
const saved = localStorage.getItem(KEY);
|
|
17
|
+
saved_mode = saved === "light" || saved === "dark" ? saved : "system";
|
|
18
|
+
} catch {}
|
|
19
|
+
mode = saved_mode;
|
|
20
|
+
apply(saved_mode);
|
|
21
|
+
}
|
|
22
|
+
function apply(m) {
|
|
23
|
+
const root = document.documentElement;
|
|
24
|
+
if (m === "system") root.removeAttribute("data-theme");
|
|
25
|
+
else root.setAttribute("data-theme", m);
|
|
26
|
+
try {
|
|
27
|
+
if (m === "system") localStorage.removeItem(KEY);
|
|
28
|
+
else localStorage.setItem(KEY, m);
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
31
|
+
function cycle() {
|
|
32
|
+
const next = NEXT[mode];
|
|
33
|
+
const flip = () => {
|
|
34
|
+
mode = next;
|
|
35
|
+
apply(next);
|
|
36
|
+
};
|
|
37
|
+
const doc = document;
|
|
38
|
+
const reduce = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
39
|
+
if (doc.startViewTransition && !reduce) doc.startViewTransition(flip);
|
|
40
|
+
else flip();
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<button
|
|
45
|
+
class="theme"
|
|
46
|
+
onclick={cycle}
|
|
47
|
+
title="Theme: {mode} (click to change)"
|
|
48
|
+
aria-label="theme: {mode}"
|
|
49
|
+
>
|
|
50
|
+
<span class="ic" aria-hidden="true">{ICON[mode]}</span>
|
|
51
|
+
<span class="lbl">{mode}</span>
|
|
52
|
+
</button>
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
.theme {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
gap: 8px;
|
|
59
|
+
width: 100%;
|
|
60
|
+
background: none;
|
|
61
|
+
border: 1px solid var(--line);
|
|
62
|
+
border-radius: var(--r-sm);
|
|
63
|
+
padding: 7px 10px;
|
|
64
|
+
color: var(--text-dim);
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
font-size: 12.5px;
|
|
67
|
+
text-transform: capitalize;
|
|
68
|
+
}
|
|
69
|
+
.theme:hover {
|
|
70
|
+
border-color: var(--accent-line);
|
|
71
|
+
color: var(--text);
|
|
72
|
+
background: var(--bg-hover);
|
|
73
|
+
}
|
|
74
|
+
.ic {
|
|
75
|
+
font-size: 14px;
|
|
76
|
+
color: var(--accent);
|
|
77
|
+
}
|
|
78
|
+
</style>
|