sparkforensics-server 0.4.0 → 0.5.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 (382) hide show
  1. package/index.js +3 -0
  2. package/package.json +3 -3
  3. package/public/assets/AutoscalingChurn-5hDGIXi4.js +1 -0
  4. package/public/assets/CacheUtilization-CT2SCnJJ.js +1 -0
  5. package/public/assets/CachingOpportunity-D_O_WkbH.js +1 -0
  6. package/public/assets/ColdStart-B38OiAOp.js +1 -0
  7. package/public/assets/ConfigAudit-BoplryCH.js +1 -0
  8. package/public/assets/CoreUsageArea-csgwNxkl.js +1 -0
  9. package/public/assets/Dashboard-BgKodJl2.js +2 -0
  10. package/public/assets/DisclosureContext-B0siMVdg.js +1 -0
  11. package/public/assets/DuplicatePlanSubtree-BqS2A2j4.js +1 -0
  12. package/public/assets/ExecutorUtilization-Dp52QGWo.js +1 -0
  13. package/public/assets/GcPressure-CT1yzR_s.js +1 -0
  14. package/public/assets/IncompleteRun-C7Qkjtjn.js +1 -0
  15. package/public/assets/JobFailures-WHl3ELfh.js +1 -0
  16. package/public/assets/MemoryUtilization-D8IUDHJI.js +1 -0
  17. package/public/assets/OverBroadcast-6oSSAQ_R.js +1 -0
  18. package/public/assets/PartitionHint-CUvbnnxy.js +1 -0
  19. package/public/assets/PartitionSizing-BwdFL-mw.js +1 -0
  20. package/public/assets/{PlanExplorer-DdmPkKO1.js → PlanExplorer-DcxmiQWL.js} +1 -1
  21. package/public/assets/PlanGraphRoute-pqnHTt4i.js +1 -0
  22. package/public/assets/RetryWaste-BTbeHkJc.js +1 -0
  23. package/public/assets/ShuffleIO-CRhrYNE0.js +1 -0
  24. package/public/assets/Skew-DSNXRG9V.js +1 -0
  25. package/public/assets/SlowHost-B7Lwu2EU.js +1 -0
  26. package/public/assets/SmallFiles-C082c2rd.js +1 -0
  27. package/public/assets/SpeculationWaste-BS2vpadr.js +1 -0
  28. package/public/assets/Spill-B4DVKHXx.js +1 -0
  29. package/public/assets/StageFailed-D3ac9Sou.js +1 -0
  30. package/public/assets/StageFindingGroup-D9naK109.js +1 -0
  31. package/public/assets/{StageHeader-BSV8ALhT.js → StageHeader-pI1nTwVf.js} +1 -1
  32. package/public/assets/StageShape-eO5le8iG.js +1 -0
  33. package/public/assets/StageSlowness-BaNyfNvL.js +1 -0
  34. package/public/assets/Straggler-D5Fnok_y.js +1 -0
  35. package/public/assets/TaskFailures-BWnC-K_G.js +1 -0
  36. package/public/assets/{TinyTask-CIPRxWfT.js → TinyTask-8_5lpq_2.js} +1 -1
  37. package/public/assets/UnderBroadcast-DJ0XSwth.js +1 -0
  38. package/public/assets/{finding-anchor-DF1cSHO1.js → finding-anchor-Cys8Z6Yn.js} +1 -1
  39. package/public/assets/{impact-sort-7z0ZcBZf.js → impact-sort-BTQndbof.js} +1 -1
  40. package/public/assets/index-DCeWge12.js +13 -0
  41. package/public/assets/parser-worker-DmeCnLWU.js +122 -0
  42. package/public/assets/{plan-graph-vendor-B0sEms9Z.js → plan-graph-vendor-BYl-AzN5.js} +1 -1
  43. package/public/assets/{react-vendor-BAKt3vny.js → react-vendor-DEdfrRTJ.js} +1 -1
  44. package/public/assets/{select-Cy5pmbrh.js → select-DIYN6D3y.js} +1 -1
  45. package/public/assets/{usePagedRows-BjPOpUU8.js → usePagedRows-BxvdFnPk.js} +1 -1
  46. package/public/assets/useRecentFiles-C3Ov47U7.js +11 -0
  47. package/public/assets/{useSortMode-CU0vzThu.js → useSortMode-B8SRfAoG.js} +1 -1
  48. package/public/docs/404.html +4 -3
  49. package/public/docs/assets/{app.C6w47a9W.js → app.D2GqAkhy.js} +1 -1
  50. package/public/docs/assets/chunks/@localSearchIndexroot.D4OdkQjy.js +1 -0
  51. package/public/docs/assets/chunks/{VPLocalSearchBox.BcTNvT55.js → VPLocalSearchBox.BS4cSmtW.js} +1 -1
  52. package/public/docs/assets/chunks/{theme.D-mE6j02.js → theme.BuxVpb0H.js} +2 -2
  53. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +1 -0
  54. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js +1 -0
  55. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js +1 -0
  56. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js +1 -0
  57. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +1 -0
  58. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js +1 -0
  59. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +1 -0
  60. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +1 -0
  61. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +1 -0
  62. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js +1 -0
  63. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +1 -0
  64. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +1 -0
  65. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.js +1 -0
  66. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.lean.js +1 -0
  67. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.js +1 -0
  68. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js +1 -0
  69. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +1 -0
  70. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js +1 -0
  71. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +1 -0
  72. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js +1 -0
  73. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +1 -0
  74. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js +1 -0
  75. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +1 -0
  76. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +1 -0
  77. package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.js +1 -0
  78. package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.lean.js +1 -0
  79. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
  80. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.lean.js +1 -0
  81. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DyCBdjuM.js +1 -0
  82. package/public/docs/assets/{contributor-guide_architecture_state-and-history.md.DOTVHjTV.lean.js → contributor-guide_architecture_state-and-history.md.DyCBdjuM.lean.js} +1 -1
  83. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.js +1 -0
  84. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.lean.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.js +1 -0
  86. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.lean.js +1 -0
  87. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +1 -0
  90. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +1 -0
  91. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.js +1 -0
  92. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.lean.js +1 -0
  93. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js +1 -0
  94. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js +1 -0
  95. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.js +1 -0
  96. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.lean.js +1 -0
  97. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js +1 -0
  98. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js +1 -0
  99. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.lean.js +1 -0
  103. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js +1 -0
  104. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js +1 -0
  105. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js +6 -0
  106. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js +1 -0
  107. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +1 -0
  108. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js +1 -0
  109. package/public/docs/assets/contributor-guide_contributing.md.CQMvGwFt.js +1 -0
  110. package/public/docs/assets/{contributor-guide_contributing.md.B2E9ni79.lean.js → contributor-guide_contributing.md.CQMvGwFt.lean.js} +1 -1
  111. package/public/docs/assets/contributor-guide_development-setup.md.D9bu4j_c.js +15 -0
  112. package/public/docs/assets/contributor-guide_development-setup.md.D9bu4j_c.lean.js +1 -0
  113. package/public/docs/assets/contributor-guide_testing.md.By6XGR9a.js +1 -0
  114. package/public/docs/assets/{contributor-guide_testing.md.DTwhpoN2.lean.js → contributor-guide_testing.md.By6XGR9a.lean.js} +1 -1
  115. package/public/docs/assets/{style.DSixAiZE.css → style.tmDhD3c8.css} +1 -1
  116. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.js +4 -0
  117. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.lean.js +1 -0
  118. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.js +4 -0
  119. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.lean.js +1 -0
  120. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.js +1 -0
  121. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.lean.js +1 -0
  122. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.js +1 -0
  123. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.lean.js +1 -0
  124. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.js +22 -0
  125. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.lean.js +1 -0
  126. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +1 -0
  127. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js +1 -0
  128. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.js +1 -0
  129. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.lean.js +1 -0
  130. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js +4 -0
  131. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js +1 -0
  132. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.js +16 -0
  133. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.lean.js +1 -0
  134. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.js +55 -0
  135. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.lean.js +1 -0
  136. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +23 -0
  137. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js +1 -0
  138. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.js +26 -0
  139. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.lean.js +1 -0
  140. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.js +14 -0
  141. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.lean.js +1 -0
  142. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js +20 -0
  143. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js +1 -0
  144. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.js +5 -0
  145. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.lean.js +1 -0
  146. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js +15 -0
  147. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js +1 -0
  148. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.js +12 -0
  149. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.lean.js +1 -0
  150. package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.js +1 -0
  151. package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.lean.js +1 -0
  152. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.js +1 -0
  153. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.lean.js +1 -0
  154. package/public/docs/assets/user-guide_understanding-findings.md.Dqt2H_C1.js +1 -0
  155. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +30 -0
  156. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +30 -0
  157. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +30 -0
  158. package/public/docs/contributor-guide/architecture/board-widgets.html +7 -6
  159. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +30 -0
  160. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +30 -0
  161. package/public/docs/contributor-guide/architecture/detector-contract.html +7 -6
  162. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
  163. package/public/docs/contributor-guide/architecture/drill-down.html +7 -6
  164. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +30 -0
  165. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +30 -0
  166. package/public/docs/contributor-guide/architecture/impact-estimation.html +7 -6
  167. package/public/docs/contributor-guide/architecture/index.html +7 -6
  168. package/public/docs/contributor-guide/architecture/overview.html +7 -6
  169. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +30 -0
  170. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +30 -0
  171. package/public/docs/contributor-guide/architecture/state-and-history.html +7 -6
  172. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +30 -0
  173. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +30 -0
  174. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +30 -0
  175. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +30 -0
  176. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +30 -0
  177. package/public/docs/contributor-guide/architecture/widget-rendering.html +7 -6
  178. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +30 -0
  179. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +30 -0
  180. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +35 -0
  181. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +30 -0
  182. package/public/docs/contributor-guide/architecture/worker-protocol.html +7 -11
  183. package/public/docs/contributor-guide/contributing.html +7 -6
  184. package/public/docs/contributor-guide/development-setup.html +11 -10
  185. package/public/docs/contributor-guide/testing.html +7 -6
  186. package/public/docs/hashmap.json +1 -1
  187. package/public/docs/index.html +5 -4
  188. package/public/docs/tuning-reference/anti-patterns.html +5 -4
  189. package/public/docs/tuning-reference/aqe.html +5 -4
  190. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +5 -4
  191. package/public/docs/tuning-reference/bottleneck-cold-start.html +5 -4
  192. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +5 -4
  193. package/public/docs/tuning-reference/bottleneck-failures.html +5 -4
  194. package/public/docs/tuning-reference/bottleneck-gc.html +5 -4
  195. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +5 -4
  196. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +5 -4
  197. package/public/docs/tuning-reference/bottleneck-retry-waste.html +5 -4
  198. package/public/docs/tuning-reference/bottleneck-shuffle.html +5 -4
  199. package/public/docs/tuning-reference/bottleneck-skew.html +5 -4
  200. package/public/docs/tuning-reference/bottleneck-slow-host.html +5 -4
  201. package/public/docs/tuning-reference/bottleneck-small-files.html +5 -4
  202. package/public/docs/tuning-reference/bottleneck-spill.html +5 -4
  203. package/public/docs/tuning-reference/bottleneck-straggler.html +5 -4
  204. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +5 -4
  205. package/public/docs/tuning-reference/bottleneck-utilization.html +5 -4
  206. package/public/docs/tuning-reference/caching.html +5 -4
  207. package/public/docs/tuning-reference/cluster-config.html +5 -4
  208. package/public/docs/tuning-reference/config.html +5 -4
  209. package/public/docs/tuning-reference/data-formats.html +5 -4
  210. package/public/docs/tuning-reference/index.html +5 -4
  211. package/public/docs/tuning-reference/intro.html +5 -4
  212. package/public/docs/tuning-reference/joins.html +5 -4
  213. package/public/docs/tuning-reference/memory-model.html +5 -4
  214. package/public/docs/tuning-reference/metrics.html +5 -4
  215. package/public/docs/tuning-reference/partitioning.html +5 -4
  216. package/public/docs/tuning-reference/pyspark.html +5 -4
  217. package/public/docs/tuning-reference/shuffle.html +5 -4
  218. package/public/docs/tuning-reference/spark-architecture.html +5 -4
  219. package/public/docs/tuning-reference/table-formats.html +5 -4
  220. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +33 -0
  221. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +30 -0
  222. package/public/docs/user-guide/alternative-log-retrieval.html +11 -7
  223. package/public/docs/user-guide/getting-started/ci-and-automation.html +51 -0
  224. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +30 -0
  225. package/public/docs/user-guide/getting-started/regression-metric-keys.html +30 -0
  226. package/public/docs/user-guide/getting-started/tuning-thresholds.html +33 -0
  227. package/public/docs/user-guide/getting-started.html +7 -11
  228. package/public/docs/user-guide/mcp-tools/compare-runs.html +84 -0
  229. package/public/docs/user-guide/mcp-tools/diagnose-run.html +52 -0
  230. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +55 -0
  231. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +43 -0
  232. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +49 -0
  233. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +34 -0
  234. package/public/docs/user-guide/mcp-tools/get-run-summary.html +44 -0
  235. package/public/docs/user-guide/mcp-tools/list-runs.html +41 -0
  236. package/public/docs/user-guide/mcp-tools.html +12 -126
  237. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
  238. package/public/docs/user-guide/run-comparison.html +7 -6
  239. package/public/docs/user-guide/understanding-findings.html +7 -6
  240. package/public/export-template.html +44 -44
  241. package/public/index.html +4 -4
  242. package/vendor-core/allocation.js +106 -0
  243. package/vendor-core/analyzer.js +13 -13
  244. package/vendor-core/api-merge-writes.js +80 -0
  245. package/vendor-core/cli/budgets.js +23 -9
  246. package/vendor-core/cli/collect-run.js +11 -4
  247. package/vendor-core/cli/regression-budgets.js +83 -0
  248. package/vendor-core/comparison-output.js +49 -0
  249. package/vendor-core/comparison-verdict.js +22 -22
  250. package/vendor-core/core-source-hash.txt +1 -1
  251. package/vendor-core/delta-commands.js +13 -0
  252. package/vendor-core/delta-log-paths.js +42 -0
  253. package/vendor-core/delta-targets.js +127 -0
  254. package/vendor-core/detectors.js +202 -68
  255. package/vendor-core/docs-content/detection/cache.md +3 -2
  256. package/vendor-core/docs-content/detection/cfg.md +9 -8
  257. package/vendor-core/docs-content/detection/chrn.md +1 -2
  258. package/vendor-core/docs-content/detection/cold.md +4 -2
  259. package/vendor-core/docs-content/detection/fail.md +3 -2
  260. package/vendor-core/docs-content/detection/gc.md +3 -2
  261. package/vendor-core/docs-content/detection/host.md +2 -1
  262. package/vendor-core/docs-content/detection/local.md +1 -1
  263. package/vendor-core/docs-content/detection/mem.md +5 -2
  264. package/vendor-core/docs-content/detection/plan.md +2 -1
  265. package/vendor-core/docs-content/detection/sfail.md +2 -1
  266. package/vendor-core/docs-content/detection/shape.md +5 -4
  267. package/vendor-core/docs-content/detection/skew.md +3 -1
  268. package/vendor-core/docs-content/detection/slow.md +2 -2
  269. package/vendor-core/docs-content/detection/spec.md +2 -3
  270. package/vendor-core/docs-content/detection/spill.md +1 -1
  271. package/vendor-core/docs-site-config.js +1 -1
  272. package/vendor-core/effective-conf.js +107 -0
  273. package/vendor-core/efficiency-model.js +8 -6
  274. package/vendor-core/event-handlers.js +255 -56
  275. package/vendor-core/event-schemas.js +5 -1
  276. package/vendor-core/evidence-report.js +31 -14
  277. package/vendor-core/finding-generic-recommendation.js +20 -1
  278. package/vendor-core/finding-names.js +7 -0
  279. package/vendor-core/finding-presentation.js +61 -26
  280. package/vendor-core/finding-tag-help.js +1 -1
  281. package/vendor-core/finding-types.js +12 -0
  282. package/vendor-core/format-utils.js +4 -3
  283. package/vendor-core/impact-estimator.js +20 -2
  284. package/vendor-core/impact-format.js +14 -13
  285. package/vendor-core/impact-model.js +27 -5
  286. package/vendor-core/ingest.js +4 -2
  287. package/vendor-core/list-runs.js +5 -2
  288. package/vendor-core/mcp-server-factory.js +13 -6
  289. package/vendor-core/mcp-tools.js +43 -34
  290. package/vendor-core/model-assembler.js +23 -1
  291. package/vendor-core/parser-worker.js +1 -1
  292. package/vendor-core/plan-graph-model.js +1 -1
  293. package/vendor-core/plan-summary.js +13 -10
  294. package/vendor-core/proxy.js +3 -1
  295. package/vendor-core/python-stage.js +25 -0
  296. package/vendor-core/recommendation-rollup.js +16 -9
  297. package/vendor-core/redact.js +51 -10
  298. package/vendor-core/remediation.js +20 -0
  299. package/vendor-core/run-comparison.js +118 -140
  300. package/vendor-core/run-interpretation.js +2 -1
  301. package/vendor-core/run-metrics.js +198 -0
  302. package/vendor-core/run-output.js +26 -0
  303. package/vendor-core/run-totals.js +24 -0
  304. package/vendor-core/run-verdict.js +3 -4
  305. package/vendor-core/scorecard-estimates.js +1 -0
  306. package/vendor-core/session-snapshot.js +7 -0
  307. package/vendor-core/shs-schemas.js +2 -2
  308. package/vendor-core/spark-memory.js +17 -0
  309. package/vendor-core/stage-alignment.js +262 -0
  310. package/vendor-core/stage-identity.js +112 -0
  311. package/vendor-core/stage-plan-nodes.js +18 -0
  312. package/vendor-core/stage-quantiles.js +4 -0
  313. package/vendor-core/types.js +54 -1
  314. package/vendor-core/wasted-core-hours.js +10 -7
  315. package/vendor-core/write-target-args.js +48 -0
  316. package/vendor-core/write-targets.js +295 -0
  317. package/public/assets/AutoscalingChurn-CUSIvvab.js +0 -1
  318. package/public/assets/CacheUtilization-Ce0K6_X2.js +0 -1
  319. package/public/assets/CachingOpportunity-Cn-LmRA4.js +0 -1
  320. package/public/assets/ColdStart-BoiYzEyV.js +0 -1
  321. package/public/assets/ConfigAudit-BoJc6PAw.js +0 -1
  322. package/public/assets/CoreUsageArea-BFfXKu7K.js +0 -1
  323. package/public/assets/Dashboard-Bo_1jGqH.js +0 -2
  324. package/public/assets/DisclosureContext-LYzHroHO.js +0 -1
  325. package/public/assets/DuplicatePlanSubtree-BaAuGAoo.js +0 -1
  326. package/public/assets/ExecutorUtilization-OE6VMFkG.js +0 -1
  327. package/public/assets/GcPressure-AoL_ZNrV.js +0 -1
  328. package/public/assets/IncompleteRun-Bv6IzgsQ.js +0 -1
  329. package/public/assets/JobFailures-DF3U2z1f.js +0 -1
  330. package/public/assets/MemoryUtilization-B65OVZlq.js +0 -1
  331. package/public/assets/OverBroadcast-DXbJREDl.js +0 -1
  332. package/public/assets/PartitionSizing-DZLnjy0d.js +0 -1
  333. package/public/assets/PlanGraphRoute-C-BGmC6j.js +0 -1
  334. package/public/assets/RetryWaste-BbEPsdMw.js +0 -1
  335. package/public/assets/ShuffleIO-wZB94LS8.js +0 -1
  336. package/public/assets/Skew-SpQE7zeJ.js +0 -1
  337. package/public/assets/SlowHost-DwDBCTvr.js +0 -1
  338. package/public/assets/SmallFiles-yjY_6sT1.js +0 -1
  339. package/public/assets/SpeculationWaste-73PDmj0c.js +0 -1
  340. package/public/assets/Spill-DEw0Kd1i.js +0 -1
  341. package/public/assets/StageFailed-BEfXdEJc.js +0 -1
  342. package/public/assets/StageFindingGroup-Bzm2TY03.js +0 -1
  343. package/public/assets/StageShape-CzR4jHOt.js +0 -1
  344. package/public/assets/StageSlowness-vMPsnAaa.js +0 -1
  345. package/public/assets/Straggler-Cf__OGl5.js +0 -1
  346. package/public/assets/TaskFailures-BZ8kLZiO.js +0 -1
  347. package/public/assets/UnderBroadcast-CBm8ZPoS.js +0 -1
  348. package/public/assets/index-CEM9bz_x.js +0 -13
  349. package/public/assets/parser-worker-Q5KCPdKT.js +0 -122
  350. package/public/assets/useRecentFiles-BrBl-AP1.js +0 -11
  351. package/public/docs/assets/chunks/@localSearchIndexroot.DbZy5EtM.js +0 -1
  352. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.VTcYTH4d.js +0 -1
  353. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.VTcYTH4d.lean.js +0 -1
  354. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CsqZHz_T.js +0 -1
  355. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CsqZHz_T.lean.js +0 -1
  356. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CFNCE8JB.js +0 -1
  357. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CFNCE8JB.lean.js +0 -1
  358. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.HT9IJGIl.js +0 -1
  359. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.HT9IJGIl.lean.js +0 -1
  360. package/public/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.js +0 -1
  361. package/public/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.lean.js +0 -1
  362. package/public/docs/assets/contributor-guide_architecture_overview.md.CTEqVeV4.js +0 -1
  363. package/public/docs/assets/contributor-guide_architecture_overview.md.CTEqVeV4.lean.js +0 -1
  364. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DOTVHjTV.js +0 -1
  365. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BmpARZve.js +0 -1
  366. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BmpARZve.lean.js +0 -1
  367. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.21pTYhFF.js +0 -6
  368. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.21pTYhFF.lean.js +0 -1
  369. package/public/docs/assets/contributor-guide_contributing.md.B2E9ni79.js +0 -1
  370. package/public/docs/assets/contributor-guide_development-setup.md.CjgZJwjK.js +0 -15
  371. package/public/docs/assets/contributor-guide_development-setup.md.CjgZJwjK.lean.js +0 -1
  372. package/public/docs/assets/contributor-guide_testing.md.DTwhpoN2.js +0 -1
  373. package/public/docs/assets/user-guide_alternative-log-retrieval.md.ZHPSbBkF.js +0 -1
  374. package/public/docs/assets/user-guide_alternative-log-retrieval.md.ZHPSbBkF.lean.js +0 -1
  375. package/public/docs/assets/user-guide_getting-started.md.CEa2ZX-l.js +0 -6
  376. package/public/docs/assets/user-guide_getting-started.md.CEa2ZX-l.lean.js +0 -1
  377. package/public/docs/assets/user-guide_mcp-tools.md.dQwIvdTF.js +0 -131
  378. package/public/docs/assets/user-guide_mcp-tools.md.dQwIvdTF.lean.js +0 -1
  379. package/public/docs/assets/user-guide_run-comparison.md.ChLI3MK0.js +0 -1
  380. package/public/docs/assets/user-guide_run-comparison.md.ChLI3MK0.lean.js +0 -1
  381. package/public/docs/assets/user-guide_understanding-findings.md.D1ccs60C.js +0 -1
  382. /package/public/docs/assets/{user-guide_understanding-findings.md.D1ccs60C.lean.js → user-guide_understanding-findings.md.Dqt2H_C1.lean.js} +0 -0
