@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,96 @@
|
|
|
1
|
+
import { Analysis } from "./analyze.js";
|
|
2
|
+
import { AwaitEdge, AwaitNode } from "./timeline.js";
|
|
3
|
+
import { GcMaker } from "./gc.js";
|
|
4
|
+
import { SpanRecord } from "./span.js";
|
|
5
|
+
export interface SyncIoRow {
|
|
6
|
+
key: string;
|
|
7
|
+
name: string;
|
|
8
|
+
/** the node module (`node:fs`) */
|
|
9
|
+
module: string;
|
|
10
|
+
self_ms: number;
|
|
11
|
+
total_ms: number;
|
|
12
|
+
calls: number | null;
|
|
13
|
+
/** the app frames that called it, nearest first, from its hottest stack */
|
|
14
|
+
callers: string[];
|
|
15
|
+
}
|
|
16
|
+
/** Synchronous file, process, compression and key-derivation calls seen on the CPU during the
|
|
17
|
+
* window: each one blocks every other request on the instance for its whole length. */
|
|
18
|
+
export declare function sync_io(a: Analysis): SyncIoRow[];
|
|
19
|
+
export interface MemoCandidate {
|
|
20
|
+
key: string;
|
|
21
|
+
name: string;
|
|
22
|
+
url: string;
|
|
23
|
+
line: number;
|
|
24
|
+
/** calls in one render (coverage) */
|
|
25
|
+
calls: number;
|
|
26
|
+
total_ms: number;
|
|
27
|
+
per_call_ms: number;
|
|
28
|
+
/** bytes it allocated per call, when the allocation profile saw it */
|
|
29
|
+
alloc_per_call?: number;
|
|
30
|
+
/** the component most of its calls sat under */
|
|
31
|
+
parent?: string;
|
|
32
|
+
}
|
|
33
|
+
/** App functions called many times per render at a steady cost each: what a cache keyed on the
|
|
34
|
+
* argument would remove. Heuristic, from counts and cost alone: the report says so. */
|
|
35
|
+
export declare function memo_candidates(a: Analysis, makers?: GcMaker[], limit?: number, runs?: number): MemoCandidate[];
|
|
36
|
+
export type WhatIf = "parallel" | "cache" | "remove";
|
|
37
|
+
export interface Simulation {
|
|
38
|
+
/** the render's wall as recorded and under the changes, ms */
|
|
39
|
+
before_ms: number;
|
|
40
|
+
after_ms: number;
|
|
41
|
+
delta_ms: number;
|
|
42
|
+
/** the longest chain of waits after the changes, in order */
|
|
43
|
+
chain: string[];
|
|
44
|
+
/** every node's new start and end */
|
|
45
|
+
nodes: {
|
|
46
|
+
label: string;
|
|
47
|
+
t0: number;
|
|
48
|
+
t1: number;
|
|
49
|
+
moved: boolean;
|
|
50
|
+
}[];
|
|
51
|
+
}
|
|
52
|
+
/** The render under a set of changes to its calls. The model: a call with a predecessor (an
|
|
53
|
+
* edge: it started once the predecessor finished) starts when the predecessor ends plus the gap
|
|
54
|
+
* that sat between them; a call without one keeps its recorded start; the CPU after the last
|
|
55
|
+
* call keeps its length. `parallel` starts a call with its predecessor instead of after it,
|
|
56
|
+
* `cache` makes it instant, `remove` takes it out. A model of the waits, not of the CPU. */
|
|
57
|
+
export declare function simulate_awaits(nodes: AwaitNode[], edges: AwaitEdge[], window_ms: number, changes: Record<string, WhatIf>): Simulation;
|
|
58
|
+
export interface SpanValueRow {
|
|
59
|
+
span: string;
|
|
60
|
+
attr: string;
|
|
61
|
+
n: number;
|
|
62
|
+
min: number;
|
|
63
|
+
p50: number;
|
|
64
|
+
max: number;
|
|
65
|
+
sum: number;
|
|
66
|
+
/** how the span's time moved with the value: ms per unit of the attribute (the slope of a
|
|
67
|
+
* straight-line fit), when the value varied across at least 3 spans */
|
|
68
|
+
ms_per_unit?: number;
|
|
69
|
+
/** how well that line fits, -1..1 (near 1: the value drives the time) */
|
|
70
|
+
r?: number;
|
|
71
|
+
}
|
|
72
|
+
/** The NUMBERS the app attached to its spans (`span('db.query', fn, (rows) => ({ rows:
|
|
73
|
+
* rows.length }))`): per span name and attribute, the range across the recording, and how
|
|
74
|
+
* the span's time moved with the value — the cost per row, per tag, per byte. What a
|
|
75
|
+
* function's name alone cannot say: the same function is cheap on 3 items and slow on 3000. */
|
|
76
|
+
export declare function span_values(spans: readonly SpanRecord[] | undefined): SpanValueRow[];
|
|
77
|
+
export interface RetainedSite {
|
|
78
|
+
name: string;
|
|
79
|
+
url: string;
|
|
80
|
+
line: number;
|
|
81
|
+
caller?: string;
|
|
82
|
+
component: string | null;
|
|
83
|
+
bytes: number;
|
|
84
|
+
share: number;
|
|
85
|
+
/** the app line it is charged to (the site when it is app code, else the nearest app caller) */
|
|
86
|
+
at?: {
|
|
87
|
+
path: string;
|
|
88
|
+
line: number;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export interface Retained {
|
|
92
|
+
/** bytes still alive after one more render and a full collection */
|
|
93
|
+
total_bytes: number;
|
|
94
|
+
render_ms: number;
|
|
95
|
+
sites: RetainedSite[];
|
|
96
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// src/profiler/insights.ts
|
|
2
|
+
const SYNC_IO = /* @__PURE__ */ new Set([
|
|
3
|
+
"readFileSync",
|
|
4
|
+
"writeFileSync",
|
|
5
|
+
"appendFileSync",
|
|
6
|
+
"readdirSync",
|
|
7
|
+
"statSync",
|
|
8
|
+
"lstatSync",
|
|
9
|
+
"fstatSync",
|
|
10
|
+
"existsSync",
|
|
11
|
+
"accessSync",
|
|
12
|
+
"mkdirSync",
|
|
13
|
+
"rmSync",
|
|
14
|
+
"rmdirSync",
|
|
15
|
+
"unlinkSync",
|
|
16
|
+
"copyFileSync",
|
|
17
|
+
"realpathSync",
|
|
18
|
+
"openSync",
|
|
19
|
+
"readSync",
|
|
20
|
+
"writeSync",
|
|
21
|
+
"closeSync",
|
|
22
|
+
"renameSync",
|
|
23
|
+
"execSync",
|
|
24
|
+
"execFileSync",
|
|
25
|
+
"spawnSync",
|
|
26
|
+
"gzipSync",
|
|
27
|
+
"gunzipSync",
|
|
28
|
+
"deflateSync",
|
|
29
|
+
"inflateSync",
|
|
30
|
+
"deflateRawSync",
|
|
31
|
+
"inflateRawSync",
|
|
32
|
+
"brotliCompressSync",
|
|
33
|
+
"brotliDecompressSync",
|
|
34
|
+
"pbkdf2Sync",
|
|
35
|
+
"scryptSync",
|
|
36
|
+
"hkdfSync",
|
|
37
|
+
"generateKeyPairSync"
|
|
38
|
+
]);
|
|
39
|
+
/** Synchronous file, process, compression and key-derivation calls seen on the CPU during the
|
|
40
|
+
* window: each one blocks every other request on the instance for its whole length. */
|
|
41
|
+
function sync_io(a) {
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const f of a.functions) {
|
|
44
|
+
if (!SYNC_IO.has(f.name)) continue;
|
|
45
|
+
if (f.category !== "node" && !f.url.startsWith("node:")) continue;
|
|
46
|
+
const callers = ((f.stacks ?? []).slice().sort((x, y) => y.ms - x.ms)[0]?.frames ?? []).filter((fr) => fr.c === "app" || fr.c === "component").slice(0, 3).map((fr) => fr.f ? `${fr.n} (${fr.f})` : fr.n);
|
|
47
|
+
out.push({
|
|
48
|
+
key: f.key,
|
|
49
|
+
name: f.name,
|
|
50
|
+
module: f.url.startsWith("node:") ? f.url.split("/")[0] : f.url || "node (native)",
|
|
51
|
+
self_ms: f.self_ms,
|
|
52
|
+
total_ms: f.total_ms,
|
|
53
|
+
calls: f.calls ?? null,
|
|
54
|
+
callers
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return out.sort((x, y) => y.total_ms - x.total_ms);
|
|
58
|
+
}
|
|
59
|
+
const GLUE_RE = /^(?:\(anonymous\)|\$\.|get |set |render|children|slot|each_|if_|await_)/;
|
|
60
|
+
/** request handlers and loads run once per request by design: not candidates */
|
|
61
|
+
const HANDLER_NAMES = /* @__PURE__ */ new Set([
|
|
62
|
+
"load",
|
|
63
|
+
"handle",
|
|
64
|
+
"GET",
|
|
65
|
+
"POST",
|
|
66
|
+
"PUT",
|
|
67
|
+
"PATCH",
|
|
68
|
+
"DELETE",
|
|
69
|
+
"fetch",
|
|
70
|
+
"resolve"
|
|
71
|
+
]);
|
|
72
|
+
const is_handler_file = (url) => url.includes("hooks.server") || url.includes("+server.");
|
|
73
|
+
/** App functions called many times per render at a steady cost each: what a cache keyed on the
|
|
74
|
+
* argument would remove. Heuristic, from counts and cost alone: the report says so. */
|
|
75
|
+
function memo_candidates(a, makers = [], limit = 8, runs = 1) {
|
|
76
|
+
const budget = Math.max(a.busy_ms / runs, 1);
|
|
77
|
+
const base = (u) => u.split("/").pop() ?? u;
|
|
78
|
+
const alloc_of = (f) => {
|
|
79
|
+
const b = base(f.url);
|
|
80
|
+
let sum = 0;
|
|
81
|
+
for (const m of makers) if (m.name === f.name && base(m.url) === b) sum += m.allocated;
|
|
82
|
+
else if (m.caller && m.caller.startsWith(f.name + " (") && m.caller.includes(b + ":")) sum += m.allocated;
|
|
83
|
+
return sum || void 0;
|
|
84
|
+
};
|
|
85
|
+
const out = [];
|
|
86
|
+
for (const f of a.functions) {
|
|
87
|
+
if (f.category !== "app") continue;
|
|
88
|
+
const calls = f.calls ?? 0;
|
|
89
|
+
if (calls < 10 || GLUE_RE.test(f.name) || HANDLER_NAMES.has(f.name) || is_handler_file(f.url)) continue;
|
|
90
|
+
const total = f.total_ms / runs;
|
|
91
|
+
if (total < Math.max(2, budget * .005)) continue;
|
|
92
|
+
const per_call = total / calls;
|
|
93
|
+
if (per_call < .02) continue;
|
|
94
|
+
const alloc = alloc_of(f);
|
|
95
|
+
out.push({
|
|
96
|
+
key: f.key,
|
|
97
|
+
name: f.name,
|
|
98
|
+
url: f.url,
|
|
99
|
+
line: f.line,
|
|
100
|
+
calls: Math.round(calls),
|
|
101
|
+
total_ms: Math.round(total * 100) / 100,
|
|
102
|
+
per_call_ms: Math.round(per_call * 1e3) / 1e3,
|
|
103
|
+
...alloc ? { alloc_per_call: Math.round(alloc / runs / calls) } : {},
|
|
104
|
+
...f.parent ? { parent: f.parent } : {}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return out.sort((x, y) => y.total_ms - x.total_ms).slice(0, limit);
|
|
108
|
+
}
|
|
109
|
+
/** The render under a set of changes to its calls. The model: a call with a predecessor (an
|
|
110
|
+
* edge: it started once the predecessor finished) starts when the predecessor ends plus the gap
|
|
111
|
+
* that sat between them; a call without one keeps its recorded start; the CPU after the last
|
|
112
|
+
* call keeps its length. `parallel` starts a call with its predecessor instead of after it,
|
|
113
|
+
* `cache` makes it instant, `remove` takes it out. A model of the waits, not of the CPU. */
|
|
114
|
+
function simulate_awaits(nodes, edges, window_ms, changes) {
|
|
115
|
+
const idx = nodes.map((n, i) => ({
|
|
116
|
+
...n,
|
|
117
|
+
i
|
|
118
|
+
})).sort((a, b) => a.t0 - b.t0);
|
|
119
|
+
const by_label = /* @__PURE__ */ new Map();
|
|
120
|
+
for (const n of idx) (by_label.get(n.label) ?? by_label.set(n.label, []).get(n.label)).push(n);
|
|
121
|
+
const pred = /* @__PURE__ */ new Map();
|
|
122
|
+
for (const e of edges) for (const b of by_label.get(e.to) ?? []) {
|
|
123
|
+
if (pred.has(b.i)) continue;
|
|
124
|
+
let best;
|
|
125
|
+
for (const a of by_label.get(e.from) ?? []) if (a.t1 <= b.t0 + .05 && (!best || a.t1 > best.t1)) best = a;
|
|
126
|
+
if (best) pred.set(b.i, {
|
|
127
|
+
p: best,
|
|
128
|
+
gap: e.gap_ms
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const last_end = Math.max(0, ...nodes.map((n) => n.t1));
|
|
132
|
+
const tail = Math.max(0, window_ms - last_end);
|
|
133
|
+
const start = /* @__PURE__ */ new Map();
|
|
134
|
+
const end = /* @__PURE__ */ new Map();
|
|
135
|
+
const chain_of = /* @__PURE__ */ new Map();
|
|
136
|
+
for (const n of idx) {
|
|
137
|
+
const change = changes[n.label];
|
|
138
|
+
const dur = change === "cache" || change === "remove" ? 0 : n.t1 - n.t0;
|
|
139
|
+
const pr = pred.get(n.i);
|
|
140
|
+
let t0;
|
|
141
|
+
let chain;
|
|
142
|
+
if (pr && change !== "remove") {
|
|
143
|
+
const p_change = changes[pr.p.label];
|
|
144
|
+
const p_start = start.get(pr.p.i) ?? pr.p.t0;
|
|
145
|
+
const p_end = end.get(pr.p.i) ?? pr.p.t1;
|
|
146
|
+
t0 = p_change === "remove" ? Math.min(n.t0, p_start) : change === "parallel" ? p_start : p_end + pr.gap;
|
|
147
|
+
chain = [...chain_of.get(pr.p.i) ?? [pr.p.label], n.label];
|
|
148
|
+
} else {
|
|
149
|
+
t0 = n.t0;
|
|
150
|
+
chain = [n.label];
|
|
151
|
+
}
|
|
152
|
+
start.set(n.i, t0);
|
|
153
|
+
end.set(n.i, t0 + dur);
|
|
154
|
+
chain_of.set(n.i, chain);
|
|
155
|
+
}
|
|
156
|
+
let after_last = 0;
|
|
157
|
+
let longest = [];
|
|
158
|
+
for (const n of idx) {
|
|
159
|
+
if (changes[n.label] === "remove") continue;
|
|
160
|
+
const e = end.get(n.i);
|
|
161
|
+
if (e > after_last) {
|
|
162
|
+
after_last = e;
|
|
163
|
+
longest = chain_of.get(n.i) ?? [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const after_ms = Math.round((after_last + tail) * 100) / 100;
|
|
167
|
+
return {
|
|
168
|
+
before_ms: window_ms,
|
|
169
|
+
after_ms,
|
|
170
|
+
delta_ms: Math.round((after_ms - window_ms) * 100) / 100,
|
|
171
|
+
chain: longest,
|
|
172
|
+
nodes: idx.map((n) => ({
|
|
173
|
+
label: n.label,
|
|
174
|
+
t0: Math.round((start.get(n.i) ?? n.t0) * 100) / 100,
|
|
175
|
+
t1: Math.round((end.get(n.i) ?? n.t1) * 100) / 100,
|
|
176
|
+
moved: Math.abs((start.get(n.i) ?? n.t0) - n.t0) > .05 || changes[n.label] !== void 0
|
|
177
|
+
}))
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/** The NUMBERS the app attached to its spans (`span('db.query', fn, (rows) => ({ rows:
|
|
181
|
+
* rows.length }))`): per span name and attribute, the range across the recording, and how
|
|
182
|
+
* the span's time moved with the value — the cost per row, per tag, per byte. What a
|
|
183
|
+
* function's name alone cannot say: the same function is cheap on 3 items and slow on 3000. */
|
|
184
|
+
function span_values(spans) {
|
|
185
|
+
if (!spans?.length) return [];
|
|
186
|
+
const by = /* @__PURE__ */ new Map();
|
|
187
|
+
for (const s of spans) {
|
|
188
|
+
if (!s.attrs || s.ms < 0) continue;
|
|
189
|
+
for (const k in s.attrs) {
|
|
190
|
+
const v = s.attrs[k];
|
|
191
|
+
if (typeof v !== "number" || !Number.isFinite(v)) continue;
|
|
192
|
+
const key = s.name + "\0" + k;
|
|
193
|
+
let e = by.get(key);
|
|
194
|
+
if (!e) by.set(key, e = {
|
|
195
|
+
values: [],
|
|
196
|
+
ms: []
|
|
197
|
+
});
|
|
198
|
+
e.values.push(v);
|
|
199
|
+
e.ms.push(s.ms);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const out = [];
|
|
203
|
+
for (const [key, e] of by) {
|
|
204
|
+
const at = key.indexOf("\0");
|
|
205
|
+
const sorted = [...e.values].sort((a, b) => a - b);
|
|
206
|
+
const n = sorted.length;
|
|
207
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
208
|
+
const row = {
|
|
209
|
+
span: key.slice(0, at),
|
|
210
|
+
attr: key.slice(at + 1),
|
|
211
|
+
n,
|
|
212
|
+
min: sorted[0],
|
|
213
|
+
p50: sorted[n >> 1],
|
|
214
|
+
max: sorted[n - 1],
|
|
215
|
+
sum: Math.round(sum * 1e3) / 1e3
|
|
216
|
+
};
|
|
217
|
+
if (n >= 3 && sorted[n - 1] - sorted[0] >= Math.abs(sorted[n - 1]) * .05) {
|
|
218
|
+
const mx = sum / n;
|
|
219
|
+
const my = e.ms.reduce((a, b) => a + b, 0) / n;
|
|
220
|
+
let sxy = 0;
|
|
221
|
+
let sxx = 0;
|
|
222
|
+
let syy = 0;
|
|
223
|
+
for (let i = 0; i < n; i++) {
|
|
224
|
+
const dx = e.values[i] - mx;
|
|
225
|
+
const dy = e.ms[i] - my;
|
|
226
|
+
sxy += dx * dy;
|
|
227
|
+
sxx += dx * dx;
|
|
228
|
+
syy += dy * dy;
|
|
229
|
+
}
|
|
230
|
+
if (sxx > 0) {
|
|
231
|
+
row.ms_per_unit = Math.round(sxy / sxx * 1e4) / 1e4;
|
|
232
|
+
row.r = syy > 0 ? Math.round(sxy / Math.sqrt(sxx * syy) * 100) / 100 : 0;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
out.push(row);
|
|
236
|
+
}
|
|
237
|
+
return out.sort((a, b) => b.n - a.n || a.span.localeCompare(b.span));
|
|
238
|
+
}
|
|
239
|
+
export { memo_candidates, simulate_awaits, span_values, sync_io };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { FrameStat } from "./analyze.js";
|
|
2
|
+
import { GcMaker } from "./gc.js";
|
|
3
|
+
import { RetainedSite } from "./insights.js";
|
|
4
|
+
export interface LedgerLine {
|
|
5
|
+
/** the resolved source path (the join key, with `line`) */
|
|
6
|
+
path: string;
|
|
7
|
+
/** a short display path */
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
/** a line of a BUILT chunk (no sourcemap): the source module whose code it is, from the build's
|
|
11
|
+
* module map (`src/lib/format.ts`) — the line number stays the chunk's */
|
|
12
|
+
module?: string;
|
|
13
|
+
/** the code on that line, when the source was available */
|
|
14
|
+
code?: string;
|
|
15
|
+
/** CPU self time that landed on this line, ms */
|
|
16
|
+
cpu_ms: number;
|
|
17
|
+
/** bytes allocated by this line (or by builtins it called) */
|
|
18
|
+
alloc_bytes: number;
|
|
19
|
+
/** GC pause time those allocations caused, ms */
|
|
20
|
+
gc_ms: number;
|
|
21
|
+
/** bytes still alive after one more render and a full collection */
|
|
22
|
+
retained_bytes: number;
|
|
23
|
+
/** the functions and builtins whose cost landed here (`formatPrice`, `replace`), heaviest first */
|
|
24
|
+
who: string[];
|
|
25
|
+
/** the key of the function whose CPU landed here most (its row in the functions table) */
|
|
26
|
+
fn?: string;
|
|
27
|
+
/** the memory numbers are the whole function's, folded onto its hottest line: an allocation
|
|
28
|
+
* profile sees the function an allocation happened in, never the line */
|
|
29
|
+
mem_in_fn?: boolean;
|
|
30
|
+
/** some of the memory numbers were made INSIDE this package, which the line calls, with no app
|
|
31
|
+
* frame above them (the library's work after an await): charged here, the way its CPU is */
|
|
32
|
+
mem_via?: string;
|
|
33
|
+
/** the growth check's verdict on what this line kept: still growing render after render
|
|
34
|
+
* (`true`), or flat after the first (`false`, a bounded cache); absent when it did not run */
|
|
35
|
+
grows?: boolean;
|
|
36
|
+
/** CPU spent inside libraries (dependencies, Node) that THIS line called, ms: a CPU profile
|
|
37
|
+
* never records a call site, so it is found by reading the calling function's source for the
|
|
38
|
+
* library function's name (or its import alias) — split evenly when it is called on several */
|
|
39
|
+
lib_ms: number;
|
|
40
|
+
/** those library calls, heaviest first */
|
|
41
|
+
libs?: LibCall[];
|
|
42
|
+
/** the line's combined share of every cost in the ledger (0..3): how it ranks */
|
|
43
|
+
score: number;
|
|
44
|
+
/** its CPU was moved here from line `from`: V8 had inlined `callee` and charged its time there
|
|
45
|
+
* (split by the un-inlined render's times) */
|
|
46
|
+
merged?: {
|
|
47
|
+
from: number;
|
|
48
|
+
callee: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export interface LibCall {
|
|
52
|
+
/** the library function's own name (`renderToString`) */
|
|
53
|
+
name: string;
|
|
54
|
+
/** its package (`@acme/ui`), `Node` for Node's own, '' when unknown */
|
|
55
|
+
pkg: string;
|
|
56
|
+
ms: number;
|
|
57
|
+
/** the call appears on this many lines of the caller, and `ms` is this line's even share */
|
|
58
|
+
shared?: number;
|
|
59
|
+
}
|
|
60
|
+
export interface LedgerInput {
|
|
61
|
+
functions: readonly FrameStat[];
|
|
62
|
+
makers?: readonly GcMaker[];
|
|
63
|
+
retained?: readonly RetainedSite[];
|
|
64
|
+
/** the growth check: what each site still held after `renders` more renders */
|
|
65
|
+
grown?: {
|
|
66
|
+
renders: number;
|
|
67
|
+
sites: readonly RetainedSite[];
|
|
68
|
+
half?: {
|
|
69
|
+
renders: number;
|
|
70
|
+
sites: readonly RetainedSite[];
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
/** the code on a source line (untrimmed is fine), or undefined */
|
|
74
|
+
code?: (path: string, line: number) => string | undefined;
|
|
75
|
+
/** lines `from`..`to` of a source (1-based, inclusive): what finds library call sites */
|
|
76
|
+
source?: (path: string, from: number, to: number) => {
|
|
77
|
+
start: number;
|
|
78
|
+
lines: string[];
|
|
79
|
+
} | undefined;
|
|
80
|
+
/** the app functions of a render profiled with V8's inlining OFF (the call-count render): each
|
|
81
|
+
* keeps its own frame there, so its time says whose share a merged caller line holds */
|
|
82
|
+
uninlined?: readonly (Pick<FrameStat, "name" | "path" | "category" | "total_ms" | "lines"> & Partial<Pick<FrameStat, "key" | "line" | "url" | "callees">>)[];
|
|
83
|
+
/** the renders `functions` add up (page mode's runs); `uninlined` is one render */
|
|
84
|
+
renders?: number;
|
|
85
|
+
limit?: number;
|
|
86
|
+
}
|
|
87
|
+
/** the package of a `node_modules`-relative path: `@acme/ui/hydrate/x.mjs:1` → `@acme/ui` */
|
|
88
|
+
export declare function pkg_of(file: string): string;
|
|
89
|
+
/** the framework the app runs on: SvelteKit, Svelte, ogygia itself, Vite */
|
|
90
|
+
export declare const is_framework_pkg: (p: string) => boolean;
|
|
91
|
+
/** After `renders` more renders a leaking line holds about `renders` × what one render left; one
|
|
92
|
+
* filling a bounded cache holds its capacity's worth. At 80 % of `renders`× or more it grew with
|
|
93
|
+
* EVERY render measured. The limit, said plainly: a cache holding at least `renders` entries is
|
|
94
|
+
* indistinguishable from a leak in that many renders. */
|
|
95
|
+
export declare const grows_of: (grown: number, one: number, renders: number) => boolean | undefined;
|
|
96
|
+
/** The steadier verdict: the same sampler read after `h` renders (`half`) and after `n` (`full`).
|
|
97
|
+
* A leaking line holds `n/h` times as much at the end as halfway; one feeding a bounded cache
|
|
98
|
+
* that filled holds about the same. The sampled objects are the same objects in both reads, so
|
|
99
|
+
* one sample's noise cancels; the middle band is still no verdict. */
|
|
100
|
+
export declare const grows_between: (half: number, full: number, h: number, n: number) => boolean | undefined;
|
|
101
|
+
/** "grows" from the halfway read, CHECKED against the one-render amount: a leak holds about one
|
|
102
|
+
* render's worth per render (all `renders` of them), a cache with a size limit holds its capacity.
|
|
103
|
+
* The two sampled reads can land far enough apart by chance to read as growth (a 4-entry cache,
|
|
104
|
+
* sampled low halfway and high at the end); a line holding well under the renders' worth is not
|
|
105
|
+
* leaking — no verdict then. Stays "grows" when the one-render amount is unknown.
|
|
106
|
+
* One render's worth is the LARGER of two estimates: the separate one-render pass, and the
|
|
107
|
+
* halfway read over its renders (the same sampler as the end read, so their noise is shared).
|
|
108
|
+
* A low draw on one of them no longer makes a filled cache look like a leak. */
|
|
109
|
+
export declare const confirmed: (between: boolean | undefined, grown: number, one: number, renders: number, half?: {
|
|
110
|
+
bytes: number;
|
|
111
|
+
renders: number;
|
|
112
|
+
}) => boolean | undefined;
|
|
113
|
+
/** A readable path: from `src/` when the path has one, the package-relative path inside
|
|
114
|
+
* `node_modules`, else the last three segments. */
|
|
115
|
+
export declare function short_source(path: string): string;
|
|
116
|
+
/** a source path the reader could open and edit: not a dependency, not Node's own, not the
|
|
117
|
+
* framework's generated build output (a compiled route chunk nobody edits), and not ogygia's own
|
|
118
|
+
* source reached through a workspace link (`packages/core/src/…`: no node_modules segment, but
|
|
119
|
+
* the library all the same, as analyze's categorize already says) */
|
|
120
|
+
export declare const is_app_path: (path: string) => boolean;
|
|
121
|
+
export declare function build_ledger(input: LedgerInput): LedgerLine[];
|