@ogygia/devtools 0.0.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +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 +105 -6
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AN APP FILE, NAMED FROM THE APP'S FOLDER: `src/lib/x.ts` and `src/routes/a/b/c/+page.svelte`
|
|
3
|
+
* alike, whatever the depth (a cut to the last few segments named one app's files four different
|
|
4
|
+
* ways). Undefined outside the app's folder, and for the build's output (its chunks read by their
|
|
5
|
+
* own short form).
|
|
6
|
+
*/
|
|
7
|
+
export declare function app_relative(path: string): string | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/profiler/app-path.ts
|
|
2
|
+
/** the app's folder (the server's working directory, with a trailing slash), read once */
|
|
3
|
+
let app_root;
|
|
4
|
+
function root_of_app() {
|
|
5
|
+
if (app_root === void 0) try {
|
|
6
|
+
const cwd = globalThis.process?.cwd?.();
|
|
7
|
+
app_root = cwd ? cwd.split("\\").join("/") : null;
|
|
8
|
+
if (app_root && !app_root.endsWith("/")) app_root += "/";
|
|
9
|
+
} catch {
|
|
10
|
+
app_root = null;
|
|
11
|
+
}
|
|
12
|
+
return app_root;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* AN APP FILE, NAMED FROM THE APP'S FOLDER: `src/lib/x.ts` and `src/routes/a/b/c/+page.svelte`
|
|
16
|
+
* alike, whatever the depth (a cut to the last few segments named one app's files four different
|
|
17
|
+
* ways). Undefined outside the app's folder, and for the build's output (its chunks read by their
|
|
18
|
+
* own short form).
|
|
19
|
+
*/
|
|
20
|
+
function app_relative(path) {
|
|
21
|
+
const root = root_of_app();
|
|
22
|
+
if (!root || !path.startsWith(root)) return void 0;
|
|
23
|
+
const rel = path.slice(root.length);
|
|
24
|
+
return rel.startsWith(".svelte-kit") || rel.startsWith("node_modules/") ? void 0 : rel;
|
|
25
|
+
}
|
|
26
|
+
export { app_relative };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { CallerSite } from "./net.js";
|
|
2
|
+
export interface IoOp {
|
|
3
|
+
/** async resource type: Timeout, FSREQCALLBACK, GETADDRINFOREQWRAP, TCPWRAP, … */
|
|
4
|
+
type: string;
|
|
5
|
+
/** resolved caller, filled in at report time */
|
|
6
|
+
caller?: string;
|
|
7
|
+
/** raw caller location (bundled), resolved to source at report time */
|
|
8
|
+
caller_site?: CallerSite;
|
|
9
|
+
/** the caller's source position, for reading the call site's code */
|
|
10
|
+
caller_at?: {
|
|
11
|
+
path: string;
|
|
12
|
+
line: number;
|
|
13
|
+
};
|
|
14
|
+
/** init → destroy duration in ms — the time the code waited on this resource */
|
|
15
|
+
ms: number;
|
|
16
|
+
/** performance.now() at init — lets page mode window ops to a single representative render */
|
|
17
|
+
start: number;
|
|
18
|
+
/** still open when the window ended (long-lived socket, watcher) */
|
|
19
|
+
open?: boolean;
|
|
20
|
+
/** a timer's period, when it repeats (`setInterval`), else its delay (ms) */
|
|
21
|
+
repeat?: number;
|
|
22
|
+
delay?: number;
|
|
23
|
+
/** an open timer that does not hold the process (`.unref()`; Node's own `AbortSignal.timeout`) */
|
|
24
|
+
unref?: true;
|
|
25
|
+
/** page mode: every render left one of these open, from the same line (index.ts reads it
|
|
26
|
+
* across the runs, before the io is scoped to one) — a timer each render starts and never ends */
|
|
27
|
+
left_each_run?: true;
|
|
28
|
+
}
|
|
29
|
+
export interface IoRecorder {
|
|
30
|
+
stop(): IoOp[];
|
|
31
|
+
/** promises created while the recorder ran, and who created them (one stack per
|
|
32
|
+
* PROMISE_SAMPLE_EVERY promises — a counter otherwise) */
|
|
33
|
+
promises(): {
|
|
34
|
+
count: number;
|
|
35
|
+
top: {
|
|
36
|
+
caller: string;
|
|
37
|
+
share: number;
|
|
38
|
+
site?: CallerSite;
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** `fn (dir/file:line)` from a generated position: what a promise origin reads as when no
|
|
43
|
+
* sourcemap maps it */
|
|
44
|
+
export declare function short_site(s: CallerSite): string;
|
|
45
|
+
/** Start timing I/O resources. Call `stop()` at the end of the window. */
|
|
46
|
+
export declare function record_async_io(): Promise<IoRecorder | null>;
|
|
47
|
+
/**
|
|
48
|
+
* A timer a render starts and never ends: an interval (or a timeout of a second or more) still open
|
|
49
|
+
* after every render, started from the same line in each of them. One per render is the proof it is
|
|
50
|
+
* per render (a cache's single refresh timer, started on the first request, is one in all). Marked
|
|
51
|
+
* on the ops themselves (`left_each_run`), so the run kept for the waterfall carries it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function mark_left_each_run(ops: IoOp[], windows: Array<{
|
|
54
|
+
start: number;
|
|
55
|
+
end: number;
|
|
56
|
+
}>): void;
|
|
57
|
+
/** A friendly bucket for a resource type, for the report. */
|
|
58
|
+
export declare function io_kind(type: string): "timer" | "file" | "dns" | "socket" | "zlib" | "other";
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { nearest_app_site } from "./net.js";
|
|
2
|
+
import { call_sites, file_of, is_profiler_file, register_profiler_file } from "./frames.js";
|
|
3
|
+
// src/profiler/async-io.ts
|
|
4
|
+
/**
|
|
5
|
+
* The general I/O-wait tracker (Tier 3).
|
|
6
|
+
*
|
|
7
|
+
* The CPU sampler can't see waiting, and the fetch/http patch only covers HTTP.
|
|
8
|
+
* This uses `async_hooks` to time the underlying I/O *primitives* — timers,
|
|
9
|
+
* file reads, DNS lookups, raw sockets — and blames the app function that
|
|
10
|
+
* started each one, with no wrapping of anyone's code. That is how we attribute
|
|
11
|
+
* a slow `load`'s database or `setTimeout` wait to a function even though it
|
|
12
|
+
* never appears on the CPU stack.
|
|
13
|
+
*
|
|
14
|
+
* Enabled ONLY during a recording (a stack is captured per tracked resource).
|
|
15
|
+
* HTTP/TLS resources are deliberately skipped — the fetch/http patch already
|
|
16
|
+
* reports those with URL + status, and undici keep-alive would inflate socket
|
|
17
|
+
* lifetimes here anyway.
|
|
18
|
+
*/
|
|
19
|
+
register_profiler_file();
|
|
20
|
+
const TRACK = /* @__PURE__ */ new Set([
|
|
21
|
+
"Timeout",
|
|
22
|
+
"Immediate",
|
|
23
|
+
"TickObject",
|
|
24
|
+
"FSREQCALLBACK",
|
|
25
|
+
"FSREQPROMISE",
|
|
26
|
+
"STATWATCHER",
|
|
27
|
+
"FSEVENTWRAP",
|
|
28
|
+
"GETADDRINFOREQWRAP",
|
|
29
|
+
"GETNAMEINFOREQWRAP",
|
|
30
|
+
"QUERYWRAP",
|
|
31
|
+
"TCPWRAP",
|
|
32
|
+
"TCPCONNECTWRAP",
|
|
33
|
+
"PIPEWRAP",
|
|
34
|
+
"PIPECONNECTWRAP",
|
|
35
|
+
"UDPWRAP",
|
|
36
|
+
"ZLIB"
|
|
37
|
+
]);
|
|
38
|
+
const MAX = 2e4;
|
|
39
|
+
/** a promise storm is counted, not stacked: one stack capture per this many promises */
|
|
40
|
+
const PROMISE_SAMPLE_EVERY = 256;
|
|
41
|
+
/** the nearest frame that belongs to the app OR a dependency (not Node, not the profiler): its
|
|
42
|
+
* generated position in full, so the report can map it back to the source (a short `dir/file`
|
|
43
|
+
* of a built chunk named `_page.server.ts.js`, which is no line anyone can open) — no regex,
|
|
44
|
+
* this runs once per sampled promise */
|
|
45
|
+
function nearest_own_or_dep_site() {
|
|
46
|
+
for (const site of call_sites(nearest_own_or_dep_site)) {
|
|
47
|
+
const file = file_of(site);
|
|
48
|
+
if (!file || file.startsWith("node:") || file.includes("node:internal") || is_profiler_file(file)) continue;
|
|
49
|
+
return {
|
|
50
|
+
fn: site.getFunctionName() || "(anonymous)",
|
|
51
|
+
file,
|
|
52
|
+
line: site.getLineNumber() ?? 0,
|
|
53
|
+
column: site.getColumnNumber() ?? 0
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** `fn (dir/file:line)` from a generated position: what a promise origin reads as when no
|
|
58
|
+
* sourcemap maps it */
|
|
59
|
+
function short_site(s) {
|
|
60
|
+
const q = s.file.indexOf("?");
|
|
61
|
+
const clean = q === -1 ? s.file : s.file.slice(0, q);
|
|
62
|
+
return `${s.fn} (${clean.split("/").slice(-2).join("/")}:${s.line})`;
|
|
63
|
+
}
|
|
64
|
+
/** Start timing I/O resources. Call `stop()` at the end of the window. */
|
|
65
|
+
async function record_async_io() {
|
|
66
|
+
let async_hooks;
|
|
67
|
+
try {
|
|
68
|
+
async_hooks = await import("node:async_hooks");
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const open = /* @__PURE__ */ new Map();
|
|
73
|
+
const ops = [];
|
|
74
|
+
let promise_count = 0;
|
|
75
|
+
/** generated `fn\0file\0line` → how many sampled promises it made, and the site itself */
|
|
76
|
+
const promise_sites = /* @__PURE__ */ new Map();
|
|
77
|
+
const hook = async_hooks.createHook({
|
|
78
|
+
init(asyncId, type, _trigger, resource) {
|
|
79
|
+
if (type === "PROMISE") {
|
|
80
|
+
if (++promise_count % PROMISE_SAMPLE_EVERY === 0 && promise_sites.size < 200) {
|
|
81
|
+
const site = nearest_own_or_dep_site() ?? null;
|
|
82
|
+
const k = site ? site.fn + "\0" + site.file + "\0" + site.line : "";
|
|
83
|
+
const hit = promise_sites.get(k);
|
|
84
|
+
if (hit) hit.n++;
|
|
85
|
+
else promise_sites.set(k, {
|
|
86
|
+
n: 1,
|
|
87
|
+
site
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (open.size >= MAX || !TRACK.has(type)) return;
|
|
93
|
+
const s = {
|
|
94
|
+
t: performance.now(),
|
|
95
|
+
type,
|
|
96
|
+
site: nearest_app_site()
|
|
97
|
+
};
|
|
98
|
+
if (type === "Timeout") {
|
|
99
|
+
const r = resource;
|
|
100
|
+
if (typeof r._repeat === "number" && r._repeat > 0) s.repeat = r._repeat;
|
|
101
|
+
else if (typeof r._idleTimeout === "number" && r._idleTimeout >= 0) s.delay = r._idleTimeout;
|
|
102
|
+
s.timer = r;
|
|
103
|
+
}
|
|
104
|
+
open.set(asyncId, s);
|
|
105
|
+
},
|
|
106
|
+
destroy(asyncId) {
|
|
107
|
+
const s = open.get(asyncId);
|
|
108
|
+
if (!s) return;
|
|
109
|
+
open.delete(asyncId);
|
|
110
|
+
if (ops.length < MAX) ops.push({
|
|
111
|
+
type: s.type,
|
|
112
|
+
caller_site: s.site,
|
|
113
|
+
ms: round2(performance.now() - s.t),
|
|
114
|
+
start: s.t
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
hook.enable();
|
|
119
|
+
return {
|
|
120
|
+
promises() {
|
|
121
|
+
const sampled = [...promise_sites.values()].reduce((a, b) => a + b.n, 0) || 1;
|
|
122
|
+
return {
|
|
123
|
+
count: promise_count,
|
|
124
|
+
top: [...promise_sites.values()].sort((a, b) => b.n - a.n).slice(0, 8).map(({ n, site }) => ({
|
|
125
|
+
caller: site ? short_site(site) : "(no frame outside node)",
|
|
126
|
+
share: Math.round(n / sampled * 100) / 100,
|
|
127
|
+
...site ? { site } : {}
|
|
128
|
+
}))
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
stop() {
|
|
132
|
+
hook.disable();
|
|
133
|
+
const now = performance.now();
|
|
134
|
+
for (const s of open.values()) if (ops.length < MAX) ops.push({
|
|
135
|
+
type: s.type,
|
|
136
|
+
caller_site: s.site,
|
|
137
|
+
ms: round2(now - s.t),
|
|
138
|
+
start: s.t,
|
|
139
|
+
open: true,
|
|
140
|
+
...s.repeat !== void 0 ? { repeat: s.repeat } : {},
|
|
141
|
+
...s.delay !== void 0 ? { delay: s.delay } : {},
|
|
142
|
+
...s.timer?.hasRef?.() === false ? { unref: true } : {}
|
|
143
|
+
});
|
|
144
|
+
open.clear();
|
|
145
|
+
return ops;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* A timer a render starts and never ends: an interval (or a timeout of a second or more) still open
|
|
151
|
+
* after every render, started from the same line in each of them. One per render is the proof it is
|
|
152
|
+
* per render (a cache's single refresh timer, started on the first request, is one in all). Marked
|
|
153
|
+
* on the ops themselves (`left_each_run`), so the run kept for the waterfall carries it.
|
|
154
|
+
*/
|
|
155
|
+
function mark_left_each_run(ops, windows) {
|
|
156
|
+
if (windows.length < 2) return;
|
|
157
|
+
const by_site = /* @__PURE__ */ new Map();
|
|
158
|
+
for (const o of ops) {
|
|
159
|
+
if (!o.open || o.type !== "Timeout" || !o.caller_site) continue;
|
|
160
|
+
if (o.repeat === void 0 && ((o.delay ?? 0) < 1e3 || o.unref)) continue;
|
|
161
|
+
const ri = windows.findIndex((w) => o.start >= w.start && o.start <= w.end);
|
|
162
|
+
if (ri === -1) continue;
|
|
163
|
+
const s = o.caller_site;
|
|
164
|
+
const k = s.file + "\0" + s.line + "\0" + s.column;
|
|
165
|
+
const g = by_site.get(k) ?? {
|
|
166
|
+
runs: /* @__PURE__ */ new Set(),
|
|
167
|
+
ops: []
|
|
168
|
+
};
|
|
169
|
+
g.runs.add(ri);
|
|
170
|
+
g.ops.push(o);
|
|
171
|
+
by_site.set(k, g);
|
|
172
|
+
}
|
|
173
|
+
for (const g of by_site.values()) if (g.runs.size === windows.length) for (const o of g.ops) o.left_each_run = true;
|
|
174
|
+
}
|
|
175
|
+
/** A friendly bucket for a resource type, for the report. */
|
|
176
|
+
function io_kind(type) {
|
|
177
|
+
if (type === "Timeout" || type === "Immediate") return "timer";
|
|
178
|
+
if (type.startsWith("FS") || type === "STATWATCHER") return "file";
|
|
179
|
+
if (type.startsWith("GETADDR") || type.startsWith("GETNAME") || type === "QUERYWRAP") return "dns";
|
|
180
|
+
if (type.includes("TCP") || type.includes("PIPE") || type === "UDPWRAP") return "socket";
|
|
181
|
+
if (type === "ZLIB") return "zlib";
|
|
182
|
+
return "other";
|
|
183
|
+
}
|
|
184
|
+
function round2(n) {
|
|
185
|
+
return Math.round(n * 100) / 100;
|
|
186
|
+
}
|
|
187
|
+
export { io_kind, mark_left_each_run, record_async_io, short_site };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The beacon itself. Runs in the browser, as an inline classic script. */
|
|
2
|
+
export declare function standalone_beacon(): void;
|
|
3
|
+
/** The inline tag the profiler puts in its own user's document, beside the beacon meta tag. */
|
|
4
|
+
export declare const BEACON_STANDALONE_TAG: string;
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { script } from "@ogygia/core";
|
|
2
|
+
// src/profiler/beacon-standalone.ts
|
|
3
|
+
/**
|
|
4
|
+
* THE BEACON FOR A PAGE WITHOUT THE OGYGIA RUNTIME — a Kit-hydrated (`csr = true`) page, or any
|
|
5
|
+
* page with no island. The runtime's beacon (runtime/beacon.ts) lives in the runtime, so without
|
|
6
|
+
* this such a page would never report its vitals, long tasks or CPU — and the page score would
|
|
7
|
+
* measure it on less than an islands page (the unfairness the score rework removed).
|
|
8
|
+
*
|
|
9
|
+
* An inline script the profiler adds for ITS OWN user only (next to the beacon meta tag; never a
|
|
10
|
+
* visitor), made with ogygia's own `script()`: `standalone_beacon` is inlined through
|
|
11
|
+
* `Function.prototype.toString`, so it is SELF-CONTAINED — browser globals only, every helper inside
|
|
12
|
+
* it. It steps aside at once when the ogygia runtime's script is already in the head (the usual
|
|
13
|
+
* order), else watches and checks again at DOMContentLoaded. It must never hold the JS sampler the
|
|
14
|
+
* runtime's beacon needs: the browser runs one at a time. Same payloads as the runtime's:
|
|
15
|
+
* `{page, vitals}`, `{page, visit}` (no islands), `{page, cpu}`. No regex. Like the runtime's, a later
|
|
16
|
+
* largest paint, a shift or a worse interaction re-sends the visit (debounced, at most 8 times): the
|
|
17
|
+
* hide-time message is the one most often lost.
|
|
18
|
+
*/
|
|
19
|
+
/** The beacon itself. Runs in the browser, as an inline classic script. */
|
|
20
|
+
function standalone_beacon() {
|
|
21
|
+
const w = window;
|
|
22
|
+
if (w.__og_beacon_standalone) return;
|
|
23
|
+
w.__og_beacon_standalone = 1;
|
|
24
|
+
const url = document.querySelector("meta[name=\"ogygia-profiler-beacon\"]")?.getAttribute("content");
|
|
25
|
+
if (!url || typeof PerformanceObserver === "undefined") return;
|
|
26
|
+
if (document.querySelector("script[data-ogygia-runtime]")) return;
|
|
27
|
+
const r2 = (n) => Math.round(n * 100) / 100;
|
|
28
|
+
const vitals = {};
|
|
29
|
+
const paints = {};
|
|
30
|
+
const shifts = [];
|
|
31
|
+
const longtasks = [];
|
|
32
|
+
const observers = [];
|
|
33
|
+
/** every resource entry (the observer sees past the browser's buffer) */
|
|
34
|
+
const seen = [];
|
|
35
|
+
let off = false;
|
|
36
|
+
let cls = 0;
|
|
37
|
+
let sent = false;
|
|
38
|
+
let resend;
|
|
39
|
+
/** visits sent while the page lives (the early one, then re-sends): at most 8 */
|
|
40
|
+
let early_sends = 0;
|
|
41
|
+
let profiler = null;
|
|
42
|
+
const watch = (type, on) => {
|
|
43
|
+
try {
|
|
44
|
+
const po = new PerformanceObserver((list) => {
|
|
45
|
+
if (!off) on(list.getEntries());
|
|
46
|
+
});
|
|
47
|
+
po.observe(type === "event" ? {
|
|
48
|
+
type,
|
|
49
|
+
buffered: true,
|
|
50
|
+
durationThreshold: 16
|
|
51
|
+
} : {
|
|
52
|
+
type,
|
|
53
|
+
buffered: true
|
|
54
|
+
});
|
|
55
|
+
observers.push(po);
|
|
56
|
+
} catch {}
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
const nav = performance.getEntriesByType("navigation")[0];
|
|
60
|
+
if (nav && nav.responseStart > 0) vitals.ttfb = r2(nav.responseStart);
|
|
61
|
+
} catch {}
|
|
62
|
+
watch("paint", (es) => {
|
|
63
|
+
for (const e of es) if (e.name === "first-contentful-paint") vitals.fcp = paints.fcp = r2(e.startTime);
|
|
64
|
+
});
|
|
65
|
+
watch("largest-contentful-paint", (es) => {
|
|
66
|
+
const e = es[es.length - 1];
|
|
67
|
+
if (!e) return;
|
|
68
|
+
vitals.lcp = paints.lcp = r2(e.startTime);
|
|
69
|
+
paints.lcp_url = e.url || void 0;
|
|
70
|
+
paints.lcp_tag = e.element?.tagName ? e.element.tagName.toLowerCase() : void 0;
|
|
71
|
+
paints.lcp_lazy = e.element?.getAttribute?.("loading") === "lazy" ? true : void 0;
|
|
72
|
+
again();
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
if (PerformanceObserver.supportedEntryTypes?.includes("layout-shift")) vitals.cls = 0;
|
|
76
|
+
} catch {}
|
|
77
|
+
watch("layout-shift", (es) => {
|
|
78
|
+
for (const e of es) {
|
|
79
|
+
if (e.hadRecentInput || typeof e.value !== "number") continue;
|
|
80
|
+
cls += e.value;
|
|
81
|
+
if (shifts.length < 60) shifts.push({
|
|
82
|
+
t: r2(e.startTime),
|
|
83
|
+
value: Math.round(e.value * 1e4) / 1e4
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
vitals.cls = Math.round(cls * 1e3) / 1e3;
|
|
87
|
+
again();
|
|
88
|
+
});
|
|
89
|
+
watch("event", (es) => {
|
|
90
|
+
for (const e of es) {
|
|
91
|
+
if (!e.interactionId) continue;
|
|
92
|
+
const d = r2(e.duration);
|
|
93
|
+
if (vitals.inp === void 0 || d > vitals.inp) {
|
|
94
|
+
vitals.inp = d;
|
|
95
|
+
clearTimeout(resend);
|
|
96
|
+
resend = setTimeout(early, 1500);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
performance.setResourceTimingBufferSize(3e3);
|
|
102
|
+
} catch {}
|
|
103
|
+
watch("resource", (es) => {
|
|
104
|
+
for (const e of es) if (seen.length < 3e3) seen.push(e);
|
|
105
|
+
});
|
|
106
|
+
watch("longtask", (es) => {
|
|
107
|
+
for (const e of es) if (longtasks.length < 100) longtasks.push({
|
|
108
|
+
t: r2(e.startTime),
|
|
109
|
+
ms: r2(e.duration)
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
if (w.Profiler) {
|
|
114
|
+
profiler = new w.Profiler({
|
|
115
|
+
sampleInterval: 10,
|
|
116
|
+
maxBufferSize: 3e4
|
|
117
|
+
});
|
|
118
|
+
setTimeout(() => cpu(false), 8e3);
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
profiler = null;
|
|
122
|
+
}
|
|
123
|
+
/** keepalive bytes still queued: the 64 KB cap is shared by every beacon and keepalive fetch the
|
|
124
|
+
* page has queued at once (a hide sends several), not each's — held until a fetch settles, a
|
|
125
|
+
* beacon (which says nothing back) for the task that sent it: the hide's burst is what overflows */
|
|
126
|
+
let queued = 0;
|
|
127
|
+
function hold(bytes, settled) {
|
|
128
|
+
queued += bytes;
|
|
129
|
+
const release = () => queued = Math.max(0, queued - bytes);
|
|
130
|
+
if (settled) settled.finally(release);
|
|
131
|
+
else setTimeout(release, 0);
|
|
132
|
+
}
|
|
133
|
+
function post(body, keepalive) {
|
|
134
|
+
try {
|
|
135
|
+
const settled = fetch(url, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
body,
|
|
138
|
+
keepalive,
|
|
139
|
+
credentials: "same-origin",
|
|
140
|
+
headers: { "content-type": "text/plain" }
|
|
141
|
+
}).catch(() => {});
|
|
142
|
+
if (keepalive) hold(body.length, settled);
|
|
143
|
+
} catch {}
|
|
144
|
+
}
|
|
145
|
+
/** a beacon when it fits (what is left of the 64 KB the queued requests share); a big body as a
|
|
146
|
+
* plain POST while the page lives, else its slim form (`slim`), else nothing */
|
|
147
|
+
function send(body, slim) {
|
|
148
|
+
const room = 6e4 - queued;
|
|
149
|
+
if (body.length > room) {
|
|
150
|
+
if (!sent && document.visibilityState === "visible") return post(body, false);
|
|
151
|
+
const smaller = slim?.();
|
|
152
|
+
if (!smaller || smaller.length > room) return;
|
|
153
|
+
body = smaller;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
if (navigator.sendBeacon?.(url, body)) return hold(body.length);
|
|
157
|
+
} catch {}
|
|
158
|
+
post(body, true);
|
|
159
|
+
}
|
|
160
|
+
/** a URL's extension, lowercased (`''` for none), with no regex */
|
|
161
|
+
function ext(name) {
|
|
162
|
+
const q = name.indexOf("?");
|
|
163
|
+
const path = q < 0 ? name : name.slice(0, q);
|
|
164
|
+
const dot = path.lastIndexOf(".");
|
|
165
|
+
return dot > path.lastIndexOf("/") ? path.slice(dot + 1).toLowerCase() : "";
|
|
166
|
+
}
|
|
167
|
+
/** what each <link> fetched as (a preload's `as`, a modulepreload's script), and its crossorigin;
|
|
168
|
+
* read once per visit */
|
|
169
|
+
let link_as = null;
|
|
170
|
+
let link_co = {};
|
|
171
|
+
function links() {
|
|
172
|
+
if (link_as) return link_as;
|
|
173
|
+
link_as = {};
|
|
174
|
+
link_co = {};
|
|
175
|
+
try {
|
|
176
|
+
for (const l of document.querySelectorAll("link[href][as],link[rel=\"modulepreload\"][href]")) {
|
|
177
|
+
link_as[l.href] = l.getAttribute("as") || "script";
|
|
178
|
+
link_co[l.href] = l.getAttribute("crossorigin");
|
|
179
|
+
}
|
|
180
|
+
} catch {}
|
|
181
|
+
return link_as;
|
|
182
|
+
}
|
|
183
|
+
const LINK_AS = {
|
|
184
|
+
style: "css",
|
|
185
|
+
script: "script",
|
|
186
|
+
font: "font",
|
|
187
|
+
image: "img",
|
|
188
|
+
fetch: "fetch"
|
|
189
|
+
};
|
|
190
|
+
/** a resource's type, the way the runtime's beacon types it */
|
|
191
|
+
function kind(r) {
|
|
192
|
+
const x = ext(r.name);
|
|
193
|
+
const by = r.initiatorType;
|
|
194
|
+
if (x === "woff" || x === "woff2" || x === "ttf" || x === "otf") return "font";
|
|
195
|
+
if (x === "css") return "css";
|
|
196
|
+
if (x === "js" || x === "mjs") return "script";
|
|
197
|
+
if (x === "png" || x === "jpg" || x === "jpeg" || x === "gif" || x === "webp" || x === "avif" || x === "svg") return "img";
|
|
198
|
+
if (by === "link") {
|
|
199
|
+
const as = links()[r.name];
|
|
200
|
+
return as ? LINK_AS[as] || "other" : "css";
|
|
201
|
+
}
|
|
202
|
+
if (by === "css" || by === "script" || by === "img") return by;
|
|
203
|
+
if (by === "fetch" || by === "xmlhttprequest" || by === "beacon") return "fetch";
|
|
204
|
+
return "other";
|
|
205
|
+
}
|
|
206
|
+
/** PRELOADED, THEN DOWNLOADED AGAIN: one URL fetched by a preload link and again by something
|
|
207
|
+
* else, its body over the network the second time (the preload's crossorigin or credentials did
|
|
208
|
+
* not match). The runtime's beacon makes the same list. */
|
|
209
|
+
function preload_misses(all) {
|
|
210
|
+
const out = [];
|
|
211
|
+
try {
|
|
212
|
+
const as = links();
|
|
213
|
+
const by_url = /* @__PURE__ */ new Map();
|
|
214
|
+
for (const r of all) if (!r.name.includes("/__profiler/")) (by_url.get(r.name) ?? by_url.set(r.name, []).get(r.name)).push(r);
|
|
215
|
+
for (const [u, list] of by_url) {
|
|
216
|
+
if (list.length < 2 || out.length >= 20) continue;
|
|
217
|
+
const pre = list.find((r) => r.initiatorType === "link");
|
|
218
|
+
if (!pre) continue;
|
|
219
|
+
const again = list.find((r) => r !== pre && r.initiatorType !== "link" && r.encodedBodySize > 0 && r.transferSize >= r.encodedBodySize);
|
|
220
|
+
if (again) out.push({
|
|
221
|
+
url: u.slice(0, 500),
|
|
222
|
+
type: kind(pre),
|
|
223
|
+
bytes: again.transferSize,
|
|
224
|
+
as: as[u] || "",
|
|
225
|
+
crossorigin: typeof link_co[u] === "string" ? link_co[u] : null
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
} catch {}
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
/** the visit as one object (the runtime's shape, with no islands) */
|
|
232
|
+
function visit() {
|
|
233
|
+
link_as = null;
|
|
234
|
+
let nav;
|
|
235
|
+
try {
|
|
236
|
+
nav = performance.getEntriesByType("navigation")[0];
|
|
237
|
+
} catch {}
|
|
238
|
+
if (!nav || !(nav.responseStart > 0)) return null;
|
|
239
|
+
const resources = [];
|
|
240
|
+
const totals = {};
|
|
241
|
+
let count = 0;
|
|
242
|
+
const all = seen.length ? seen : performance.getEntriesByType("resource");
|
|
243
|
+
try {
|
|
244
|
+
for (const r of all) {
|
|
245
|
+
if (r.name.includes("/__profiler/")) continue;
|
|
246
|
+
count++;
|
|
247
|
+
const type = kind(r);
|
|
248
|
+
const t = totals[type] ??= {
|
|
249
|
+
type,
|
|
250
|
+
count: 0,
|
|
251
|
+
transfer: 0,
|
|
252
|
+
size: 0
|
|
253
|
+
};
|
|
254
|
+
t.count++;
|
|
255
|
+
t.transfer += r.transferSize || 0;
|
|
256
|
+
t.size += r.decodedBodySize || 0;
|
|
257
|
+
}
|
|
258
|
+
for (const r of all) {
|
|
259
|
+
if (resources.length >= 200) break;
|
|
260
|
+
if (r.name.includes("/__profiler/")) continue;
|
|
261
|
+
const o = {
|
|
262
|
+
url: r.name.slice(0, 500),
|
|
263
|
+
type: kind(r),
|
|
264
|
+
start: r2(r.startTime),
|
|
265
|
+
end: r2(r.responseEnd || r.startTime + r.duration)
|
|
266
|
+
};
|
|
267
|
+
if (r.requestStart) o.req_start = r2(r.requestStart);
|
|
268
|
+
if (r.responseStart) o.res_start = r2(r.responseStart);
|
|
269
|
+
if (r.transferSize) o.transfer = r.transferSize;
|
|
270
|
+
if (r.decodedBodySize) o.size = r.decodedBodySize;
|
|
271
|
+
if (r.renderBlockingStatus === "blocking") o.blocking = true;
|
|
272
|
+
resources.push(o);
|
|
273
|
+
}
|
|
274
|
+
} catch {}
|
|
275
|
+
const n = {
|
|
276
|
+
req_start: r2(nav.requestStart),
|
|
277
|
+
res_start: r2(nav.responseStart),
|
|
278
|
+
res_end: r2(nav.responseEnd)
|
|
279
|
+
};
|
|
280
|
+
if (nav.domInteractive) n.dom_interactive = r2(nav.domInteractive);
|
|
281
|
+
if (nav.domContentLoadedEventEnd) n.dcl = r2(nav.domContentLoadedEventEnd);
|
|
282
|
+
if (nav.loadEventEnd) n.load = r2(nav.loadEventEnd);
|
|
283
|
+
if (nav.transferSize) n.transfer = nav.transferSize;
|
|
284
|
+
if (nav.decodedBodySize) n.size = nav.decodedBodySize;
|
|
285
|
+
if (nav.nextHopProtocol) n.protocol = nav.nextHopProtocol;
|
|
286
|
+
const out = {
|
|
287
|
+
at: Math.round(performance.timeOrigin),
|
|
288
|
+
nav: n,
|
|
289
|
+
paints,
|
|
290
|
+
resources,
|
|
291
|
+
longtasks,
|
|
292
|
+
islands: [],
|
|
293
|
+
firsts: [],
|
|
294
|
+
shifts,
|
|
295
|
+
viewport: [innerWidth, innerHeight],
|
|
296
|
+
ua: navigator.userAgent.slice(0, 200)
|
|
297
|
+
};
|
|
298
|
+
if (count > resources.length) {
|
|
299
|
+
out.resource_totals = Object.values(totals);
|
|
300
|
+
out.resources_all = count;
|
|
301
|
+
}
|
|
302
|
+
const misses = preload_misses(all);
|
|
303
|
+
if (misses.length) out.preload_misses = misses;
|
|
304
|
+
out.origin = location.origin;
|
|
305
|
+
try {
|
|
306
|
+
const types = PerformanceObserver.supportedEntryTypes || [];
|
|
307
|
+
const missing = [
|
|
308
|
+
"layout-shift",
|
|
309
|
+
"longtask",
|
|
310
|
+
"event",
|
|
311
|
+
"largest-contentful-paint",
|
|
312
|
+
"long-animation-frame"
|
|
313
|
+
].filter((t) => !types.includes(t));
|
|
314
|
+
if (missing.length) out.unsupported = missing;
|
|
315
|
+
} catch {}
|
|
316
|
+
if (Object.keys(vitals).length) out.vitals = { ...vitals };
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
/** the load's CPU trace: once, after 8 s, or as the page hides (only when it fits a beacon) */
|
|
320
|
+
function cpu(hiding) {
|
|
321
|
+
if (runtime_claimed() || !profiler) return;
|
|
322
|
+
const p = profiler;
|
|
323
|
+
profiler = null;
|
|
324
|
+
p.stop().then((trace) => {
|
|
325
|
+
if (off || !trace) return;
|
|
326
|
+
const body = JSON.stringify({
|
|
327
|
+
page: location.pathname,
|
|
328
|
+
cpu: trace
|
|
329
|
+
});
|
|
330
|
+
if (hiding && body.length > 6e4 - queued) return;
|
|
331
|
+
post(body, hiding);
|
|
332
|
+
}, () => {});
|
|
333
|
+
}
|
|
334
|
+
/** the page is going away: the vitals (final only now), the visit, the CPU trace */
|
|
335
|
+
function hide() {
|
|
336
|
+
if (runtime_claimed() || sent) return;
|
|
337
|
+
sent = true;
|
|
338
|
+
if (Object.keys(vitals).length) send(JSON.stringify({
|
|
339
|
+
page: location.pathname,
|
|
340
|
+
at: Math.round(performance.timeOrigin),
|
|
341
|
+
vitals
|
|
342
|
+
}));
|
|
343
|
+
const v = visit();
|
|
344
|
+
if (v) send(JSON.stringify({
|
|
345
|
+
page: location.pathname,
|
|
346
|
+
visit: v
|
|
347
|
+
}), () => JSON.stringify({
|
|
348
|
+
page: location.pathname,
|
|
349
|
+
visit: {
|
|
350
|
+
...v,
|
|
351
|
+
resources: []
|
|
352
|
+
}
|
|
353
|
+
}));
|
|
354
|
+
cpu(true);
|
|
355
|
+
}
|
|
356
|
+
/** a later paint or shift: the visit again a moment later (debounced), once one has gone */
|
|
357
|
+
function again() {
|
|
358
|
+
if (!early_sends || early_sends >= 8) return;
|
|
359
|
+
clearTimeout(resend);
|
|
360
|
+
resend = setTimeout(early, 1500);
|
|
361
|
+
}
|
|
362
|
+
/** the runtime's beacon claimed the page after all — it came without the runtime's script tag (the
|
|
363
|
+
* devtools carry it onto a Kit page): it reports, this one hands back its observers and sampler.
|
|
364
|
+
* Both sent the same visit, and their two bodies together passed the 64 KB a page's queued
|
|
365
|
+
* keepalive requests share — Safari refused the second */
|
|
366
|
+
function runtime_claimed() {
|
|
367
|
+
if (off) return true;
|
|
368
|
+
if (!w[Symbol.for("ogygia.beacon")]) return false;
|
|
369
|
+
stand_down();
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
/** the visit while the page lives: the early one, and each re-send */
|
|
373
|
+
function early() {
|
|
374
|
+
if (runtime_claimed() || early_sends >= 8) return;
|
|
375
|
+
early_sends++;
|
|
376
|
+
const v = visit();
|
|
377
|
+
if (v) send(JSON.stringify({
|
|
378
|
+
page: location.pathname,
|
|
379
|
+
visit: v
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
/** hand back the observers and the sampler: another beacon reports */
|
|
383
|
+
function stand_down() {
|
|
384
|
+
off = true;
|
|
385
|
+
for (const o of observers) try {
|
|
386
|
+
o.disconnect();
|
|
387
|
+
} catch {}
|
|
388
|
+
if (profiler) {
|
|
389
|
+
try {
|
|
390
|
+
profiler.stop();
|
|
391
|
+
} catch {}
|
|
392
|
+
profiler = null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
396
|
+
if (document.querySelector("script[data-ogygia-runtime]")) return stand_down();
|
|
397
|
+
document.addEventListener("visibilitychange", () => {
|
|
398
|
+
if (document.visibilityState === "hidden") hide();
|
|
399
|
+
});
|
|
400
|
+
addEventListener("pagehide", hide);
|
|
401
|
+
addEventListener("load", () => setTimeout(early, 1500));
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
/** The inline tag the profiler puts in its own user's document, beside the beacon meta tag. */
|
|
405
|
+
const BEACON_STANDALONE_TAG = script({
|
|
406
|
+
run: standalone_beacon,
|
|
407
|
+
"data-ogygia-beacon": true
|
|
408
|
+
});
|
|
409
|
+
export { BEACON_STANDALONE_TAG, standalone_beacon };
|