package/public/index.html CHANGED
@@ -23,12 +23,12 @@
23
23
  document.documentElement.setAttribute('data-theme', 'light');
24
24
  } catch (_) {}
25
25
  </script>
26
- <script type="module" crossorigin src="./assets/index-CEM9bz_x.js"></script>
26
+ <script type="module" crossorigin src="./assets/index-DCeWge12.js"></script>
27
27
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-hePW80VL.js">
28
28
  <link rel="modulepreload" crossorigin href="./assets/charts-vendor-0VMaLVLN.js">
29
- <link rel="modulepreload" crossorigin href="./assets/plan-graph-vendor-B0sEms9Z.js">
30
- <link rel="modulepreload" crossorigin href="./assets/react-vendor-BAKt3vny.js">
31
- <link rel="modulepreload" crossorigin href="./assets/DisclosureContext-LYzHroHO.js">
29
+ <link rel="modulepreload" crossorigin href="./assets/plan-graph-vendor-BYl-AzN5.js">
30
+ <link rel="modulepreload" crossorigin href="./assets/react-vendor-DEdfrRTJ.js">
31
+ <link rel="modulepreload" crossorigin href="./assets/DisclosureContext-B0siMVdg.js">
32
32
  <link rel="stylesheet" crossorigin href="./assets/plan-graph-vendor-DLioOiRN.css">
