sparkforensics-server 0.5.2 → 0.8.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 (445) hide show
  1. package/index.js +16 -7
  2. package/package.json +1 -1
  3. package/public/assets/AutoscalingChurn-DenWKTv-.js +1 -0
  4. package/public/assets/CacheUtilization-DLz7NdjV.js +1 -0
  5. package/public/assets/CachingOpportunity-zpQIqhzm.js +1 -0
  6. package/public/assets/ChartTheme-DAiARbLP.js +8 -0
  7. package/public/assets/ColdStart-C-9HjxWl.js +1 -0
  8. package/public/assets/ConfigAudit-D0V9tcrJ.js +1 -0
  9. package/public/assets/CoreUsageArea-yeerrljd.js +1 -0
  10. package/public/assets/Dashboard-DkUPrHKV.js +1 -0
  11. package/public/assets/DisclosureContext-DK1_iz6J.js +1 -0
  12. package/public/assets/DuplicatePlanSubtree-xDNDbCd3.js +1 -0
  13. package/public/assets/DurationHistogram-B6jsE0Ov.js +1 -0
  14. package/public/assets/EmptyState-C1uL40VU.js +1 -0
  15. package/public/assets/ExecutorUtilization-DXDZKmSc.js +1 -0
  16. package/public/assets/GcPressure-MRf7I4IG.js +1 -0
  17. package/public/assets/ImpactEstimate-Dm3xWbzV.js +1 -0
  18. package/public/assets/IncompleteRun-CaLOhMtf.js +1 -0
  19. package/public/assets/JobFailures-CzmzlvhO.js +1 -0
  20. package/public/assets/MemoryUtilization-AWUMUjCH.js +1 -0
  21. package/public/assets/NestedLoopJoin-C9sNr6Ss.js +1 -0
  22. package/public/assets/OverBroadcast-BOmRE6ca.js +1 -0
  23. package/public/assets/PartitionHint-BL2EmJfE.js +1 -0
  24. package/public/assets/PartitionSizing-BzBySvUq.js +1 -0
  25. package/public/assets/PlanExplorer-DdiQRcJU.js +1 -0
  26. package/public/assets/PlanGraphRoute-CIH9gBuR.js +1 -0
  27. package/public/assets/PlanView-B1hxzFRs.js +2 -0
  28. package/public/assets/PythonUdf-DvgCCueD.js +1 -0
  29. package/public/assets/RetryWaste-uXXu_CyM.js +1 -0
  30. package/public/assets/RowPagination-C-LzWkI5.js +1 -0
  31. package/public/assets/RowStatusCluster-Dbtmte7l.js +1 -0
  32. package/public/assets/ShuffleIO-nALwzC1_.js +1 -0
  33. package/public/assets/Skew-CHMEKgvk.js +1 -0
  34. package/public/assets/SlowHost-yyvjpHk6.js +1 -0
  35. package/public/assets/SmallFiles-y6odJ97n.js +1 -0
  36. package/public/assets/SpeculationWaste-C-SD2LOt.js +1 -0
  37. package/public/assets/Spill-DHTMmOup.js +1 -0
  38. package/public/assets/StageFailed-CQ3DxC9R.js +1 -0
  39. package/public/assets/StageFindingGroup-BgYLyj5q.js +1 -0
  40. package/public/assets/StageHeader-CIETkjVZ.js +1 -0
  41. package/public/assets/StagePill--P7PDd3s.js +1 -0
  42. package/public/assets/StageShape-BIv8vx_o.js +1 -0
  43. package/public/assets/StageSlowness-MMqzERxN.js +1 -0
  44. package/public/assets/Straggler-BgrY7AyW.js +1 -0
  45. package/public/assets/TaskFailures-BHHyKYTs.js +1 -0
  46. package/public/assets/TinyTask-DDScNhCl.js +1 -0
  47. package/public/assets/UnderBroadcast-CGIC04TI.js +1 -0
  48. package/public/assets/WidgetLeadSummary-CXFZP00C.js +1 -0
  49. package/public/assets/button-DbpTz2yk.js +1 -0
  50. package/public/assets/charts-vendor-CtTxwTeu.js +59 -0
  51. package/public/assets/composite-C9Nu2EI9.js +1 -0
  52. package/public/assets/d3-vendor-DNlD2-cz.js +1 -0
  53. package/public/assets/detector-registry-Bp8hrLua.js +2 -0
  54. package/public/assets/downsample-WmNM1qc9.js +1 -0
  55. package/public/assets/finding-anchor-Dr40LkS9.js +1 -0
  56. package/public/assets/findings-of-type-BL2u3RGZ.js +1 -0
  57. package/public/assets/impact-sort-mC5ISgX8.js +1 -0
  58. package/public/assets/inbox-KPU1Gppm.js +1 -0
  59. package/public/assets/index-CaRVPvAT.css +2 -0
  60. package/public/assets/index-fDiwfuoO.js +14 -0
  61. package/public/assets/inertValue-L9VxV0VE.js +1 -0
  62. package/public/assets/instrument-sans-latin-ext-wdth-normal-C5E2Gvlv.woff2 +0 -0
  63. package/public/assets/instrument-sans-latin-wdth-normal-BVScPF0l.woff2 +0 -0
  64. package/public/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  65. package/public/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  66. package/public/assets/layout.worker-CLcBHxVp.js +3 -0
  67. package/public/assets/{parser-worker-CA5RxqKc.js → parser-worker-CRIOPMOW.js} +8 -8
  68. package/public/assets/plan-graph-vendor-Ce6d4HU6.js +7 -0
  69. package/public/assets/react-vendor-C6KcUD36.js +9 -0
  70. package/public/assets/select-BHqOqun5.js +1 -0
  71. package/public/assets/toggle-DPeN6aNa.js +1 -0
  72. package/public/assets/toggle-group-Kl3uyHSr.js +1 -0
  73. package/public/assets/usePagedRows-BafD6bEN.js +1 -0
  74. package/public/assets/useRecentFiles-DzJZAoOT.js +3 -0
  75. package/public/assets/useRegisterFieldControl-pQv-ZDoU.js +1 -0
  76. package/public/assets/useScrollLock-BCxn_dMp.js +1 -0
  77. package/public/assets/useSortMode-DjrAN-z4.js +1 -0
  78. package/public/docs/404.html +3 -7
  79. package/public/docs/assets/{app.eYTPqTeq.js → app.1gvzd5Zb.js} +1 -1
  80. package/public/docs/assets/chunks/@localSearchIndexroot.DS2TQP5_.js +1 -0
  81. package/public/docs/assets/chunks/{VPLocalSearchBox.DsZrQKQR.js → VPLocalSearchBox.CNxT3s0I.js} +1 -1
  82. package/public/docs/assets/chunks/{theme.DsTNE2pE.js → theme.C5lLqUv7.js} +2 -2
  83. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.js +1 -0
  84. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.lean.js +1 -0
  85. package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.js} +1 -1
  86. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.js +1 -0
  87. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.lean.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CHHgywoP.js +1 -0
  90. package/public/docs/assets/{contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js → contributor-guide_architecture_detector-contract.md.CHHgywoP.lean.js} +1 -1
  91. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.js +1 -0
  92. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.lean.js +1 -0
  93. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.js +1 -0
  94. package/public/docs/assets/{contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.lean.js → contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.lean.js} +1 -1
  95. package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.js} +1 -1
  96. package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.lean.js} +1 -1
  97. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.js +1 -0
  98. package/public/docs/assets/{contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js → contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.lean.js} +1 -1
  99. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.K71YB-Rr.js +1 -0
  100. package/public/docs/assets/{contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js → contributor-guide_architecture_impact-estimation.md.K71YB-Rr.lean.js} +1 -1
  101. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.js +1 -0
  102. package/public/docs/assets/{contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js → contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.lean.js} +1 -1
  103. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.js +1 -0
  104. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.lean.js +1 -0
  105. package/public/docs/assets/contributor-guide_architecture_overview.md.nxkpOhgf.js +1 -0
  106. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.js +1 -0
  107. package/public/docs/assets/{contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js → contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.lean.js} +1 -1
  108. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.js +1 -0
  109. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.js +1 -0
  110. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.lean.js +1 -0
  111. package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.js} +1 -1
  112. package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.js} +1 -1
  113. package/public/docs/assets/{contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js → contributor-guide_architecture_worker-protocol.md.Br6OE7RW.js} +1 -1
  114. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.Br6OE7RW.lean.js +1 -0
  115. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.js +1 -0
  116. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.lean.js → contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.lean.js} +1 -1
  117. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.js} +1 -1
  118. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.js} +2 -2
  119. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.lean.js} +1 -1
  120. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.js +1 -0
  121. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js → contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.lean.js} +1 -1
  122. package/public/docs/assets/contributor-guide_contributing.md.DBW4drtZ.js +1 -0
  123. package/public/docs/assets/{contributor-guide_development-setup.md.D9bu4j_c.js → contributor-guide_development-setup.md.B21ZTDhn.js} +3 -3
  124. package/public/docs/assets/contributor-guide_development-setup.md.B21ZTDhn.lean.js +1 -0
  125. package/public/docs/assets/contributor-guide_testing.md.DpkozNoG.js +4 -0
  126. package/public/docs/assets/{contributor-guide_testing.md.By6XGR9a.lean.js → contributor-guide_testing.md.DpkozNoG.lean.js} +1 -1
  127. package/public/docs/assets/{index.md.CHJVslga.js → index.md.lMm7SUMz.js} +1 -1
  128. package/public/docs/assets/{index.md.CHJVslga.lean.js → index.md.lMm7SUMz.lean.js} +1 -1
  129. package/public/docs/assets/instrument-sans-latin-ext-wdth-normal.C5E2Gvlv.woff2 +0 -0
  130. package/public/docs/assets/instrument-sans-latin-wdth-normal.BVScPF0l.woff2 +0 -0
  131. package/public/docs/assets/jetbrains-mono-cyrillic-wght-normal.D73BlboJ.woff2 +0 -0
  132. package/public/docs/assets/jetbrains-mono-greek-wght-normal.Bw9x6K1M.woff2 +0 -0
  133. package/public/docs/assets/jetbrains-mono-latin-ext-wght-normal.DBQx-q_a.woff2 +0 -0
  134. package/public/docs/assets/jetbrains-mono-latin-wght-normal.B9CIFXIH.woff2 +0 -0
  135. package/public/docs/assets/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2 +0 -0
  136. package/public/docs/assets/style.Cfxqlj0y.css +1 -0
  137. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.C0pcQoNc.js +29 -0
  138. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.C0pcQoNc.lean.js +1 -0
  139. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.js +1 -0
  140. package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.js} +2 -2
  141. package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.lean.js} +1 -1
  142. package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.js → user-guide_mcp-tools.md.49r3L2YE.js} +1 -1
  143. package/public/docs/assets/{user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js → user-guide_mcp-tools_compare-runs.md.BQ_kIptC.js} +11 -6
  144. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.BQ_kIptC.lean.js +1 -0
  145. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.azCZXkH8.js +44 -0
  146. package/public/docs/assets/{user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js → user-guide_mcp-tools_diagnose-run.md.azCZXkH8.lean.js} +1 -1
  147. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.js} +2 -2
  148. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.lean.js} +1 -1
  149. package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.js} +2 -1
  150. package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.lean.js} +1 -1
  151. package/public/docs/assets/{user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js → user-guide_mcp-tools_get-run-summary.md.EhaIsele.js} +1 -1
  152. package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.js +1 -0
  153. package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.lean.js +1 -0
  154. package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.js +1 -0
  155. package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.lean.js +1 -0
  156. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +6 -10
  157. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +6 -10
  158. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +6 -10
  159. package/public/docs/contributor-guide/architecture/board-widgets.html +6 -10
  160. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +6 -10
  161. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +6 -10
  162. package/public/docs/contributor-guide/architecture/detector-contract.html +6 -10
  163. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +6 -10
  164. package/public/docs/contributor-guide/architecture/drill-down.html +6 -10
  165. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +6 -10
  166. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +6 -10
  167. package/public/docs/contributor-guide/architecture/impact-estimation.html +6 -10
  168. package/public/docs/contributor-guide/architecture/index.html +5 -9
  169. package/public/docs/contributor-guide/architecture/overview.html +6 -10
  170. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +5 -9
  171. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +6 -10
  172. package/public/docs/contributor-guide/architecture/state-and-history.html +5 -9
  173. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +6 -10
  174. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +5 -9
  175. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +6 -10
  176. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +5 -9
  177. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +6 -10
  178. package/public/docs/contributor-guide/architecture/widget-rendering.html +6 -10
  179. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +6 -10
  180. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +6 -10
  181. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +7 -11
  182. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +6 -10
  183. package/public/docs/contributor-guide/architecture/worker-protocol.html +6 -10
  184. package/public/docs/contributor-guide/contributing.html +6 -10
  185. package/public/docs/contributor-guide/development-setup.html +8 -12
  186. package/public/docs/contributor-guide/testing.html +9 -10
  187. package/public/docs/favicon.svg +2 -2
  188. package/public/docs/hashmap.json +1 -1
  189. package/public/docs/index.html +6 -10
  190. package/public/docs/logo-dark.svg +1 -0
  191. package/public/docs/logo-light.svg +1 -0
  192. package/public/docs/tuning-reference/anti-patterns.html +5 -9
  193. package/public/docs/tuning-reference/aqe.html +5 -9
  194. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +5 -9
  195. package/public/docs/tuning-reference/bottleneck-cold-start.html +5 -9
  196. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +5 -9
  197. package/public/docs/tuning-reference/bottleneck-failures.html +5 -9
  198. package/public/docs/tuning-reference/bottleneck-gc.html +5 -9
  199. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +5 -9
  200. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +5 -9
  201. package/public/docs/tuning-reference/bottleneck-retry-waste.html +5 -9
  202. package/public/docs/tuning-reference/bottleneck-shuffle.html +5 -9
  203. package/public/docs/tuning-reference/bottleneck-skew.html +5 -9
  204. package/public/docs/tuning-reference/bottleneck-slow-host.html +5 -9
  205. package/public/docs/tuning-reference/bottleneck-small-files.html +5 -9
  206. package/public/docs/tuning-reference/bottleneck-spill.html +5 -9
  207. package/public/docs/tuning-reference/bottleneck-straggler.html +5 -9
  208. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +5 -9
  209. package/public/docs/tuning-reference/bottleneck-utilization.html +5 -9
  210. package/public/docs/tuning-reference/caching.html +5 -9
  211. package/public/docs/tuning-reference/cluster-config.html +5 -9
  212. package/public/docs/tuning-reference/config.html +5 -9
  213. package/public/docs/tuning-reference/data-formats.html +5 -9
  214. package/public/docs/tuning-reference/index.html +5 -9
  215. package/public/docs/tuning-reference/intro.html +5 -9
  216. package/public/docs/tuning-reference/joins.html +5 -9
  217. package/public/docs/tuning-reference/memory-model.html +5 -9
  218. package/public/docs/tuning-reference/metrics.html +5 -9
  219. package/public/docs/tuning-reference/partitioning.html +5 -9
  220. package/public/docs/tuning-reference/pyspark.html +5 -9
  221. package/public/docs/tuning-reference/shuffle.html +5 -9
  222. package/public/docs/tuning-reference/spark-architecture.html +5 -9
  223. package/public/docs/tuning-reference/table-formats.html +5 -9
  224. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +5 -9
  225. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +5 -9
  226. package/public/docs/user-guide/alternative-log-retrieval.html +5 -9
  227. package/public/docs/user-guide/getting-started/ci-and-automation.html +17 -15
  228. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +6 -10
  229. package/public/docs/user-guide/getting-started/regression-metric-keys.html +5 -9
  230. package/public/docs/user-guide/getting-started/tuning-thresholds.html +7 -11
  231. package/public/docs/user-guide/getting-started.html +5 -9
  232. package/public/docs/user-guide/mcp-tools/compare-runs.html +16 -15
  233. package/public/docs/user-guide/mcp-tools/diagnose-run.html +28 -11
  234. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +6 -10
  235. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +5 -9
  236. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +7 -10
  237. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +5 -9
  238. package/public/docs/user-guide/mcp-tools/get-run-summary.html +6 -10
  239. package/public/docs/user-guide/mcp-tools/list-runs.html +5 -9
  240. package/public/docs/user-guide/mcp-tools.html +6 -10
  241. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +5 -9
  242. package/public/docs/user-guide/run-comparison.html +6 -10
  243. package/public/docs/user-guide/understanding-findings.html +6 -10
  244. package/public/export-template.html +46 -46
  245. package/public/favicon.svg +2 -2
  246. package/public/index.html +8 -10
  247. package/public/sample-runs/sample-run.ndjson.gz +0 -0
  248. package/vendor-core/allocation.js +126 -39
  249. package/vendor-core/analyzer.js +85 -58
  250. package/vendor-core/api-merge-writes.js +3 -0
  251. package/vendor-core/aqe-skew.js +347 -0
  252. package/vendor-core/cli/budgets.js +6 -4
  253. package/vendor-core/cli/collect-run-pair.js +89 -0
  254. package/vendor-core/cli/collect-run-worker.js +16 -0
  255. package/vendor-core/cli/collect-run.js +1 -2
  256. package/vendor-core/cli/native-zstd.js +12 -16
  257. package/vendor-core/comparability.js +234 -0
  258. package/vendor-core/comparison-output.js +15 -7
  259. package/vendor-core/comparison-verdict.js +105 -25
  260. package/vendor-core/core-source-hash.txt +1 -1
  261. package/vendor-core/core-time-series.js +0 -1
  262. package/vendor-core/core-usage-locality.js +1 -1
  263. package/vendor-core/delta-log-paths.js +10 -1
  264. package/vendor-core/detectors.js +1078 -416
  265. package/vendor-core/docs-config.js +0 -4
  266. package/vendor-core/docs-content/detection/cache.md +2 -1
  267. package/vendor-core/docs-content/detection/cfg.md +19 -11
  268. package/vendor-core/docs-content/detection/cold.md +2 -1
  269. package/vendor-core/docs-content/detection/gc.md +7 -3
  270. package/vendor-core/docs-content/detection/host.md +11 -1
  271. package/vendor-core/docs-content/detection/mem.md +17 -6
  272. package/vendor-core/docs-content/detection/part.md +3 -1
  273. package/vendor-core/docs-content/detection/plan.md +55 -13
  274. package/vendor-core/docs-content/detection/shfl.md +11 -5
  275. package/vendor-core/docs-content/detection/skew.md +48 -1
  276. package/vendor-core/docs-content/detection/slow.md +4 -2
  277. package/vendor-core/docs-content/detection/spec.md +6 -1
  278. package/vendor-core/docs-content/detection/spill.md +6 -3
  279. package/vendor-core/docs-content/detection/strag.md +19 -6
  280. package/vendor-core/docs-content/detection/tiny.md +9 -3
  281. package/vendor-core/docs-content/detection/util.md +6 -2
  282. package/vendor-core/effective-conf.js +7 -10
  283. package/vendor-core/efficiency-model.js +24 -16
  284. package/vendor-core/etl-phases.js +1 -2
  285. package/vendor-core/event-handlers.js +175 -57
  286. package/vendor-core/event-schemas.js +21 -16
  287. package/vendor-core/evidence-availability.js +9 -2
  288. package/vendor-core/evidence-report.js +118 -35
  289. package/vendor-core/executor-peaks.js +41 -0
  290. package/vendor-core/export-data.js +5 -5
  291. package/vendor-core/finding-identity.js +78 -0
  292. package/vendor-core/finding-presentation.js +129 -39
  293. package/vendor-core/finding-types.js +108 -30
  294. package/vendor-core/format-utils.js +14 -15
  295. package/vendor-core/html-export.js +1 -1
  296. package/vendor-core/impact-band.js +2 -2
  297. package/vendor-core/impact-format.js +47 -19
  298. package/vendor-core/impact-model.js +28 -17
  299. package/vendor-core/job-groups.js +4 -4
  300. package/vendor-core/mcp-server-factory.js +48 -12
  301. package/vendor-core/mcp-tools.js +179 -24
  302. package/vendor-core/median.js +2 -2
  303. package/vendor-core/nested-loop-join.js +73 -0
  304. package/vendor-core/occupancy.js +1 -2
  305. package/vendor-core/parser-worker.js +1 -1
  306. package/vendor-core/plan-graph-model.js +14 -22
  307. package/vendor-core/plan-node-detail.js +2 -3
  308. package/vendor-core/plan-summary.js +24 -19
  309. package/vendor-core/plan-tree-walk.js +6 -8
  310. package/vendor-core/python-stage.js +12 -1
  311. package/vendor-core/recommendation-rollup.js +13 -12
  312. package/vendor-core/redact.js +52 -46
  313. package/vendor-core/remediation.js +10 -5
  314. package/vendor-core/run-aggregates.js +6 -8
  315. package/vendor-core/run-comparison.js +226 -66
  316. package/vendor-core/run-identity.js +12 -0
  317. package/vendor-core/run-interpretation.js +21 -0
  318. package/vendor-core/run-metrics.js +2 -2
  319. package/vendor-core/run-output.js +44 -11
  320. package/vendor-core/run-totals.js +12 -0
  321. package/vendor-core/run-verdict.js +76 -3
  322. package/vendor-core/scaling-sim.js +3 -4
  323. package/vendor-core/snappy-block.js +2 -2
  324. package/vendor-core/spark-conf.js +168 -0
  325. package/vendor-core/sql-stages.js +2 -2
  326. package/vendor-core/stage-alignment.js +2 -12
  327. package/vendor-core/stage-identity.js +58 -3
  328. package/vendor-core/stage-label.js +14 -0
  329. package/vendor-core/stage-plan-nodes.js +17 -3
  330. package/vendor-core/stage-quantiles.js +141 -9
  331. package/vendor-core/stage-structure.js +17 -3
  332. package/vendor-core/threshold-overrides.js +9 -3
  333. package/vendor-core/types.js +70 -23
  334. package/vendor-core/wall-clock.js +8 -0
  335. package/vendor-core/wasted-core-hours.js +12 -12
  336. package/vendor-core/write-targets.js +146 -14
  337. package/vendor-core/zstd-worker-client.js +1 -1
  338. package/vendor-core/zstd-worker.js +1 -1
  339. package/public/assets/AutoscalingChurn-8t3U3isq.js +0 -1
  340. package/public/assets/CacheUtilization-CJs9tTum.js +0 -1
  341. package/public/assets/CachingOpportunity-D1NcHs0V.js +0 -1
  342. package/public/assets/ColdStart-CdRJ9gD8.js +0 -1
  343. package/public/assets/ConfigAudit-B5Gbe5js.js +0 -1
  344. package/public/assets/CoreUsageArea-a--NTRGY.js +0 -1
  345. package/public/assets/Dashboard-C1hNeX49.js +0 -2
  346. package/public/assets/DisclosureContext-ZpE6CkHx.js +0 -1
  347. package/public/assets/DuplicatePlanSubtree-dobXdz7E.js +0 -1
  348. package/public/assets/ExecutorUtilization-D9grwi7t.js +0 -1
  349. package/public/assets/GcPressure-8-G2ri10.js +0 -1
  350. package/public/assets/IncompleteRun-B91lky7a.js +0 -1
  351. package/public/assets/JobFailures-BsWvBMyJ.js +0 -1
  352. package/public/assets/MemoryUtilization-BM5schZI.js +0 -1
  353. package/public/assets/OverBroadcast-DdN8JIJH.js +0 -1
  354. package/public/assets/PartitionHint-CUvbnnxy.js +0 -1
  355. package/public/assets/PartitionSizing-CY5HDYtp.js +0 -1
  356. package/public/assets/PlanExplorer-DgHCDBD_.js +0 -1
  357. package/public/assets/PlanGraphRoute-DH3KJb8j.js +0 -1
  358. package/public/assets/RetryWaste-DOnE9vGF.js +0 -1
  359. package/public/assets/ShuffleIO-MMnDESkF.js +0 -1
  360. package/public/assets/Skew-BmZZJ5ms.js +0 -1
  361. package/public/assets/SlowHost-CsJzfEqo.js +0 -1
  362. package/public/assets/SmallFiles-BoK3R1Ex.js +0 -1
  363. package/public/assets/SpeculationWaste-CnVxB5AJ.js +0 -1
  364. package/public/assets/Spill-BhNicyBy.js +0 -1
  365. package/public/assets/StageFailed-mAhHU4l_.js +0 -1
  366. package/public/assets/StageFindingGroup-BEtPWcT0.js +0 -1
  367. package/public/assets/StageHeader-DJkYkrsU.js +0 -1
  368. package/public/assets/StageShape-BDxRknLp.js +0 -1
  369. package/public/assets/StageSlowness-BPRw-otN.js +0 -1
  370. package/public/assets/Straggler-CZCbnOWP.js +0 -1
  371. package/public/assets/TaskFailures-C57YcLFQ.js +0 -1
  372. package/public/assets/TinyTask-Bi84qU7n.js +0 -1
  373. package/public/assets/UnderBroadcast-DdQV7htE.js +0 -1
  374. package/public/assets/charts-vendor-0VMaLVLN.js +0 -59
  375. package/public/assets/finding-anchor-Cl6_5hfV.js +0 -1
  376. package/public/assets/impact-sort-CssZ6MT2.js +0 -1
  377. package/public/assets/index-2h_TgUAb.css +0 -2
  378. package/public/assets/index-B-03mG7K.js +0 -14
  379. package/public/assets/plan-graph-vendor-BYl-AzN5.js +0 -7
  380. package/public/assets/react-vendor-DEdfrRTJ.js +0 -9
  381. package/public/assets/select-CQjNhprm.js +0 -1
  382. package/public/assets/usePagedRows-Dyl6T8Ok.js +0 -1
  383. package/public/assets/useRecentFiles-XZEnLlal.js +0 -11
  384. package/public/assets/useSortMode-Dhvit8dt.js +0 -1
  385. package/public/docs/assets/chunks/@localSearchIndexroot.u_e5bQz4.js +0 -1
  386. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +0 -1
  387. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js +0 -1
  388. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +0 -1
  389. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +0 -1
  390. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +0 -1
  391. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +0 -1
  392. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +0 -1
  393. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +0 -1
  394. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.js +0 -1
  395. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +0 -1
  396. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +0 -1
  397. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +0 -1
  398. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +0 -1
  399. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +0 -1
  400. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +0 -1
  401. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +0 -1
  402. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +0 -1
  403. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +0 -1
  404. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +0 -1
  405. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +0 -1
  406. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.js +0 -1
  407. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +0 -1
  408. package/public/docs/assets/contributor-guide_contributing.md.CQMvGwFt.js +0 -1
  409. package/public/docs/assets/contributor-guide_development-setup.md.D9bu4j_c.lean.js +0 -1
  410. package/public/docs/assets/contributor-guide_testing.md.By6XGR9a.js +0 -1
  411. package/public/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  412. package/public/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  413. package/public/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  414. package/public/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  415. package/public/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  416. package/public/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  417. package/public/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  418. package/public/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  419. package/public/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  420. package/public/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  421. package/public/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  422. package/public/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  423. package/public/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  424. package/public/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  425. package/public/docs/assets/style.tmDhD3c8.css +0 -1
  426. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.js +0 -23
  427. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.lean.js +0 -1
  428. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +0 -1
  429. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js +0 -1
  430. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +0 -23
  431. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +0 -1
  432. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +0 -1
  433. package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.js +0 -1
  434. package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.lean.js +0 -1
  435. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.lean.js} +0 -0
  436. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js → contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.lean.js} +0 -0
  437. /package/public/docs/assets/{contributor-guide_architecture_overview.md.C1sSvm9i.lean.js → contributor-guide_architecture_overview.md.nxkpOhgf.lean.js} +0 -0
  438. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js → contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.lean.js} +0 -0
  439. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.lean.js} +0 -0
  440. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.lean.js} +0 -0
  441. /package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.lean.js} +0 -0
  442. /package/public/docs/assets/{contributor-guide_contributing.md.CQMvGwFt.lean.js → contributor-guide_contributing.md.DBW4drtZ.lean.js} +0 -0
  443. /package/public/docs/assets/{user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js → user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.lean.js} +0 -0
  444. /package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.lean.js → user-guide_mcp-tools.md.49r3L2YE.lean.js} +0 -0
  445. /package/public/docs/assets/{user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js → user-guide_mcp-tools_get-run-summary.md.EhaIsele.lean.js} +0 -0
