@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.
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 +105 -6
@@ -0,0 +1,276 @@
1
+ <script>import { PATTERN_LABEL } from "../patterns.js";
2
+ import { fmt_ms } from "./format.js";
3
+ let { root } = $props();
4
+ const KIND = {
5
+ render: "",
6
+ phase: "",
7
+ lane: "load file",
8
+ cpu: "CPU",
9
+ wait: "waiting",
10
+ gap: "nothing recorded",
11
+ line: "",
12
+ more: ""
13
+ };
14
+ </script>
15
+
16
+ {#snippet kind(n)}
17
+ {#if KIND[n.kind]}
18
+ <span class="dr-kind {n.kind}">
19
+ {KIND[n.kind]}{#if n.calls && n.calls > 1}
20
+ · {n.calls} {n.kind === 'cpu' ? 'renders' : 'calls'}{/if}
21
+ {#if n.runs}<span
22
+ title="Its lowest and highest over the profiled renders (for a wait: its calls' own time, which the network makes jitter): a difference smaller than this spread is noise"
23
+ >
24
+ · {n.kind === 'wait' ? 'calls took ' : ''}{fmt_ms(n.runs[0])}–{fmt_ms(n.runs[1])} ms over renders</span
25
+ >{/if}
26
+ {#if n.cold !== undefined}<span
27
+ class="dr-cold"
28
+ title="What it took in the first, cold render (loading and compiling its code, filling first-call caches): what a fresh serverless instance pays on its first request"
29
+ >
30
+ · {fmt_ms(n.cold)} ms cold</span
31
+ >{/if}
32
+ {#if n.alone === undefined}{:else if n.alone < 0.05}<span
33
+ title="Other calls were in flight the whole time: making this one faster saves nothing unless they get faster too"
34
+ >
35
+ · always beside other calls</span
36
+ >
37
+ {:else if n.alone < n.ms - 0.05}<span
38
+ title="Nothing else was in flight for this long: a faster answer saves at least this. The rest of its time it waited alongside other calls."
39
+ >
40
+ · {fmt_ms(n.alone)} ms alone</span
41
+ >
42
+ {:else}<span
43
+ title="Nothing else was in flight while it ran: every ms it gets faster, the render does too"
44
+ >
45
+ · alone</span
46
+ >{/if}
47
+ </span>
48
+ {/if}
49
+ {/snippet}
50
+
51
+ {#snippet fills(n)}
52
+ {#if n.fills}
53
+ {@const unread = n.fills.every((f) => !f.read)}
54
+ <!-- what the row was for: the page-data keys its lines feed; all unread = time for nothing -->
55
+ <span
56
+ class="dr-fills"
57
+ class:unread
58
+ title={unread
59
+ ? 'Every key this feeds is read by nothing on the page: this time buys nothing. Drop it from the load, or move it where it is used.'
60
+ : 'The page-data keys this feeds (from the load’s return)'}
61
+ >
62
+ <!-- read keys first; an unread one in a mixed list is marked, its share of the work is waste -->
63
+ → {#each [...n.fills]
64
+ .sort((a, b) => Number(b.read) - Number(a.read))
65
+ .slice(0, 5) as f, i (f.key)}{i ? ', ' : ''}<span
66
+ class:gone={!f.read && !unread}
67
+ title={f.read ? '' : 'nothing on the page reads this key'}>{f.key}</span
68
+ >{/each}{n.fills.length > 5 ? ` +${n.fills.length - 5}` : ''}{#if unread}&nbsp;· nothing
69
+ reads it{/if}
70
+ </span>
71
+ {/if}
72
+ {/snippet}
73
+
74
+ {#snippet split(n)}
75
+ {#if n.split}
76
+ {@const s = n.split}
77
+ {@const per = (ms) => fmt_ms(ms / s.calls)}
78
+ <!-- each call's own clock: what the other side claims, the rest until headers, the body -->
79
+ <span
80
+ class="dr-split"
81
+ title="Each call's own clock (averaged over its calls): until the answer's headers came, then reading the body. Their side is what the service's own Server-Timing header says it spent; the rest is the network, TLS, their framework or a queue in front of them."
82
+ >
83
+ {s.calls > 1 ? 'each call' : 'the call'}:
84
+ {#if s.theirs_ms !== undefined}
85
+ <b>{per(s.theirs_ms)} ms their side</b>{#if s.top}&nbsp;({s.top
86
+ .map((t) => `${t.name} ${per(t.ms)}`)
87
+ .join(', ')}){/if}{#if s.told && s.told < s.calls}
88
+ on {s.told} of {s.calls}{/if}
89
+ · {per(s.network_ms)} ms network &amp; the rest
90
+ {:else}
91
+ {per(s.network_ms)} ms until headers
92
+ {/if}
93
+ · {per(s.body_ms)} ms body
94
+ </span>
95
+ {/if}
96
+ {/snippet}
97
+
98
+ {#snippet row(n, depth)}
99
+ {@const pct = root.ms > 0 ? (n.ms / root.ms) * 100 : 0}
100
+ {#if n.children?.length}
101
+ <details class="d{depth}" open={depth < 1 && n.kind !== 'more'}>
102
+ <summary>
103
+ <span class="dr-bar"
104
+ ><i class="dr-fill {n.kind}" style="width:{Math.max(0.5, pct).toFixed(1)}%"></i></span
105
+ >
106
+ <span class="dr-ms">{fmt_ms(n.ms)} ms</span>
107
+ <span class="dr-lab">{n.label}</span>
108
+ {@render kind(n)}
109
+ {@render fills(n)}
110
+ {#if n.why}<span class="dr-why"
111
+ >{#each n.why as k (k)}<span title="a slow pattern the report found here"
112
+ >{PATTERN_LABEL[k] ?? k}</span
113
+ >{/each}</span
114
+ >{/if}
115
+ {#if n.at && n.kind !== 'line'}<code class="dr-at">{n.at}</code>{/if}
116
+ {@render split(n)}
117
+ </summary>
118
+ <div class="kids">
119
+ {#each n.children as c, i (c.label + '\0' + i)}{@render row(c, depth + 1)}{/each}
120
+ </div>
121
+ </details>
122
+ {:else}
123
+ <div class="leaf d{depth}">
124
+ <span class="dr-bar"
125
+ ><i class="dr-fill {n.kind}" style="width:{Math.max(0.5, pct).toFixed(1)}%"></i></span
126
+ >
127
+ <span class="dr-ms">{fmt_ms(n.ms)} ms</span>
128
+ {#if n.kind === 'line'}<code class="dr-lab">{n.at ?? n.label}</code>{:else}<span
129
+ class="dr-lab"
130
+ class:dim={n.kind === 'more'}>{n.label}</span
131
+ >{/if}
132
+ {@render kind(n)}
133
+ {@render fills(n)}
134
+ {#if n.why}<span class="dr-why"
135
+ >{#each n.why as k (k)}<span title="a slow pattern the report found here"
136
+ >{PATTERN_LABEL[k] ?? k}</span
137
+ >{/each}</span
138
+ >{/if}
139
+ {#if n.at && n.kind !== 'line'}<code class="dr-at">{n.at}</code>{/if}
140
+ {@render split(n)}
141
+ </div>
142
+ {/if}
143
+ {/snippet}
144
+
145
+ <div class="drill">
146
+ <p class="hint">
147
+ One {fmt_ms(root.ms)} ms render, split until it reaches a line: each part of the request, who held
148
+ its time (your function, a library, a call it waited on) and the line to open. A library's time goes
149
+ to your lines that call it; a wait, to the line that made the call; calls waiting at once share the
150
+ stretch. Every level adds up to the one above.
151
+ </p>
152
+ {#each root.children ?? [] as c, i (c.label + '\0' + i)}{@render row(c, 0)}{/each}
153
+ </div>
154
+
155
+ <style>
156
+ .drill {
157
+ font-size: 13px;
158
+ }
159
+ summary,
160
+ .leaf {
161
+ display: flex;
162
+ flex-wrap: wrap;
163
+ align-items: center;
164
+ gap: 10px;
165
+ padding: 3px 0;
166
+ }
167
+ summary {
168
+ cursor: pointer;
169
+ list-style: none;
170
+ }
171
+ summary::-webkit-details-marker {
172
+ display: none;
173
+ }
174
+ .kids {
175
+ margin-left: 18px;
176
+ border-left: 1px solid var(--line);
177
+ padding-left: 8px;
178
+ }
179
+ .dr-bar {
180
+ flex: 0 0 120px;
181
+ height: 10px;
182
+ background: var(--bg-hover);
183
+ border-radius: 3px;
184
+ overflow: hidden;
185
+ }
186
+ .dr-fill {
187
+ display: block;
188
+ height: 100%;
189
+ background: #4a9d6e;
190
+ }
191
+ .dr-fill.wait {
192
+ background: #3b82f6;
193
+ }
194
+ .dr-fill.gap,
195
+ .dr-fill.more {
196
+ background: var(--text-dim);
197
+ opacity: 0.45;
198
+ }
199
+ .dr-fill.phase {
200
+ background: var(--text);
201
+ opacity: 0.55;
202
+ }
203
+ .dr-ms {
204
+ flex: 0 0 64px;
205
+ font-variant-numeric: tabular-nums;
206
+ text-align: right;
207
+ }
208
+ .dr-lab {
209
+ flex: 1 1 auto;
210
+ min-width: 0;
211
+ overflow-wrap: anywhere;
212
+ }
213
+ /* a line is a chip that fits its text, not a second bar */
214
+ code.dr-lab {
215
+ flex: 0 1 auto;
216
+ margin-right: auto;
217
+ }
218
+ .d0 > summary .dr-lab {
219
+ font-weight: 600;
220
+ }
221
+ .dr-kind {
222
+ flex: 0 0 auto;
223
+ font-size: 11px;
224
+ color: var(--text-dim);
225
+ white-space: nowrap;
226
+ }
227
+ .dr-at {
228
+ flex: 0 1 auto;
229
+ font-size: 11.5px;
230
+ color: var(--text-dim);
231
+ white-space: nowrap;
232
+ overflow: hidden;
233
+ text-overflow: ellipsis;
234
+ }
235
+ .dim {
236
+ color: var(--text-dim);
237
+ }
238
+ .dr-cold {
239
+ color: #2563eb;
240
+ }
241
+ .dr-fills {
242
+ flex: 0 0 auto;
243
+ font-size: 11.5px;
244
+ color: var(--text-dim);
245
+ font-family: var(--mono, ui-monospace, monospace);
246
+ }
247
+ .dr-fills .gone {
248
+ color: #b45309;
249
+ text-decoration: line-through;
250
+ }
251
+ .dr-fills.unread {
252
+ color: #b45309;
253
+ font-weight: 600;
254
+ }
255
+ /* the call's own clock, on its own line under the row's label */
256
+ .dr-split {
257
+ flex-basis: 100%;
258
+ padding-left: 204px;
259
+ font-size: 11.5px;
260
+ color: var(--text-dim);
261
+ margin-top: -2px;
262
+ }
263
+ .dr-why {
264
+ flex: 0 0 auto;
265
+ display: flex;
266
+ gap: 4px;
267
+ }
268
+ .dr-why > span {
269
+ font-size: 10.5px;
270
+ padding: 0 6px;
271
+ border-radius: 999px;
272
+ background: color-mix(in srgb, #d97706 18%, transparent);
273
+ color: var(--text);
274
+ white-space: nowrap;
275
+ }
276
+ </style>
@@ -0,0 +1,12 @@
1
+ /**
2
+ * THE DRILL-DOWN (drill.ts): one render's time as a tree that adds up at every level — phase →
3
+ * who held it (a function, a library, a call) → the line. Static markup: `<details>` open the
4
+ * levels, no island. Each bar is its share of the whole render.
5
+ */
6
+ import type { DrillNode } from '../drill.js';
7
+ type $$ComponentProps = {
8
+ root: DrillNode;
9
+ };
10
+ declare const Drill: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type Drill = ReturnType<typeof Drill>;
12
+ export default Drill;
@@ -0,0 +1,59 @@
1
+ <script>/**
2
+ * Export the report as its encrypted `.ogp` — a `wake:'load'` island. With the bytes on the page
3
+ * (`ogpB64`, base64) they are decoded to a Blob and downloaded client-side, so it works even after
4
+ * the server evicts the report. A report shown from this browser's kept copy has no bytes (only a
5
+ * server holds the key): the kept copy rides to whichever instance answers, and it encrypts it
6
+ * (report-request.ts). No key in the browser either way.
7
+ */
8
+ import { page_ogp, report_request } from "./report-request.js";
9
+ let { id, ogpB64 = "", base = "" } = $props();
10
+ let busy = $state(false);
11
+ let error = $state("");
12
+ function save(blob) {
13
+ const a = document.createElement("a");
14
+ a.href = URL.createObjectURL(blob);
15
+ a.download = `profile-${id}.ogp`;
16
+ document.body.appendChild(a);
17
+ a.click();
18
+ a.remove();
19
+ setTimeout(() => URL.revokeObjectURL(a.href), 1500);
20
+ }
21
+ async function download() {
22
+ // the bytes: a prop when given one, else the page's one copy (report-request.ts page_ogp)
23
+ const b64 = ogpB64 || page_ogp();
24
+ if (b64) {
25
+ const bin = atob(b64);
26
+ const arr = new Uint8Array(bin.length);
27
+ for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
28
+ save(new Blob([arr], { type: "application/octet-stream" }));
29
+ return;
30
+ }
31
+ if (busy) return;
32
+ busy = true;
33
+ error = "";
34
+ try {
35
+ // no bytes on the page: the kept copy goes along (ogpB64 empty, so report_request sends it)
36
+ const res = await report_request(base, id, "ogp");
37
+ if (!res.ok) throw new Error((await res.text()).slice(0, 200) || String(res.status));
38
+ save(await res.blob());
39
+ } catch (err) {
40
+ error = err instanceof Error ? err.message : "export failed";
41
+ } finally {
42
+ busy = false;
43
+ }
44
+ }
45
+ </script>
46
+
47
+ <button type="button" class="btn primary" onclick={download} aria-busy={busy}>
48
+ <svg
49
+ class="ic"
50
+ viewBox="0 0 24 24"
51
+ fill="none"
52
+ stroke="currentColor"
53
+ stroke-width="2"
54
+ stroke-linecap="round"
55
+ stroke-linejoin="round"><path d="M12 3v13m0 0l-4.5-4.5M12 16l4.5-4.5M4 21h16" /></svg
56
+ >
57
+ {busy ? 'Encrypting…' : 'Export'}<span class="sub">.ogp · encrypted</span>
58
+ </button>
59
+ {#if error}<span class="hint warn" role="alert">Export failed: {error}</span>{/if}
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ id: string;
3
+ ogpB64?: string;
4
+ base?: string;
5
+ };
6
+ declare const ExportButton: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type ExportButton = ReturnType<typeof ExportButton>;
8
+ export default ExportButton;
@@ -0,0 +1,222 @@
1
+ <script>import { CATEGORY_COLOR } from "./format.js";
2
+ let { flame } = $props();
3
+ const ROW = 20;
4
+ let canvas;
5
+ // the zoom starts at the root it was given; clicks own it after
6
+ // svelte-ignore state_referenced_locally
7
+ let zoom = $state(flame);
8
+ let zstack = [];
9
+ let resizeTick = $state(0);
10
+ let tip = $state(null);
11
+ let rects = [];
12
+ // SEARCH: bars whose name or file contains the query stay lit, everything else dims; the count
13
+ // and the summed time tell how much of the graph the match covers; Enter zooms to the heaviest hit.
14
+ let query = $state("");
15
+ const q = $derived(query.trim().toLowerCase());
16
+ const matches = $derived.by(() => {
17
+ if (!q) return {
18
+ count: 0,
19
+ ms: 0,
20
+ best: null
21
+ };
22
+ let count = 0, ms = 0, best = null;
23
+ const walk = (n, inside) => {
24
+ const hit = n.n.toLowerCase().includes(q) || n.f.toLowerCase().includes(q);
25
+ if (hit) {
26
+ count++;
27
+ if (!best || n.t > best.t) best = n;
28
+ }
29
+ // time counts once per matching subtree (a match inside a match is already covered)
30
+ if (hit && !inside) ms += n.t;
31
+ for (const c of n.ch ?? []) walk(c, inside || hit);
32
+ };
33
+ walk(flame, false);
34
+ return {
35
+ count,
36
+ ms,
37
+ best
38
+ };
39
+ });
40
+ const is_match = (n) => !!q && (n.n.toLowerCase().includes(q) || n.f.toLowerCase().includes(q));
41
+ function zoom_to_match() {
42
+ const best = matches.best;
43
+ if (!best || best === zoom) return;
44
+ zstack.push(zoom);
45
+ zoom = best;
46
+ }
47
+ function depth(n) {
48
+ let d = 1;
49
+ for (const c of n.ch ?? []) d = Math.max(d, 1 + depth(c));
50
+ return d;
51
+ }
52
+ function layout() {
53
+ if (!canvas) return;
54
+ const ctx = canvas.getContext("2d");
55
+ const dpr = window.devicePixelRatio || 1;
56
+ const w = canvas.clientWidth || 900;
57
+ const h = Math.min(600, Math.max(260, depth(zoom) * ROW + 10));
58
+ canvas.style.height = h + "px";
59
+ canvas.width = w * dpr;
60
+ canvas.height = h * dpr;
61
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
62
+ ctx.clearRect(0, 0, w, h);
63
+ rects = [];
64
+ const draw = (node, d, x, width) => {
65
+ if (width < .5) return;
66
+ const y = d * ROW;
67
+ ctx.fillStyle = CATEGORY_COLOR[node.c] || "#6b7280";
68
+ ctx.globalAlpha = q ? is_match(node) ? 1 : .22 : 1;
69
+ ctx.beginPath();
70
+ if (ctx.roundRect) ctx.roundRect(x + .5, y + 1, Math.max(width - 1, 1), ROW - 2, 2);
71
+ else ctx.rect(x + .5, y + 1, Math.max(width - 1, 1), ROW - 2);
72
+ ctx.fill();
73
+ if (width > 30) {
74
+ ctx.fillStyle = "#0d1014";
75
+ ctx.font = "11px ui-monospace, monospace";
76
+ ctx.save();
77
+ ctx.beginPath();
78
+ ctx.rect(x + 4, y, width - 8, ROW);
79
+ ctx.clip();
80
+ ctx.fillText(node.n + " (" + node.t.toFixed(1) + "ms)", x + 5, y + 14);
81
+ ctx.restore();
82
+ }
83
+ ctx.globalAlpha = 1;
84
+ rects.push({
85
+ x,
86
+ y,
87
+ w: width,
88
+ h: ROW,
89
+ node
90
+ });
91
+ let cx = x;
92
+ const scale = node.t > 0 ? width / node.t : 0;
93
+ for (const c of node.ch ?? []) {
94
+ const cw = c.t * scale;
95
+ draw(c, d + 1, cx, cw);
96
+ cx += cw;
97
+ }
98
+ };
99
+ draw(zoom, 0, 0, w);
100
+ }
101
+ // lay out again when the zoom, the size or the search changes: the effect reads all three
102
+ $effect(() => {
103
+ void zoom;
104
+ void resizeTick;
105
+ void q;
106
+ layout();
107
+ });
108
+ $effect(() => {
109
+ const on = () => resizeTick++;
110
+ window.addEventListener("resize", on);
111
+ return () => window.removeEventListener("resize", on);
112
+ });
113
+ function hit(ev) {
114
+ const r = canvas.getBoundingClientRect();
115
+ const mx = ev.clientX - r.left, my = ev.clientY - r.top;
116
+ for (let i = rects.length - 1; i >= 0; i--) {
117
+ const q = rects[i];
118
+ if (mx >= q.x && mx <= q.x + q.w && my >= q.y && my <= q.y + q.h) return q;
119
+ }
120
+ return null;
121
+ }
122
+ function onMove(ev) {
123
+ const q = hit(ev);
124
+ tip = q ? {
125
+ node: q.node,
126
+ x: Math.min(ev.clientX + 14, window.innerWidth - 360),
127
+ y: ev.clientY + 14
128
+ } : null;
129
+ }
130
+ function onClick(ev) {
131
+ const q = hit(ev);
132
+ if (!q) return;
133
+ if (q.node === zoom) zoom = zstack.pop() || flame;
134
+ else {
135
+ zstack.push(zoom);
136
+ zoom = q.node;
137
+ }
138
+ }
139
+ </script>
140
+
141
+ <div class="tools">
142
+ <input
143
+ type="search"
144
+ placeholder="find a function or file in the graph…"
145
+ bind:value={query}
146
+ onkeydown={(e) => e.key === 'Enter' && zoom_to_match()}
147
+ aria-label="search the flame graph"
148
+ />
149
+ {#if q}
150
+ <span class="hint">
151
+ {#if matches.count}
152
+ {matches.count} bar{matches.count === 1 ? '' : 's'} · {matches.ms.toFixed(1)} ms · Enter zooms
153
+ to the heaviest
154
+ {:else}no match{/if}
155
+ </span>
156
+ {/if}
157
+ <span class="crumb">
158
+ {#if zoom !== flame}zoomed: {zoom.n} — click the top bar to go back{/if}
159
+ </span>
160
+ </div>
161
+ <canvas
162
+ class="flame"
163
+ bind:this={canvas}
164
+ onmousemove={onMove}
165
+ onmouseleave={() => (tip = null)}
166
+ onclick={onClick}
167
+ ></canvas>
168
+ {#if tip}
169
+ <div class="flame-tip" style="display:block;left:{tip.x}px;top:{tip.y}px">
170
+ <b>{tip.node.n}</b><br />total {tip.node.t.toFixed(2)} ms · self {tip.node.s.toFixed(2)} ms
171
+ {#if tip.node.f}<br /><span style="color:var(--text-faint)">{tip.node.f}</span>{/if}
172
+ </div>
173
+ {/if}
174
+
175
+ <style>
176
+ .tools {
177
+ display: flex;
178
+ gap: 12px;
179
+ align-items: center;
180
+ flex-wrap: wrap;
181
+ margin: 6px 0 8px;
182
+ }
183
+ .tools input {
184
+ font: inherit;
185
+ font-size: 13px;
186
+ background: var(--bg-raised);
187
+ color: var(--text);
188
+ border: 1px solid var(--line);
189
+ border-radius: 6px;
190
+ padding: 5px 10px;
191
+ min-width: 280px;
192
+ }
193
+ .tools .hint,
194
+ .tools .crumb {
195
+ color: var(--text-faint);
196
+ font-size: 12px;
197
+ }
198
+ .flame {
199
+ width: 100%;
200
+ height: 460px;
201
+ border: 1px solid var(--line);
202
+ border-radius: 8px;
203
+ background: var(--bg-sunken);
204
+ cursor: pointer;
205
+ }
206
+ .flame-tip {
207
+ position: fixed;
208
+ pointer-events: none;
209
+ background: var(--bg-hover);
210
+ border: 1px solid var(--line);
211
+ border-radius: 6px;
212
+ padding: 6px 10px;
213
+ font-size: 12px;
214
+ display: none;
215
+ max-width: 480px;
216
+ z-index: 10;
217
+ box-shadow: 0 4px 16px #0008;
218
+ }
219
+ .flame-tip b {
220
+ font-family: ui-monospace, monospace;
221
+ }
222
+ </style>
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Flame graph — a `wake:'load'` canvas island (was FLAME_JS). Width = time; click a bar to zoom,
3
+ * click the top bar to zoom back out; hover for detail. Zoom + tooltip are `$state`; the canvas
4
+ * redraws in an `$effect` on zoom/resize. `rects` (hit regions) is plain, rebuilt each draw.
5
+ */
6
+ import type { FlameNode } from '../analyze.js';
7
+ type $$ComponentProps = {
8
+ flame: FlameNode;
9
+ };
10
+ declare const Flame: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type Flame = ReturnType<typeof Flame>;
12
+ export default Flame;