33
33
  <link rel="stylesheet" crossorigin href="./assets/index-BcqTLKFp.css">
34
34
  </head>
@@ -0,0 +1,106 @@
1
+ import { parseSparkMemoryMB } from './spark-memory.js';
2
+
3
+
4
+ const MS_PER_HOUR = 3_600_000;
5
+ const MIB_PER_GIB = 1024;
6
+ // Spark's documented floor and factor for the default executor memory overhead
7
+ // (spark.executor.memoryOverhead = max(factor * executor memory, 384 MiB)).
8
+ const MIN_OVERHEAD_MIB = 384;
9
+ const DEFAULT_OVERHEAD_FACTOR = 0.1;
10
+
11
+
12
+  
13
+
14
+  
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+ /** The latest timestamp the log records: the application start and end and every stage and
26
+ * executor event. Where a log with no ApplicationEnd stops. */
27
+ export function lastObservedTimestamp(input ) {
28
+ let last = null;
29
+ const take = (t ) => {
30
+ if (typeof t === 'number' && Number.isFinite(t) && t > 0 && (last == null || t > last)) last = t;
31
+ };
32
+ take(input.app?.startTime);
33
+ take(input.app?.endTime);
34
+ for (const s of input.stages.values()) { take(s.submittedAt); take(s.completedAt); }
35
+ for (const e of [...input.executors.added, ...input.executors.removed]) take(e.timestamp);
36
+ return last;
37
+ }
38
+
39
+ // Spark's default executor memory when spark.executor.memory is unset.
40
+ const DEFAULT_EXECUTOR_MEMORY_MIB = 1024;
41
+
42
+ // One container's memory in MiB, as Spark requests it from the cluster manager: executor heap,
43
+ // plus overhead, plus off-heap and PySpark worker memory when configured. Null when the log
44
+ // records no Spark properties (nothing to tell a default from a missing config) or a memory key it
45
+ // does record cannot be read.
46
+ function executorMemoryMiB(app ) {
47
+ const config = app?.config;
48
+ if (config == null) return null;
49
+ // undefined: key absent; null: present but unreadable.
50
+ const mib = (key ) => (config[key] == null ? undefined : parseSparkMemoryMB(config[key]));
51
+ const heap = mib('spark.executor.memory') ?? (config['spark.executor.memory'] == null ? DEFAULT_EXECUTOR_MEMORY_MIB : null);
52
+ if (heap == null) return null;
53
+
54
+ let overhead = mib('spark.executor.memoryOverhead');
55
+ if (overhead === undefined) overhead = mib('spark.yarn.executor.memoryOverhead'); // legacy key
56
+ if (overhead === undefined) {
57
+ const factor = Number.parseFloat(config['spark.executor.memoryOverheadFactor'] ?? '');
58
+ overhead = Math.max(MIN_OVERHEAD_MIB, Math.round(heap * (Number.isFinite(factor) && factor > 0 ? factor : DEFAULT_OVERHEAD_FACTOR)));
59
+ }
60
+ const offHeap = String(config['spark.memory.offHeap.enabled']).toLowerCase() === 'true' ? mib('spark.memory.offHeap.size') ?? 0 : 0;
61
+ const pyspark = mib('spark.executor.pyspark.memory') ?? 0;
62
+ if (overhead === null || offHeap === null || pyspark === null) return null;
63
+ return heap + overhead + offHeap + pyspark;
64
+ }
65
+
66
+ /** Allocated core-hours and memory GiB-hours from the executor lifecycle: each executor counts
67
+ * from its ExecutorAdded timestamp to its first later ExecutorRemoved timestamp. One with no
68
+ * removal closes at the application end when the log has one, else at the last timestamp the log
69
+ * records (a cut-off log). Cores are the ExecutorAdded event's Total Cores, else
70
+ * spark.executor.cores; memory per executor is spark.executor.memory (default 1g) plus the
71
+ * overhead (spark.executor.memoryOverhead, else the legacy spark.yarn.executor.memoryOverhead,
72
+ * else the larger of 384 MiB and spark.executor.memoryOverheadFactor, default 0.1, times the
73
+ * memory), plus spark.memory.offHeap.size when spark.memory.offHeap.enabled is true, plus
74
+ * spark.executor.pyspark.memory.
75
+ * Null, never 0, for a figure whose inputs the log lacks. */
76
+ export function computeAllocation(input ) {
77
+ const added = input.executors.added.filter((e) => e.kind === 'added');
78
+ if (added.length === 0) return { coreHours: null, memoryGbHours: null };
79
+ const removedAt = new Map ();
80
+ for (const e of input.executors.removed) {
81
+ if (e.kind !== 'removed') continue;
82
+ const times = removedAt.get(e.executorId);
83
+ if (times) times.push(e.timestamp); else removedAt.set(e.executorId, [e.timestamp]);
84
+ }
85
+ const closeAt = input.app?.endTime ?? lastObservedTimestamp(input);
86
+ const configuredCores = Number.parseInt(input.app?.config?.['spark.executor.cores'] ?? '', 10);
87
+ const memoryMiB = executorMemoryMiB(input.app);
88
+
89
+ let coreMs = 0;
90
+ let memoryMiBMs = 0;
91
+ let coresKnown = true;
92
+ const seen = new Set ();
93
+ for (const e of added) {
94
+ if (seen.has(e.executorId)) continue; // a replayed ExecutorAdded is the same executor
95
+ seen.add(e.executorId);
96
+ const removal = (removedAt.get(e.executorId) ?? []).filter((t) => t >= e.timestamp).sort((a, b) => a - b)[0];
97
+ const aliveMs = Math.max(0, (removal ?? closeAt ?? e.timestamp) - e.timestamp);
98
+ const cores = e.totalCores > 0 ? e.totalCores : Number.isFinite(configuredCores) ? configuredCores : null;
99
+ if (cores == null) coresKnown = false; else coreMs += cores * aliveMs;
100
+ memoryMiBMs += (memoryMiB ?? 0) * aliveMs;
101
+ }
102
+ return {
103
+ coreHours: coresKnown ? coreMs / MS_PER_HOUR : null,
104
+ memoryGbHours: memoryMiB != null ? memoryMiBMs / MIB_PER_GIB / MS_PER_HOUR : null,
105
+ };
106
+ }
@@ -88,32 +88,32 @@ export function findingId(f ) {
88
88
  return fnv1a(`${f.type}|${locationKey(f)}|${f.metric ?? ''}|${f.value ?? f.valueText ?? ''}|${disc}`);
89
89
  }