@@ -6,17 +6,21 @@ import { hasFinishedStage, isCleanRun } from './check-coverage.js';
6
6
  import { findingActionLabel } from './finding-action-label.js';
7
7
  import { singleStageId } from './finding-filter-predicate.js';
8
8
  import { recommendationText } from './finding-names.js';
9
- import { formatDuration, IMPACT_BAND_ORDER } from './format-utils.js';
9
+ import { formatDuration, formatRawWaste, IMPACT_BAND_ORDER, readsAsZero } from './format-utils.js';
10
10
  import { impactFigure, savingsMeaning } from './impact-format.js';
11
11
  import { isEligible } from './recommendation-rollup.js';
12
12
  import { FAILURE_TYPES, quotesReasonOf, summarizeRunOutcome, } from './run-outcome.js';
13
13
  import { getScorecardEstimates, hasCompleteApplicationInterval } from './scorecard-estimates.js';
14
14
  import { computeWallClock } from './wall-clock.js';
15
+ import { computeWastedCoreHours } from './wasted-core-hours.js';
15
16
 
16
17
 
17
18
  /** How many next steps the verdict lists before pointing at the full list. */
18
19
  export const NEXT_STEP_LIMIT = 3;
19
20
 
21
+ /** How many cost-lane entries the verdict lists. */
22
+ export const COST_LANE_LIMIT = 3;
23
+
20
24
  /** Finding types whose widget sits in the board's reference region (listed after every action
21
25
  * widget). The dashboard's registry must agree: tests/view/detector-registry.test.tsx checks it. */
