@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.
Files changed (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -1
  3. package/dist/client.d.ts +3 -0
  4. package/dist/client.js +24 -0
  5. package/dist/devtools/BoundaryOverlay.svelte +396 -0
  6. package/dist/devtools/BoundaryOverlay.svelte.d.ts +19 -0
  7. package/dist/devtools/Devtools.svelte +663 -0
  8. package/dist/devtools/Devtools.svelte.d.ts +15 -0
  9. package/dist/devtools/HubTab.svelte +206 -0
  10. package/dist/devtools/HubTab.svelte.d.ts +11 -0
  11. package/dist/devtools/HydrationTab.svelte +237 -0
  12. package/dist/devtools/HydrationTab.svelte.d.ts +13 -0
  13. package/dist/devtools/IslandDetail.svelte +615 -0
  14. package/dist/devtools/IslandDetail.svelte.d.ts +15 -0
  15. package/dist/devtools/LedgerTab.svelte +287 -0
  16. package/dist/devtools/LedgerTab.svelte.d.ts +11 -0
  17. package/dist/devtools/LensTab.svelte +290 -0
  18. package/dist/devtools/LensTab.svelte.d.ts +19 -0
  19. package/dist/devtools/NavTab.svelte +229 -0
  20. package/dist/devtools/NavTab.svelte.d.ts +11 -0
  21. package/dist/devtools/PageTab.svelte +1122 -0
  22. package/dist/devtools/PageTab.svelte.d.ts +15 -0
  23. package/dist/devtools/ProfilerTab.svelte +755 -0
  24. package/dist/devtools/ProfilerTab.svelte.d.ts +15 -0
  25. package/dist/devtools/PropsTree.svelte +155 -0
  26. package/dist/devtools/PropsTree.svelte.d.ts +15 -0
  27. package/dist/devtools/SessionTab.svelte +796 -0
  28. package/dist/devtools/SessionTab.svelte.d.ts +11 -0
  29. package/dist/devtools/TimelineTab.svelte +267 -0
  30. package/dist/devtools/TimelineTab.svelte.d.ts +11 -0
  31. package/dist/devtools/WireTab.svelte +149 -0
  32. package/dist/devtools/WireTab.svelte.d.ts +11 -0
  33. package/dist/devtools/bytes.d.ts +70 -0
  34. package/dist/devtools/bytes.js +82 -0
  35. package/dist/devtools/cache-headers.d.ts +20 -0
  36. package/dist/devtools/cache-headers.js +81 -0
  37. package/dist/devtools/highlight.d.ts +7 -0
  38. package/dist/devtools/highlight.js +83 -0
  39. package/dist/devtools/hydration.d.ts +48 -0
  40. package/dist/devtools/hydration.js +93 -0
  41. package/dist/devtools/index.d.ts +3 -0
  42. package/dist/devtools/index.js +3 -0
  43. package/dist/devtools/ledger-dom.d.ts +7 -0
  44. package/dist/devtools/ledger-dom.js +151 -0
  45. package/dist/devtools/leftovers.d.ts +77 -0
  46. package/dist/devtools/leftovers.js +319 -0
  47. package/dist/devtools/names.d.ts +1 -0
  48. package/dist/devtools/names.js +34 -0
  49. package/dist/devtools/page.d.ts +96 -0
  50. package/dist/devtools/page.js +957 -0
  51. package/dist/devtools/profile-anchors.d.ts +22 -0
  52. package/dist/devtools/profile-anchors.js +135 -0
  53. package/dist/devtools/profile-store.d.ts +175 -0
  54. package/dist/devtools/profile-store.js +239 -0
  55. package/dist/devtools/regions.d.ts +77 -0
  56. package/dist/devtools/regions.js +132 -0
  57. package/dist/devtools/session.d.ts +31 -0
  58. package/dist/devtools/session.js +455 -0
  59. package/dist/devtools/since-load.d.ts +50 -0
  60. package/dist/devtools/since-load.js +89 -0
  61. package/dist/devtools/source-lines.d.ts +24 -0
  62. package/dist/devtools/source-lines.js +106 -0
  63. package/dist/devtools/styles.d.ts +52 -0
  64. package/dist/devtools/styles.js +248 -0
  65. package/dist/devtools/testing-engine.d.ts +83 -0
  66. package/dist/devtools/testing-engine.js +220 -0
  67. package/dist/devtools/ui-app.d.ts +5 -0
  68. package/dist/devtools/ui-app.js +69 -0
  69. package/dist/devtools/ui.d.ts +50 -0
  70. package/dist/devtools/ui.js +175 -0
  71. package/dist/profiler/alloc.d.ts +75 -0
  72. package/dist/profiler/alloc.js +76 -0
  73. package/dist/profiler/analyze.d.ts +437 -0
  74. package/dist/profiler/analyze.js +1941 -0
  75. package/dist/profiler/app-path.d.ts +7 -0
  76. package/dist/profiler/app-path.js +26 -0
  77. package/dist/profiler/async-io.d.ts +58 -0
  78. package/dist/profiler/async-io.js +187 -0
  79. package/dist/profiler/beacon-standalone.d.ts +4 -0
  80. package/dist/profiler/beacon-standalone.js +409 -0
  81. package/dist/profiler/browser-findings.d.ts +25 -0
  82. package/dist/profiler/browser-findings.js +260 -0
  83. package/dist/profiler/byte-strip.d.ts +58 -0
  84. package/dist/profiler/byte-strip.js +228 -0
  85. package/dist/profiler/client-files.d.ts +16 -0
  86. package/dist/profiler/client-files.js +60 -0
  87. package/dist/profiler/client-windows.d.ts +49 -0
  88. package/dist/profiler/client-windows.js +95 -0
  89. package/dist/profiler/client.d.ts +40 -0
  90. package/dist/profiler/client.js +82 -0
  91. package/dist/profiler/compare.d.ts +308 -0
  92. package/dist/profiler/compare.js +733 -0
  93. package/dist/profiler/compress.d.ts +10 -0
  94. package/dist/profiler/compress.js +36 -0
  95. package/dist/profiler/contention.d.ts +60 -0
  96. package/dist/profiler/contention.js +86 -0
  97. package/dist/profiler/crypto.d.ts +19 -0
  98. package/dist/profiler/crypto.js +101 -0
  99. package/dist/profiler/doc-diff.d.ts +47 -0
  100. package/dist/profiler/doc-diff.js +214 -0
  101. package/dist/profiler/dom-diff.d.ts +43 -0
  102. package/dist/profiler/dom-diff.js +121 -0
  103. package/dist/profiler/drill.d.ts +89 -0
  104. package/dist/profiler/drill.js +553 -0
  105. package/dist/profiler/embedded-maps.d.ts +37 -0
  106. package/dist/profiler/embedded-maps.js +135 -0
  107. package/dist/profiler/env.d.ts +6 -0
  108. package/dist/profiler/env.js +10 -0
  109. package/dist/profiler/forecast.d.ts +76 -0
  110. package/dist/profiler/forecast.js +225 -0
  111. package/dist/profiler/frames.d.ts +11 -0
  112. package/dist/profiler/frames.js +42 -0
  113. package/dist/profiler/gc.d.ts +144 -0
  114. package/dist/profiler/gc.js +271 -0
  115. package/dist/profiler/hole-slots.d.ts +31 -0
  116. package/dist/profiler/hole-slots.js +90 -0
  117. package/dist/profiler/index.d.ts +288 -0
  118. package/dist/profiler/index.js +4921 -0
  119. package/dist/profiler/insights.d.ts +96 -0
  120. package/dist/profiler/insights.js +239 -0
  121. package/dist/profiler/ledger.d.ts +121 -0
  122. package/dist/profiler/ledger.js +440 -0
  123. package/dist/profiler/lineage.d.ts +81 -0
  124. package/dist/profiler/lineage.js +359 -0
  125. package/dist/profiler/module-map.d.ts +12 -0
  126. package/dist/profiler/module-map.js +94 -0
  127. package/dist/profiler/net.d.ts +169 -0
  128. package/dist/profiler/net.js +586 -0
  129. package/dist/profiler/on-demand.d.ts +11 -0
  130. package/dist/profiler/on-demand.js +15 -0
  131. package/dist/profiler/own-requests.d.ts +9 -0
  132. package/dist/profiler/own-requests.js +17 -0
  133. package/dist/profiler/patterns.d.ts +476 -0
  134. package/dist/profiler/patterns.js +2203 -0
  135. package/dist/profiler/profiler-router.d.ts +245 -0
  136. package/dist/profiler/profiler-router.js +113 -0
  137. package/dist/profiler/refresh-stub.d.ts +2 -0
  138. package/dist/profiler/refresh-stub.js +37 -0
  139. package/dist/profiler/report.d.ts +1404 -0
  140. package/dist/profiler/report.js +2638 -0
  141. package/dist/profiler/river.d.ts +65 -0
  142. package/dist/profiler/river.js +298 -0
  143. package/dist/profiler/route-files.d.ts +22 -0
  144. package/dist/profiler/route-files.js +55 -0
  145. package/dist/profiler/route-imports.d.ts +14 -0
  146. package/dist/profiler/route-imports.js +83 -0
  147. package/dist/profiler/saving.d.ts +22 -0
  148. package/dist/profiler/saving.js +42 -0
  149. package/dist/profiler/score.d.ts +109 -0
  150. package/dist/profiler/score.js +261 -0
  151. package/dist/profiler/session-cookie.d.ts +28 -0
  152. package/dist/profiler/session-cookie.js +45 -0
  153. package/dist/profiler/sink.d.ts +81 -0
  154. package/dist/profiler/sink.js +124 -0
  155. package/dist/profiler/site-fixes.d.ts +25 -0
  156. package/dist/profiler/site-fixes.js +62 -0
  157. package/dist/profiler/source-scan.d.ts +210 -0
  158. package/dist/profiler/source-scan.js +1724 -0
  159. package/dist/profiler/span.d.ts +90 -0
  160. package/dist/profiler/span.js +109 -0
  161. package/dist/profiler/stacks.d.ts +43 -0
  162. package/dist/profiler/stacks.js +107 -0
  163. package/dist/profiler/standalone.d.ts +27 -0
  164. package/dist/profiler/standalone.js +98 -0
  165. package/dist/profiler/steps.d.ts +39 -0
  166. package/dist/profiler/steps.js +74 -0
  167. package/dist/profiler/storage/index.d.ts +87 -0
  168. package/dist/profiler/storage/index.js +76 -0
  169. package/dist/profiler/storage/postgres.d.ts +12 -0
  170. package/dist/profiler/storage/postgres.js +94 -0
  171. package/dist/profiler/storage/redis.d.ts +19 -0
  172. package/dist/profiler/storage/redis.js +148 -0
  173. package/dist/profiler/storage/sqlite.d.ts +2 -0
  174. package/dist/profiler/storage/sqlite.js +85 -0
  175. package/dist/profiler/storage/visits.d.ts +4 -0
  176. package/dist/profiler/storage/visits.js +6 -0
  177. package/dist/profiler/stream-tail.d.ts +26 -0
  178. package/dist/profiler/stream-tail.js +74 -0
  179. package/dist/profiler/timeline.d.ts +279 -0
  180. package/dist/profiler/timeline.js +690 -0
  181. package/dist/profiler/ui/AllocStrip.svelte +146 -0
  182. package/dist/profiler/ui/AllocStrip.svelte.d.ts +17 -0
  183. package/dist/profiler/ui/Awaits.svelte +363 -0
  184. package/dist/profiler/ui/Awaits.svelte.d.ts +16 -0
  185. package/dist/profiler/ui/ByteStrip.svelte +182 -0
  186. package/dist/profiler/ui/ByteStrip.svelte.d.ts +18 -0
  187. package/dist/profiler/ui/Compare.svelte +24 -0
  188. package/dist/profiler/ui/Compare.svelte.d.ts +16 -0
  189. package/dist/profiler/ui/CompareBody.svelte +429 -0
  190. package/dist/profiler/ui/CompareBody.svelte.d.ts +8 -0
  191. package/dist/profiler/ui/ComponentsTable.svelte +265 -0
  192. package/dist/profiler/ui/ComponentsTable.svelte.d.ts +20 -0
  193. package/dist/profiler/ui/Dashboard.svelte +492 -0
  194. package/dist/profiler/ui/Dashboard.svelte.d.ts +31 -0
  195. package/dist/profiler/ui/DataRiver.svelte +206 -0
  196. package/dist/profiler/ui/DataRiver.svelte.d.ts +13 -0
  197. package/dist/profiler/ui/DomTravel.svelte +207 -0
  198. package/dist/profiler/ui/DomTravel.svelte.d.ts +9 -0
  199. package/dist/profiler/ui/DownloadHtml.svelte +52 -0
  200. package/dist/profiler/ui/DownloadHtml.svelte.d.ts +10 -0
  201. package/dist/profiler/ui/Drill.svelte +276 -0
  202. package/dist/profiler/ui/Drill.svelte.d.ts +12 -0
  203. package/dist/profiler/ui/ExportButton.svelte +59 -0
  204. package/dist/profiler/ui/ExportButton.svelte.d.ts +8 -0
  205. package/dist/profiler/ui/Flame.svelte +222 -0
  206. package/dist/profiler/ui/Flame.svelte.d.ts +12 -0
  207. package/dist/profiler/ui/FrameDetails.svelte +308 -0
  208. package/dist/profiler/ui/FrameDetails.svelte.d.ts +16 -0
  209. package/dist/profiler/ui/FunctionsTable.svelte +156 -0
  210. package/dist/profiler/ui/FunctionsTable.svelte.d.ts +17 -0
  211. package/dist/profiler/ui/IslandHeatmap.svelte +151 -0
  212. package/dist/profiler/ui/IslandHeatmap.svelte.d.ts +16 -0
  213. package/dist/profiler/ui/IslandsTable.svelte +448 -0
  214. package/dist/profiler/ui/IslandsTable.svelte.d.ts +16 -0
  215. package/dist/profiler/ui/KeepLocal.svelte +46 -0
  216. package/dist/profiler/ui/KeepLocal.svelte.d.ts +8 -0
  217. package/dist/profiler/ui/Ledger.svelte +196 -0
  218. package/dist/profiler/ui/Ledger.svelte.d.ts +18 -0
  219. package/dist/profiler/ui/LocalCompare.svelte +48 -0
  220. package/dist/profiler/ui/LocalCompare.svelte.d.ts +8 -0
  221. package/dist/profiler/ui/LocalReports.svelte +68 -0
  222. package/dist/profiler/ui/LocalReports.svelte.d.ts +7 -0
  223. package/dist/profiler/ui/Login.svelte +13 -0
  224. package/dist/profiler/ui/Login.svelte.d.ts +12 -0
  225. package/dist/profiler/ui/LoginForm.svelte +42 -0
  226. package/dist/profiler/ui/LoginForm.svelte.d.ts +7 -0
  227. package/dist/profiler/ui/Message.svelte +10 -0
  228. package/dist/profiler/ui/Message.svelte.d.ts +12 -0
  229. package/dist/profiler/ui/OnThisPage.svelte +137 -0
  230. package/dist/profiler/ui/OnThisPage.svelte.d.ts +3 -0
  231. package/dist/profiler/ui/OneClock.svelte +297 -0
  232. package/dist/profiler/ui/OneClock.svelte.d.ts +27 -0
  233. package/dist/profiler/ui/Panel.svelte +10 -0
  234. package/dist/profiler/ui/Panel.svelte.d.ts +16 -0
  235. package/dist/profiler/ui/Paths.svelte +269 -0
  236. package/dist/profiler/ui/Paths.svelte.d.ts +15 -0
  237. package/dist/profiler/ui/Patterns.svelte +364 -0
  238. package/dist/profiler/ui/Patterns.svelte.d.ts +15 -0
  239. package/dist/profiler/ui/PermalinkGate.svelte +143 -0
  240. package/dist/profiler/ui/PermalinkGate.svelte.d.ts +10 -0
  241. package/dist/profiler/ui/RenderSteps.svelte +218 -0
  242. package/dist/profiler/ui/RenderSteps.svelte.d.ts +13 -0
  243. package/dist/profiler/ui/Report.svelte +28 -0
  244. package/dist/profiler/ui/Report.svelte.d.ts +20 -0
  245. package/dist/profiler/ui/ReportBody.svelte +2592 -0
  246. package/dist/profiler/ui/ReportBody.svelte.d.ts +21 -0
  247. package/dist/profiler/ui/Run.svelte +17 -0
  248. package/dist/profiler/ui/Run.svelte.d.ts +14 -0
  249. package/dist/profiler/ui/RunView.svelte +146 -0
  250. package/dist/profiler/ui/RunView.svelte.d.ts +10 -0
  251. package/dist/profiler/ui/ScoreCard.svelte +197 -0
  252. package/dist/profiler/ui/ScoreCard.svelte.d.ts +7 -0
  253. package/dist/profiler/ui/Scrub.svelte +293 -0
  254. package/dist/profiler/ui/Scrub.svelte.d.ts +18 -0
  255. package/dist/profiler/ui/SeedExplainer.svelte +193 -0
  256. package/dist/profiler/ui/SeedExplainer.svelte.d.ts +16 -0
  257. package/dist/profiler/ui/ShareLink.svelte +148 -0
  258. package/dist/profiler/ui/ShareLink.svelte.d.ts +8 -0
  259. package/dist/profiler/ui/Shell.svelte +50 -0
  260. package/dist/profiler/ui/Shell.svelte.d.ts +27 -0
  261. package/dist/profiler/ui/Sidebar.svelte +312 -0
  262. package/dist/profiler/ui/Sidebar.svelte.d.ts +7 -0
  263. package/dist/profiler/ui/Site.svelte +20 -0
  264. package/dist/profiler/ui/Site.svelte.d.ts +23 -0
  265. package/dist/profiler/ui/SiteView.svelte +385 -0
  266. package/dist/profiler/ui/SiteView.svelte.d.ts +27 -0
  267. package/dist/profiler/ui/ThemeSwitch.svelte +78 -0
  268. package/dist/profiler/ui/ThemeSwitch.svelte.d.ts +3 -0
  269. package/dist/profiler/ui/Timeline.svelte +672 -0
  270. package/dist/profiler/ui/Timeline.svelte.d.ts +22 -0
  271. package/dist/profiler/ui/Treemap.svelte +278 -0
  272. package/dist/profiler/ui/Treemap.svelte.d.ts +20 -0
  273. package/dist/profiler/ui/Upload.svelte +19 -0
  274. package/dist/profiler/ui/Upload.svelte.d.ts +10 -0
  275. package/dist/profiler/ui/UploadForm.svelte +59 -0
  276. package/dist/profiler/ui/UploadForm.svelte.d.ts +6 -0
  277. package/dist/profiler/ui/WaitingTable.svelte +55 -0
  278. package/dist/profiler/ui/WaitingTable.svelte.d.ts +14 -0
  279. package/dist/profiler/ui/Waterfall.svelte +371 -0
  280. package/dist/profiler/ui/Waterfall.svelte.d.ts +7 -0
  281. package/dist/profiler/ui/WhatIf.svelte +87 -0
  282. package/dist/profiler/ui/WhatIf.svelte.d.ts +18 -0
  283. package/dist/profiler/ui/fake-progress.svelte.d.ts +15 -0
  284. package/dist/profiler/ui/fake-progress.svelte.js +38 -0
  285. package/dist/profiler/ui/format.d.ts +20 -0
  286. package/dist/profiler/ui/format.js +71 -0
  287. package/dist/profiler/ui/permalink.d.ts +9 -0
  288. package/dist/profiler/ui/permalink.js +108 -0
  289. package/dist/profiler/ui/press.d.ts +5 -0
  290. package/dist/profiler/ui/press.js +19 -0
  291. package/dist/profiler/ui/profiler.css +650 -0
  292. package/dist/profiler/ui/report-data.d.ts +189 -0
  293. package/dist/profiler/ui/report-data.js +393 -0
  294. package/dist/profiler/ui/report-request.d.ts +6 -0
  295. package/dist/profiler/ui/report-request.js +50 -0
  296. package/dist/profiler/ui/row-anchor.svelte.d.ts +12 -0
  297. package/dist/profiler/ui/row-anchor.svelte.js +44 -0
  298. package/dist/profiler/ui/sort.svelte.d.ts +14 -0
  299. package/dist/profiler/ui/sort.svelte.js +37 -0
  300. package/dist/profiler/ui/store.d.ts +45 -0
  301. package/dist/profiler/ui/store.js +92 -0
  302. package/dist/profiler/visit.d.ts +389 -0
  303. package/dist/profiler/visit.js +1002 -0
  304. package/dist/report.d.ts +5 -0
  305. package/dist/report.js +4 -0
  306. package/dist/server.d.ts +11 -0
  307. package/dist/server.js +38 -0
  308. package/dist/vite.d.ts +12 -0
  309. package/dist/vite.js +78 -0
  310. package/package.json +99 -6
@@ -0,0 +1,49 @@
1
+ import { Analysis, CpuProfile, FrameCategory } from "./analyze.js";
2
+ import { Visit } from "./visit.js";
3
+ export interface WindowFn {
4
+ name: string;
5
+ file: string;
6
+ line: number | null;
7
+ category: FrameCategory;
8
+ self_ms: number;
9
+ total_ms: number;
10
+ }
11
+ export interface ClientWindows {
12
+ /** per island fingerprint: CPU inside its hydrate window, and the functions that took it */
13
+ islands: Record<string, {
14
+ ms: number;
15
+ top: WindowFn[];
16
+ }>;
17
+ /** the long tasks that overlapped no island's hydrate window */
18
+ outside: {
19
+ ms: number;
20
+ top: WindowFn[];
21
+ };
22
+ }
23
+ /** The profile with every sample outside `spans` (ms, the page clock) turned idle. */
24
+ export declare function window_profile(p: CpuProfile, spans: readonly (readonly [number, number])[]): CpuProfile;
25
+ /** THE SLOWEST INTERACTION'S TWO SPANS, analyzed: what ran while its input waited, and what its
26
+ * handlers ran (the island's own code, by its source-mapped name — the long frames only name the
27
+ * event dispatcher it came through). A span too short for the sampler says nothing. */
28
+ export interface InteractionCpu {
29
+ /** the interaction's start (the page clock): which interaction of the visit this is */
30
+ t: number;
31
+ /** the frames went through a source map (their lines are the source's) */
32
+ mapped: boolean;
33
+ wait: {
34
+ ms: number;
35
+ top: WindowFn[];
36
+ } | null;
37
+ handler: {
38
+ ms: number;
39
+ top: WindowFn[];
40
+ } | null;
41
+ }
42
+ export declare function interaction_windows(profile: CpuProfile, spans: {
43
+ t: number;
44
+ wait: readonly [number, number];
45
+ handler: readonly [number, number];
46
+ }, run: (p: CpuProfile) => Analysis): InteractionCpu;
47
+ /** Each island's hydrate window and the long tasks outside them, analyzed. `run` is the report's
48
+ * own analysis (its resolver, renamer and chunk categories). */
49
+ export declare function client_windows(profile: CpuProfile, visit: Visit, run: (p: CpuProfile) => Analysis): ClientWindows;
@@ -0,0 +1,95 @@
1
+ // src/profiler/client-windows.ts
2
+ /** a slice this short says nothing a sampler (~10 ms) can back up */
3
+ const MIN_WINDOW_MS = 20;
4
+ const MAX_WINDOWS = 16;
5
+ const NOT_CODE = /* @__PURE__ */ new Set([
6
+ "idle",
7
+ "gc",
8
+ "v8",
9
+ "profiler"
10
+ ]);
11
+ /** The profile with every sample outside `spans` (ms, the page clock) turned idle. */
12
+ function window_profile(p, spans) {
13
+ const samples = p.samples ?? [];
14
+ const deltas = p.timeDeltas ?? [];
15
+ let t_us = p.startTime;
16
+ const out = samples.map((sample, i) => {
17
+ t_us += deltas[i] ?? 0;
18
+ const t = t_us / 1e3;
19
+ let inside = false;
20
+ for (const [a, b] of spans) if (t >= a && t <= b) {
21
+ inside = true;
22
+ break;
23
+ }
24
+ return inside ? sample : 2;
25
+ });
26
+ return {
27
+ ...p,
28
+ samples: out
29
+ };
30
+ }
31
+ function top_of(a) {
32
+ return a.functions.filter((f) => !NOT_CODE.has(f.category) && f.self_ms >= 1).sort((x, y) => y.self_ms - x.self_ms).slice(0, 3).map((f) => ({
33
+ name: f.label && f.name.startsWith("(anonymous") ? f.label : f.name,
34
+ file: f.url,
35
+ line: f.line || null,
36
+ category: f.category,
37
+ self_ms: f.self_ms,
38
+ total_ms: f.total_ms
39
+ }));
40
+ }
41
+ function interaction_windows(profile, spans, run) {
42
+ let mapped = false;
43
+ const one = (s) => {
44
+ if (s[1] - s[0] < MIN_WINDOW_MS) return null;
45
+ const a = run(window_profile(profile, [s]));
46
+ mapped ||= a.sourcemapped;
47
+ return {
48
+ ms: Math.round(a.busy_ms * 10) / 10,
49
+ top: top_of(a)
50
+ };
51
+ };
52
+ const wait = one(spans.wait);
53
+ const handler = one(spans.handler);
54
+ return {
55
+ t: spans.t,
56
+ mapped,
57
+ wait,
58
+ handler
59
+ };
60
+ }
61
+ /** Each island's hydrate window and the long tasks outside them, analyzed. `run` is the report's
62
+ * own analysis (its resolver, renamer and chunk categories). */
63
+ function client_windows(profile, visit, run) {
64
+ const end = (i) => Math.max(i.done, i.fx ?? 0);
65
+ const windows = visit.islands.map((i) => ({
66
+ fp: i.fp,
67
+ from: i.turn ?? i.loaded,
68
+ to: end(i)
69
+ })).filter((w) => w.to - w.from >= MIN_WINDOW_MS).sort((x, y) => y.to - y.from - (x.to - x.from)).slice(0, MAX_WINDOWS);
70
+ const islands = {};
71
+ for (const w of windows) {
72
+ const a = run(window_profile(profile, [[w.from, w.to]]));
73
+ islands[w.fp] = {
74
+ ms: Math.round(a.busy_ms * 10) / 10,
75
+ top: top_of(a)
76
+ };
77
+ }
78
+ const outside = visit.longtasks.filter((t) => !visit.islands.some((i) => t.t < end(i) && t.t + t.ms > (i.turn ?? i.loaded))).map((t) => [t.t, t.t + t.ms]);
79
+ let out = {
80
+ ms: 0,
81
+ top: []
82
+ };
83
+ if (outside.length) {
84
+ const a = run(window_profile(profile, outside));
85
+ out = {
86
+ ms: Math.round(a.busy_ms * 10) / 10,
87
+ top: top_of(a)
88
+ };
89
+ }
90
+ return {
91
+ islands,
92
+ outside: out
93
+ };
94
+ }
95
+ export { client_windows, interaction_windows, window_profile };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `mark` — the browser twin of `span`: name a stretch of the visitor's time the server profile
3
+ * cannot see (a design system's CDN components hydrating, a widget booting, a font arriving) and
4
+ * the report shows it next to the islands' own hydration times, for the page.
5
+ *
6
+ * • `mark(name, fn)` times a function or the promise it returns; `mark(name, promise)` times a
7
+ * promise you already hold. A throw or rejection is recorded (`error: true`) and rethrown.
8
+ * • `mark.event(name, type, target?)` times until an event fires — a web-components runtime's
9
+ * "all hydrated" event, a widget's `ready` — from the call (or from navigation start with
10
+ * `{ from: 'navigation' }`).
11
+ * • `mark.start(name)` opens one by hand; `end(attrs?)` closes it. `{ from: 'navigation' }` on
12
+ * either measures from the page's start instead of the call.
13
+ *
14
+ * Cost: nothing without the profiler's beacon tag in the document (the profiler puts it there
15
+ * for its own logged-in user only) — one `querySelector` on the first call, then a boolean. With
16
+ * it, one entry in a batch sent when the page is idle or hides. Never a request per mark.
17
+ *
18
+ * ```ts
19
+ * import { mark } from '@ogygia/devtools/profiler/client';
20
+ * mark.event('ds.hydrate', 'components-ready'); // the design system's runtime: every component hydrated
21
+ * await mark('widget.boot', () => widget.init(), { version: '3' });
22
+ * ```
23
+ */
24
+ export type MarkAttrs = Record<string, string | number | boolean>;
25
+ export interface MarkHandle {
26
+ /** close it; ending twice is a no-op */
27
+ end(attrs?: MarkAttrs): void;
28
+ }
29
+ declare function mark_event(name: string, type: string, target?: EventTarget | null, opts?: {
30
+ from?: "call" | "navigation";
31
+ attrs?: MarkAttrs;
32
+ }): Promise<void>;
33
+ declare function mark_start(name: string, attrs?: MarkAttrs, opts?: {
34
+ from?: "call" | "navigation";
35
+ }): MarkHandle;
36
+ export declare const mark: (<T>(name: string, work: (() => T) | PromiseLike<T>, attrs?: MarkAttrs) => T) & {
37
+ event: typeof mark_event;
38
+ start: typeof mark_start;
39
+ };
40
+ export {};
@@ -0,0 +1,82 @@
1
+ // src/profiler/client.ts
2
+ const BEACON_KEY = Symbol.for("ogygia.beacon");
3
+ const page_beacon = () => globalThis[BEACON_KEY];
4
+ const waiting = [];
5
+ let retrying = false;
6
+ function beacon_mark(...args) {
7
+ if (typeof document === "undefined") return;
8
+ const b = page_beacon();
9
+ if (b) {
10
+ for (const w of waiting.splice(0)) b.beacon_mark(...w);
11
+ b.beacon_mark(...args);
12
+ return;
13
+ }
14
+ if (waiting.length < 400) waiting.push(args);
15
+ if (retrying) return;
16
+ retrying = true;
17
+ let tries = 0;
18
+ const retry = () => {
19
+ const later = page_beacon();
20
+ if (later) {
21
+ retrying = false;
22
+ for (const w of waiting.splice(0)) later.beacon_mark(...w);
23
+ } else if (++tries < 40) setTimeout(retry, 250);
24
+ else {
25
+ retrying = false;
26
+ waiting.length = 0;
27
+ }
28
+ };
29
+ setTimeout(retry, 250);
30
+ }
31
+ const now = () => typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
32
+ const is_thenable = (v) => !!v && typeof v.then === "function";
33
+ function mark_impl(name, work, attrs) {
34
+ const t0 = now();
35
+ const done = (error) => beacon_mark(name, now() - t0, error ? {
36
+ ...attrs,
37
+ error: true
38
+ } : attrs, t0);
39
+ let out;
40
+ try {
41
+ out = typeof work === "function" ? work() : work;
42
+ } catch (e) {
43
+ done(true);
44
+ throw e;
45
+ }
46
+ if (is_thenable(out)) return out.then((v) => {
47
+ done();
48
+ return v;
49
+ }, (e) => {
50
+ done(true);
51
+ throw e;
52
+ });
53
+ done();
54
+ return out;
55
+ }
56
+ function mark_event(name, type, target = typeof document !== "undefined" ? document : null, opts = {}) {
57
+ if (!target) return Promise.resolve();
58
+ const t0 = opts.from === "navigation" ? 0 : now();
59
+ return new Promise((resolve) => {
60
+ target.addEventListener(type, () => {
61
+ beacon_mark(name, now() - t0, opts.attrs, t0);
62
+ resolve();
63
+ }, { once: true });
64
+ });
65
+ }
66
+ function mark_start(name, attrs, opts = {}) {
67
+ const t0 = opts.from === "navigation" ? 0 : now();
68
+ let ended = false;
69
+ return { end(extra) {
70
+ if (ended) return;
71
+ ended = true;
72
+ beacon_mark(name, now() - t0, extra ? {
73
+ ...attrs,
74
+ ...extra
75
+ } : attrs, t0);
76
+ } };
77
+ }
78
+ const mark = Object.assign(mark_impl, {
79
+ event: mark_event,
80
+ start: mark_start
81
+ });
82
+ export { mark };
@@ -0,0 +1,308 @@
1
+ import { Analysis } from "./analyze.js";
2
+ import { ReportMeta } from "./report.js";
3
+ import { GcAttribution } from "./gc.js";
4
+ import { Pattern, PatternKind } from "./patterns.js";
5
+ import { LedgerLine } from "./ledger.js";
6
+ import { DrillNode } from "./drill.js";
7
+ import { Forecast } from "./forecast.js";
8
+ import { Phase } from "./timeline.js";
9
+ import { VitalMove, VitalMove as VitalMove$1, vitals_moved } from "@ogygia/core/insights";
10
+ export interface DeltaRow {
11
+ /** the identity both sides were matched on (unique in its list: what a view keys rows by —
12
+ * two anonymous functions in one file share a name and a file) */
13
+ key: string;
14
+ name: string;
15
+ /** where it lives (b's, else a's) */
16
+ file: string;
17
+ line: number;
18
+ a_self: number;
19
+ b_self: number;
20
+ d_self: number;
21
+ a_total: number;
22
+ b_total: number;
23
+ d_total: number;
24
+ a_calls: number | null;
25
+ b_calls: number | null;
26
+ /** only in one side */
27
+ only?: "a" | "b";
28
+ }
29
+ export interface SummaryRow {
30
+ label: string;
31
+ a: number;
32
+ b: number;
33
+ d: number;
34
+ unit: "ms" | "n" | "%" | "KB" | "MB";
35
+ }
36
+ /** one allocation line before and after: what it allocated and the pause time it caused */
37
+ export interface GcMakerDelta {
38
+ name: string;
39
+ caller: string | null;
40
+ component: string | null;
41
+ a_mb: number;
42
+ b_mb: number;
43
+ d_mb: number;
44
+ a_gc_ms: number;
45
+ b_gc_ms: number;
46
+ d_gc_ms: number;
47
+ only?: "a" | "b";
48
+ }
49
+ export interface Comparison {
50
+ a: {
51
+ id: string;
52
+ label: string;
53
+ created: number;
54
+ };
55
+ b: {
56
+ id: string;
57
+ label: string;
58
+ created: number;
59
+ };
60
+ summary: SummaryRow[];
61
+ phases: {
62
+ phase: Phase;
63
+ label: string;
64
+ a: number;
65
+ b: number;
66
+ d: number;
67
+ }[];
68
+ components: DeltaRow[];
69
+ functions: DeltaRow[];
70
+ findings: {
71
+ added: string[];
72
+ gone: string[];
73
+ };
74
+ /** the paths to fix, matched by their owner: what each path cost before and after */
75
+ paths: PathDelta[];
76
+ /** the garbage makers, matched by line: what each allocated and the GC it caused, before and after */
77
+ gc_makers: GcMakerDelta[];
78
+ /** the slow patterns, matched by kind (and library): which a change fixed, which it introduced */
79
+ patterns: PatternDelta[];
80
+ /** DID THE FIX PAY: what the patterns that went away or got better were expected to save, per
81
+ * render, against how much faster the median render actually got (page mode, both sides) */
82
+ fix_check?: {
83
+ predicted_ms: number;
84
+ measured_ms: number;
85
+ verdict: "as-expected" | "less" | "more";
86
+ /** B ran after A on the heap A filled (`order`): its render reads slow, the measured saving low */
87
+ skewed?: true;
88
+ };
89
+ /** the costly app lines, matched by file + code (an edit above them moves the number, not the
90
+ * code), per render on each side: what the change did to each */
91
+ lines: LineDelta[];
92
+ /** WHERE ONE RENDER'S TIME MOVED: the drill-down rows that changed, deepest cause only */
93
+ drill?: DrillDelta[];
94
+ /** THE RUN ORDER TRAP: both ran on one server, and the earlier one saw every render keep memory
95
+ * alive — the later one ran on a fuller heap (more GC, slower allocation), so part of its
96
+ * difference is the order, not the code */
97
+ order?: {
98
+ earlier: "a" | "b";
99
+ kept_mb: number;
100
+ requests_between: number;
101
+ };
102
+ /** the render took the same time on both sides (within the runs' own spread): CPU moves between
103
+ * patterns and lines are V8 charging the time elsewhere (inlining differs run to run), so they
104
+ * read `shifted`, not fixed or worse */
105
+ render_same?: {
106
+ a_ms: number;
107
+ b_ms: number;
108
+ noise_ms: number;
109
+ };
110
+ }
111
+ export interface LineDelta {
112
+ file: string;
113
+ /** the line number in b (a's when the line is gone) */
114
+ line: number;
115
+ code: string;
116
+ /** CPU per render (own + inside libraries), ms */
117
+ a_ms: number;
118
+ b_ms: number;
119
+ d_ms: number;
120
+ /** bytes made per render */
121
+ a_bytes: number;
122
+ b_bytes: number;
123
+ /** bytes kept alive per render */
124
+ a_kept: number;
125
+ b_kept: number;
126
+ status: "fixed" | "new" | "better" | "worse" | "same" | "shifted";
127
+ }
128
+ /** With the render unchanged, a CPU row's fixed / new / better / worse is where V8 charged the time. */
129
+ export declare function mark_shifted(cmp: Comparison): Comparison;
130
+ /** The two ledgers side by side. Keyed by file + the code on the line: code that changed reads as
131
+ * one line gone and another new (the fix), code that did not keeps its row though its number moved. */
132
+ export declare function line_deltas(a?: readonly LedgerLine[], b?: readonly LedgerLine[], ra?: number, rb?: number): LineDelta[];
133
+ /** A report against the previous profile of its page, cut for the top of the report: the render's
134
+ * change, the drill rows that really moved (inlining shifts left out), the order trap, the fix check */
135
+ export interface Since {
136
+ prev: string;
137
+ a_ms?: number;
138
+ b_ms?: number;
139
+ /** the render took the same time within the runs' own spread */
140
+ same?: boolean;
141
+ moved: DrillDelta[];
142
+ order?: Comparison["order"];
143
+ fix_check?: Comparison["fix_check"];
144
+ /** the page score then and now, and the categories that moved it most */
145
+ score?: {
146
+ a: number;
147
+ b: number;
148
+ a_grade: string;
149
+ b_grade: string;
150
+ moved: {
151
+ key: string;
152
+ label: string;
153
+ a: number;
154
+ b: number;
155
+ }[];
156
+ };
157
+ /** what changed in the page's JS between the two builds (both weighed): the files behind a JS move */
158
+ assets?: import("@ogygia/core/insights").AssetsDiff;
159
+ /** the browser's findings fixed and new (both reports with a visit), as `code (islands)` */
160
+ browser?: {
161
+ fixed: string[];
162
+ added: string[];
163
+ };
164
+ /** which islands changed file between the two builds (what a returning visitor downloads again) */
165
+ islands?: IslandFilesDiff;
166
+ /** the browser's vitals that moved (each report's own visit), and for TTFB / FCP / LCP the part
167
+ * of it that moved most — "LCP 1.2 s → 2.6 s: its download 300 → 1700 ms" */
168
+ vitals?: VitalMove$1[];
169
+ }
170
+ /**
171
+ * WHAT A DEPLOY COSTS A RETURNING VISITOR. Each island's file is named by its content, so between two
172
+ * profiles of one page an island either KEPT its file (a returning visitor's cache still serves it) or
173
+ * MOVED to a new one (downloaded again). Islands keyed by identity (`entry`), files by location
174
+ * (`module_url`); `bytes` weighs a moved island's new file when the second build weighed it.
175
+ * `all_moved`: every island moved — when their code did not all change, the build's names are not
176
+ * stable (SvelteKit bakes its version, the build time by default, into its client chunk: pin
177
+ * `kit.version.name`). Null when there is nothing to compare (a page without islands, or a build from
178
+ * before content hashing, where a file's name never moved).
179
+ */
180
+ export interface IslandFilesDiff {
181
+ moved: {
182
+ name: string;
183
+ bytes: number | null;
184
+ }[];
185
+ kept: number;
186
+ /** the moved files' bytes, where weighed */
187
+ bytes: number;
188
+ all_moved: boolean;
189
+ }
190
+ export declare function island_files_diff(a: readonly {
191
+ entry: string;
192
+ name: string;
193
+ module_url: string;
194
+ }[], b: readonly {
195
+ entry: string;
196
+ name: string;
197
+ module_url: string;
198
+ }[], weight?: (url: string) => number | undefined): IslandFilesDiff | null;
199
+ export interface DrillDelta {
200
+ /** the row's place in the render, top down: `load functions › +page.server.ts › GET api/x` */
201
+ path: string[];
202
+ kind: DrillNode["kind"];
203
+ at?: string;
204
+ a_ms: number;
205
+ b_ms: number;
206
+ d_ms: number;
207
+ /** that side had no row of its own, only an "N more" fold: its ms is the fold's, an upper bound */
208
+ upto?: "a" | "b";
209
+ /** a wait, a gap, or a line under one: clock time, never re-attributed by inlining */
210
+ waiting?: boolean;
211
+ /** `shifted`: the render took the same time on both sides, so a CPU row's move is V8 charging the
212
+ * time to another function (inlining differs run to run), not a change */
213
+ status: "fixed" | "new" | "better" | "worse" | "shifted";
214
+ }
215
+ /** One server for both (its start, from each report's time and the server's age then, within the
216
+ * length of a profile; and it had served more requests by the later one), and the earlier report
217
+ * saw each render keep 8 MB+ alive: the later one inherited the fuller heap. */
218
+ export declare function order_of(a: {
219
+ meta: ReportMeta;
220
+ patterns?: readonly Pattern[];
221
+ }, b: {
222
+ meta: ReportMeta;
223
+ patterns?: readonly Pattern[];
224
+ }): Comparison["order"];
225
+ /**
226
+ * THE DRILL-DOWNS SIDE BY SIDE: every row of one render's tree matched by its place (phase › load
227
+ * file › who › line), and the rows that moved — only the DEEPEST of a chain: a row whose moved
228
+ * children carry most (70 %) of its move is left out, so the list names the cause (the call, the
229
+ * line), not every level above it. Both trees are one render, so no per-render division.
230
+ */
231
+ export declare function drill_deltas(a: DrillNode | undefined, b: DrillNode | undefined): DrillDelta[];
232
+ export interface PatternDelta {
233
+ /** the title as the newer report words it (the older one's when it is gone) */
234
+ title: string;
235
+ kind: PatternKind;
236
+ /** measured cost on each side (CPU + GC, or waiting for a wait pattern), ms per render */
237
+ a_ms: number;
238
+ b_ms: number;
239
+ d_ms: number;
240
+ /** what fixing it was expected to save, each side, ms per render */
241
+ a_save: number;
242
+ b_save: number;
243
+ a_sites: number;
244
+ b_sites: number;
245
+ /** fixed: gone in b · new: only in b · better / worse: cost moved by a fifth and 2 ms or more · same */
246
+ status: "fixed" | "new" | "better" | "worse" | "same" | "shifted";
247
+ wait?: boolean;
248
+ }
249
+ export declare function pattern_deltas(a?: readonly Pattern[], b?: readonly Pattern[], ra?: number, rb?: number): PatternDelta[];
250
+ /**
251
+ * WHAT THE CHANGE PROMISED, COUNTED THE FORECAST'S WAY: the older report's forecast parts that got
252
+ * done — a part whose fix (or one it covers) went away counts whole, one that shrank by its share,
253
+ * work for unread data when the newer report no longer has it — added with the same overlap rules
254
+ * and caps as the forecast itself. The forecast was held to account against fixed pages; this is
255
+ * the same arithmetic for the fix you actually made.
256
+ */
257
+ /**
258
+ * HOW MUCH OF EACH OLDER PATTERN IS STILL THERE, BY ITS OWN SITES: the share of its cost on sites
259
+ * whose code the newer report still flags with the same kind (matched by the code, not the line
260
+ * number an edit above moves). "Waits in a row" in both reports can be two different sets of calls —
261
+ * the chains fixed, a new one between the batches — and its title alone would say it only shrank.
262
+ */
263
+ export declare function still_there(a: readonly Pattern[] | undefined, b: readonly Pattern[] | undefined): Map<string, number>;
264
+ /** The patterns a change fixed or shrank, their expected saving summed, against the median render's
265
+ * measured change. A saving well under the prediction usually moved somewhere (another line got
266
+ * slower) or drowned in run-to-run noise; well over it, the fix removed more than the pattern saw. */
267
+ export declare function fix_check_of(patterns: readonly PatternDelta[], a: ReportMeta, b: ReportMeta, fa?: Forecast, fb?: Forecast, left?: Map<string, number>): Comparison["fix_check"];
268
+ export interface PathDelta {
269
+ owner: string;
270
+ file: string;
271
+ line: number;
272
+ a_ms: number;
273
+ b_ms: number;
274
+ d_ms: number;
275
+ a_fns: string[];
276
+ b_fns: string[];
277
+ only?: "a" | "b";
278
+ }
279
+ export declare function compare_reports(a: {
280
+ meta: ReportMeta;
281
+ analysis: Analysis;
282
+ findings: string[];
283
+ gc?: GcAttribution;
284
+ patterns?: Pattern[];
285
+ ledger?: LedgerLine[];
286
+ drill?: DrillNode;
287
+ forecast?: Forecast;
288
+ }, b: {
289
+ meta: ReportMeta;
290
+ analysis: Analysis;
291
+ findings: string[];
292
+ gc?: GcAttribution;
293
+ patterns?: Pattern[];
294
+ ledger?: LedgerLine[];
295
+ drill?: DrillNode;
296
+ forecast?: Forecast;
297
+ }): Comparison;
298
+ /** Page-mode reports grouped by page, oldest first, with each run's median — the history line. */
299
+ export declare function page_history(metas: ReportMeta[], score_of?: (id: string) => number | undefined): {
300
+ page: string;
301
+ points: {
302
+ id: string;
303
+ created: number;
304
+ median: number;
305
+ score?: number;
306
+ }[];
307
+ }[];
308
+ export { type VitalMove, vitals_moved };