@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,586 @@
|
|
|
1
|
+
import { may_be_chunk, module_category, module_lookup } from "./module-map.js";
|
|
2
|
+
import { call_sites, file_of, is_profiler_file, register_profiler_file } from "./frames.js";
|
|
3
|
+
// src/profiler/net.ts
|
|
4
|
+
const is_internal_or_dep = (file) => file.includes("node:internal") || file.includes("/node_modules/") || file.includes("\\node_modules\\") || file.includes("/runtime/server/") || file.includes("/runtime/app/") || file.includes("\\runtime\\server\\") || file.includes("\\runtime\\app\\");
|
|
5
|
+
/** a frame in a BUILT server chunk whose line the module map files under a package (Kit's runtime
|
|
6
|
+
* bundled into `output/server/index.js`, a bundled library): not the app's caller, though its file
|
|
7
|
+
* sits outside node_modules. Only chunk paths are looked up (the map is read once per folder) */
|
|
8
|
+
const lookup = module_lookup();
|
|
9
|
+
const in_bundled_package = (file, line) => {
|
|
10
|
+
if (!may_be_chunk(file)) return false;
|
|
11
|
+
const id = lookup(file, line);
|
|
12
|
+
return !!id && !!module_category(id);
|
|
13
|
+
};
|
|
14
|
+
/** the file's base name without its query (`/a/b/c.ts?x` → `c.ts`) */
|
|
15
|
+
const base_name = (file) => {
|
|
16
|
+
const q = file.indexOf("?");
|
|
17
|
+
const s = q === -1 ? file : file.slice(0, q);
|
|
18
|
+
const cut = Math.max(s.lastIndexOf("/"), s.lastIndexOf("\\"));
|
|
19
|
+
return cut === -1 ? s : s.slice(cut + 1);
|
|
20
|
+
};
|
|
21
|
+
const MAX_TIMINGS = 12;
|
|
22
|
+
/**
|
|
23
|
+
* Parse a `Server-Timing` header (RFC-ish: comma-separated metrics, `;`-separated params, `dur` in
|
|
24
|
+
* ms, `desc` quoted or bare). Tolerant: a malformed entry is skipped, never thrown on. Exported for
|
|
25
|
+
* the tests.
|
|
26
|
+
*/
|
|
27
|
+
/** an HTTP token (RFC 7230 tchar), checked char by char — this runs per response header */
|
|
28
|
+
function is_token(s) {
|
|
29
|
+
if (!s.length) return false;
|
|
30
|
+
for (let i = 0; i < s.length; i++) {
|
|
31
|
+
const c = s.charCodeAt(i);
|
|
32
|
+
if (!(c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || c === 33 || c >= 35 && c <= 39 || c === 42 || c === 43 || c === 45 || c === 46 || c === 94 || c === 95 || c === 96 || c === 124 || c === 126)) return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
function parse_server_timing(header) {
|
|
37
|
+
if (!header) return void 0;
|
|
38
|
+
const out = [];
|
|
39
|
+
for (const raw of header.split(",")) {
|
|
40
|
+
if (out.length >= MAX_TIMINGS) break;
|
|
41
|
+
const parts = raw.split(";").map((p) => p.trim());
|
|
42
|
+
const name = parts.shift();
|
|
43
|
+
if (!name || !is_token(name)) continue;
|
|
44
|
+
let ms = 0;
|
|
45
|
+
let desc;
|
|
46
|
+
for (const p of parts) {
|
|
47
|
+
const eq = p.indexOf("=");
|
|
48
|
+
const k = (eq === -1 ? p : p.slice(0, eq)).trim().toLowerCase();
|
|
49
|
+
let v = eq === -1 ? "" : p.slice(eq + 1).trim();
|
|
50
|
+
if (v.startsWith("\"") && v.endsWith("\"") && v.length >= 2) v = v.slice(1, -1);
|
|
51
|
+
if (k === "dur") {
|
|
52
|
+
const n = Number(v);
|
|
53
|
+
if (Number.isFinite(n) && n >= 0) ms = Math.round(n * 100) / 100;
|
|
54
|
+
} else if (k === "desc" && v) desc = v.slice(0, 80);
|
|
55
|
+
}
|
|
56
|
+
out.push(desc ? {
|
|
57
|
+
name: name.slice(0, 40),
|
|
58
|
+
ms,
|
|
59
|
+
desc
|
|
60
|
+
} : {
|
|
61
|
+
name: name.slice(0, 40),
|
|
62
|
+
ms
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return out.length ? out : void 0;
|
|
66
|
+
}
|
|
67
|
+
const NET_STATE = Symbol.for("ogygia.profiler.net-state");
|
|
68
|
+
const shared = globalThis[NET_STATE] ??= { stacks: false };
|
|
69
|
+
function set_stack_capture(on) {
|
|
70
|
+
shared.stacks = on;
|
|
71
|
+
}
|
|
72
|
+
register_profiler_file();
|
|
73
|
+
/** The nearest first-party frame that issued an I/O call — skips node internals,
|
|
74
|
+
* node_modules, and the profiler's own frames, so the caller reads like your
|
|
75
|
+
* code. Shared by the network patch and the async_hooks I/O tracker. */
|
|
76
|
+
function nearest_app_site() {
|
|
77
|
+
for (const site of call_sites(nearest_app_site)) {
|
|
78
|
+
const file = file_of(site);
|
|
79
|
+
if (!file) continue;
|
|
80
|
+
if (is_profiler_file(file) || file.startsWith("node:") || is_internal_or_dep(file) || in_bundled_package(file, site.getLineNumber() ?? 0)) continue;
|
|
81
|
+
return {
|
|
82
|
+
fn: site.getFunctionName() || "(anonymous)",
|
|
83
|
+
file,
|
|
84
|
+
line: site.getLineNumber() ?? 0,
|
|
85
|
+
column: site.getColumnNumber() ?? 0
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** The first-party CALL PATH above an I/O call — the nearest app frame and the ones that called it
|
|
90
|
+
* (`fetchStock` ← `load`), skipping the same internals as `nearest_app_site`. Read off the one
|
|
91
|
+
* stack that call already captured, so it costs nothing extra beyond a few more frames. */
|
|
92
|
+
function app_call_chain(limit = 5) {
|
|
93
|
+
const out = [];
|
|
94
|
+
for (const site of call_sites(app_call_chain)) {
|
|
95
|
+
const file = file_of(site);
|
|
96
|
+
if (!file) continue;
|
|
97
|
+
if (is_profiler_file(file) || file.startsWith("node:") || is_internal_or_dep(file) || in_bundled_package(file, site.getLineNumber() ?? 0)) continue;
|
|
98
|
+
out.push({
|
|
99
|
+
fn: site.getFunctionName() || "(anonymous)",
|
|
100
|
+
file,
|
|
101
|
+
line: site.getLineNumber() ?? 0,
|
|
102
|
+
column: site.getColumnNumber() ?? 0
|
|
103
|
+
});
|
|
104
|
+
if (out.length >= limit) break;
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
/** Best-effort caller string with no sourcemap (fallback before resolution). */
|
|
109
|
+
function nearest_app_frame() {
|
|
110
|
+
const s = nearest_app_site();
|
|
111
|
+
if (!s) return void 0;
|
|
112
|
+
return `${s.fn} (${base_name(s.file)}:${s.line})`;
|
|
113
|
+
}
|
|
114
|
+
let installed = false;
|
|
115
|
+
let net_emit = null;
|
|
116
|
+
/** the current request's kept-answer lookup (its context's `answer`), set by install */
|
|
117
|
+
let net_answer = null;
|
|
118
|
+
const KEEP_ONE = 1048576;
|
|
119
|
+
const KEEP_ALL = 8388608;
|
|
120
|
+
let keep_bodies = false;
|
|
121
|
+
let kept_bytes = 0;
|
|
122
|
+
const kept = /* @__PURE__ */ new Map();
|
|
123
|
+
/** each GET's FIRST answer in the recording too: against the last, what changed between renders */
|
|
124
|
+
const kept_first = /* @__PURE__ */ new Map();
|
|
125
|
+
/** start (clearing what was kept) or stop keeping answers */
|
|
126
|
+
function keep_answers(on) {
|
|
127
|
+
keep_bodies = on;
|
|
128
|
+
if (on) {
|
|
129
|
+
kept.clear();
|
|
130
|
+
kept_first.clear();
|
|
131
|
+
kept_bytes = 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** drop every kept answer */
|
|
135
|
+
function drop_answers() {
|
|
136
|
+
kept.clear();
|
|
137
|
+
kept_first.clear();
|
|
138
|
+
kept_bytes = 0;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* WHAT CHANGED IN AN ANSWER between the first render and the last: the one stretch that differs
|
|
142
|
+
* (the bytes before it and after it are the same), and that stretch as text with a little around
|
|
143
|
+
* it — a timestamp, a request id. Undefined when either answer was not kept, or they are the same.
|
|
144
|
+
*/
|
|
145
|
+
function answer_diff(key) {
|
|
146
|
+
const a = kept_first.get(key);
|
|
147
|
+
const b = kept.get(key)?.body;
|
|
148
|
+
if (!a || !b) return void 0;
|
|
149
|
+
let pre = 0;
|
|
150
|
+
const min = Math.min(a.length, b.length);
|
|
151
|
+
while (pre < min && a[pre] === b[pre]) pre++;
|
|
152
|
+
if (pre === a.length && pre === b.length) return void 0;
|
|
153
|
+
let suf = 0;
|
|
154
|
+
while (suf < min - pre && a[a.length - 1 - suf] === b[b.length - 1 - suf]) suf++;
|
|
155
|
+
const bytes = Math.max(a.length, b.length) - pre - suf;
|
|
156
|
+
const from = Math.max(0, pre - 24);
|
|
157
|
+
const to = Math.min(b.length, b.length - suf + 8);
|
|
158
|
+
let text = new TextDecoder().decode(b.subarray(from, to));
|
|
159
|
+
if (text.length > 90) text = text.slice(0, 89) + "…";
|
|
160
|
+
return {
|
|
161
|
+
bytes,
|
|
162
|
+
size: Math.max(a.length, b.length),
|
|
163
|
+
text
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** a kept answer as a fresh Response, or undefined */
|
|
167
|
+
function kept_answer(key) {
|
|
168
|
+
const k = kept.get(key);
|
|
169
|
+
if (!k) return void 0;
|
|
170
|
+
return new Response(k.body.slice(), {
|
|
171
|
+
status: k.status,
|
|
172
|
+
headers: k.headers
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const OG_FETCH_PATCH = Symbol.for("ogygia.profiler.net.fetch-patch");
|
|
176
|
+
/**
|
|
177
|
+
* Install the patches. `fallback` receives calls made outside any request
|
|
178
|
+
* context (startup work, background jobs) so window recordings still see them.
|
|
179
|
+
* Idempotent: a second call just re-asserts the fetch patch (see `ensure_fetch_patched`).
|
|
180
|
+
*/
|
|
181
|
+
async function install_net_capture(als, fallback) {
|
|
182
|
+
net_answer = (method, url) => als.getStore()?.answer?.(method, url);
|
|
183
|
+
net_emit = (call) => {
|
|
184
|
+
const ctx = als.getStore();
|
|
185
|
+
if (ctx) {
|
|
186
|
+
call.route = ctx.route;
|
|
187
|
+
call.path = ctx.path;
|
|
188
|
+
ctx.on_net(call);
|
|
189
|
+
} else fallback(call);
|
|
190
|
+
};
|
|
191
|
+
if (installed) {
|
|
192
|
+
ensure_fetch_patched();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
installed = true;
|
|
196
|
+
patch_fetch(net_emit);
|
|
197
|
+
await patch_http(net_emit);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Re-assert the `globalThis.fetch` patch. `globalThis.fetch` can be REPLACED out from under us after
|
|
201
|
+
* install — a late undici init, a framework fetch polyfill, an HMR reload of this module — which
|
|
202
|
+
* silently kills capture, so reports "sometimes catch network, sometimes don't". Call this before every
|
|
203
|
+
* profile: if the live fetch isn't ours (identified by the brand symbol, so we never wrap our own
|
|
204
|
+
* wrapper) re-wrap whatever is there now with the same sink. One property read, idempotent — safe to
|
|
205
|
+
* call every request/run.
|
|
206
|
+
*/
|
|
207
|
+
function ensure_fetch_patched() {
|
|
208
|
+
if (!net_emit) return;
|
|
209
|
+
const cur = globalThis.fetch;
|
|
210
|
+
if (typeof cur === "function" && cur[OG_FETCH_PATCH]) return;
|
|
211
|
+
patch_fetch(net_emit);
|
|
212
|
+
}
|
|
213
|
+
const INTERNAL_HEADER = "x-og-profiler-internal";
|
|
214
|
+
function is_internal(input, init) {
|
|
215
|
+
if (input instanceof Request && input.headers.has(INTERNAL_HEADER)) return true;
|
|
216
|
+
const h = init?.headers;
|
|
217
|
+
if (!h) return false;
|
|
218
|
+
if (h instanceof Headers) return h.has(INTERNAL_HEADER);
|
|
219
|
+
if (Array.isArray(h)) return h.some(([k]) => k.toLowerCase() === INTERNAL_HEADER);
|
|
220
|
+
return Object.keys(h).some((k) => k.toLowerCase() === INTERNAL_HEADER);
|
|
221
|
+
}
|
|
222
|
+
function host_of(url) {
|
|
223
|
+
try {
|
|
224
|
+
return new URL(url).host;
|
|
225
|
+
} catch {
|
|
226
|
+
return "";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function patch_fetch(emit) {
|
|
230
|
+
const orig = globalThis.fetch;
|
|
231
|
+
if (typeof orig !== "function") return;
|
|
232
|
+
if (orig[OG_FETCH_PATCH]) return;
|
|
233
|
+
globalThis.fetch = wrap_fetch(orig, emit);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* KIT'S OWN `event.fetch` never reaches the global one for a same-origin call: it dispatches the
|
|
237
|
+
* request to the app's `respond()` in-process. A load that calls its own API through it is
|
|
238
|
+
* invisible to the global patch — so the profiler wraps `event.fetch` per attributed request with
|
|
239
|
+
* the same recorder. Idempotent (the brand), nothing when capture is not installed.
|
|
240
|
+
*/
|
|
241
|
+
function wrap_event_fetch(f) {
|
|
242
|
+
if (!net_emit || typeof f !== "function" || f[OG_FETCH_PATCH]) return f;
|
|
243
|
+
return wrap_fetch(f, net_emit);
|
|
244
|
+
}
|
|
245
|
+
/** the request header that asks, and the response header that answers */
|
|
246
|
+
const TRACE_HEADER = "x-og-trace";
|
|
247
|
+
const MAX_TRACE_CHARS = 4e3;
|
|
248
|
+
function encode_trace(t) {
|
|
249
|
+
let top = t.top ?? [];
|
|
250
|
+
let s = "";
|
|
251
|
+
for (;;) {
|
|
252
|
+
s = Buffer.from(JSON.stringify({
|
|
253
|
+
...t,
|
|
254
|
+
...top.length ? { top } : {}
|
|
255
|
+
}), "utf8").toString("base64");
|
|
256
|
+
if (s.length <= MAX_TRACE_CHARS || !top.length) break;
|
|
257
|
+
top = top.slice(0, -1);
|
|
258
|
+
}
|
|
259
|
+
return s;
|
|
260
|
+
}
|
|
261
|
+
function decode_trace(h) {
|
|
262
|
+
if (!h || h.length > MAX_TRACE_CHARS * 2) return void 0;
|
|
263
|
+
try {
|
|
264
|
+
const t = JSON.parse(Buffer.from(h, "base64").toString("utf8"));
|
|
265
|
+
if (typeof t?.ms !== "number" || !Number.isFinite(t.ms)) return void 0;
|
|
266
|
+
const out = {
|
|
267
|
+
ms: t.ms,
|
|
268
|
+
cpu_ms: Number(t.cpu_ms) || 0,
|
|
269
|
+
wait_ms: Number(t.wait_ms) || 0,
|
|
270
|
+
calls: Number(t.calls) || 0
|
|
271
|
+
};
|
|
272
|
+
if (typeof t.route === "string" || t.route === null) out.route = t.route;
|
|
273
|
+
if (Array.isArray(t.top)) out.top = t.top.filter((x) => x && typeof x.url === "string" && typeof x.ms === "number").slice(0, 8).map((x) => ({
|
|
274
|
+
url: x.url.slice(0, 200),
|
|
275
|
+
ms: x.ms
|
|
276
|
+
}));
|
|
277
|
+
if (typeof t.profiler === "string") out.profiler = t.profiler.slice(0, 200);
|
|
278
|
+
return out;
|
|
279
|
+
} catch {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** The recording wrapper around a fetch-shaped function: one NetCall per call, timed to headers
|
|
284
|
+
* and body, the caller captured while a recording runs. */
|
|
285
|
+
function wrap_fetch(orig, emit) {
|
|
286
|
+
const patched = async function fetch(input, init) {
|
|
287
|
+
if (is_internal(input, init)) return orig(input, init);
|
|
288
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input?.url ?? "";
|
|
289
|
+
const method = init?.method ?? (input?.method || "GET");
|
|
290
|
+
const canned = net_answer?.(method.toUpperCase(), url);
|
|
291
|
+
if (canned) return canned;
|
|
292
|
+
const call = {
|
|
293
|
+
start: performance.now(),
|
|
294
|
+
epoch: Date.now(),
|
|
295
|
+
ms: -1,
|
|
296
|
+
method: method.toUpperCase(),
|
|
297
|
+
url,
|
|
298
|
+
host: host_of(url),
|
|
299
|
+
status: 0,
|
|
300
|
+
kind: "fetch",
|
|
301
|
+
route: null,
|
|
302
|
+
path: null
|
|
303
|
+
};
|
|
304
|
+
if (shared.stacks) {
|
|
305
|
+
const chain = app_call_chain();
|
|
306
|
+
call.caller_site = chain[0];
|
|
307
|
+
if (chain.length > 1) call.caller_chain = chain;
|
|
308
|
+
}
|
|
309
|
+
capture_req_payload(init, call);
|
|
310
|
+
try {
|
|
311
|
+
const h = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
312
|
+
if (h.has("cookie") || h.has("authorization")) call.personal = true;
|
|
313
|
+
} catch {}
|
|
314
|
+
emit(call);
|
|
315
|
+
try {
|
|
316
|
+
let init2 = init;
|
|
317
|
+
if (shared.stacks) try {
|
|
318
|
+
const h = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
319
|
+
h.set(TRACE_HEADER, "1");
|
|
320
|
+
init2 = {
|
|
321
|
+
...init,
|
|
322
|
+
headers: h
|
|
323
|
+
};
|
|
324
|
+
} catch {
|
|
325
|
+
init2 = init;
|
|
326
|
+
}
|
|
327
|
+
const res = await orig(input, init2);
|
|
328
|
+
call.ms = round2(performance.now() - call.start);
|
|
329
|
+
call.status = res.status;
|
|
330
|
+
call.type = short_ct(res.headers.get("content-type"));
|
|
331
|
+
call.encoding = res.headers.get("content-encoding") || void 0;
|
|
332
|
+
const len = res.headers.get("content-length");
|
|
333
|
+
if (len) call.transfer_bytes = Number(len) || void 0;
|
|
334
|
+
call.headers = headers_of(res.headers);
|
|
335
|
+
call.timings = parse_server_timing(res.headers.get("server-timing"));
|
|
336
|
+
const trace = decode_trace(res.headers.get(TRACE_HEADER));
|
|
337
|
+
if (trace) call.trace = trace;
|
|
338
|
+
count_decoded(res, call);
|
|
339
|
+
wrap_body(res, call);
|
|
340
|
+
return res;
|
|
341
|
+
} catch (e) {
|
|
342
|
+
call.ms = round2(performance.now() - call.start);
|
|
343
|
+
call.error = e instanceof Error ? e.message : String(e);
|
|
344
|
+
throw e;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
patched[OG_FETCH_PATCH] = true;
|
|
348
|
+
return patched;
|
|
349
|
+
}
|
|
350
|
+
/** time json()/text()/arrayBuffer() so "slow API" vs "slow body download" is visible */
|
|
351
|
+
function wrap_body(res, call) {
|
|
352
|
+
for (const name of [
|
|
353
|
+
"json",
|
|
354
|
+
"text",
|
|
355
|
+
"arrayBuffer"
|
|
356
|
+
]) {
|
|
357
|
+
const orig = res[name]?.bind(res);
|
|
358
|
+
if (!orig) continue;
|
|
359
|
+
try {
|
|
360
|
+
Object.defineProperty(res, name, {
|
|
361
|
+
configurable: true,
|
|
362
|
+
writable: true,
|
|
363
|
+
value: async function() {
|
|
364
|
+
const t = performance.now();
|
|
365
|
+
const v = await orig();
|
|
366
|
+
call.body_ms = round2((call.body_ms ?? 0) + (performance.now() - t));
|
|
367
|
+
if (call.bytes === void 0) {
|
|
368
|
+
if (typeof v === "string") call.bytes = v.length;
|
|
369
|
+
else if (v instanceof ArrayBuffer) call.bytes = v.byteLength;
|
|
370
|
+
}
|
|
371
|
+
return v;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
} catch {}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/** Strip params from a content-type: "application/json; charset=utf-8" → "application/json". */
|
|
378
|
+
function short_ct(ct) {
|
|
379
|
+
if (!ct) return void 0;
|
|
380
|
+
const i = ct.indexOf(";");
|
|
381
|
+
return (i === -1 ? ct : ct.slice(0, i)).trim() || void 0;
|
|
382
|
+
}
|
|
383
|
+
/** Snapshot response headers for the request side panel. Capped (≤40 entries, ≤512 chars each) so a
|
|
384
|
+
* rogue header value can't bloat the `.ogp`. Returns undefined when there are none. */
|
|
385
|
+
function headers_of(h) {
|
|
386
|
+
const out = {};
|
|
387
|
+
let n = 0;
|
|
388
|
+
for (const [k, v] of h) {
|
|
389
|
+
if (n++ >= 40) break;
|
|
390
|
+
out[k] = v.length > 512 ? v.slice(0, 512) + "…" : v;
|
|
391
|
+
}
|
|
392
|
+
return n ? out : void 0;
|
|
393
|
+
}
|
|
394
|
+
const MAX_PAYLOAD = 4e6;
|
|
395
|
+
const utf8_len = (s) => {
|
|
396
|
+
try {
|
|
397
|
+
return new TextEncoder().encode(s).byteLength;
|
|
398
|
+
} catch {
|
|
399
|
+
return s.length;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
const clip = (s) => s.length > MAX_PAYLOAD ? s.slice(0, MAX_PAYLOAD) + "\n… (truncated)" : s;
|
|
403
|
+
/** Capture the request body's size (+ a preview for string / form bodies). Never consumes a stream. */
|
|
404
|
+
function capture_req_payload(init, call) {
|
|
405
|
+
const body = init?.body;
|
|
406
|
+
if (body == null) return;
|
|
407
|
+
try {
|
|
408
|
+
if (typeof body === "string") {
|
|
409
|
+
call.req_bytes = utf8_len(body);
|
|
410
|
+
call.req_payload = clip(body);
|
|
411
|
+
} else if (body instanceof URLSearchParams) {
|
|
412
|
+
const s = body.toString();
|
|
413
|
+
call.req_bytes = utf8_len(s);
|
|
414
|
+
call.req_payload = clip(s);
|
|
415
|
+
} else if (body instanceof ArrayBuffer) call.req_bytes = body.byteLength;
|
|
416
|
+
else if (ArrayBuffer.isView(body)) call.req_bytes = body.byteLength;
|
|
417
|
+
} catch {}
|
|
418
|
+
}
|
|
419
|
+
/** a body is fingerprinted over this many bytes at most (with its full length) */
|
|
420
|
+
const BODY_HASH_MAX = 4194304;
|
|
421
|
+
/** Count the DECODED response body size by draining a CLONE in the background (the original is left
|
|
422
|
+
* untouched). Sets `call.bytes` when done — the render finishes first, so it's ready by report time. */
|
|
423
|
+
function count_decoded(res, call) {
|
|
424
|
+
let clone;
|
|
425
|
+
try {
|
|
426
|
+
clone = res.clone();
|
|
427
|
+
} catch {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const stream = clone.body;
|
|
431
|
+
if (!stream) return;
|
|
432
|
+
const keep = keep_bodies && !call.personal && call.method === "GET" && res.status >= 200 && res.status < 300;
|
|
433
|
+
const status = res.status;
|
|
434
|
+
(async () => {
|
|
435
|
+
try {
|
|
436
|
+
const reader = stream.getReader();
|
|
437
|
+
let total = 0;
|
|
438
|
+
let parts = keep ? [] : null;
|
|
439
|
+
let h = 2166136261;
|
|
440
|
+
for (;;) {
|
|
441
|
+
const { done, value } = await reader.read();
|
|
442
|
+
if (done) break;
|
|
443
|
+
if (value) {
|
|
444
|
+
const n = Math.min(value.byteLength, Math.max(0, BODY_HASH_MAX - total));
|
|
445
|
+
for (let i = 0; i < n; i++) h = Math.imul(h ^ value[i], 16777619);
|
|
446
|
+
}
|
|
447
|
+
total += value?.byteLength ?? 0;
|
|
448
|
+
if (parts && value) {
|
|
449
|
+
if (total > KEEP_ONE) parts = null;
|
|
450
|
+
else parts.push(value);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
call.bytes = total;
|
|
454
|
+
call.body_hash = (h >>> 0).toString(16) + ":" + total;
|
|
455
|
+
if (parts && keep_bodies) {
|
|
456
|
+
const key = `GET ${call.url}`;
|
|
457
|
+
const before = kept.get(key)?.body.byteLength ?? 0;
|
|
458
|
+
if (kept_bytes - before + total <= KEEP_ALL) {
|
|
459
|
+
const body = new Uint8Array(total);
|
|
460
|
+
let at = 0;
|
|
461
|
+
for (const p of parts) {
|
|
462
|
+
body.set(p, at);
|
|
463
|
+
at += p.byteLength;
|
|
464
|
+
}
|
|
465
|
+
const headers = [];
|
|
466
|
+
clone.headers.forEach((v, k) => {
|
|
467
|
+
if (k !== "content-encoding" && k !== "content-length" && k !== "transfer-encoding") headers.push([k, v]);
|
|
468
|
+
});
|
|
469
|
+
kept.set(key, {
|
|
470
|
+
status,
|
|
471
|
+
headers,
|
|
472
|
+
body
|
|
473
|
+
});
|
|
474
|
+
kept_bytes += total - before;
|
|
475
|
+
if (!kept_first.has(key) && kept_bytes + total <= KEEP_ALL) {
|
|
476
|
+
kept_first.set(key, body);
|
|
477
|
+
kept_bytes += total;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} catch {}
|
|
482
|
+
})();
|
|
483
|
+
}
|
|
484
|
+
async function patch_http(emit) {
|
|
485
|
+
for (const proto of ["http", "https"]) {
|
|
486
|
+
let mod;
|
|
487
|
+
try {
|
|
488
|
+
mod = (await import(
|
|
489
|
+
/* @vite-ignore */
|
|
490
|
+
`node:${proto}`
|
|
491
|
+
)).default;
|
|
492
|
+
} catch {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const orig_request = mod.request;
|
|
496
|
+
const orig_get = mod.get;
|
|
497
|
+
if (typeof orig_request !== "function") continue;
|
|
498
|
+
try {
|
|
499
|
+
mod.request = function(...args) {
|
|
500
|
+
const req = orig_request.apply(this, args);
|
|
501
|
+
instrument_client_request(req, args, proto, emit);
|
|
502
|
+
return req;
|
|
503
|
+
};
|
|
504
|
+
if (typeof orig_get === "function") mod.get = function(...args) {
|
|
505
|
+
const req = orig_get.apply(this, args);
|
|
506
|
+
instrument_client_request(req, args, proto, emit);
|
|
507
|
+
return req;
|
|
508
|
+
};
|
|
509
|
+
} catch {}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function instrument_client_request(req, args, proto, emit) {
|
|
513
|
+
const r = req;
|
|
514
|
+
if (typeof r?.on !== "function") return;
|
|
515
|
+
let url = "";
|
|
516
|
+
let method = "GET";
|
|
517
|
+
const a0 = args[0];
|
|
518
|
+
if (typeof a0 === "string") url = a0;
|
|
519
|
+
else if (a0 instanceof URL) url = a0.href;
|
|
520
|
+
const opts = typeof a0 === "object" && !(a0 instanceof URL) ? a0 : args[1];
|
|
521
|
+
if (opts && typeof opts === "object") {
|
|
522
|
+
if (typeof opts.method === "string") method = opts.method;
|
|
523
|
+
if (!url) url = `${proto}://${opts.hostname || opts.host || "localhost"}${opts.port ? `:${opts.port}` : ""}${opts.path || "/"}`;
|
|
524
|
+
}
|
|
525
|
+
if (r.method) method = r.method;
|
|
526
|
+
const call = {
|
|
527
|
+
start: performance.now(),
|
|
528
|
+
epoch: Date.now(),
|
|
529
|
+
ms: -1,
|
|
530
|
+
method: method.toUpperCase(),
|
|
531
|
+
url,
|
|
532
|
+
host: host_of(url),
|
|
533
|
+
status: 0,
|
|
534
|
+
kind: "http",
|
|
535
|
+
route: null,
|
|
536
|
+
path: null
|
|
537
|
+
};
|
|
538
|
+
if (shared.stacks) {
|
|
539
|
+
const chain = app_call_chain();
|
|
540
|
+
call.caller_site = chain[0];
|
|
541
|
+
if (chain.length > 1) call.caller_chain = chain;
|
|
542
|
+
}
|
|
543
|
+
emit(call);
|
|
544
|
+
r.on("response", (res) => {
|
|
545
|
+
call.ms = round2(performance.now() - call.start);
|
|
546
|
+
const rr = res;
|
|
547
|
+
call.status = rr.statusCode ?? 0;
|
|
548
|
+
const h = rr.headers ?? {};
|
|
549
|
+
const str_h = (v) => Array.isArray(v) ? v[0] : v;
|
|
550
|
+
const len = str_h(h["content-length"]);
|
|
551
|
+
if (len) call.transfer_bytes = Number(len) || void 0;
|
|
552
|
+
call.encoding = str_h(h["content-encoding"]) || void 0;
|
|
553
|
+
call.type = short_ct(str_h(h["content-type"]) ?? null);
|
|
554
|
+
call.timings = parse_server_timing(str_h(h["server-timing"]));
|
|
555
|
+
rr.on?.("end", () => {
|
|
556
|
+
call.body_ms = round2(performance.now() - call.start - call.ms);
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
r.on("error", (e) => {
|
|
560
|
+
if (call.ms === -1) call.ms = round2(performance.now() - call.start);
|
|
561
|
+
call.error = e instanceof Error ? e.message : String(e);
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
function round2(n) {
|
|
565
|
+
return Math.round(n * 100) / 100;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* How much of the wall time is explained by network calls running one after
|
|
569
|
+
* another (no overlap). High sequential time with several calls = the classic
|
|
570
|
+
* "await in a loop" page.
|
|
571
|
+
*/
|
|
572
|
+
function sequential_ms(calls) {
|
|
573
|
+
const done = calls.filter((c) => c.ms >= 0).map((c) => ({
|
|
574
|
+
s: c.start,
|
|
575
|
+
e: c.start + c.ms + (c.body_ms ?? 0)
|
|
576
|
+
})).sort((a, b) => a.s - b.s);
|
|
577
|
+
let total = 0;
|
|
578
|
+
let edge = -Infinity;
|
|
579
|
+
for (const c of done) {
|
|
580
|
+
if (c.s >= edge) total += c.e - c.s;
|
|
581
|
+
else if (c.e > edge) total += c.e - edge;
|
|
582
|
+
edge = Math.max(edge, c.e);
|
|
583
|
+
}
|
|
584
|
+
return Math.round(total * 100) / 100;
|
|
585
|
+
}
|
|
586
|
+
export { TRACE_HEADER, answer_diff, app_call_chain, decode_trace, drop_answers, encode_trace, ensure_fetch_patched, install_net_capture, keep_answers, kept_answer, nearest_app_frame, nearest_app_site, parse_server_timing, register_profiler_file, sequential_ms, set_stack_capture, wrap_event_fetch };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DEMAND-ONLY profiler routing (`profiler: { onDemand: true }`).
|
|
3
|
+
*
|
|
4
|
+
* The whole decision the handle makes each request BEFORE it would import the profiler: skip the
|
|
5
|
+
* profiler entirely — no import, no parse, no init, no per-request wrap — when the mode is on, it has
|
|
6
|
+
* not been mounted yet this process, and this request is not for the profiler UI. Reads only plain
|
|
7
|
+
* values, so no profiler code is loaded to decide. `/__profiler` is a reserved, clash-safe path, so a
|
|
8
|
+
* substring test is safe: it can only ever mount the profiler EARLY (harmless — the same cost normal
|
|
9
|
+
* mode always pays), never MISS a real profiler request (which would break the dashboard).
|
|
10
|
+
*/
|
|
11
|
+
export declare function profiler_demand_skip(on_demand: boolean, profiler_mounted: boolean, pathname: string, ui_path: string): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/profiler/on-demand.ts
|
|
2
|
+
/**
|
|
3
|
+
* DEMAND-ONLY profiler routing (`profiler: { onDemand: true }`).
|
|
4
|
+
*
|
|
5
|
+
* The whole decision the handle makes each request BEFORE it would import the profiler: skip the
|
|
6
|
+
* profiler entirely — no import, no parse, no init, no per-request wrap — when the mode is on, it has
|
|
7
|
+
* not been mounted yet this process, and this request is not for the profiler UI. Reads only plain
|
|
8
|
+
* values, so no profiler code is loaded to decide. `/__profiler` is a reserved, clash-safe path, so a
|
|
9
|
+
* substring test is safe: it can only ever mount the profiler EARLY (harmless — the same cost normal
|
|
10
|
+
* mode always pays), never MISS a real profiler request (which would break the dashboard).
|
|
11
|
+
*/
|
|
12
|
+
function profiler_demand_skip(on_demand, profiler_mounted, pathname, ui_path) {
|
|
13
|
+
return on_demand && !profiler_mounted && !pathname.includes(ui_path);
|
|
14
|
+
}
|
|
15
|
+
export { profiler_demand_skip };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ReportMeta, RequestEntry } from "./report.js";
|
|
2
|
+
/**
|
|
3
|
+
* THE PROFILED PAGE'S OWN REQUESTS among every request the window saw: a page profile's renders
|
|
4
|
+
* (the profiler's own, of that path), a caught or header-profiled request's path. Another visitor's
|
|
5
|
+
* page answered meanwhile carries ITS islands and ITS seed — read from it, a report said the
|
|
6
|
+
* profiled page shipped the other page's data. A window profile has no page: all of them.
|
|
7
|
+
* (Its own module, type imports only: the compare view runs in the browser.)
|
|
8
|
+
*/
|
|
9
|
+
export declare function own_requests(meta: ReportMeta): RequestEntry[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/profiler/own-requests.ts
|
|
2
|
+
/**
|
|
3
|
+
* THE PROFILED PAGE'S OWN REQUESTS among every request the window saw: a page profile's renders
|
|
4
|
+
* (the profiler's own, of that path), a caught or header-profiled request's path. Another visitor's
|
|
5
|
+
* page answered meanwhile carries ITS islands and ITS seed — read from it, a report said the
|
|
6
|
+
* profiled page shipped the other page's data. A window profile has no page: all of them.
|
|
7
|
+
* (Its own module, type imports only: the compare view runs in the browser.)
|
|
8
|
+
*/
|
|
9
|
+
function own_requests(meta) {
|
|
10
|
+
if (meta.trigger === "page" && meta.page) {
|
|
11
|
+
const path = meta.page.split("?")[0];
|
|
12
|
+
return meta.requests.filter((r) => r.internal && r.path === path);
|
|
13
|
+
}
|
|
14
|
+
if (meta.request) return meta.requests.filter((r) => r.path === meta.request.path);
|
|
15
|
+
return meta.requests;
|
|
16
|
+
}
|
|
17
|
+
export { own_requests };
|