22
26
  export const REFERENCE_DISPLAY_TYPES = new Set([
@@ -69,6 +73,56 @@ export function rankBySavings(findings ) {
69
73
  return candidates.map(({ finding }) => finding);
70
74
  }
71
75
 
76
+ /** One entry of the cost lane: a finding and the measured busy core time its fix removes. */
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+ /** The cost lane: rankable findings ordered by measured busy core time (`coreTimeMs.high`), the
86
+ * one unit all of them share. An idle capacity figure, a modeled figure and a bytes-only figure
87
+ * never carry `coreTimeMs`, and a finding without one is in neither lane. Ties fall back to impact
88
+ * band, widget display order, then input order, like `rankBySavings`. */
89
+ export function buildCostLane(findings ) {
90
+ const entries = findings
91
+ .map((finding, index) => ({ finding, index, coreTimeMs: finding.impactEstimate?.coreTimeMs?.high }))
92
+ .filter((entry) =>
93
+ isRankable(entry.finding) && typeof entry.coreTimeMs === 'number' && Number.isFinite(entry.coreTimeMs) && entry.coreTimeMs > 0);
94
+ entries.sort((left, right) => (
95
+ right.coreTimeMs - left.coreTimeMs
96
+ || (IMPACT_BAND_ORDER[left.finding.impactBand] ?? 9) - (IMPACT_BAND_ORDER[right.finding.impactBand] ?? 9)
97
+ || (DISPLAY_INDEX.get(left.finding.type) ?? Number.MAX_SAFE_INTEGER) - (DISPLAY_INDEX.get(right.finding.type) ?? Number.MAX_SAFE_INTEGER)
98
+ || left.index - right.index
99
+ ));
100
+ return entries
101
+ .map(({ finding, coreTimeMs }) => ({ finding, coreTimeMs, figure: formatRawWaste({ value: coreTimeMs, unit: 'coreMs' }) }))
102
+ .filter((entry) => !readsAsZero(entry.figure));
103
+ }
104
+
105
+ /** The run's idle capacity, bounded by the run: allocated core time that ran no task. */
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+ export function buildRunIdleLine(appModel ) {
116
+ const { wastedCoreHours, totalCoreHours } = computeWastedCoreHours(
117
+ appModel.app, appModel.executors.added, appModel.runAggregates, appModel.executors.removed,
118
+ );
119
+ if (wastedCoreHours == null || totalCoreHours == null || totalCoreHours <= 0) return null;
120
+ const figure = formatRawWaste({ value: wastedCoreHours, unit: 'coreHours' });
121
+ if (readsAsZero(figure)) return null;
122
+ const pct = Math.round((wastedCoreHours / totalCoreHours) * 100);
123
+ return { coreHours: wastedCoreHours, allocatedCoreHours: totalCoreHours, pct, text: `${figure} (${pct}%) of the allocation ran no task.` };
124
+ }
125
+
72
126
  /** One place worth a look: the highest-ranked finding at a location, plus the
73
127
  * other finding types flagged at that same location. Findings that share a
74
128
  * stage usually share one root cause, so they read as one step, not several. */
@@ -179,6 +233,9 @@ function plural(count , noun ) {
179
233
 
180
234
 
181
235
 
236
+
237
+
238
+
182
239
 
183
240
 
184
241
  function isFailedRun(facts ) {
@@ -215,6 +272,13 @@ export function verdictTitle(eligible , steps , facts
215
272
  return `Start here: ${lowerFirst(findingActionLabel(lead.lead))}`;
216
273
  }
217
274
 
275
+ /** What the finding count leaves out, as a sentence tail: notes about an incomplete log or data the
276
+ * log lacked are not fixes, so the count does not include them. */
277
+ function uncountedNote(uncounted ) {
278
+ if (!uncounted) return '';
279
+ return ` The count leaves out ${plural(uncounted, 'note')} on incomplete or missing log data.`;
280
+ }
281
+
218
282
  /** The run-level summary under the title: how much was found and where, what
219
283
  * the first fix is worth, and the run's idle capacity when that is large
220
284
  * enough to matter but is not the first step (whose title already says it). */
@@ -234,7 +298,7 @@ export function verdictSummary(eligible , steps , facts
234
298
  } else if (steps.length === 0) {
235
299
  sentences.push('They are listed by impact under Findings.');
236
300
  } else {
237
- sentences.push(`${plural(eligible.length, 'finding')} in ${plural(steps.length, 'place')}.`);
301
+ sentences.push(`${plural(eligible.length, 'finding')} in ${plural(steps.length, 'place')}.${uncountedNote(facts.uncounted)}`);
238
302
  const wallClock = steps[0].lead.impactEstimate?.wallClock;
239
303
  // An idle-capacity lead needs no sentence here: the title gives the idle share and step 1 the fix.
240
304
  if (!isIdleCapacityStep(steps[0]) && wallClock && facts.runMs != null) {
@@ -323,6 +387,10 @@ export function stepCopyRecommendation(step , outcome )
323
387
 
324
388
 
325
389
 
390
+
391
+
392
+
393
+
326
394
 
327
395
 
328
396
  export function buildRunVerdict(appModel , allFindings ) {
@@ -336,6 +404,7 @@ export function buildRunVerdict(appModel , allFindings )
336
404
  clean: isCleanRun(appModel, allFindings),
337
405
  outcome,
338
406
  noFinishedStages: !hasFinishedStage(appModel.stages),
407
+ uncounted: allFindings.length - eligible.length,
339
408
  };
340
409
  const title = verdictTitle(eligible, steps, facts);
341
410
  const shown = steps.slice(0, NEXT_STEP_LIMIT);
@@ -348,5 +417,9 @@ export function buildRunVerdict(appModel , allFindings )
348
417
  remaining,
349
418
  })
350
419
  : null;
351
- return { eligible, outcome, steps, facts, title, summary: verdictSummary(eligible, steps, facts), shown, remaining, copyText };
420
+ return {
421
+ eligible, outcome, steps, facts, title, summary: verdictSummary(eligible, steps, facts), shown, remaining, copyText,
422
+ costLane: buildCostLane(eligible).slice(0, COST_LANE_LIMIT),
423
+ idle: buildRunIdleLine(appModel),
424
+ };
352
425
  }
@@ -1,7 +1,6 @@
1
- // §4 What-if executor-scaling simulator.
2
- // DESIGN SPIKE: the makespan model is a first-cut approximation and is NOT
3
- // validated against ground truth (a single event log only ever observes one
4
- // scale). Every prediction ships with a Model Error confidence indicator.
1
+ // What-if executor-scaling simulator. The makespan model is a first-cut approximation and is NOT
2
+ // validated against ground truth (a single event log only ever observes one scale). Every
3
+ // prediction ships with a Model Error confidence indicator.
5
4
  //
6
5
  // Builds on computeCoreTimeSeries' clamp conceptually, but operates on the
7
6
  // worker-posted per-stage aggregates (runAggregates.perStage) so raw task data
@@ -5,7 +5,7 @@
5
5
  // Each raw block is the standard Snappy format (varint uncompressed length +
6
6
  // a sequence of literal/copy tagged elements): see
7
7
  // https://github.com/google/snappy/blob/main/format_description.txt. This is
8
- // a different framing from Spark's own LZ4Block format (src/lz4-block.js).
8
+ // a different framing from Spark's own LZ4Block format (lz4-block.ts).
9
9
 
10
10
  const MAGIC = [0x82, 0x53, 0x4e, 0x41, 0x50, 0x50, 0x59, 0x00]; // 0x82 'S' 'N' 'A' 'P' 'P' 'Y' 0x00
11
11
  const HEADER_SIZE = 16; // 8-byte magic + 4-byte version + 4-byte compatible version
@@ -102,7 +102,7 @@ export function decodeSnappyBlock(bytes ) {
102
102
 
103
103
  // Streaming counterpart: push arbitrary byte slices, each fully-received
104
104
  // block is decompressed and handed to `onChunk` as it completes. Mirrors
105
- // createLz4BlockDecoder's shape/contract (see src/lz4-block.js).
105
+ // createLz4BlockDecoder's shape/contract (see lz4-block.ts).
106
106
  export function createSnappyBlockDecoder(
107
107
  onChunk ,
108
108
  ) {
@@ -0,0 +1,168 @@
1
+ // The Spark configuration a run actually executed under, resolved key by key from three layers:
2
+ // 1. a per-query value: `SparkListenerSQLExecutionStart.modifiedConfigs`, the session settings of
3
+ // one SQL execution that differ from the SparkContext's (what `spark.conf.set` leaves),
4
+ // 2. the app's Spark Properties from `SparkListenerEnvironmentUpdate`,
5
+ // 3. Spark's own default for the run's Spark version (SPARK_DEFAULTS below).
6
+ // A detector scoped to one SQL execution passes that execution's modifiedConfigs; any other caller
7
+ // leaves it out and gets the run-wide answer.
8
+ //
9
+ // Properties with no entry in SPARK_DEFAULTS have no fixed default: spark.executor.instances and
10
+ // spark.default.parallelism depend on the cluster manager and the cluster, spark.executor.memoryOverhead
11
+ // is derived from spark.executor.memory (executorOverheadSettings gives the pair it is derived from),
12
+ // spark.dynamicAllocation.initialExecutors from minExecutors,
13
+ // and spark.sql.adaptive.autoBroadcastJoinThreshold falls back to spark.sql.autoBroadcastJoinThreshold
14
+ // when unset. The lookup returns undefined for them rather than guess.
15
+
16
+ import { parseSparkMemoryMB } from './spark-memory.js';
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+ const ALWAYS = 'always';
38
+
39
+ // The per-query (`modifiedConfigs`) keys a detector reads: every key with a default above, and the
40
+ // two SQL settings it reads with none. A per-query value under any other key is the job's own text
41
+ // (a path, a bucket, a host) that no detector needs.
42
+ const PER_QUERY_KEYS_WITHOUT_DEFAULT = [
43
+ 'spark.sql.adaptive.autoBroadcastJoinThreshold',
44
+ 'spark.sql.adaptive.coalescePartitions.initialPartitionNum',
45
+ ] ;
46
+ export function isDetectorConfKey(key ) {
47
+ return key in SPARK_DEFAULTS || (PER_QUERY_KEYS_WITHOUT_DEFAULT ).includes(key);
48
+ }
49
+
50
+ /** The text a per-query value is replaced with once it leaves the run: Spark's own placeholder for a
51
+ * hidden value, which the effective-conf lookup already ignores. */
52
+ export const REDACTED_CONF_VALUE = '*********(redacted)';
53
+
54
+ // Defaults by the Spark release that introduced them (table: detector-contract/threshold-tables.md,
55
+ // Spark defaults by release), each key's steps in ascending order. A key whose first step is
56
+ // 'always' has that default on every release, including a run whose version the log did not
57
+ // record; any other key is only answered for a known version at or after its first step (before
58
+ // it the property does not exist). Values are the strings Spark's ConfigBuilder declares. Each was read from the SQLConf.scala / config/package.scala sources at the release tags
59
+ // from v3.0.0 through v4.2.0, e.g. https://github.com/apache/spark/blob/v4.0.0/core/src/main/scala/org/apache/spark/internal/config/package.scala
60
+ const SPARK_DEFAULTS = {
61
+ 'spark.speculation': [[ALWAYS, 'false']],
62
+ 'spark.speculation.multiplier': [[ALWAYS, '1.5'], [[4, 0], '3']],
63
+ 'spark.speculation.quantile': [[ALWAYS, '0.75'], [[4, 0], '0.9']],
64
+
65
+ 'spark.executor.memory': [[ALWAYS, '1g']],
66
+ 'spark.serializer': [[ALWAYS, 'org.apache.spark.serializer.JavaSerializer']],
67
+ 'spark.shuffle.service.enabled': [[ALWAYS, 'false']],
68
+ 'spark.eventLog.logBlockUpdates.enabled': [[ALWAYS, 'false']],
69
+ 'spark.eventLog.logStageExecutorMetrics': [[ALWAYS, 'false']],
70
+
71
+ 'spark.dynamicAllocation.enabled': [[ALWAYS, 'false']],
72
+ 'spark.dynamicAllocation.minExecutors': [[ALWAYS, '0']],
73
+ 'spark.dynamicAllocation.maxExecutors': [[ALWAYS, '2147483647']],
74
+ 'spark.dynamicAllocation.executorIdleTimeout': [[ALWAYS, '60s']],
75
+
76
+ 'spark.sql.shuffle.partitions': [[ALWAYS, '200']],
77
+ 'spark.sql.autoBroadcastJoinThreshold': [[ALWAYS, '10MB']],
78
+ 'spark.sql.files.maxPartitionBytes': [[ALWAYS, '128MB']],
79
+ 'spark.sql.adaptive.enabled': [[ALWAYS, 'false'], [[3, 2], 'true']],
80
+ 'spark.sql.adaptive.skewJoin.enabled': [[[3, 0], 'true']],
81
+ 'spark.sql.adaptive.coalescePartitions.enabled': [[[3, 0], 'true']],
82
+ 'spark.sql.adaptive.skewJoin.skewedPartitionFactor': [[[3, 0], '5']],
83
+ 'spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes': [[[3, 0], '256MB']],
84
+ 'spark.sql.adaptive.forceOptimizeSkewedJoin': [[[3, 3], 'false']],
85
+ // Falls back to spark.sql.adaptive.shuffle.targetPostShuffleInputSize, itself 64MB.
86
+ 'spark.sql.adaptive.advisoryPartitionSizeInBytes': [[[3, 0], '64MB']],
87
+ 'spark.sql.adaptive.coalescePartitions.parallelismFirst': [[[3, 2], 'true']],
88
+ // The factor is 0.4 instead on Kubernetes non-JVM jobs, which the log does not tell apart.
89
+ 'spark.executor.memoryOverheadFactor': [[[3, 3], '0.1']],
90
+ 'spark.executor.minMemoryOverhead': [[[4, 0], '384m']],
91
+ 'spark.sql.execution.pythonUDF.arrow.enabled': [[[3, 4], 'false'], [[4, 2], 'true']],
92
+ };
93
+
94
+ // The text Spark substitutes for a sensitive value in modifiedConfigs (a fixed constant; only the
95
+ // pattern that selects values is configurable).
96
+ const DEFAULT_REDACTION = REDACTED_CONF_VALUE;
97
+
98
+ function parseVersion(sparkVersion ) {
99
+ const m = /^(\d+)\.(\d+)/.exec(sparkVersion ?? '');
100
+ return m == null ? null : [Number(m[1]), Number(m[2])];
101
+ }
102
+
103
+ /** Spark's default for `key` on `sparkVersion`; undefined when the property has no fixed default,
104
+ * does not exist on that version, or the default depends on a version the log did not record. */
105
+ export function sparkConfDefault(sparkVersion , key ) {
106
+ const steps = SPARK_DEFAULTS[key];
107
+ if (steps == null) return undefined;
108
+ const version = parseVersion(sparkVersion);
109
+ if (version == null) return steps.length === 1 && steps[0][0] === ALWAYS ? steps[0][1] : undefined;
110
+ let value ;
111
+ for (const [since, stepValue] of steps) {
112
+ if (since === ALWAYS || version[0] > since[0] || (version[0] === since[0] && version[1] >= since[1])) value = stepValue;
113
+ }
114
+ return value;
115
+ }
116
+
117
+ /** A per-query setting that can override: not Spark's redaction placeholder for a hidden value. */
118
+ function isUsableModified(value ) {
119
+ return value !== DEFAULT_REDACTION;
120
+ }
121
+
122
+ /** The Spark Properties with one execution's modifiedConfigs applied over them. Returns
123
+ * `properties` itself when there is nothing to apply. */
124
+ export function overlayModifiedConfigs(
125
+ properties ,
126
+ modified ,
127
+ ) {
128
+ if (modified == null) return properties;
129
+ const usable = Object.entries(modified).filter(([, value]) => isUsableModified(value));
130
+ return usable.length === 0 ? properties : { ...properties, ...Object.fromEntries(usable) };
131
+ }
132
+
133
+ /** The run's effective value of `key` and the layer it came from, or undefined when no layer has it. */
134
+ export function effectiveSparkConf(layers , key ) {
135
+ const modified = layers.modified?.[key];
136
+ if (modified !== undefined && isUsableModified(modified)) return { value: modified.trim(), source: 'query' };
137
+ const logged = layers.properties?.[key];
138
+ // A log can carry a real boolean or number where Spark wrote a string.
139
+ if (logged !== undefined) return { value: String(logged).trim(), source: 'app' };
140
+ const fallback = sparkConfDefault(layers.sparkVersion, key);
141
+ return fallback === undefined ? undefined : { value: fallback, source: 'default' };
142
+ }
143
+
144
+ /** A Spark byte-size value as bytes (a plain number, or with a k/m/g/t suffix, optionally 'b'), or
145
+ * null when it is not one. Negative numbers pass through: -1 turns auto-broadcast off. */
146
+ export function parseSparkBytes(value ) {
147
+ const m = /^(-?\d+(?:\.\d+)?)\s*([kmgt]?)b?$/i.exec(value?.trim() ?? '');
148
+ if (m == null) return null;
149
+ return m[2] === '' ? Number(m[1]) : Number(m[1]) * 1024 ** ('kmgt'.indexOf(m[2].toLowerCase()) + 1);
150
+ }
151
+
152
+ /** The factor and minimum (MiB) Spark sizes the executor overhead from when
153
+ * spark.executor.memoryOverhead is unset: max(factor x executor memory, minimum). The effective
154
+ * settings win, each only on a Spark version that reads it (the factor from 3.3.0, the minimum from
155
+ * 4.0.0); an unrecorded version is read as current. `fallback` is the pair used where the effective
156
+ * conf has none (a version before the key, or an unreadable value). */
157
+ export function executorOverheadSettings(
158
+ layers , fallback ,
159
+ ) {
160
+ const version = parseVersion(layers.sparkVersion);
161
+ const [major, minor] = version ?? [Infinity, 0];
162
+ const factor = Number.parseFloat(effectiveSparkConf(layers, 'spark.executor.memoryOverheadFactor')?.value ?? '');
163
+ const minMB = parseSparkMemoryMB(effectiveSparkConf(layers, 'spark.executor.minMemoryOverhead')?.value);
164
+ return {
165
+ factor: (major > 3 || (major === 3 && minor >= 3)) && Number.isFinite(factor) && factor > 0 ? factor : fallback.floorPct,
166
+ minMB: major >= 4 && minMB != null && minMB >= 0 ? minMB : fallback.floorMB,
167
+ };
168
+ }
@@ -1,5 +1,5 @@
1
- // Shared by every scope:'sql' detector and the plan views. sql.get(id).stageIds is always empty (parser-worker
2
- // never populates it), so stage linkage is derived from each stage's own sqlExecutionId.
1
+ // Shared by every scope:'sql' detector and the plan views. sql.get(id).stageIds is always empty
2
+ // (see detector-contract/plan-attribution.md), so stages are linked by their own sqlExecutionId.
3
3
  // Minimal param shape (not DetectorStage): external callers pass Map<StageId, Stage>.
4
4
  export function stageIdsForSqlExec(
5
5
  executionId ,
@@ -16,7 +16,7 @@ import { withEarlierAttempts, totalExecutorCpuMs } from './run-totals.js';
16
16
  import { planNodesOfStage } from './stage-plan-nodes.js';
17
17
  import { normalizeStageName, stageIdentityWith, } from './stage-identity.js';
18
18
  import { alignSequences } from './sequence-alignment.js';
19
- import { attributeNames, detailTokens, jaccard, structuralKey } from './stage-structure.js';
19
+ import { attributeNamesOf, detailTokens, jaccard, structuralKey } from './stage-structure.js';
20
20
 
21
21
 
22
22
 
@@ -24,8 +24,6 @@ import { attributeNames, detailTokens, jaccard, structuralKey } from './stage-st
24
24
  * `runtimeCoverage`). Additive changes keep it; a consumer refuses a version it does not know. */
25
25
  export const COMPARISON_SCHEMA_VERSION = 1;
26
26
 
27
- // ---- Comparison normalizer ------------------------------------------------------------------
28
-
29
27
  // Targeted patches for the volatile tokens that differ between two runs of one job. Each applies
30
28
  // on top of `normalizeDetail`, in this order. No blanket numeric or path stripping: those fuse
31
29
  // distinct stages of one run.
@@ -95,8 +93,6 @@ export function comparisonDetailNormalizer(callerPatterns = [
95
93
  };
96
94
  }
97
95
 
98
- // ---- Identity and bookkeeping ---------------------------------------------------------------
99
-
100
96
  /** `stageIdentity`'s recipe (normalized stage name plus the attributed nodes' fingerprint) with the
101
97
  * comparison normalizer. Internal to the aligner: consumers key on a pair's `pairId`. */
102
98
  export function comparisonIdentity(
@@ -125,8 +121,6 @@ export function isDeltaBookkeepingStage(
125
121
  return scans > 0;
126
122
  }
127
123
 
128
- // ---- Alignment ------------------------------------------------------------------------------
129
-
130
124
  export const PAIR_DELTA_METRICS = [
131
125
  'executorRunTime', 'executorCpuTime', 'memoryBytesSpilled', 'diskBytesSpilled',
132
126
  'inputBytes', 'outputBytes', 'shuffleReadBytes', 'shuffleWriteBytes',
@@ -268,8 +262,6 @@ function sumFigures(rows ) {
268
262
 
269
263
  const ascending = (a , b ) => a - b;
270
264
 
271
- // ---- Per-run index --------------------------------------------------------------------------
272
-
273
265
  // What the aligner knows about one SQL execution (or the pseudo-execution of stages outside any):
274
266
  // the non-bookkeeping stages it ran and the three things its similarity reads.
275
267
 
@@ -325,7 +317,7 @@ function planStructure(root ) {
325
317
  (function walk(node ) {
326
318
  if (!/^(WholeStageCodegen|InputAdapter|ColumnarToRow)\b/.test(node.name ?? '')) {
327
319
  tokens.push(normalizeStageName(node.name ?? ''));
328
- for (const name of attributeNames(node.detail ?? '')) attributes.add(`@${name}`);
320
+ for (const name of attributeNamesOf(node)) attributes.add(`@${name}`);
329
321
  }
330
322
  for (const child of node.children ?? []) walk(child);
331
323
  })(root);
@@ -395,8 +387,6 @@ function executionSimilarity(a , b ) {
395
387
  return Math.round((1000 * sum) / weight);
396
388
  }
397
389
 
398
- // ---- Stage alignment inside one execution pair -----------------------------------------------
399
-
400
390
 
401
391
 
402
392
  function alignWithin(
@@ -13,6 +13,24 @@ import { planNodesOfStage } from './stage-plan-nodes.js';
13
13
  // Replace run-varying tokens (digit runs, long hex ids) with a stable marker so
14
14
  // the same logical stage across two runs normalizes to one identity.
15
15
  export function normalizeStageName(name ) {
16
+ if (name.length > STAGE_NAME_CACHE_MAX_LENGTH) return computeStageName(name);
17
+ let normalized = stageNameCache.get(name);
18
+ if (normalized === undefined) {
19
+ if (stageNameCache.size >= STAGE_NAME_CACHE_MAX_ENTRIES) stageNameCache.clear();
20
+ normalized = computeStageName(name);
21
+ stageNameCache.set(name, normalized);
22
+ }
23
+ return normalized;
24
+ }
25
+
26
+ // A comparison normalizes the same few operator and stage names once per stage, identity and key.
27
+ // Only short names are kept (long inputs are call-site text, rarely repeated), and the whole table
28
+ // resets when full, so it stays small however many runs a long-lived process analyzes.
29
+ const STAGE_NAME_CACHE_MAX_LENGTH = 256;
30
+ const STAGE_NAME_CACHE_MAX_ENTRIES = 4096;
31
+ const stageNameCache = new Map ();
32
+
33
+ function computeStageName(name ) {
16
34
  return String(name)
17
35
  .toLowerCase()
18
36
  .replace(/\b[0-9a-f]{8,}\b/g, '#') // hex ids/uuids first (they contain digits)
@@ -21,6 +39,28 @@ export function normalizeStageName(name ) {
21
39
  .trim();
22
40
  }
23
41
 
42
+ // A plan node belongs to every stage that ran part of it, and each of those stages reads its
43
+ // normalized detail again (exact key, structural key, similarity tokens), on both runs of a
44
+ // comparison. The normalizer is a pure function of the detail and a node is never mutated after the
45
+ // parser posts it, so the result is memoized per node. Like the per-tree identity below, each node
46
+ // keeps at most MAX_NORMALIZERS_PER_TREE normalizers and drops the oldest first.
47
+ const nodeDetailCache = new WeakMap ();
48
+
49
+ export function normalizedDetailOf(node , normalizeDetail ) {
50
+ let byNormalizer = nodeDetailCache.get(node);
51
+ if (!byNormalizer) {
52
+ byNormalizer = new Map();
53
+ nodeDetailCache.set(node, byNormalizer);
54
+ }
55
+ let normalized = byNormalizer.get(normalizeDetail);
56
+ if (normalized === undefined) {
57
+ normalized = normalizeDetail(node.detail ?? '');
58
+ if (byNormalizer.size >= MAX_NORMALIZERS_PER_TREE) byNormalizer.delete(byNormalizer.keys().next().value );
59
+ byNormalizer.set(normalizeDetail, normalized);
60
+ }
61
+ return normalized;
62
+ }
63
+
24
64
  // Bottom-up, order-independent structural identity of a resolved plan tree:
25
65
  // each node folds its normalized name/detail with its children's digests
26
66
  // (children sorted, so AQE picking a different broadcast side still matches),
@@ -32,13 +72,28 @@ export function normalizeStageName(name ) {
32
72
  // instead of embedding full child identity strings, which would re-escape
33
73
  // every level below and blow identity size up to ~2^depth on the 20-60+
34
74
  // operator-deep plans real Spark produces.
75
+ //
76
+ // Memoized per resolved tree (a tree is never mutated after the parser posts it), because every
77
+ // stage of an execution that has no attributed node falls back to the same whole-tree identity.
78
+ // The aligner builds a fresh normalizer closure per call, so each tree keeps at most
79
+ // MAX_NORMALIZERS_PER_TREE entries and drops the oldest first.
80
+ const MAX_NORMALIZERS_PER_TREE = 4;
81
+ const planTreeIdentityCache = new WeakMap ();
82
+
35
83
  function planTreeIdentity(root , normalizeDetail ) {
36
84
  if (!root) return null;
85
+ const byNormalizer = planTreeIdentityCache.get(root) ?? new Map ();
86
+ const cached = byNormalizer.get(normalizeDetail);
87
+ if (cached !== undefined) return cached;
37
88
  function visit(node ) {
38
89
  const childDigests = (node.children ?? []).map(visit).sort();
39
- return cyrb53(JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? ''), childDigests]));
90
+ return cyrb53(JSON.stringify([normalizeStageName(node.name ?? ''), normalizedDetailOf(node, normalizeDetail), childDigests]));
40
91
  }
41
- return visit(root);
92
+ const identity = visit(root);
93
+ if (byNormalizer.size >= MAX_NORMALIZERS_PER_TREE) byNormalizer.delete(byNormalizer.keys().next().value );
94
+ byNormalizer.set(normalizeDetail, identity);
95
+ planTreeIdentityCache.set(root, byNormalizer);
96
+ return identity;
42
97
  }
43
98
 
44
99
  // Plan identity for the stage's SQL execution, scoped to only the plan nodes
@@ -56,7 +111,7 @@ function sqlNodeIdentity(
56
111
  const root = snapshot.sql.get(execId)?.planTree ?? null;
57
112
  if (!root) return '';
58
113
  const fingerprints = (nodes ?? planNodesOfStage(stage, snapshot.sql))
59
- .map((node) => JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? '')]));
114
+ .map((node) => JSON.stringify([normalizeStageName(node.name ?? ''), normalizedDetailOf(node, normalizeDetail)]));
60
115
  if (fingerprints.length === 0) return planTreeIdentity(root, normalizeDetail) ?? '';
61
116
  return cyrb53(JSON.stringify(fingerprints.sort()));
62
117
  }
@@ -0,0 +1,14 @@
1
+ // A path inside a stage name: an absolute path, optionally behind a URI scheme and authority
2
+ // (`hdfs://nn/...`). It starts at the beginning of the name or after a space, quote, bracket, `=`
3
+ // or `,`, so a `/` inside a word ("a/b") is never taken for one. A `:` ends the path, which leaves
4
+ // a trailing `:<line>` in place.
5
+ const PATH_IN_STAGE_NAME = /(?<![^\s"'(\[=,])(?:[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s/]*)?(?:\/[^\s/:"')\]]+)+/g;
6
+
7
+ /** A stage name for display and output: every path in it reduced to its last segment, so the
8
+ * directories of the job's call site (a YARN container directory such as
9
+ * `.../usercache/<user>/appcache/<app>/container_<id>/`, a home directory) and the user name in
10
+ * them never reach the output. `collect at /home/jdoe/jobs/etl.py:42` reads `collect at etl.py:42`.
11
+ * Display only: `normalizeStageName` stays the stage identity, so stage pairing does not move. */
12
+ export function stageDisplayLabel(name ) {
13
+ return name.replace(PATH_IN_STAGE_NAME, (path) => path.slice(path.lastIndexOf('/') + 1));
14
+ }
@@ -9,10 +9,24 @@ export function planNodesOfStage(stage , sq
9
9
  if (execId == null) return [];
10
10
  const root = sql.get(execId)?.planTree ?? null;
11
11
  if (!root) return [];
12
- const nodes = [];
12
+ return [...(nodesByStage(root).get(stage.id) ?? [])];
13
+ }
14
+
15
+ // One preorder walk per resolved plan tree serves every stage of its execution. A resolved tree is
16
+ // never mutated after the parser posts it, so keying by root identity is safe.
17
+ const nodesByStageCache = new WeakMap ();
18
+
19
+ function nodesByStage(root ) {
20
+ const cached = nodesByStageCache.get(root);
21
+ if (cached) return cached;
22
+ const index = new Map ();
13
23
  (function collect(node ) {
14
- if (node.stageIds?.includes(stage.id)) nodes.push(node);
24
+ for (const id of new Set(node.stageIds ?? [])) {
25
+ const list = index.get(id);
26
+ if (list) list.push(node); else index.set(id, [node]);
27
+ }
15
28
  for (const child of node.children ?? []) collect(child);
16
29
  })(root);
17
- return nodes;
30
+ nodesByStageCache.set(root, index);
31
+ return index;
18
32
  }