@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,135 @@
|
|
|
1
|
+
import { PROFILER_MAPS_GZ_PREFIX } from "@ogygia/core/internal/compiler-ids";
|
|
2
|
+
// src/profiler/embedded-maps.ts
|
|
3
|
+
let loaded;
|
|
4
|
+
let ready = null;
|
|
5
|
+
function load_embedded_maps() {
|
|
6
|
+
loaded ??= import("virtual:ogygia/profiler-maps").then(async (m) => {
|
|
7
|
+
let raw = m.default;
|
|
8
|
+
if (typeof raw === "string" && raw.startsWith(PROFILER_MAPS_GZ_PREFIX)) raw = (await import("node:zlib")).gunzipSync(Buffer.from(raw.slice(PROFILER_MAPS_GZ_PREFIX.length), "base64")).toString("utf8");
|
|
9
|
+
if (typeof raw !== "string" || !raw.startsWith("{")) return null;
|
|
10
|
+
const p = JSON.parse(raw);
|
|
11
|
+
return p.v === 1 && p.maps && p.modules ? ready = {
|
|
12
|
+
modules: p.modules,
|
|
13
|
+
maps: p.maps,
|
|
14
|
+
...p.sources && typeof p.sources === "object" ? { sources: p.sources } : {},
|
|
15
|
+
...p.chunks && typeof p.chunks === "object" ? { chunks: p.chunks } : {}
|
|
16
|
+
} : null;
|
|
17
|
+
}).catch(() => null);
|
|
18
|
+
return loaded;
|
|
19
|
+
}
|
|
20
|
+
/** the maps once loaded (synchronous readers: the module map lookup), else null */
|
|
21
|
+
const embedded_maps = () => ready;
|
|
22
|
+
/** a script's key in the payload: its path under the server output (`chunks/format.js`), whether it
|
|
23
|
+
* runs from Kit's output itself (a host that copies it, like Vercel's) or is named as a map source
|
|
24
|
+
* by an adapter that re-bundled it (adapter-node: `…/.svelte-kit/output/server/chunks/x.js`) */
|
|
25
|
+
function embedded_key(path) {
|
|
26
|
+
const p = path.split("\\").join("/");
|
|
27
|
+
const at = p.lastIndexOf("/output/server/");
|
|
28
|
+
if (at === -1) {
|
|
29
|
+
const imm = p.lastIndexOf("/immutable/");
|
|
30
|
+
if (imm <= 0) return void 0;
|
|
31
|
+
const app = p.lastIndexOf("/", imm - 1);
|
|
32
|
+
if (app === -1) return void 0;
|
|
33
|
+
const q = p.indexOf("?", imm);
|
|
34
|
+
return "client/" + p.slice(app + 1, q === -1 ? void 0 : q);
|
|
35
|
+
}
|
|
36
|
+
const kit = p.lastIndexOf("/.svelte-kit/", at);
|
|
37
|
+
if (kit !== -1 && kit + 12 === at) roots.add(p.slice(0, kit));
|
|
38
|
+
return p.slice(at + 15);
|
|
39
|
+
}
|
|
40
|
+
/** app roots seen in chunk paths (`<root>/.svelte-kit/output/server/…`) */
|
|
41
|
+
const roots = /* @__PURE__ */ new Set();
|
|
42
|
+
const VIRTUAL_OUTPUT = "/ROOT/.svelte-kit/output/";
|
|
43
|
+
const VIRTUAL_SERVER = VIRTUAL_OUTPUT + "server/";
|
|
44
|
+
const VIRTUAL_SRC = "/ROOT/src/";
|
|
45
|
+
/** `a/b/../c/./d` → `a/c/d` (posix, absolute input) */
|
|
46
|
+
function normalize(p) {
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const part of p.split("/")) {
|
|
49
|
+
if (part === "" || part === ".") continue;
|
|
50
|
+
if (part === "..") out.pop();
|
|
51
|
+
else out.push(part);
|
|
52
|
+
}
|
|
53
|
+
return "/" + out.join("/");
|
|
54
|
+
}
|
|
55
|
+
/** the build's map of a chunk, by its path: the text the build wrote, or undefined */
|
|
56
|
+
function embedded_map_of(path) {
|
|
57
|
+
const key = embedded_key(path);
|
|
58
|
+
return key !== void 0 ? ready?.maps[key] : void 0;
|
|
59
|
+
}
|
|
60
|
+
/** a built chunk's text the build carried (a build without maps, for a host that re-bundled it) */
|
|
61
|
+
function embedded_chunk(path) {
|
|
62
|
+
const key = embedded_key(path);
|
|
63
|
+
return key !== void 0 ? ready?.chunks?.[key] : void 0;
|
|
64
|
+
}
|
|
65
|
+
/** src-relative path (`routes/x/+page.server.ts`) → the source text the maps carry, built once */
|
|
66
|
+
let sources;
|
|
67
|
+
/**
|
|
68
|
+
* AN APP SOURCE FILE'S TEXT FROM THE MAPS (`sourcesContent`): the only copy on a deployed host, where
|
|
69
|
+
* `src/` is not there. What the source-reading checks (a load read line by line, a component's
|
|
70
|
+
* `{#each}`, a late island's data) read when the file is not on disk. `file` is src-relative or has
|
|
71
|
+
* a leading `src/`; undefined when no map carries it.
|
|
72
|
+
*/
|
|
73
|
+
function embedded_source(file) {
|
|
74
|
+
const index = source_index();
|
|
75
|
+
if (!index) return void 0;
|
|
76
|
+
const f = file.split("\\").join("/");
|
|
77
|
+
if (f.includes("/node_modules/")) return void 0;
|
|
78
|
+
let rel;
|
|
79
|
+
if (f.startsWith("src/")) rel = f.slice(4);
|
|
80
|
+
else if (f.startsWith("/")) {
|
|
81
|
+
for (const r of [...roots, cwd()]) if (r && f.startsWith(r + "/src/")) {
|
|
82
|
+
rel = f.slice(r.length + 5);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
} else rel = f;
|
|
86
|
+
return rel === void 0 ? void 0 : index.get(rel);
|
|
87
|
+
}
|
|
88
|
+
function cwd() {
|
|
89
|
+
try {
|
|
90
|
+
return process.cwd().split("\\").join("/");
|
|
91
|
+
} catch {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** every app source file the maps carry, src-relative */
|
|
96
|
+
function embedded_files() {
|
|
97
|
+
return [...source_index()?.keys() ?? []];
|
|
98
|
+
}
|
|
99
|
+
function source_index() {
|
|
100
|
+
if (!ready) return void 0;
|
|
101
|
+
if (!sources) {
|
|
102
|
+
sources = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const [key, raw] of Object.entries(ready.maps)) {
|
|
104
|
+
let map;
|
|
105
|
+
try {
|
|
106
|
+
map = JSON.parse(raw);
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const dir = (key.startsWith("client/") ? VIRTUAL_OUTPUT : VIRTUAL_SERVER) + key.slice(0, key.lastIndexOf("/") + 1);
|
|
111
|
+
const list = map.sources ?? [];
|
|
112
|
+
for (let i = 0; i < list.length; i++) {
|
|
113
|
+
const text = map.sourcesContent?.[i];
|
|
114
|
+
if (typeof text !== "string") continue;
|
|
115
|
+
const s = ((map.sourceRoot ?? "") + list[i]).split("\\").join("/");
|
|
116
|
+
if (s.startsWith("/") || s.includes(":")) continue;
|
|
117
|
+
const p = normalize(dir + s);
|
|
118
|
+
if (!p.startsWith(VIRTUAL_SRC)) continue;
|
|
119
|
+
const rel = p.slice(10);
|
|
120
|
+
if (!sources.has(rel)) sources.set(rel, text);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const [id, text] of Object.entries(ready.sources ?? {})) if (id.startsWith("src/") && typeof text === "string" && !sources.has(id.slice(4))) sources.set(id.slice(4), text);
|
|
124
|
+
}
|
|
125
|
+
return sources;
|
|
126
|
+
}
|
|
127
|
+
/** the module at a chunk line, from the embedded module map (a host that dropped the file beside the
|
|
128
|
+
* chunks) */
|
|
129
|
+
function embedded_module_at(path, line) {
|
|
130
|
+
const key = embedded_key(path);
|
|
131
|
+
const ranges = key !== void 0 ? ready?.modules[key] : void 0;
|
|
132
|
+
if (!ranges) return void 0;
|
|
133
|
+
for (const [a, b, id] of ranges) if (line >= a && line <= b) return id;
|
|
134
|
+
}
|
|
135
|
+
export { embedded_chunk, embedded_files, embedded_key, embedded_map_of, embedded_maps, embedded_module_at, embedded_source, load_embedded_maps };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is this a dev server? Vite replaces `import.meta.env.DEV` at build time; a plain-Node run falls
|
|
3
|
+
* back to NODE_ENV. Its own module so a test can mock the answer (the value is otherwise a
|
|
4
|
+
* compile-time constant inside the profiler) and exercise the production request path.
|
|
5
|
+
*/
|
|
6
|
+
export declare function detect_dev(): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/profiler/env.ts
|
|
2
|
+
/**
|
|
3
|
+
* Is this a dev server? Vite replaces `import.meta.env.DEV` at build time; a plain-Node run falls
|
|
4
|
+
* back to NODE_ENV. Its own module so a test can mock the answer (the value is otherwise a
|
|
5
|
+
* compile-time constant inside the profiler) and exercise the production request path.
|
|
6
|
+
*/
|
|
7
|
+
function detect_dev() {
|
|
8
|
+
return typeof import.meta !== "undefined" && import.meta.env?.DEV === true || process.env.NODE_ENV === "development";
|
|
9
|
+
}
|
|
10
|
+
export { detect_dev };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { DrillNode } from "./drill.js";
|
|
2
|
+
import { Pattern } from "./patterns.js";
|
|
3
|
+
import { CallStack } from "./analyze.js";
|
|
4
|
+
import { saving_of } from "./saving.js";
|
|
5
|
+
export interface Forecast {
|
|
6
|
+
/** one render now: the median of the profiled renders, ms, as the app paid it */
|
|
7
|
+
now_ms: number;
|
|
8
|
+
/** one render with every fix below, ms (an estimate) */
|
|
9
|
+
after_ms: number;
|
|
10
|
+
/** what the render used, which capped the savings: its waiting and its CPU, ms (the compare's
|
|
11
|
+
* fix check counts a later report's fixes the same way) */
|
|
12
|
+
caps?: {
|
|
13
|
+
wait?: number;
|
|
14
|
+
cpu?: number;
|
|
15
|
+
};
|
|
16
|
+
/** the parts of the saving: CPU, waiting, and work deleted, ms per render */
|
|
17
|
+
cpu_ms: number;
|
|
18
|
+
wait_ms: number;
|
|
19
|
+
delete_ms: number;
|
|
20
|
+
/** the main thread's CPU a render, now and after every fix (ms): what bounds how many renders a
|
|
21
|
+
* core serves — the waiting fixes cut the time a visitor waits, only the CPU ones raise that */
|
|
22
|
+
cpu_now_ms?: number;
|
|
23
|
+
cpu_after_ms?: number;
|
|
24
|
+
/** the fixes counted, biggest first; `with`: the other fixes on the same lines, whose saving
|
|
25
|
+
* this one already covers; `after_ms`: one render once this fix AND every one above it is done */
|
|
26
|
+
parts: {
|
|
27
|
+
title: string;
|
|
28
|
+
kind: string;
|
|
29
|
+
ms: number;
|
|
30
|
+
wait: boolean;
|
|
31
|
+
after_ms?: number;
|
|
32
|
+
with?: string[];
|
|
33
|
+
}[];
|
|
34
|
+
/** caching the services' answers on top (same answer every render): one render after that, and
|
|
35
|
+
* those fixes. A decision about how stale an answer may be, so left out of `after_ms` */
|
|
36
|
+
answers?: {
|
|
37
|
+
after_ms: number;
|
|
38
|
+
wait_ms: number;
|
|
39
|
+
parts: Forecast["parts"];
|
|
40
|
+
clamped?: boolean;
|
|
41
|
+
/** the page was rendered with those answers served from memory: `ms` is that render (the
|
|
42
|
+
* median of `renders`), and `after_ms` is it with the code fixes the caching does not cover
|
|
43
|
+
* taken off; `model_ms` what the lines alone estimated */
|
|
44
|
+
measured?: {
|
|
45
|
+
ms: number;
|
|
46
|
+
renders: number;
|
|
47
|
+
model_ms: number;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
/** the whole document is cacheable: a cached copy costs the render nothing — a different lever,
|
|
51
|
+
* left out of `after_ms` */
|
|
52
|
+
cache?: boolean;
|
|
53
|
+
/** the savings claimed more waiting or CPU than the render had (they overlap in ways the lines
|
|
54
|
+
* do not show): each was held at what the render used */
|
|
55
|
+
clamped?: boolean;
|
|
56
|
+
/** the build had no sourcemaps: the profiler saw fewer lines, so fewer fixes — the real time
|
|
57
|
+
* after them is likely lower */
|
|
58
|
+
partial?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export interface ForecastInputs {
|
|
61
|
+
/** the waiting on one render's critical path — whatever the wait fixes claim, together they
|
|
62
|
+
* cannot take off more than the render waited */
|
|
63
|
+
wait_cap?: number;
|
|
64
|
+
/** the app's CPU in one render: every CPU saving together cannot take off more */
|
|
65
|
+
cpu_cap?: number;
|
|
66
|
+
/** a function's heaviest call paths (functions table key → stacks): which work runs only
|
|
67
|
+
* inside a call another fix takes out of the request */
|
|
68
|
+
stacks?: (fn: string) => readonly CallStack[] | undefined;
|
|
69
|
+
/** the page rendered with its same-every-time answers served from memory (ms per render) */
|
|
70
|
+
answers_runs?: readonly number[];
|
|
71
|
+
/** the main thread's CPU in one render: the sampled stacks (app, components, packages, Node) and
|
|
72
|
+
* the collector's pauses there */
|
|
73
|
+
cpu_main?: number;
|
|
74
|
+
}
|
|
75
|
+
export declare function forecast_of(runs: readonly number[] | undefined, patterns: readonly Pattern[] | undefined, drill: DrillNode | undefined, inputs?: ForecastInputs): Forecast | undefined;
|
|
76
|
+
export { saving_of };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { tokens } from "./source-scan.js";
|
|
2
|
+
import { saving_of } from "./saving.js";
|
|
3
|
+
// src/profiler/forecast.ts
|
|
4
|
+
const r1 = (n) => Math.round(n * 10) / 10;
|
|
5
|
+
const place = (p) => p.startsWith("src/") ? p.slice(4) : p;
|
|
6
|
+
function forecast_of(runs, patterns, drill, inputs = {}) {
|
|
7
|
+
const { wait_cap, cpu_cap, stacks } = inputs;
|
|
8
|
+
if (!runs?.length) return void 0;
|
|
9
|
+
const sorted = [...runs].sort((a, b) => a - b);
|
|
10
|
+
const mid = sorted.length >> 1;
|
|
11
|
+
const now = sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
12
|
+
const n = runs.length;
|
|
13
|
+
const items = [];
|
|
14
|
+
const gone = /* @__PURE__ */ new Set();
|
|
15
|
+
const unread = (d, above, waiting) => {
|
|
16
|
+
const in_wait = waiting || d.kind === "wait";
|
|
17
|
+
if (d.fills?.length && d.fills.every((f) => !f.read)) {
|
|
18
|
+
const keys = [`unread\0${items.length}`, ...above];
|
|
19
|
+
const all_lines = (m) => {
|
|
20
|
+
if (m.at) {
|
|
21
|
+
gone.add(place(m.at));
|
|
22
|
+
keys.push(place(m.at));
|
|
23
|
+
}
|
|
24
|
+
for (const c of m.children ?? []) all_lines(c);
|
|
25
|
+
};
|
|
26
|
+
all_lines(d);
|
|
27
|
+
const names = /* @__PURE__ */ new Set();
|
|
28
|
+
const called = (m) => {
|
|
29
|
+
if (m.code) {
|
|
30
|
+
const t = tokens(m.code);
|
|
31
|
+
for (let k = 0; k + 1 < t.length; k++) if (t[k + 1] === "(" && t[k - 1] !== "." && t[k - 1] !== "?.") names.add(t[k]);
|
|
32
|
+
}
|
|
33
|
+
for (const c of m.children ?? []) called(c);
|
|
34
|
+
};
|
|
35
|
+
called(d);
|
|
36
|
+
items.push({
|
|
37
|
+
title: `Delete work for data nothing reads (${d.fills.map((f) => f.key).join(", ")})`,
|
|
38
|
+
kind: "unread-work",
|
|
39
|
+
ms: d.ms,
|
|
40
|
+
wait: in_wait,
|
|
41
|
+
keys,
|
|
42
|
+
calls: names,
|
|
43
|
+
soft: new Set(above)
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const next = d.kind === "line" && d.at ? [...above, place(d.at)] : above;
|
|
48
|
+
for (const c of d.children ?? []) unread(c, next, in_wait);
|
|
49
|
+
};
|
|
50
|
+
if (drill) unread(drill, [], false);
|
|
51
|
+
for (let i = 0; i < items.length; i++) {
|
|
52
|
+
const a = items[i];
|
|
53
|
+
if (a.kind !== "unread-work") continue;
|
|
54
|
+
for (let j = items.length - 1; j > i; j--) {
|
|
55
|
+
const b = items[j];
|
|
56
|
+
if (b.kind !== "unread-work" || b.title !== a.title || b.wait !== a.wait) continue;
|
|
57
|
+
a.ms += b.ms;
|
|
58
|
+
a.keys.push(...b.keys);
|
|
59
|
+
for (const c of b.calls ?? []) (a.calls ??= /* @__PURE__ */ new Set()).add(c);
|
|
60
|
+
for (const s of b.soft ?? []) (a.soft ??= /* @__PURE__ */ new Set()).add(s);
|
|
61
|
+
items.splice(j, 1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let cache = false;
|
|
65
|
+
for (const p of patterns ?? []) {
|
|
66
|
+
if (p.kind === "same-document" || p.kind === "almost-same-document") {
|
|
67
|
+
cache = true;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const ms = (p.save_ms ?? 0) / (p.wait ? 1 : n);
|
|
71
|
+
if (ms <= 0) continue;
|
|
72
|
+
const key = (x) => place(`${x.file}:${x.line}`);
|
|
73
|
+
if (p.sites.length && p.sites.every((s) => gone.has(key(s)) || !!s.via?.length && s.via.every((v) => gone.has(key(v))))) continue;
|
|
74
|
+
const keys = /* @__PURE__ */ new Set();
|
|
75
|
+
for (const s of p.sites) {
|
|
76
|
+
keys.add(key(s));
|
|
77
|
+
for (const v of s.via ?? []) keys.add(key(v));
|
|
78
|
+
}
|
|
79
|
+
items.push({
|
|
80
|
+
title: p.title ?? p.kind,
|
|
81
|
+
kind: p.kind,
|
|
82
|
+
ms,
|
|
83
|
+
wait: !!p.wait,
|
|
84
|
+
keys: [...keys],
|
|
85
|
+
p
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
for (const mover of items.filter((it) => it.kind === "same-every-request")) {
|
|
89
|
+
const moved = new Set(mover.p.sites.flatMap((s) => s.moves ?? (s.fn_name ? [s.fn_name] : [])));
|
|
90
|
+
if (!moved.size) continue;
|
|
91
|
+
const under = (s) => {
|
|
92
|
+
if (s.fn_name && moved.has(s.fn_name)) return true;
|
|
93
|
+
const st = s.fn ? stacks?.(s.fn) : void 0;
|
|
94
|
+
if (!st?.length) return false;
|
|
95
|
+
const total = st.reduce((t, x) => t + x.ms, 0);
|
|
96
|
+
const inside = st.filter((x) => x.frames.some((f) => moved.has(f.n))).reduce((t, x) => t + x.ms, 0);
|
|
97
|
+
return total > 0 && inside >= total * .9;
|
|
98
|
+
};
|
|
99
|
+
for (const it of items) {
|
|
100
|
+
if (it === mover || it.wait || !it.p?.sites.length || !it.p.sites.every(under)) continue;
|
|
101
|
+
it.keys.push(mover.keys[0]);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
for (const del of items.filter((it) => it.kind === "unread-work" && it.calls?.size && !it.wait)) for (const it of items) {
|
|
105
|
+
if (!it.p || it.wait || !it.p.sites.length || !it.p.sites.every((s) => !!s.fn_name && del.calls.has(s.fn_name))) continue;
|
|
106
|
+
it.keys.push(del.keys[0]);
|
|
107
|
+
}
|
|
108
|
+
/** the saving of a set of fixes: grouped by shared lines (a union of every pattern that touches
|
|
109
|
+
* a line another touches), CPU and waits apart — a line's wait and its CPU are different time */
|
|
110
|
+
const saving = (set) => {
|
|
111
|
+
const parent = set.map((_, i) => i);
|
|
112
|
+
const find = (i) => parent[i] === i ? i : parent[i] = find(parent[i]);
|
|
113
|
+
const owners = /* @__PURE__ */ new Map();
|
|
114
|
+
set.forEach((it, i) => {
|
|
115
|
+
for (const k of it.keys) {
|
|
116
|
+
const tag = `${it.wait ? "w" : "c"}\0${k}`;
|
|
117
|
+
const on = owners.get(tag) ?? owners.set(tag, []).get(tag);
|
|
118
|
+
for (const j of on) {
|
|
119
|
+
const other = set[j];
|
|
120
|
+
if (it.kind === "unread-work" && other.kind === "unread-work" && (it.soft?.has(k) || other.soft?.has(k))) continue;
|
|
121
|
+
parent[find(i)] = find(j);
|
|
122
|
+
}
|
|
123
|
+
on.push(i);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
const groups = /* @__PURE__ */ new Map();
|
|
127
|
+
set.forEach((it, i) => {
|
|
128
|
+
const g = find(i);
|
|
129
|
+
(groups.get(g) ?? groups.set(g, []).get(g)).push(it);
|
|
130
|
+
});
|
|
131
|
+
const tops = [...groups.values()].map((g) => {
|
|
132
|
+
g.sort((a, b) => b.ms - a.ms);
|
|
133
|
+
return {
|
|
134
|
+
top: g[0],
|
|
135
|
+
others: g.slice(1).map((x) => x.title)
|
|
136
|
+
};
|
|
137
|
+
}).sort((a, b) => b.top.ms - a.top.ms);
|
|
138
|
+
/** what a set of groups takes off together. EACH RESOURCE CAPPED BY WHAT THE RENDER USED:
|
|
139
|
+
* every saving on the clock (the wait fixes and the waits deleted) takes off at most the
|
|
140
|
+
* render's waiting; every CPU saving at most its CPU. Past that they overlap in a way the
|
|
141
|
+
* lines do not show */
|
|
142
|
+
const together = (set) => saving_of(set, {
|
|
143
|
+
wait: wait_cap,
|
|
144
|
+
cpu: cpu_cap
|
|
145
|
+
}, now);
|
|
146
|
+
const seen = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const { top } of tops) seen.set(top.title, (seen.get(top.title) ?? 0) + 1);
|
|
148
|
+
const named = /* @__PURE__ */ new Set();
|
|
149
|
+
const title_of = (top) => {
|
|
150
|
+
if (seen.get(top.title) === 1) return top.title;
|
|
151
|
+
const site = top.p?.sites[0];
|
|
152
|
+
let t = site ? `${top.title} (${site.file}:${site.line})` : top.title;
|
|
153
|
+
for (let n = 2; named.has(t); n++) t = `${top.title} (${n})`;
|
|
154
|
+
named.add(t);
|
|
155
|
+
return t;
|
|
156
|
+
};
|
|
157
|
+
const parts = tops.map(({ top, others }, i) => ({
|
|
158
|
+
title: title_of(top),
|
|
159
|
+
kind: top.kind,
|
|
160
|
+
ms: r1(top.ms),
|
|
161
|
+
wait: top.wait,
|
|
162
|
+
after_ms: r1(together(tops.slice(0, i + 1).map((x) => x.top)).after),
|
|
163
|
+
...others.length ? { with: others } : {}
|
|
164
|
+
}));
|
|
165
|
+
return {
|
|
166
|
+
...together(tops.map((x) => x.top)),
|
|
167
|
+
parts,
|
|
168
|
+
tops
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
const caching = (it) => CACHING.has(it.kind);
|
|
172
|
+
const code = saving(items.filter((it) => !caching(it)));
|
|
173
|
+
const cached = items.some(caching) ? saving(items) : void 0;
|
|
174
|
+
let measured;
|
|
175
|
+
const ar = inputs.answers_runs;
|
|
176
|
+
if (cached && ar?.length) {
|
|
177
|
+
const s = [...ar].sort((a, b) => a - b);
|
|
178
|
+
const h = s.length >> 1;
|
|
179
|
+
const ms = s.length % 2 ? s[h] : (s[h - 1] + s[h]) / 2;
|
|
180
|
+
const rest = cached.tops.filter((t) => !caching(t.top)).map((t) => t.top);
|
|
181
|
+
measured = {
|
|
182
|
+
after: saving_of(rest, {
|
|
183
|
+
wait: wait_cap,
|
|
184
|
+
cpu: cpu_cap
|
|
185
|
+
}, ms).after,
|
|
186
|
+
ms,
|
|
187
|
+
renders: s.length
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const answers_after = measured?.after ?? cached?.after;
|
|
191
|
+
if (!code.parts.length && !cached && !cache) return void 0;
|
|
192
|
+
return {
|
|
193
|
+
now_ms: r1(now),
|
|
194
|
+
...wait_cap !== void 0 || cpu_cap !== void 0 ? { caps: {
|
|
195
|
+
...wait_cap !== void 0 ? { wait: r1(wait_cap) } : {},
|
|
196
|
+
...cpu_cap !== void 0 ? { cpu: r1(cpu_cap) } : {}
|
|
197
|
+
} } : {},
|
|
198
|
+
after_ms: r1(code.after),
|
|
199
|
+
cpu_ms: r1(code.cpu),
|
|
200
|
+
...inputs.cpu_main && inputs.cpu_main > 0 ? {
|
|
201
|
+
cpu_now_ms: r1(inputs.cpu_main),
|
|
202
|
+
cpu_after_ms: r1(Math.max(inputs.cpu_main * .03, inputs.cpu_main - code.cpu - code.del_cpu))
|
|
203
|
+
} : {},
|
|
204
|
+
wait_ms: r1(code.wait),
|
|
205
|
+
delete_ms: r1(code.del),
|
|
206
|
+
parts: code.parts,
|
|
207
|
+
...cached && answers_after !== void 0 && answers_after < code.after - .5 ? { answers: {
|
|
208
|
+
after_ms: r1(answers_after),
|
|
209
|
+
wait_ms: r1(cached.wait),
|
|
210
|
+
parts: cached.parts.filter((p) => CACHING.has(p.kind)),
|
|
211
|
+
...cached.clamped && !measured ? { clamped: true } : {},
|
|
212
|
+
...measured ? { measured: {
|
|
213
|
+
ms: r1(measured.ms),
|
|
214
|
+
renders: measured.renders,
|
|
215
|
+
model_ms: r1(cached.after)
|
|
216
|
+
} } : {}
|
|
217
|
+
} } : {},
|
|
218
|
+
...cache ? { cache: true } : {},
|
|
219
|
+
...code.clamped ? { clamped: true } : {}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** fixes that keep an ANSWER for later renders: worth it only where the answer may be a little
|
|
223
|
+
* stale, so a decision about freshness, not a code fix — told apart from the rest */
|
|
224
|
+
const CACHING = /* @__PURE__ */ new Set(["same-answer", "almost-same-answer"]);
|
|
225
|
+
export { forecast_of, saving_of };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Grab the structured call-sites WITHOUT triggering Node's stack-string formatter
|
|
2
|
+
* (`defaultPrepareStackTrace`). Reading `new Error().stack` on every I/O call formatted the
|
|
3
|
+
* whole stack and showed up as node-core CPU in the profile — the profiler measuring itself. */
|
|
4
|
+
export declare function call_sites(below: (...a: never[]) => unknown): NodeJS.CallSite[];
|
|
5
|
+
/** A call site's file: its script's name, or the `//# sourceURL` of code made from a string — how
|
|
6
|
+
* the dev server's module runner evaluates every server module (`new AsyncFunction(code)`), where
|
|
7
|
+
* `getFileName()` is empty and every frame looked like no file at all */
|
|
8
|
+
export declare function file_of(site: NodeJS.CallSite): string | undefined;
|
|
9
|
+
/** Call at module load from any profiler module: its runtime file name joins the skip set. */
|
|
10
|
+
export declare function register_profiler_file(): void;
|
|
11
|
+
export declare function is_profiler_file(file: string): boolean;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/profiler/frames.ts
|
|
2
|
+
/**
|
|
3
|
+
* The profiler's OWN frames, so a caller lookup skips them whatever the bundler named the file.
|
|
4
|
+
*
|
|
5
|
+
* Bundlers rename our source (net.ts → chunks/net2.js, or straight into the app's chunk when the
|
|
6
|
+
* package is a workspace link), so a path substring like '/profiler/' won't match at runtime.
|
|
7
|
+
* Each profiler module calls `register_profiler_file()` at load; it reads the CALLER's file from
|
|
8
|
+
* a structured stack trace — the same format the caller lookup later compares against — so those
|
|
9
|
+
* frames are skipped by identity. Dependency-free and V8-guarded: `span.ts` (universal) registers
|
|
10
|
+
* itself too, and on an engine without `captureStackTrace` it simply does nothing.
|
|
11
|
+
*/
|
|
12
|
+
const profiler_files = /* @__PURE__ */ new Set();
|
|
13
|
+
/** Grab the structured call-sites WITHOUT triggering Node's stack-string formatter
|
|
14
|
+
* (`defaultPrepareStackTrace`). Reading `new Error().stack` on every I/O call formatted the
|
|
15
|
+
* whole stack and showed up as node-core CPU in the profile — the profiler measuring itself. */
|
|
16
|
+
function call_sites(below) {
|
|
17
|
+
const E = Error;
|
|
18
|
+
if (typeof E.captureStackTrace !== "function") return [];
|
|
19
|
+
const orig = E.prepareStackTrace;
|
|
20
|
+
E.prepareStackTrace = (_e, sites) => sites;
|
|
21
|
+
const holder = {};
|
|
22
|
+
E.captureStackTrace(holder, below);
|
|
23
|
+
const sites = holder.stack;
|
|
24
|
+
E.prepareStackTrace = orig;
|
|
25
|
+
return Array.isArray(sites) ? sites : [];
|
|
26
|
+
}
|
|
27
|
+
/** A call site's file: its script's name, or the `//# sourceURL` of code made from a string — how
|
|
28
|
+
* the dev server's module runner evaluates every server module (`new AsyncFunction(code)`), where
|
|
29
|
+
* `getFileName()` is empty and every frame looked like no file at all */
|
|
30
|
+
function file_of(site) {
|
|
31
|
+
return site.getFileName() ?? site.getScriptNameOrSourceURL?.() ?? void 0;
|
|
32
|
+
}
|
|
33
|
+
/** Call at module load from any profiler module: its runtime file name joins the skip set. */
|
|
34
|
+
function register_profiler_file() {
|
|
35
|
+
const s = call_sites(register_profiler_file)[0];
|
|
36
|
+
const f = s ? file_of(s) : void 0;
|
|
37
|
+
if (f) profiler_files.add(f);
|
|
38
|
+
}
|
|
39
|
+
function is_profiler_file(file) {
|
|
40
|
+
return profiler_files.has(file) || file.includes("/profiler/");
|
|
41
|
+
}
|
|
42
|
+
export { call_sites, file_of, is_profiler_file, register_profiler_file };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { FrameCategory, HeapNode } from "./analyze.js";
|
|
2
|
+
export interface GcEvent {
|
|
3
|
+
/** ms on the profile's clock (from the window's start) */
|
|
4
|
+
t: number;
|
|
5
|
+
ms: number;
|
|
6
|
+
kind: "minor" | "major" | "incremental" | "weak" | "other";
|
|
7
|
+
/** Node's GC flags (forced, all available garbage, …) */
|
|
8
|
+
flags: number;
|
|
9
|
+
}
|
|
10
|
+
/** one sampled allocation site: a stack, keyed by a hash of the frames above it */
|
|
11
|
+
export interface AllocSite {
|
|
12
|
+
key: string;
|
|
13
|
+
name: string;
|
|
14
|
+
url: string;
|
|
15
|
+
line: number;
|
|
16
|
+
category: FrameCategory;
|
|
17
|
+
/** the nearest component on the stack, when one is there */
|
|
18
|
+
component: string | null;
|
|
19
|
+
/** the nearest app frame above, formatted: `toProductVM (lib/mappers.ts:11)` */
|
|
20
|
+
caller?: string;
|
|
21
|
+
/** …and its raw parts, so a source map can rename the file and line afterwards */
|
|
22
|
+
caller_name?: string;
|
|
23
|
+
caller_url?: string;
|
|
24
|
+
caller_line?: number;
|
|
25
|
+
/** THE APP LINE this allocation is charged to, resolved to source: the site itself when it is
|
|
26
|
+
* the app's code, else the nearest app caller — so a builtin (`replace`, `structuredClone`)
|
|
27
|
+
* lands on the line of your code that called it. Set once the source map has run. */
|
|
28
|
+
at?: {
|
|
29
|
+
path: string;
|
|
30
|
+
line: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface AllocSlice {
|
|
34
|
+
t0: number;
|
|
35
|
+
t1: number;
|
|
36
|
+
/** bytes allocated in the slice, per site key (only sites that allocated) */
|
|
37
|
+
bytes: Record<string, number>;
|
|
38
|
+
}
|
|
39
|
+
export interface GcPause {
|
|
40
|
+
t: number;
|
|
41
|
+
/** the pause AS THE APP WOULD HAVE PAID IT: the measured length minus the profiler's own share
|
|
42
|
+
* of the allocations that filled the heap before it */
|
|
43
|
+
ms: number;
|
|
44
|
+
/** the length the observer measured, before that share was taken out */
|
|
45
|
+
ms_measured: number;
|
|
46
|
+
kind: GcEvent["kind"];
|
|
47
|
+
forced: boolean;
|
|
48
|
+
/** ms since the pause this one is charged from */
|
|
49
|
+
since_ms: number;
|
|
50
|
+
/** bytes allocated in that stretch — measured when the window was read in slices, else the
|
|
51
|
+
* window's average rate over the stretch (`estimated`) */
|
|
52
|
+
allocated: number;
|
|
53
|
+
estimated?: boolean;
|
|
54
|
+
why: string;
|
|
55
|
+
/** the allocators of that stretch (only when the window was read in slices) */
|
|
56
|
+
top: {
|
|
57
|
+
key: string;
|
|
58
|
+
name: string;
|
|
59
|
+
component: string | null;
|
|
60
|
+
bytes: number;
|
|
61
|
+
share: number;
|
|
62
|
+
}[];
|
|
63
|
+
/** what the CPU was running when the pause fell (the timeline's segment at that moment) */
|
|
64
|
+
running?: {
|
|
65
|
+
label: string;
|
|
66
|
+
category: string;
|
|
67
|
+
file?: string;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export interface GcMaker {
|
|
71
|
+
key: string;
|
|
72
|
+
name: string;
|
|
73
|
+
url: string;
|
|
74
|
+
line: number;
|
|
75
|
+
category: FrameCategory;
|
|
76
|
+
component: string | null;
|
|
77
|
+
caller?: string;
|
|
78
|
+
/** the app line it is charged to (see `AllocSite.at`) */
|
|
79
|
+
at?: {
|
|
80
|
+
path: string;
|
|
81
|
+
line: number;
|
|
82
|
+
};
|
|
83
|
+
allocated: number;
|
|
84
|
+
/** share of everything allocated in the window */
|
|
85
|
+
share: number;
|
|
86
|
+
/** pause time attributed to it, by its share of each pause's cause */
|
|
87
|
+
gc_ms: number;
|
|
88
|
+
/** pauses where it was among the top three causes */
|
|
89
|
+
pauses: number;
|
|
90
|
+
}
|
|
91
|
+
export interface GcAttribution {
|
|
92
|
+
summary: {
|
|
93
|
+
count: number;
|
|
94
|
+
/** pause time as the app would have paid it (the profiler's share taken out) */
|
|
95
|
+
total_ms: number;
|
|
96
|
+
max_ms: number;
|
|
97
|
+
/** what the observer measured, and the part of it that was the profiler's own garbage */
|
|
98
|
+
measured_ms: number;
|
|
99
|
+
overhead_ms: number;
|
|
100
|
+
minor: number;
|
|
101
|
+
major: number;
|
|
102
|
+
incremental: number;
|
|
103
|
+
weak: number;
|
|
104
|
+
allocated_mb: number;
|
|
105
|
+
alloc_rate_mb_s: number;
|
|
106
|
+
/** heap still held at the end against the start, when the host measured it */
|
|
107
|
+
retained_mb?: number;
|
|
108
|
+
/** slices read (the attribution's resolution) and their typical length */
|
|
109
|
+
slices: number;
|
|
110
|
+
slice_ms: number;
|
|
111
|
+
};
|
|
112
|
+
pauses: GcPause[];
|
|
113
|
+
makers: GcMaker[];
|
|
114
|
+
/** components by pause time attributed to their allocations */
|
|
115
|
+
components: {
|
|
116
|
+
name: string;
|
|
117
|
+
allocated: number;
|
|
118
|
+
gc_ms: number;
|
|
119
|
+
}[];
|
|
120
|
+
/** the profiled request's window starts this many ms into the capture (pause times are on the
|
|
121
|
+
* capture's clock; the timeline's is the window's) */
|
|
122
|
+
window_offset_ms?: number;
|
|
123
|
+
}
|
|
124
|
+
export declare function gc_kind(kind: number): GcEvent["kind"];
|
|
125
|
+
/** The sampling profile flattened to allocation sites: every node with self bytes, keyed by the
|
|
126
|
+
* hash of its stack, with its cumulative self bytes. `dict` collects what each key is. */
|
|
127
|
+
export declare function heap_sites(head: HeapNode, dict: Record<string, AllocSite>): Map<string, number>;
|
|
128
|
+
/** What was allocated between two reads: per site, the growth of its cumulative bytes. */
|
|
129
|
+
export declare function slice_delta(prev: Map<string, number>, cur: Map<string, number>): Record<string, number>;
|
|
130
|
+
export declare function attribute_gc(input: {
|
|
131
|
+
slices: AllocSlice[];
|
|
132
|
+
events: GcEvent[];
|
|
133
|
+
dict: Record<string, AllocSite>;
|
|
134
|
+
window_ms: number;
|
|
135
|
+
retained_mb?: number;
|
|
136
|
+
/** the CPU timeline's segments on the same clock as the events: what was running at each pause */
|
|
137
|
+
running?: {
|
|
138
|
+
t0: number;
|
|
139
|
+
t1: number;
|
|
140
|
+
label: string;
|
|
141
|
+
category: string;
|
|
142
|
+
file?: string;
|
|
143
|
+
}[];
|
|
144
|
+
}): GcAttribution;
|