90
90
 
91
- // skew's max/median branch (stage.taskCount below minTasksForP95) and straggler are both driven
92
- // by the identical (taskDurationMax - taskDurationP50) delta on the same stage: the same
93
- // dominant outlier task reported by two detectors, each independently clipped (see "Overlap
94
- // caveat: skew / straggler" in impact-estimation.md). skew's P95/median branch samples a
95
- // different task and stays independent. Flags both sides via validationRequired (rather than
96
- // suppressing either) so neither finding's own diagnostic value is lost; the flag rides the same
91
+ // skew (either branch) and straggler both claim the stage's replayed tail recovery
92
+ // (tailReplayRecoveryMs via tailRecoveryMs): the same slow-task tail reported by two detectors
93
+ // (see "Overlap caveat: skew / straggler" in impact-estimation/caveats-tuning-and-coverage.md). skew's branch only changes
94
+ // the fallback single-task delta on a stage without the replay, so every skew + straggler pair
95
+ // on a stage is flagged. Flags both sides via validationRequired (rather than suppressing
96
+ // either) so neither finding's own diagnostic value is lost; the flag rides the same
97
97
  // confidence-caveat UI a reader already sees before trusting either finding's magnitude.
98
98
  function overlapNote(otherType ) {
99
- return `This overlaps with the ${otherType} finding on this stage: both are driven by the same dominant outlier task, so don't add their recoverable-time figures together.`;
99
+ return `This overlaps with the ${otherType} finding on this stage: both measure the same slow-task tail, so don't add their recoverable-time figures together.`;
100
100
  }
