@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,385 @@
1
+ <script>/**
2
+ * THE WHOLE SITE — two pictures from the sink's rows (sink.ts), or this instance's own ring and
3
+ * sampler windows: the REQUEST CLOUD (every request as a dot: time across, duration up, colour
4
+ * by route; drag a range to get the hot functions of the sampler windows inside it) and the
5
+ * LAYER CAKE (per route, where the time went: CPU, waiting on calls, the rest). A `wake:'load'`
6
+ * island; `from` is a URL the rows are read from in this browser (the sink you own).
7
+ */
8
+ import { parse_sink, cloud_points, layer_cake, hot_for_range } from "../sink.js";
9
+ import { fmt_ms } from "./format.js";
10
+ let { base, rows: initial, from = null, sink = null, ephemeral = false } = $props();
11
+ // the rows and the URL field are seeds: a `from` URL is read once, at setup, and replaces the rows
12
+ // svelte-ignore state_referenced_locally
13
+ let rows = $state(initial);
14
+ // svelte-ignore state_referenced_locally
15
+ let loading = $state(!!from);
16
+ let load_error = $state("");
17
+ // svelte-ignore state_referenced_locally
18
+ let from_input = $state(from ?? sink?.url ?? "");
19
+ // svelte-ignore state_referenced_locally
20
+ if (from) {
21
+ void fetch(from, { credentials: "omit" }).then(async (r) => {
22
+ if (!r.ok) throw new Error(`${r.status}`);
23
+ rows = parse_sink(await r.text());
24
+ }).catch((e) => load_error = `Could not read ${from}: ${e instanceof Error ? e.message : e}. The URL must be readable by this browser (CORS).`).finally(() => loading = false);
25
+ }
26
+ const points = $derived(cloud_points(rows));
27
+ const cake = $derived(layer_cake(rows));
28
+ const traps = $derived(rows.filter((r) => r.k === "trap"));
29
+ const windows = $derived(rows.filter((r) => r.k === "win").length);
30
+ // the cloud
31
+ const W = 1e3;
32
+ const H = 260;
33
+ const L = 48;
34
+ const B = 22;
35
+ const t0 = $derived(points.length ? points[0].t : 0);
36
+ const t1 = $derived(points.length ? Math.max(points[points.length - 1].t, t0 + 1e3) : 1);
37
+ const max_ms = $derived(Math.max(...points.map((p) => p.ms), 10));
38
+ const x = (t) => L + (t - t0) / (t1 - t0) * (W - L - 8);
39
+ const y = (ms) => H - B - Math.log10(Math.max(ms, 1)) / Math.log10(max_ms) * (H - B - 12);
40
+ const routes = $derived([...new Set(points.map((p) => p.route))]);
41
+ const PALETTE = [
42
+ "#58a6ff",
43
+ "#3fb950",
44
+ "#d9a03d",
45
+ "#a371f7",
46
+ "#ff7b72",
47
+ "#79c0ff",
48
+ "#f0883e",
49
+ "#56d364",
50
+ "#ffd166",
51
+ "#8b949e"
52
+ ];
53
+ const color = (route) => PALETTE[routes.indexOf(route) % PALETTE.length];
54
+ const y_ticks = $derived([
55
+ 1,
56
+ 10,
57
+ 100,
58
+ 1e3,
59
+ 1e4
60
+ ].filter((v) => v <= max_ms * 1.5));
61
+ let brush = $state(null);
62
+ let drag = $state(null);
63
+ let svg_el = $state(null);
64
+ const t_at = (clientX) => {
65
+ if (!svg_el) return t0;
66
+ const r = svg_el.getBoundingClientRect();
67
+ const px = (clientX - r.left) / r.width * W;
68
+ return t0 + (px - L) / (W - L - 8) * (t1 - t0);
69
+ };
70
+ const range = $derived(brush ? {
71
+ lo: Math.min(brush.a, brush.b),
72
+ hi: Math.max(brush.a, brush.b)
73
+ } : null);
74
+ const selected = $derived(range ? points.filter((p) => p.t >= range.lo && p.t <= range.hi) : []);
75
+ const hot = $derived(range ? hot_for_range(rows, range.lo, range.hi) : []);
76
+ const p50 = (xs) => xs.length ? [...xs].sort((a, b) => a - b)[Math.floor(xs.length / 2)] : 0;
77
+ let hover = $state(null);
78
+ const when = (ms) => new Date(ms).toLocaleString();
79
+ const cake_max = $derived(Math.max(...cake.map((c) => c.total), 1));
80
+ </script>
81
+
82
+ <form
83
+ class="from"
84
+ onsubmit={(e) => {
85
+ e.preventDefault();
86
+ location.href = `${base}/site${from_input ? `?from=${encodeURIComponent(from_input)}` : ''}`;
87
+ }}
88
+ >
89
+ <label
90
+ >read rows from <input
91
+ type="url"
92
+ bind:value={from_input}
93
+ placeholder="https://…/profiler-rows.ndjson (a sink you serve)"
94
+ size="60"
95
+ /></label
96
+ >
97
+ <button class="btn" type="submit">load</button>
98
+ {#if sink}
99
+ <span class="hint"
100
+ >sink: <code>{sink.url}</code>{#if sink.last}
101
+ · last post {sink.last.ok ? 'ok' : 'failed'} ({sink.last.rows} rows{sink.last.error
102
+ ? `: ${sink.last.error}`
103
+ : ''}){/if}{#if sink.buffered}
104
+ · {sink.buffered} buffered{/if}</span
105
+ >
106
+ {/if}
107
+ </form>
108
+ {#if load_error}<p class="verdict">{load_error}</p>{/if}
109
+ {#if loading}
110
+ <p class="hint">Reading rows…</p>
111
+ {:else if !points.length}
112
+ <p class="hint">
113
+ No requests to draw yet.{#if ephemeral}
114
+ This instance is ephemeral: it forgets its requests within seconds. Configure <code
115
+ >profiler.sink</code
116
+ > so every instance posts its rows somewhere you serve, then load that URL above.{:else}
117
+ Requests to the site show up here as they happen; the always-on sampler's windows make the
118
+ brushed selection answer with hot functions.{/if}
119
+ </p>
120
+ {:else}
121
+ <h2>
122
+ The request cloud <span class="hint" style="font-weight:400"
123
+ >({points.length} requests · {routes.length} routes · {windows} sampler windows{traps.length
124
+ ? ` · ${traps.length} caught`
125
+ : ''} · from {when(t0)} to {when(t1)})</span
126
+ >
127
+ </h2>
128
+ <p class="hint">
129
+ Every request is a dot: time across, duration up (log scale), colour by route. Drag across a
130
+ range to select it; the hot functions of the sampler windows inside it appear below.
131
+ </p>
132
+ <!-- a picture to a reader; the brush is a pointer extra over it (no widget role fits a drag) -->
133
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
134
+ <svg
135
+ bind:this={svg_el}
136
+ viewBox="0 0 {W} {H}"
137
+ class="cloud"
138
+ role="img"
139
+ aria-label="every request as a dot"
140
+ onmousedown={(e) => {
141
+ drag = t_at(e.clientX);
142
+ brush = { a: drag, b: drag };
143
+ }}
144
+ onmousemove={(e) => {
145
+ if (drag !== null && brush) brush = { a: drag, b: t_at(e.clientX) };
146
+ }}
147
+ onmouseup={() => {
148
+ if (brush && Math.abs(brush.a - brush.b) < (t1 - t0) / 500) brush = null;
149
+ drag = null;
150
+ }}
151
+ onmouseleave={() => {
152
+ drag = null;
153
+ hover = null;
154
+ }}
155
+ >
156
+ {#each y_ticks as v (v)}
157
+ <line x1={L} x2={W - 8} y1={y(v)} y2={y(v)} class="grid" />
158
+ <text x={L - 4} y={y(v) + 3} class="tick" text-anchor="end">{v} ms</text>
159
+ {/each}
160
+ {#if brush}
161
+ <rect
162
+ x={x(Math.min(brush.a, brush.b))}
163
+ y="6"
164
+ width={Math.max(x(Math.max(brush.a, brush.b)) - x(Math.min(brush.a, brush.b)), 1)}
165
+ height={H - B - 6}
166
+ class="brush"
167
+ />
168
+ {/if}
169
+ {#each points as p, i (i)}
170
+ <circle
171
+ cx={x(p.t)}
172
+ cy={y(p.ms)}
173
+ r={p.status >= 500 ? 4 : 2.6}
174
+ fill={color(p.route)}
175
+ class="dot"
176
+ class:err={p.status >= 500}
177
+ role="presentation"
178
+ onmouseenter={(e) => (hover = { p, x: e.clientX, y: e.clientY })}
179
+ />
180
+ {/each}
181
+ {#each traps as t, i (i)}
182
+ {#if t.k === 'trap'}<text x={x(t.t)} y="12" class="trap">▼ caught</text>{/if}
183
+ {/each}
184
+ <text x={L} y={H - 6} class="tick">{when(t0)}</text>
185
+ <text x={W - 8} y={H - 6} class="tick" text-anchor="end">{when(t1)}</text>
186
+ </svg>
187
+ {#if hover}
188
+ <div class="tip" style="left:{hover.x + 12}px;top:{hover.y + 12}px">
189
+ <b>{hover.p.path}</b> · {fmt_ms(hover.p.ms)} ms · {hover.p.status}<br />{fmt_ms(hover.p.cpu)} ms
190
+ CPU · {fmt_ms(hover.p.wait)} ms waiting · {when(hover.p.t)}
191
+ </div>
192
+ {/if}
193
+ <div class="legend">
194
+ {#each routes.slice(0, 12) as r (r)}<span><i style="background:{color(r)}"></i>{r}</span>{/each}
195
+ </div>
196
+ {#if brush}
197
+ <div class="sel">
198
+ <p>
199
+ <b>{selected.length} requests selected</b> · p50 {fmt_ms(p50(selected.map((p) => p.ms)))} ms ·
200
+ max {fmt_ms(Math.max(...selected.map((p) => p.ms), 0))} ms · {[
201
+ ...new Set(selected.map((p) => p.route))
202
+ ]
203
+ .slice(0, 5)
204
+ .join(', ')} <button class="link" onclick={() => (brush = null)}>clear</button>
205
+ </p>
206
+ {#if hot.length}
207
+ <table>
208
+ <thead
209
+ ><tr
210
+ ><th>function</th><th>where</th><th class="num">self ms</th><th class="num"
211
+ >windows</th
212
+ ></tr
213
+ ></thead
214
+ >
215
+ <tbody>
216
+ {#each hot.slice(0, 20) as f, i (f.name + '\0' + f.file + '\0' + i)}
217
+ <tr
218
+ ><td class="fn"><b>{f.name}</b></td><td class="file"
219
+ >{f.file} <span class="hint">{f.category}</span></td
220
+ ><td class="num">{fmt_ms(f.self_ms)}</td><td class="num">{f.windows}</td></tr
221
+ >
222
+ {/each}
223
+ </tbody>
224
+ </table>
225
+ {:else}
226
+ <p class="hint">
227
+ No sampler window overlaps this range. Turn on <code>profiler.sample</code> so the site samples
228
+ itself; the windows then say what was hot when these requests ran.
229
+ </p>
230
+ {/if}
231
+ </div>
232
+ {/if}
233
+
234
+ <h2>
235
+ The layer cake <span class="hint" style="font-weight:400"
236
+ >(where the site's time goes, per route)</span
237
+ >
238
+ </h2>
239
+ <div class="cake">
240
+ {#each cake.slice(0, 25) as c (c.route)}
241
+ <div class="row">
242
+ <div class="name">
243
+ <b>{c.route}</b>
244
+ <span class="hint">{c.n} req · p50 {fmt_ms(c.p50)} · p95 {fmt_ms(c.p95)} ms</span>
245
+ </div>
246
+ <div
247
+ class="bar"
248
+ title="{fmt_ms(c.cpu)} ms CPU · {fmt_ms(c.wait)} ms waiting · {fmt_ms(c.rest)} ms other"
249
+ >
250
+ <i class="cpu" style="width:{(c.cpu / cake_max) * 100}%"></i><i
251
+ class="wait"
252
+ style="width:{(c.wait / cake_max) * 100}%"
253
+ ></i><i class="rest" style="width:{(c.rest / cake_max) * 100}%"></i>
254
+ </div>
255
+ <div class="tot">{fmt_ms(c.total)} ms</div>
256
+ </div>
257
+ {/each}
258
+ </div>
259
+ <div class="legend">
260
+ <span><i class="cpu"></i>CPU</span><span><i class="wait"></i>waiting on calls</span><span
261
+ ><i class="rest"></i>the rest (event loop, other requests, the platform)</span
262
+ >
263
+ </div>
264
+ <p class="hint">
265
+ Bars are the sum over the route's requests, so a route that is slow AND busy sits on top. A
266
+ route mostly “waiting” wants parallel calls or a cache; one mostly CPU wants a profile of one of
267
+ its pages; a wide “rest” means the instance was doing something else at the time.
268
+ </p>
269
+ {/if}
270
+
271
+ <style>
272
+ .from {
273
+ display: flex;
274
+ gap: 10px;
275
+ align-items: center;
276
+ flex-wrap: wrap;
277
+ margin-bottom: 8px;
278
+ }
279
+ .cloud {
280
+ width: 100%;
281
+ height: auto;
282
+ display: block;
283
+ background: var(--bg-sunken);
284
+ border: 1px solid var(--line);
285
+ border-radius: 6px;
286
+ cursor: crosshair;
287
+ user-select: none;
288
+ }
289
+ .grid {
290
+ stroke: #1c2129;
291
+ }
292
+ .tick {
293
+ fill: #6b7280;
294
+ font-size: 9px;
295
+ }
296
+ .dot {
297
+ opacity: 0.85;
298
+ }
299
+ .dot.err {
300
+ stroke: var(--bad);
301
+ stroke-width: 1.5;
302
+ }
303
+ .brush {
304
+ fill: rgba(88, 166, 255, 0.15);
305
+ stroke: var(--c-blue);
306
+ }
307
+ .trap {
308
+ fill: var(--bad);
309
+ font-size: 9px;
310
+ }
311
+ .legend {
312
+ display: flex;
313
+ flex-wrap: wrap;
314
+ gap: 6px 14px;
315
+ font-size: 12px;
316
+ margin: 6px 0;
317
+ }
318
+ .legend i,
319
+ .bar i {
320
+ display: inline-block;
321
+ width: 10px;
322
+ height: 10px;
323
+ border-radius: 2px;
324
+ margin-right: 4px;
325
+ vertical-align: middle;
326
+ }
327
+ .sel {
328
+ border: 1px solid var(--line);
329
+ border-radius: 6px;
330
+ padding: 8px 12px;
331
+ margin: 8px 0;
332
+ }
333
+ .link {
334
+ background: none;
335
+ border: 0;
336
+ color: inherit;
337
+ text-decoration: underline;
338
+ cursor: pointer;
339
+ font: inherit;
340
+ }
341
+ .cake .row {
342
+ display: grid;
343
+ grid-template-columns: 300px 1fr 80px;
344
+ gap: 10px;
345
+ align-items: center;
346
+ margin: 4px 0;
347
+ font-size: 13px;
348
+ }
349
+ .cake .bar {
350
+ height: 14px;
351
+ background: var(--bg-sunken);
352
+ border-radius: 3px;
353
+ overflow: hidden;
354
+ display: flex;
355
+ }
356
+ .cake .bar i {
357
+ height: 100%;
358
+ border-radius: 0;
359
+ margin: 0;
360
+ display: block;
361
+ }
362
+ .cpu {
363
+ background: var(--c-orange);
364
+ }
365
+ .wait {
366
+ background: var(--c-blue);
367
+ }
368
+ .rest {
369
+ background: #374151;
370
+ }
371
+ .tot {
372
+ text-align: right;
373
+ font-variant-numeric: tabular-nums;
374
+ }
375
+ .tip {
376
+ position: fixed;
377
+ z-index: 20;
378
+ background: var(--bg-raised);
379
+ border: 1px solid var(--line-strong);
380
+ border-radius: 6px;
381
+ padding: 6px 8px;
382
+ font-size: 12px;
383
+ pointer-events: none;
384
+ }
385
+ </style>
@@ -0,0 +1,27 @@
1
+ /**
2
+ * THE WHOLE SITE — two pictures from the sink's rows (sink.ts), or this instance's own ring and
3
+ * sampler windows: the REQUEST CLOUD (every request as a dot: time across, duration up, colour
4
+ * by route; drag a range to get the hot functions of the sampler windows inside it) and the
5
+ * LAYER CAKE (per route, where the time went: CPU, waiting on calls, the rest). A `wake:'load'`
6
+ * island; `from` is a URL the rows are read from in this browser (the sink you own).
7
+ */
8
+ import { type SinkRow } from '../sink.js';
9
+ type $$ComponentProps = {
10
+ base: string;
11
+ rows: SinkRow[];
12
+ from?: string | null;
13
+ sink?: {
14
+ url: string;
15
+ last: {
16
+ at: number;
17
+ ok: boolean;
18
+ rows: number;
19
+ error?: string;
20
+ } | null;
21
+ buffered: number;
22
+ } | null;
23
+ ephemeral?: boolean;
24
+ };
25
+ declare const SiteView: import("svelte").Component<$$ComponentProps, {}, "">;
26
+ type SiteView = ReturnType<typeof SiteView>;
27
+ export default SiteView;
@@ -0,0 +1,78 @@
1
+ <script>const KEY = "og-profiler-theme";
2
+ const NEXT = {
3
+ system: "light",
4
+ light: "dark",
5
+ dark: "system"
6
+ };
7
+ const ICON = {
8
+ system: "◐",
9
+ light: "☀",
10
+ dark: "☾"
11
+ };
12
+ let mode = $state("system");
13
+ if (typeof window !== "undefined") {
14
+ let saved_mode = "system";
15
+ try {
16
+ const saved = localStorage.getItem(KEY);
17
+ saved_mode = saved === "light" || saved === "dark" ? saved : "system";
18
+ } catch {}
19
+ mode = saved_mode;
20
+ apply(saved_mode);
21
+ }
22
+ function apply(m) {
23
+ const root = document.documentElement;
24
+ if (m === "system") root.removeAttribute("data-theme");
25
+ else root.setAttribute("data-theme", m);
26
+ try {
27
+ if (m === "system") localStorage.removeItem(KEY);
28
+ else localStorage.setItem(KEY, m);
29
+ } catch {}
30
+ }
31
+ function cycle() {
32
+ const next = NEXT[mode];
33
+ const flip = () => {
34
+ mode = next;
35
+ apply(next);
36
+ };
37
+ const doc = document;
38
+ const reduce = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
39
+ if (doc.startViewTransition && !reduce) doc.startViewTransition(flip);
40
+ else flip();
41
+ }
42
+ </script>
43
+
44
+ <button
45
+ class="theme"
46
+ onclick={cycle}
47
+ title="Theme: {mode} (click to change)"
48
+ aria-label="theme: {mode}"
49
+ >
50
+ <span class="ic" aria-hidden="true">{ICON[mode]}</span>
51
+ <span class="lbl">{mode}</span>
52
+ </button>
53
+
54
+ <style>
55
+ .theme {
56
+ display: inline-flex;
57
+ align-items: center;
58
+ gap: 8px;
59
+ width: 100%;
60
+ background: none;
61
+ border: 1px solid var(--line);
62
+ border-radius: var(--r-sm);
63
+ padding: 7px 10px;
64
+ color: var(--text-dim);
65
+ cursor: pointer;
66
+ font-size: 12.5px;
67
+ text-transform: capitalize;
68
+ }
69
+ .theme:hover {
70
+ border-color: var(--accent-line);
71
+ color: var(--text);
72
+ background: var(--bg-hover);
73
+ }
74
+ .ic {
75
+ font-size: 14px;
76
+ color: var(--accent);
77
+ }
78
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const ThemeSwitch: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ThemeSwitch = ReturnType<typeof ThemeSwitch>;
3
+ export default ThemeSwitch;