101
101
 
102
102
  function flagSkewStragglerOverlap(findings ) {
103
- const maxMedianSkewStages = new Set(
104
- findings.filter((f) => f.type === 'skew' && f.metric === 'max/median' && f.stageId != null).map((f) => f.stageId),
103
+ const skewStages = new Set(
104
+ findings.filter((f) => f.type === 'skew' && f.stageId != null).map((f) => f.stageId),
105
105
  );
106
- if (maxMedianSkewStages.size === 0) return;
106
+ if (skewStages.size === 0) return;
107
107
  const stragglerStages = new Set(
108
108
  findings.filter((f) => f.type === 'straggler' && f.stageId != null).map((f) => f.stageId),
109
109
  );
110
- const overlapStages = new Set([...maxMedianSkewStages].filter((id) => stragglerStages.has(id)));
110
+ const overlapStages = new Set([...skewStages].filter((id) => stragglerStages.has(id)));
111
111
  if (overlapStages.size === 0) return;
112
112
  for (const f of findings) {
113
113
  if (f.stageId == null || !overlapStages.has(f.stageId)) continue;
114
114
  const note = f.type === 'skew' ? overlapNote('straggler') : f.type === 'straggler' ? overlapNote('skew') : null;
115
115
  if (!note) continue;
116
- f.validationRequired = f.validationRequired ? `${f.validationRequired} ${note}` : note;
116
+ f.validationRequired = [f.validationRequired, note].filter(Boolean).join(' ');
117
117
  }
118
118
  }
119
119
 
@@ -201,7 +201,7 @@ export function analyze(
201
201
  // One occupancy sweep per analysis, shared by the detectors' runtime floors and every entry's
202
202
  // estimate(), so a floor gates on the same occupancy-clipped figure displayed as savings.
203
203
  const impact = {
204
- stages, totalCores,
204
+ stages, totalCores, sql,
205
205
  occupancy: computeOccupancy(stages , totalCores),
206
206
  };
207
207
  // The one cast from the posted-model types to the detector-side shapes: types.ts's Stage and
@@ -0,0 +1,80 @@
1
+ // DeltaTable API merges run no command node: each internal query is its own root execution, tied
2
+ // together only by a "MERGE operation" description and adjacent ids. One DeltaMerge write is
3
+ // reported per run of such executions, with a path only when no other merge can be mixed into it.
4
+ import { soleLogPath } from './delta-log-paths.js';
5
+
6
+
7
+
8
+
9
+
10
+ const API_MERGE_COMMAND = 'DeltaMerge';
11
+
12
+ // Delta labels its MERGE sub-queries "MERGE operation - <phase>", prefixed "Delta: " in some.
13
+ const MERGE_DESCRIPTION = /^(?:Delta: )?MERGE operation\b/;
14
+
15
+ // The sub-queries that write: "writing new files ...", "Rewriting N files". A group without one
16
+ // only scanned, and a scan may read the merge's source rather than its target.
17
+ const MERGE_WRITE_PHASE = /\b(?:re)?writing\b/i;
18
+
19
+
20
+
21
+ // Selected executions have a MERGE description. A SQL MERGE's own sub-queries carry the same
22
+ // description but belong to the MergeIntoCommand execution they share a root with, which is
23
+ // attributed through its own node (resolveDeltaCommandTarget), so they are left out. A sub-query
24
+ // of an API merge may have another of its executions as its root, so a root alone does not exclude.
25
+ function selectMergeExecutions(sql ) {
26
+ const isSqlMergeRoot = (id ) =>
27
+ id !== undefined && /^Execute\s+MergeIntoCommand\b/.test(sql.get(id)?.planTree?.name ?? '');
28
+ return [...sql.values()]
29
+ .filter((exec) => MERGE_DESCRIPTION.test(exec.description ?? '') && !isSqlMergeRoot(exec.rootExecutionId))
30
+ .sort((a, b) => a.id - b.id);
31
+ }
32
+
33
+ // A run of consecutive execution ids. Nothing in the log ties the members of one merge together,
34
+ // so adjacency is a guess, and a wrong one fuses or splits merges. Each way it can go wrong is
35
+ // marked `unclean` instead of attributed:
36
+ // - the ids of two merges that ran concurrently interleave, so their start-time spans overlap;
37
+ // - a start time is missing, so the span is unknown.
38
+ // - no member writes, so the one path it names may be a source's.
39
+ // Merges that ran back to back with no other execution between them still fuse into one run; the
40
+ // run then names two paths and gets no target.
41
+ function groupMerges(selected ) {
42
+ const groups = [];
43
+ for (const exec of selected) {
44
+ const last = groups[groups.length - 1];
45
+ if (last && last.members[last.members.length - 1].id + 1 === exec.id) last.members.push(exec);
46
+ else groups.push({ members: [exec], unclean: null });
47
+ }
48
+ const spans = groups.map(({ members }) => {
49
+ const starts = members.map((m) => m.startTime);
50
+ return starts.every((t) => typeof t === 'number') ? [Math.min(...starts), Math.max(...starts)] : null;
51
+ });
52
+ groups.forEach((group, i) => {
53
+ const mine = spans[i];
54
+ if (mine === null) { group.unclean = 'start time missing'; return; }
55
+ const overlaps = spans.some((other, j) => j !== i && other !== null && other[0] <= mine[1] && mine[0] <= other[1]);
56
+ if (overlaps) group.unclean = 'overlaps another merge in time';
57
+ else if (!group.members.some((m) => MERGE_WRITE_PHASE.test(m.description ?? ''))) group.unclean = 'no write phase';
58
+ });
59
+ return groups;
60
+ }
61
+
62
+ /** One DeltaMerge write per run of API merge executions; null target unless one path is certain. */
63
+ export function apiMergeWrites(sql ) {
64
+ return groupMerges(selectMergeExecutions(sql)).map((group) => {
65
+ const first = group.members[0].id;
66
+ const last = group.members[group.members.length - 1].id;
67
+ const resolved = group.unclean === null ? soleLogPath(group.members) : null;
68
+ const span = first === last ? `${first}` : `${first}-${last}`;
69
+ return {
70
+ sqlExecutionId: first,
71
+ nodeId: null,
72
+ command: API_MERGE_COMMAND,
73
+ recognized: true,
74
+ kind: resolved?.kind ?? null,
75
+ target: resolved?.target ?? null,
76
+ outputRows: null,
77
+ raw: `Delta MERGE operation, executions ${span}${group.unclean ? ` (${group.unclean})` : ''}`,
78
+ };
79
+ });
80
+ }
@@ -4,6 +4,7 @@ import { effectiveThresholds } from '../threshold-overrides.js';
4
4
  import { IMPACT_BAND_ORDER } from '../format-utils.js';
5
5
 
6
6
 
7
+
7
8
 
8
9
  const IMPACT_BANDS = Object.keys(IMPACT_BAND_ORDER) ;
9
10
 
@@ -11,12 +12,16 @@ const IMPACT_BANDS = Object.keys(IMPACT_BAND_ORDER) ;
11
12
 
12
13
 
13
14
 
15
+
16
+
14
17
 
15
18
 
16
19
 
17
20
 
18
21
 
19
22
 
23
+
24
+
20
25
 
21
26
 
22
27
  // The skew entry's minTasksForP95 under the run's overrides, so the budget measures the same
@@ -107,7 +112,8 @@ function checkEfficiency(appModel , minPct ) {
107
112
  }
108
113
  const model = computeEfficiencyModel({
109
114
  app: appModel.app, stages: appModel.stages,
110
- executorsAdded: appModel.executors.added, runAggregates: appModel.runAggregates,
115
+ executorsAdded: appModel.executors.added, executorsRemoved: appModel.executors.removed,
116
+ runAggregates: appModel.runAggregates,
111
117
  });
112
118
  if (model.wastagePct == null) {
113
119
  return { name: 'min-efficiency', status: 'inconclusive', detail: 'Busy core time could not be computed (no available compute hours).' };
@@ -121,23 +127,23 @@ function checkEfficiency(appModel , minPct ) {
121
127
  function checkRegression(comparison , maxRegressionPct , regressionMetric ) {
122
128
  const row = comparison.metrics.find((m) => m.key === regressionMetric);
123
129
  if (!row || row.direction === 'unavailable' || row.baseline == null || row.delta == null) {
124
- return { name: 'max-regression', status: 'inconclusive', detail: `Metric "${regressionMetric}" is unavailable for this comparison.` };
130
+ return { name: 'max-regression', metric: regressionMetric, status: 'inconclusive', detail: `Metric "${regressionMetric}" is unavailable for this comparison.` };
125
131
  }
126
132
  // A neutral-direction metric (inputBytes/outputBytes/taskCount/executorsAdded) measures
127
133
  // workload volume, not performance: an increase isn't a regression, so no direction to check.
128
134
  if (row.direction === 'neutral') {
129
- return { name: 'max-regression', status: 'inconclusive', detail: `Metric "${regressionMetric}" measures workload volume, not performance: it has no regression direction to check.` };
135
+ return { name: 'max-regression', metric: regressionMetric, status: 'inconclusive', detail: `Metric "${regressionMetric}" measures workload volume, not performance: it has no regression direction to check.` };
130
136
  }
131
137
  if (row.direction !== 'regression') {
132
- return { name: 'max-regression', status: 'pass', detail: `Metric "${regressionMetric}" did not regress (${row.direction}).` };
138
+ return { name: 'max-regression', metric: regressionMetric, status: 'pass', detail: `Metric "${regressionMetric}" did not regress (${row.direction}).` };
133
139
  }
134
140
  const pct = row.baseline === 0 ? Infinity : Math.abs(row.delta / row.baseline) * 100;
135
141
  const pctLabel = row.baseline === 0
136
142
  ? `regressed from 0 to ${row.delta} (was absent/zero in baseline)`
137
143
  : `regressed ${pct.toFixed(1)}%`;
138
144
  return pct > maxRegressionPct
139
- ? { name: 'max-regression', status: 'violation', detail: `Metric "${regressionMetric}" ${pctLabel}, exceeding budget ${maxRegressionPct}%.` }
140
- : { name: 'max-regression', status: 'pass', detail: `Metric "${regressionMetric}" ${pctLabel}, within budget ${maxRegressionPct}%.` };
145
+ ? { name: 'max-regression', metric: regressionMetric, status: 'violation', detail: `Metric "${regressionMetric}" ${pctLabel}, exceeding budget ${maxRegressionPct}%.` }
146
+ : { name: 'max-regression', metric: regressionMetric, status: 'pass', detail: `Metric "${regressionMetric}" ${pctLabel}, within budget ${maxRegressionPct}%.` };
141
147
  }
142
148
 
143
149
  function checkFailOnIntroduced(comparison , band ) {
@@ -158,8 +164,12 @@ function pushComparisonBudget(
158
164
  comparison ,
159
165
  name ,
160
166
  check ,
167
+ metric ,
161
168
  ) {
162
- results.push(comparison ? check(comparison) : { name, status: 'inconclusive', detail: 'No baseline comparison available to evaluate this budget.' });
169
+ results.push(comparison ? check(comparison) : {
170
+ name, status: 'inconclusive', detail: 'No baseline comparison available to evaluate this budget.',
171
+ ...(metric !== undefined ? { metric } : {}),
172
+ });
163
173
  }
164
174
 
165
175
  /** `thresholds`: the overrides the catalog was analyzed with, so a budget that recomputes a
@@ -177,12 +187,16 @@ export function evaluateBudgets({ appModel, catalog, budgets, comparison, thresh
177
187
  // Guarded here so any evaluateBudgets caller benefits: regressionMetric without
178
188
  // maxRegressionPct would otherwise skip the `if` silently, reporting nothing.
179
189
  if (budgets.regressionMetric !== undefined && budgets.maxRegressionPct === undefined) {
180
- results.push({ name: 'max-regression', status: 'inconclusive', detail: `regressionMetric "${budgets.regressionMetric}" was set without maxRegressionPct; the regression budget was not evaluated.` });
190
+ results.push({ name: 'max-regression', metric: budgets.regressionMetric, status: 'inconclusive', detail: `regressionMetric "${budgets.regressionMetric}" was set without maxRegressionPct; the regression budget was not evaluated.` });
181
191
  } else if (budgets.maxRegressionPct !== undefined) {
182
192
  // `!== undefined`, not Number.isFinite: a zero-baseline regression's pct is Infinity,
183
193
  // so an "unlimited" budget is a legitimate input (the CLI already rejects non-finite flags).
184
194
  pushComparisonBudget(results, comparison, 'max-regression',
185
- (c) => checkRegression(c, budgets.maxRegressionPct , budgets.regressionMetric ?? 'wallClock'));
195
+ (c) => checkRegression(c, budgets.maxRegressionPct , budgets.regressionMetric ?? 'wallClock'),
196
+ budgets.regressionMetric ?? 'wallClock');
197
+ }
198
+ for (const { metric, maxPct } of budgets.regressionBudgets ?? []) {
199
+ pushComparisonBudget(results, comparison, 'max-regression', (c) => checkRegression(c, maxPct, metric), metric);
186
200
  }
187
201
  if (budgets.failOnIntroduced !== undefined) {
188
202
  pushComparisonBudget(results, comparison, 'fail-on-introduced',
@@ -5,6 +5,7 @@ import { nodeParseCodecs } from './native-zstd.js';
5
5
  import { createModelCallbacks } from '../model-assembler.js';
6
6
  import { routeMessage, } from '../ingest.js';
7
7
 
8
+ import { mcpError } from '../mcp-error.js';
8
9
 
9
10
  // No whole-file arrayBuffer(): the parser only ever reads bounded slices, and a
10
11
  // whole-file read is what capped local logs at 2 GiB.
@@ -129,7 +130,10 @@ export function collectViaDispatch(
129
130
  return new Promise((resolve, reject) => {
130
131
  const handlers = {
131
132
  ...cb,
132
- onDone: (msg ) => resolve({ appModel, skippedLines: (msg )?.skippedLines ?? 0 }),
133
+ onDone: (msg ) => {
134
+ cb.onDone(msg); // records the parse gaps on the model, as the dashboard's ingest does
135
+ resolve({ appModel, skippedLines: (msg )?.skippedLines ?? 0 });
136
+ },
133
137
  onError: (msg ) => reject(onDecodeError(msg)),
134
138
  };
135
139
  const emit = (msg ) => dispatch(msg, handlers);
@@ -149,10 +153,13 @@ export async function collectRun(inputPath )
149
153
  return file;
150
154
  };
151
155
  try {
156
+ // A file or folder that isn't a decodable event log rejects with invalid-event-log, the code
157
+ // shs-load.ts gives an archive that fails to decode, so MCP reports both the same way. The
158
+ // CLI reads only the message.
152
159
  return await collectViaDispatch((state, emit, reject) => {
153
160
  if (stat.isDirectory()) {
154
161
  if (!isRollingLogDirectory(inputPath)) {
155
- reject(new Error("This isn't a Spark rolling event-log directory. Pass a single event-log file instead."));
162
+ reject(mcpError('invalid-event-log', "This isn't a Spark rolling event-log directory. Pass a single event-log file instead."));
156
163
  return;
157
164
  }
158
165
  const names = readdirSync(inputPath);
@@ -160,7 +167,7 @@ export async function collectRun(inputPath )
160
167
  try {
161
168
  ordered = reassembleRollingEntries(names);
162
169
  } catch (e) {
163
- reject(e);
170
+ reject(mcpError('invalid-event-log', (e ).message));
164
171
  return;
165
172
  }
166
173
  const files = ordered.map((name) => open(join(inputPath, name)));
@@ -170,7 +177,7 @@ export async function collectRun(inputPath )
170
177
  } else {
171
178
  runParse(open(inputPath), state, { emit, ...nodeParseCodecs }).catch(reject);
172
179
  }
173
- }, (msg) => new Error((msg ).message));
180
+ }, (msg) => mcpError('invalid-event-log', (msg ).message));
174
181
  } finally {
175
182
  for (const file of opened) file.close();
176
183
  }
@@ -0,0 +1,83 @@
1
+ // Parses the regression budgets a CLI caller lists: repeated `--regression-budget <metric>:<pct>`
2
+ // flags and a `--budgets <file.json>` file. Every failure throws an Error naming the problem, so
3
+ // the caller refuses to run (exit 2) instead of quietly dropping a budget the user meant to gate on.
4
+ import { readFileSync } from 'node:fs';
5
+ import { resolve } from 'node:path';
6
+ import { COMPARISON_METRIC_KEYS } from '../run-comparison.js';
7
+
8
+
9
+
10
+
11
+ // Plain non-negative decimals only: Number() would also accept '', ' ', '0x10' and '1e3'.
12
+ const PCT_PATTERN = /^\d+(\.\d+)?$/;
13
+
14
+ function assertKnownMetric(metric , origin ) {
15
+ if (!COMPARISON_METRIC_KEYS.includes(metric)) {
16
+ throw new Error(`${origin}: unknown metric "${metric}" (expected one of: ${COMPARISON_METRIC_KEYS.join(', ')}).`);
17
+ }
18
+ }
19
+
20
+ /** One `--regression-budget` value, `<metric>:<pct>`. */
21
+ export function parseRegressionBudgetFlag(spec ) {
22
+ const origin = `--regression-budget "${spec}"`;
23
+ const colon = spec.indexOf(':');
24
+ if (colon === -1) throw new Error(`${origin}: expected <metric>:<pct>.`);
25
+ const metric = spec.slice(0, colon);
26
+ const pct = spec.slice(colon + 1);
27
+ assertKnownMetric(metric, origin);
28
+ if (!PCT_PATTERN.test(pct)) throw new Error(`${origin}: "${pct}" is not a non-negative percentage.`);
29
+ return { metric, maxPct: Number(pct) };
30
+ }
31
+
32
+ /** The parsed `--budgets` JSON: `{"regression": {"<metric>": <pct>}}`. */
33
+ export function parseBudgetsFile(raw , origin ) {
34
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
35
+ throw new Error(`${origin}: expected a JSON object like {"regression": {"wallClock": 10}}.`);
36
+ }
37
+ const unknownKeys = Object.keys(raw).filter((k) => k !== 'regression');
38
+ if (unknownKeys.length > 0) {
39
+ throw new Error(`${origin}: unknown key ${unknownKeys.map((k) => `"${k}"`).join(', ')} (the only key is "regression").`);
40
+ }
41
+ const regression = (raw ).regression;
42
+ if (regression === null || typeof regression !== 'object' || Array.isArray(regression)) {
43
+ throw new Error(`${origin}: "regression" must be an object mapping a metric key to a percentage.`);
44
+ }
45
+ return Object.entries(regression).map(([metric, pct]) => {
46
+ assertKnownMetric(metric, origin);
47
+ if (typeof pct !== 'number' || !Number.isFinite(pct) || pct < 0) {
48
+ throw new Error(`${origin}: "${metric}" must be a non-negative number (a percentage), got ${JSON.stringify(pct)}.`);
49
+ }
50
+ return { metric, maxPct: pct };
51
+ });
52
+ }
53
+
54
+ export function loadBudgetsFile(path ) {
55
+ const fullPath = resolve(path);
56
+ let text ;
57
+ try {
58
+ text = readFileSync(fullPath, 'utf8');
59
+ } catch (e) {
60
+ throw new Error(`Cannot read budgets file ${fullPath}: ${(e ).message}`, { cause: e });
61
+ }
62
+ let raw ;
63
+ try {
64
+ raw = JSON.parse(text);
65
+ } catch (e) {
66
+ throw new Error(`Budgets file ${fullPath} is not valid JSON: ${(e ).message}`, { cause: e });
67
+ }
68
+ return parseBudgetsFile(raw, `Budgets file ${fullPath}`);
69
+ }
70
+
71
+ /** Checks that no metric is budgeted twice across the sources (the legacy
72
+ * `--max-regression-pct`/`--regression-metric` pair counts as one), and returns the budgets. */
73
+ export function combineRegressionBudgets(sources ) {
74
+ const seen = new Map ();
75
+ for (const { origin, budget } of sources) {
76
+ const first = seen.get(budget.metric);
77
+ if (first !== undefined) {
78
+ throw new Error(`Metric "${budget.metric}" has two regression budgets (${first} and ${origin}); give each metric one.`);
79
+ }
80
+ seen.set(budget.metric, origin);
81
+ }
82
+ return sources.map((s) => s.budget);
83
+ }
@@ -0,0 +1,49 @@
1
+ import { buildComparison, } from './run-comparison.js';
2
+ import { comparisonVerdict, } from './comparison-verdict.js';
3
+ import { redactComparison } from './redact.js';
4
+
5
+
6
+
7
+
8
+ // The comparison a surface reports: the CLI's `comparison` object and the shared part of MCP
9
+ // compare_runs. A field added here reaches every surface.
10
+ // `stagePairs` is the one large field (a row per paired stage), so the `summary` view leaves it out.
11
+
12
+
13
+
14
+
15
+
16
+
17
+ /** `full` carries every field (the CLI); `summary` leaves out `stagePairs` (MCP, which returns them
18
+ * only when asked). */
19
+
20
+
21
+ /** The reported projection of a comparison. Key order is the CLI's JSON order. */
22
+ export function comparisonOutput(comparison , view = 'full') {
23
+ return {
24
+ verdict: comparisonVerdict(comparison),
25
+ confidence: comparison.confidence,
26
+ reason: comparison.reason,
27
+ matchedCoverage: comparison.matchedCoverage,
28
+ runtimeCoverage: comparison.runtimeCoverage,
29
+ metrics: comparison.metrics,
30
+ findings: comparison.findings,
31
+ comparisonSchemaVersion: comparison.comparisonSchemaVersion,
32
+ ...(view === 'full' ? { stagePairs: comparison.stagePairs } : {}),
33
+ unmatched: comparison.unmatched,
34
+ replanned: comparison.replanned,
35
+ bookkeepingStageIds: comparison.bookkeepingStageIds,
36
+ };
37
+ }
38
+
39
+ /** Diffs the two runs and applies `redact` here, so no surface can skip it (stage names carry raw
40
+ * Spark text that can embed a host or IP). `comparison` is the (redacted) raw result for budgets
41
+ * and Markdown; `output` is the projection to serialize. */
42
+ export function buildComparisonOutput(
43
+ baseline , candidate ,
44
+ { redact, normalizePath, view } = {},
45
+ ) {
46
+ const built = buildComparison(baseline, candidate, { normalizePath });
47
+ const comparison = redact ? redactComparison(built) : built;
48
+ return { comparison, output: comparisonOutput(comparison, view) };
49
+ }