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
@@ -18,12 +18,14 @@ import {
18
18
 
19
19
 
20
20
  } from './event-schemas.js';
21
+ import { DELTA_COMMANDS } from './delta-commands.js';
21
22
  import { assertNever } from './assert-never.js';
22
23
  import { finalizeStage } from './stage-quantiles.js';
23
24
  import { MAX_FAILURE_DETAILS_PER_STAGE, extractTaskFailureDetail, taskFailureKey, } from './task-failure.js';
24
25
  import { computeRunAggregates } from './run-aggregates.js';
26
+ import { parseSparkMemoryMB } from './spark-memory.js';
25
27
 
26
-
28
+
27
29
 
28
30
 
29
31
  // Internal parser-state shapes: the real runtime objects the handlers build and mutate, not the
@@ -129,6 +131,8 @@ const MAX_TASK_SAMPLES = 20;
129
131
 
130
132
 
131
133
 
134
+
135
+
132
136
 
133
137
 
134
138
 
@@ -149,12 +153,29 @@ const MAX_TASK_SAMPLES = 20;
149
153
 
150
154
 
151
155
 
156
+
157
+
152
158
 
153
159
 
154
160
 
155
161
 
156
162
 
157
163
 
164
+
165
+
166
+
167
+
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
158
179
 
159
180
 
160
181
 
@@ -178,6 +199,9 @@ const MAX_TASK_SAMPLES = 20;
178
199
 
179
200
 
180
201
 
202
+
203
+
204
+
181
205
 
182
206
 
183
207
 
@@ -198,6 +222,9 @@ const MAX_TASK_SAMPLES = 20;
198
222
 
199
223
 
200
224
 
225
+
226
+
227
+
201
228
 
202
229
 
203
230
 
@@ -256,6 +283,10 @@ export function buildChunkDecoder() {
256
283
  // line again after every slice scanned about 10 GB on a 100 MB line.
257
284
  let pendingIsSqlEvent = false;
258
285
  let keyTail = '';
286
+ // Where the pending line's plan-description value starts, once its key is found but too little
287
+ // of the value has arrived to tell whether it is a small Delta command description (see
288
+ // descriptionDisposition); -1 otherwise.
289
+ let undecidedValueStart = -1;
259
290
  let skippingPlanDescription = false;
260
291
  // Length of the backslash run the skipped bytes ended with, which escapes a quote at the start
261
292
  // of the next chunk when odd.
@@ -289,11 +320,24 @@ export function buildChunkDecoder() {
289
320
  keyTail = '';
290
321
  appended = pending; // nothing of it was searched while it was shorter than the prefix
291
322
  }
292
- const keyAt = findPlanKey(appended);
293
- if (keyAt === -1) return; // the key may still arrive in a later chunk
323
+ let valueStart = undecidedValueStart;
324
+ if (valueStart === -1) {
325
+ const keyAt = findPlanKey(appended);
326
+ if (keyAt === -1) return; // the key may still arrive in a later chunk
327
+ valueStart = keyAt + PLAN_DESCRIPTION_KEY.length;
328
+ }
329
+ if (closingQuoteIndex(pending, valueStart) !== -1) { // complete: stripPlanDescription handles it
330
+ undecidedValueStart = -1;
331
+ pendingSettled = true;
332
+ return;
333
+ }
334
+ // A small kept description is not skipped: the line completes in a later chunk and
335
+ // stripPlanDescription keeps its arguments line.
336
+ const disposition = descriptionDisposition(pending, valueStart);
337
+ if (disposition === 'wait') { undecidedValueStart = valueStart; return; }
338
+ undecidedValueStart = -1;
294
339
  pendingSettled = true;
295
- const valueStart = keyAt + PLAN_DESCRIPTION_KEY.length;
296
- if (closingQuoteIndex(pending, valueStart) !== -1) return; // complete: stripPlanDescription empties it
340
+ if (disposition === 'keep') return;
297
341
  // Only a chunk whose last byte is a backslash carries a run over; any other last byte (such
298
342
  // as part of a split multibyte char) ends it.
299
343
  let run = 0;
@@ -355,6 +399,7 @@ export function buildChunkDecoder() {
355
399
  pending = pendingHead = appended = start < text.length ? text.substring(start) : '';
356
400
  pendingSettled = false;
357
401
  pendingIsSqlEvent = false;
402
+ undecidedValueStart = -1;
358
403
  }
359
404
  if (!pendingSettled && pending !== '') settlePending(buffer[buffer.length - 1], appended);
360
405
  }
@@ -391,6 +436,7 @@ export function createState() {
391
436
  jobs: new Map(),
392
437
  executors: { added: [], removed: [] },
393
438
  skippedLines: 0,
439
+ unreadableSqlStarts: new Set(),
394
440
  accumState: new Map(),
395
441
  rddInfo: new Map(),
396
442
  rddBlocks: new Map(),
@@ -432,23 +478,7 @@ export function normalizeSparkProperties(
432
478
  return map;
433
479
  }
434
480
 
435
- // Parse a Spark memory-size string to MiB. Spark's JVM-memory configs use bytesConf(ByteUnit.MiB),
436
- // so a bare number means MiB. A k/m/g/t suffix sets the unit (trailing "b" redundant); a lone "b"
437
- // ("10b") means bytes.
438
- export function parseSparkMemoryMB(value ) {
439
- if (value == null) return null;
440
- const m = String(value).trim().toLowerCase().match(/^([\d.]+)\s*([kmgt]?)(b?)$/);
441
- if (!m) return null;
442
- const n = parseFloat(m[1]);
443
- if (!Number.isFinite(n)) return null;
444
- switch (m[2]) {
445
- case 'k': return Math.round(n / 1024);
446
- case 'g': return Math.round(n * 1024);
447
- case 't': return Math.round(n * 1024 * 1024);
448
- case 'm': return Math.round(n);
449
- default: return m[3] === 'b' ? Math.round(n / (1024 * 1024)) : Math.round(n);
450
- }
451
- }
481
+ export { parseSparkMemoryMB };
452
482
 
453
483
  // Derive an allocated-resource summary from the Spark config map. Absent keys degrade to null,
454
484
  // not guessed defaults.
@@ -533,29 +563,11 @@ function internTaskFailure(stage , endReason
533
563
  return detail;
534
564
  }
535
565
 
536
- export function accumulateTask(event , state ) {
537
- const stageId = event['Stage ID'];
538
- const stage = state.stages.get(stageId);
539
- if (!stage) return null;
540
- // Late TaskEnd for a stage whose StageCompleted already freed taskAttempts (finalizeStage): its
541
- // stats are already baked into the finalized stage, don't re-add. The one exception is a losing
542
- // speculative attempt, whose wasted time the finalized stage never saw.
543
- if (stage.taskAttempts === null) {
544
- accountLateSpeculativeLoser(event, stage);
545
- return null;
546
- }
547
-
548
- state.evidenceInputs.taskRecords++;
549
-
550
- const accumulables = event['Task Info']?.Accumulables ?? [];
551
- for (const acc of accumulables) {
552
- if (!state.taskAccumStages.has(acc.ID)) state.taskAccumStages.set(acc.ID, new Set());
553
- state.taskAccumStages.get(acc.ID) .add(stageId);
554
- }
566
+ // 'Task Info' and its Failed/Killed/Speculative fields are optional in the schema; Partial<>
567
+ // lets the {} fallback type-check while reads below default via ??/||.
568
+
555
569
 
556
- // 'Task Info' and its Failed/Killed/Speculative fields are optional in the schema; Partial<>
557
- // lets the {} fallback type-check while reads below default via ??/||.
558
-
570
+ function taskRecordOf(event , failure ) {
559
571
  const info = event['Task Info'] ?? {};
560
572
  const m = event['Task Metrics'] ?? {};
561
573
  const sr = m['Shuffle Read Metrics'] ?? {};
@@ -566,14 +578,14 @@ export function accumulateTask(event , state
566
578
  const duration = (info['Finish Time'] ?? 0) - (info['Launch Time'] ?? 0);
567
579
  const failed = !!(info['Failed'] || info['Killed']);
568
580
 
569
- const record = {
581
+ return {
570
582
  duration, failed,
571
583
  taskId: info['Task ID'] ?? null,
572
584
  attemptNumber: info['Attempt Number'] ?? 0,
573
585
  launchTime: info['Launch Time'] ?? 0,
574
586
  finishTime: info['Finish Time'] ?? 0,
575
587
  reason: event['Task End Reason']?.['Reason'] ?? null,
576
- failure: failed ? internTaskFailure(stage, event['Task End Reason']) : null,
588
+ failure,
577
589
  speculative: info['Speculative'] === true,
578
590
  host: info['Host'] ?? '',
579
591
  executorId: info['Executor ID'] ?? '',
@@ -589,7 +601,37 @@ export function accumulateTask(event , state
589
601
  executorCpuTime: m['Executor CPU Time'] ?? 0,
590
602
  inputBytes: inp['Bytes Read'] ?? 0,
591
603
  outputBytes: out['Bytes Written'] ?? 0,
604
+ outputRecords: out['Records Written'] ?? null,
592
605
  };
606
+ }
607
+
608
+ export function accumulateTask(event , state ) {
609
+ const stageId = event['Stage ID'];
610
+ const stage = state.stages.get(stageId);
611
+ if (!stage) return null;
612
+ // Late TaskEnd for a stage whose StageCompleted already freed taskAttempts (finalizeStage): the
613
+ // finalized stage's figures stay as posted. A losing speculative attempt adds the wasted time the
614
+ // finalized stage never saw; any other task of a failed or earlier attempt is work only the
615
+ // metrics block reads, from lateAttemptWork.
616
+ if (stage.taskAttempts === null) {
617
+ const earlierAttempt = (event['Stage Attempt ID'] ?? 0) !== stage.stageAttemptId;
618
+ if (!accountLateSpeculativeLoser(event, stage) && (stage.stageFailureReason != null || earlierAttempt)) {
619
+ stage.lateAttemptWork = mergeAttemptTotals(taskAttemptTotals(taskRecordOf(event, null)), stage.lateAttemptWork);
620
+ }
621
+ return null;
622
+ }
623
+
624
+ state.evidenceInputs.taskRecords++;
625
+
626
+ const accumulables = event['Task Info']?.Accumulables ?? [];
627
+ for (const acc of accumulables) {
628
+ if (!state.taskAccumStages.has(acc.ID)) state.taskAccumStages.set(acc.ID, new Set());
629
+ state.taskAccumStages.get(acc.ID) .add(stageId);
630
+ }
631
+
632
+ const info = event['Task Info'] ?? {};
633
+ const failed = !!(info['Failed'] || info['Killed']);
634
+ const record = taskRecordOf(event, failed ? internTaskFailure(stage, event['Task End Reason']) : null);
593
635
 
594
636
  // Dedupe only when Index is present (always true for real logs). Without it every event is a
595
637
  // distinct task, preserving behavior for fixtures that omit Index.
@@ -614,9 +656,11 @@ export function accumulateTask(event , state
614
656
  stage.retryTaskSamples.push(taskRecordToSample(existing));
615
657
  }
616
658
  }
659
+ stage.lateAttemptWork = mergeAttemptTotals(discardedAttemptTotals(existing), stage.lateAttemptWork);
617
660
  stage.taskAttempts.set(key, record);
618
661
  if (record.speculative) stage.speculativeWinners.add(key);
619
662
  } else {
663
+ stage.lateAttemptWork = mergeAttemptTotals(discardedAttemptTotals(record), stage.lateAttemptWork);
620
664
  // Non-winning duplicate (both failed, or a race where a winner is
621
665
  // already recorded): its time is waste, its metrics are discarded.
622
666
  if (existing.speculative || record.speculative) {
@@ -639,14 +683,16 @@ export function accumulateTask(event , state
639
683
  // its time as speculation waste, pairing it the same way accumulateTask does: the late attempt is
640
684
  // the speculative copy itself, or the original that a speculative winner beat. Every other stat
641
685
  // of a late attempt stays excluded, as the finalized stage already posted them.
642
- function accountLateSpeculativeLoser(event , stage ) {
686
+ function accountLateSpeculativeLoser(event , stage ) {
643
687
  const info = event['Task Info'];
644
- if (info?.['Index'] == null) return;
688
+ if (info?.['Index'] == null) return false;
645
689
  const key = `${event['Stage Attempt ID'] ?? 0}:${info['Index']}`;
646
- if (info['Speculative'] !== true && !stage.speculativeWinners.has(key)) return;
690
+ if (info['Speculative'] !== true && !stage.speculativeWinners.has(key)) return false;
647
691
  stage.speculationWasteMs += (info['Finish Time'] ?? 0) - (info['Launch Time'] ?? 0);
648
692
  stage.speculationWastedAttempts++;
649
693
  stage.lateSpeculationWaste = true;
694
+ stage.lateAttemptWork = mergeAttemptTotals(discardedAttemptTotals(taskRecordOf(event, null)), stage.lateAttemptWork);
695
+ return true;
650
696
  }
651
697
 
652
698
  export function resolvePlanTree(
@@ -853,10 +899,63 @@ export function endJob(event , state
853
899
  return { type: 'job', data: { ...job } };
854
900
  }
855
901
 
902
+ const SUMMED_ATTEMPT_FIELDS = [
903
+ 'taskCount', 'failedTasks', 'wastedAttempts', 'executorRunTime', 'executorCpuTime', 'jvmGCTime',
904
+ 'memoryBytesSpilled', 'diskBytesSpilled', 'shuffleReadBytes', 'shuffleWriteBytes', 'inputBytes', 'outputBytes',
905
+ ] ;
906
+
907
+ const addNullable = (a , b ) => (a == null ? b : b == null ? a : a + b);
908
+
909
+ function mergeAttemptTotals(a , b ) {
910
+ if (b == null) return a;
911
+ const totals = {
912
+ outputRecords: addNullable(a.outputRecords, b.outputRecords),
913
+ peakExecutionMemoryMax: Math.max(a.peakExecutionMemoryMax, b.peakExecutionMemoryMax),
914
+ durationMs: addNullable(a.durationMs, b.durationMs),
915
+ } ;
916
+ for (const field of SUMMED_ATTEMPT_FIELDS) totals[field] = a[field] + b[field];
917
+ return totals;
918
+ }
919
+
920
+ // One task's work; a late task adds no stage duration.
921
+ function taskAttemptTotals(t ) {
922
+ return {
923
+ taskCount: 1, failedTasks: t.failed ? 1 : 0, wastedAttempts: 0,
924
+ executorRunTime: t.executorRunTime, executorCpuTime: t.executorCpuTime, jvmGCTime: t.gcTime,
925
+ memoryBytesSpilled: t.memSpilled, diskBytesSpilled: t.diskSpilled,
926
+ shuffleReadBytes: t.shuffleRead, shuffleWriteBytes: t.shuffleWrite,
927
+ inputBytes: t.inputBytes, outputBytes: t.outputBytes, outputRecords: t.outputRecords,
928
+ peakExecutionMemoryMax: t.peakExecMem, durationMs: null,
929
+ };
930
+ }
931
+
932
+ // A task attempt the stage's figures drop because another attempt of the same task won (a retry
933
+ // after a failure, a speculative twin): its CPU, run time and I/O were still spent, so the
934
+ // metrics block counts them, but the task itself is already counted once.
935
+ function discardedAttemptTotals(t ) {
936
+ return { ...taskAttemptTotals(t), taskCount: 0, failedTasks: 0 };
937
+ }
938
+
939
+ // The replaced record's finalized attempt added to the attempts it had already folded. An attempt resubmitted before its StageCompleted was never finalized, so its
940
+ // tasks are not counted.
941
+ function foldEarlierAttempts(replaced ) {
942
+ if (!replaced) return null;
943
+ if (replaced.taskAttempts !== null) return replaced.earlierAttempts;
944
+ const attempt = {
945
+ ...Object.fromEntries(SUMMED_ATTEMPT_FIELDS.map((field) => [field, replaced[field]])) ,
946
+ outputRecords: replaced.outputRecords,
947
+ peakExecutionMemoryMax: replaced.peakExecutionMemoryMax ?? 0,
948
+ durationMs: replaced.submittedAt > 0 && replaced.completedAt >= replaced.submittedAt
949
+ ? replaced.completedAt - replaced.submittedAt : null,
950
+ };
951
+ return mergeAttemptTotals(attempt, replaced.earlierAttempts);
952
+ }
953
+
856
954
  export function submitStage(event , state ) {
857
955
  state.evidenceInputs.stageSubmissions++;
858
956
  const info = event['Stage Info'];
859
957
  const id = info['Stage ID'];
958
+ const replaced = state.stages.get(id);
860
959
  state.stages.set(id, {
861
960
  id, name: info['Stage Name'] ?? '', details: info['Details'] ?? '',
862
961
  submittedAt: info['Submission Time'] ?? 0, completedAt: 0,
@@ -864,13 +963,18 @@ export function submitStage(event , st
864
963
  shuffleReadBytes: 0, shuffleWriteBytes: 0, fetchWaitTime: 0,
865
964
  memoryBytesSpilled: 0, diskBytesSpilled: 0,
866
965
  jvmGCTime: 0, executorRunTime: 0, executorCpuTime: 0,
867
- inputBytes: 0, outputBytes: 0,
966
+ inputBytes: 0, outputBytes: 0, outputRecords: null,
868
967
  sqlExecutionId: state.stageToSqlExec.get(id) ?? null,
869
968
  parentIds: info['Parent IDs'] ?? [],
870
969
  hostStats: new Map(),
871
970
  speculativeTasks: 0,
872
971
  failureReasons: new Map(),
873
972
  stageFailureReason: null,
973
+ stageAttemptId: info['Stage Attempt ID'] ?? 0,
974
+ stageAttempts: (replaced?.stageAttempts ?? 0) + 1,
975
+ failedStageAttempts: replaced?.failedStageAttempts ?? 0,
976
+ earlierAttempts: foldEarlierAttempts(replaced),
977
+ lateAttemptWork: replaced?.lateAttemptWork ?? null,
874
978
  taskAttempts: new Map(),
875
979
  failureDetails: new Map(),
876
980
  retryTaskSamples: [],
@@ -1027,6 +1131,10 @@ export function startSqlExecution(event
1027
1131
  sparkPlanInfo,
1028
1132
  hadAdaptiveUpdate: false,
1029
1133
  };
1134
+ if (event.rootExecutionId !== undefined) exec.rootExecutionId = event.rootExecutionId;
1135
+ if (event.physicalPlanDescription && KEPT_ARGUMENTS.test(event.physicalPlanDescription)) {
1136
+ exec.commandArguments = event.physicalPlanDescription;
1137
+ }
1030
1138
  state.sqlExecutions.set(exec.id, exec);
1031
1139
  // A restarted execution carries a new plan: its next end must resolve it again.
1032
1140
  state.resolvedPlanExecutions.delete(exec.id);
@@ -1164,6 +1272,8 @@ export function processEvent(event , state ) {
1164
1272
  // every stage-duration figure becomes the epoch timestamp itself (a "47-year" stage).
1165
1273
  if (!stage.submittedAt && info['Submission Time'] != null) stage.submittedAt = info['Submission Time'];
1166
1274
  stage.stageFailureReason = info['Failure Reason'] ?? null;
1275
+ // A duplicate StageCompleted of an already-finalized attempt is not another failed attempt.
1276
+ if (stage.stageFailureReason != null && stage.taskAttempts !== null) stage.failedStageAttempts++;
1167
1277
  // finalizeStage keeps its `stage` parameter typed as a loose Record (see that module); bridge
1168
1278
  // StageRecord's more precise shape across that boundary with an explicit cast.
1169
1279
  return finalizeStage(
@@ -1211,13 +1321,32 @@ const KNOWN_EVENT_TYPES = new Set(
1211
1321
  );
1212
1322
 
1213
1323
  // SQLExecutionStart and SQLAdaptiveExecutionUpdate carry `physicalPlanDescription`, Spark's text
1214
- // rendering of the plan. Nothing reads it (the plan tree comes from sparkPlanInfo), yet on a real
1215
- // 3.5 GB log it was 72% of the AQE-update bytes, which were themselves 73% of the log. Cutting its
1324
+ // rendering of the plan. The plan tree comes from sparkPlanInfo, yet on a real 3.5 GB log the
1325
+ // description was 72% of the AQE-update bytes, which were themselves 73% of the log. Cutting its
1216
1326
  // string value out before JSON.parse halves the parse cost of those lines.
1327
+ //
1328
+ // One exception: a Delta write command's node prints only its bare name in sparkPlanInfo, and its
1329
+ // target table appears only on its `Arguments:` line of the description. When the description is
1330
+ // that of one of KEPT_ARGUMENTS_COMMANDS (checked on its first bytes, so no other line pays for it),
1331
+ // the value is cut down to that one line, never kept whole. A value that spans decoder chunks is
1332
+ // still dropped unread (buildChunkDecoder), so the write target then falls back to its child paths.
1217
1333
  const SQL_UI_EVENT_PREFIX = '{"Event":"org.apache.spark.sql.execution.ui.SparkListenerSQL';
1218
1334
  const PLAN_DESCRIPTION_KEY = '"physicalPlanDescription":"';
1219
1335
 
1220
- const QUOTE = 0x22, BACKSLASH = 0x5c, NEWLINE = 0x0a;
1336
+ const QUOTE = 0x22, BACKSLASH = 0x5c, NEWLINE = 0x0a, LOWER_N = 0x6e;
1337
+
1338
+ // Delta commands whose root node carries no target in sparkPlanInfo. The description of a
1339
+ // command execution starts `== Physical Plan ==\nExecute <Command> (1)` (JSON-escaped newline).
1340
+ const KEPT_ARGUMENTS_COMMANDS = new Set(DELTA_COMMANDS);
1341
+ // Those whose description is just the command (no child plan printed under it), so it is small
1342
+ // enough to decode whole when it spans decoder chunks. SaveIntoDataSourceCommand prints the whole
1343
+ // query under it, which can be as large as any other plan description.
1344
+ const SMALL_DESCRIPTION_COMMANDS = new Set(
1345
+ [...KEPT_ARGUMENTS_COMMANDS].filter((command) => command !== 'SaveIntoDataSourceCommand'),
1346
+ );
1347
+ const PLAN_HEADER = '== Physical Plan ==\\nExecute ';
1348
+ // The kept form once JSON-parsed: `<Command>\nArguments: <line>`.
1349
+ const KEPT_ARGUMENTS = /^[A-Za-z]+\nArguments: /;
1221
1350
 
1222
1351
  // Index of the closing quote of the JSON string whose content starts at `valueStart`: the first
1223
1352
  // quote preceded by an even number of backslashes. -1 when the string is unterminated.
@@ -1242,7 +1371,48 @@ function closingQuoteAt(buf , limit , carried )
1242
1371
  return -1;
1243
1372
  }
1244
1373
 
1245
- // Returns `line` with the physicalPlanDescription string value emptied, or `line` unchanged when
1374
+ // The command named by a description that starts at text[valueStart] with PLAN_HEADER, when text
1375
+ // holds the whole name; null otherwise.
1376
+ function describedCommand(text , valueStart ) {
1377
+ if (!text.startsWith(PLAN_HEADER, valueStart)) return null;
1378
+ const nameStart = valueStart + PLAN_HEADER.length;
1379
+ let nameEnd = nameStart;
1380
+ while (nameEnd < text.length && /[A-Za-z]/.test(text[nameEnd])) nameEnd++;
1381
+ return nameEnd < text.length ? text.slice(nameStart, nameEnd) : null;
1382
+ }
1383
+
1384
+ // What to do with an open description value whose first bytes are text[valueStart...]: 'keep' it
1385
+ // (a small Delta command description), 'skip' it, or 'wait' when too little has arrived to tell.
1386
+ function descriptionDisposition(text , valueStart ) {
1387
+ const seen = text.slice(valueStart, valueStart + PLAN_HEADER.length);
1388
+ if (!PLAN_HEADER.startsWith(seen)) return 'skip';
1389
+ if (seen.length < PLAN_HEADER.length) return 'wait';
1390
+ const command = describedCommand(text, valueStart);
1391
+ if (command === null) return 'wait'; // the command name is still arriving
1392
+ return SMALL_DESCRIPTION_COMMANDS.has(command) ? 'keep' : 'skip';
1393
+ }
1394
+
1395
+ // The `<Command>\nArguments: <line>` part (still JSON-escaped) of the root command of a plan
1396
+ // description whose value spans line[valueStart, quote), or null when it is not one of
1397
+ // KEPT_ARGUMENTS_COMMANDS. SaveIntoDataSourceCommand is kept only for the Delta source: its other
1398
+ // providers (JDBC) print option maps that this tool has no use for.
1399
+ function keptCommandArguments(line , valueStart , quote ) {
1400
+ const command = describedCommand(line, valueStart);
1401
+ if (command === null || !KEPT_ARGUMENTS_COMMANDS.has(command)) return null;
1402
+ const key = `${command}\\nArguments: `;
1403
+ const at = line.indexOf(key, valueStart + PLAN_HEADER.length + command.length);
1404
+ if (at === -1 || at >= quote) return null;
1405
+ // The line ends at the first escaped newline: a backslash starts a two-char escape.
1406
+ let end = at + key.length;
1407
+ while (end < quote && !(line.charCodeAt(end) === BACKSLASH && line.charCodeAt(end + 1) === LOWER_N)) {
1408
+ end += line.charCodeAt(end) === BACKSLASH ? 2 : 1;
1409
+ }
1410
+ const kept = line.slice(at, Math.min(end, quote));
1411
+ return command === 'SaveIntoDataSourceCommand' && !kept.includes('DeltaDataSource') ? null : kept;
1412
+ }
1413
+
1414
+ // Returns `line` with the physicalPlanDescription string value emptied (or cut to the kept
1415
+ // arguments line, see keptCommandArguments), or `line` unchanged when
1246
1416
  // the key isn't found in Spark's compact form. The key pattern can't match inside another JSON
1247
1417
  // string: there its quotes would be backslash-escaped. buildChunkDecoder already empties a value
1248
1418
  // that spans chunks, so here the value is empty or within one chunk.
@@ -1255,7 +1425,7 @@ export function stripPlanDescription(line ) {
1255
1425
  const quote = closingQuoteIndex(line, valueStart);
1256
1426
  // Unterminated string (a truncated line): leave it for JSON.parse to reject.
1257
1427
  if (quote === -1) return line;
1258
- return line.slice(0, valueStart) + line.slice(quote);
1428
+ return line.slice(0, valueStart) + (keptCommandArguments(line, valueStart, quote) ?? '') + line.slice(quote);
1259
1429
  }
1260
1430
 
1261
1431
  const TASK_END_PREFIX = '{"Event":"SparkListenerTaskEnd",';
@@ -1363,6 +1533,18 @@ export function dispatchLine(
1363
1533
  const BLOCK_UPDATED_PREFIX = '{"Event":"SparkListenerBlockUpdated",';
1364
1534
  const RDD_BLOCK_ID_FRAGMENT = '"Block ID":"rdd_';
1365
1535
 
1536
+ const SQL_START_EVENT = 'org.apache.spark.sql.execution.ui.SparkListenerSQLExecutionStart';
1537
+ const SQL_START_ID = /"executionId":(\d+)/;
1538
+
1539
+ // Records the execution id of a skipped SQL start line, when the line's head still names it: a
1540
+ // line cut off mid-plan fails JSON.parse but keeps its leading fields.
1541
+ function noteUnreadableSqlStart(line , state ) {
1542
+ const head = line.slice(0, 300);
1543
+ if (!head.includes(`${SQL_START_EVENT}"`)) return;
1544
+ const id = SQL_START_ID.exec(head);
1545
+ if (id) state.unreadableSqlStarts.add(Number(id[1]));
1546
+ }
1547
+
1366
1548
  function parseAndDispatch(line , state , emit ) {
1367
1549
  if (line.startsWith(BLOCK_UPDATED_PREFIX) && !line.includes(RDD_BLOCK_ID_FRAGMENT)) return;
1368
1550
  let parsed ;
@@ -1370,6 +1552,7 @@ function parseAndDispatch(line , state , emit
1370
1552
  parsed = parseTaskEnd(line) ?? JSON.parse(stripPlanDescription(line));
1371
1553
  } catch {
1372
1554
  state.skippedLines++;
1555
+ noteUnreadableSqlStart(line, state);
1373
1556
  return;
1374
1557
  }
1375
1558
  // A real Spark event log carries many event types this tool never modeled (BlockManagerAdded,
@@ -1382,6 +1565,7 @@ function parseAndDispatch(line , state , emit
1382
1565
  const result = SparkEventSchema.safeParse(parsed);
1383
1566
  if (!result.success) {
1384
1567
  state.skippedLines++;
1568
+ if (eventType === SQL_START_EVENT) noteUnreadableSqlStart(line, state);
1385
1569
  return;
1386
1570
  }
1387
1571
  if (result.data.Event === 'org.apache.spark.sql.execution.ui.SparkListenerSQLExecutionEnd') {
@@ -1428,14 +1612,29 @@ export function collectLateSpeculationWaste(
1428
1612
  return out;
1429
1613
  }
1430
1614
 
1615
+ // Late work of every stage a failed attempt's late TaskEnd added to, re-posted once before `done`:
1616
+ // the stage message posted at completion predates it.
1617
+ export function collectLateAttemptWork(state ) {
1618
+ const out = new Map ();
1619
+ for (const [id, stage] of state.stages) {
1620
+ if (stage.lateAttemptWork != null) out.set(id, stage.lateAttemptWork);
1621
+ }
1622
+ return out;
1623
+ }
1624
+
1431
1625
  export function emitParseCompletion(state , emit , linesProcessed ) {
1432
1626
  // Executions that never ended keep their latest AQE update, as they did before it was deferred.
1433
1627
  for (const executionId of [...state.pendingAdaptiveUpdates.keys()]) flushAdaptiveUpdate(executionId, state, emit);
1434
1628
  emit({ type: 'progress', pct: 1, linesProcessed });
1629
+ emit({ type: 'stageLateAttemptWork', data: collectLateAttemptWork(state) });
1435
1630
  emit({ type: 'runAggregates', data: computeRunAggregates(state.taskStore) });
1436
1631
  emit({ type: 'stageSpeculationWaste', data: collectLateSpeculationWaste(state) });
1437
1632
  emit({ type: 'stageExecutorMetrics', data: collectStageExecutorMetrics(state) });
1438
1633
  emit(appMessage(state));
1439
- emit({ type: 'done', skippedLines: state.skippedLines });
1634
+ emit({
1635
+ type: 'done',
1636
+ skippedLines: state.skippedLines,
1637
+ ...(state.unreadableSqlStarts.size > 0 ? { unreadableSqlExecutions: [...state.unreadableSqlStarts].sort((a, b) => a - b) } : {}),
1638
+ });
1440
1639
  state.accumState.clear();
1441
1640
  }
@@ -201,6 +201,7 @@ export const StageSubmittedEventSchema = z.object({
201
201
  Event: z.literal('SparkListenerStageSubmitted'),
202
202
  'Stage Info': z.object({
203
203
  'Stage ID': z.number(),
204
+ 'Stage Attempt ID': z.number().optional(),
204
205
  'Stage Name': z.string().optional(),
205
206
  Details: z.string().optional(),
206
207
  'Submission Time': z.number().optional(),
@@ -311,6 +312,7 @@ export const TaskEndEventSchema = z.object({
311
312
  }).optional(),
312
313
  'Output Metrics': z.object({
313
314
  'Bytes Written': z.number().optional(),
315
+ 'Records Written': z.number().optional(),
314
316
  }).optional(),
315
317
  }).optional(),
316
318
  });
@@ -319,13 +321,15 @@ export const TaskEndEventSchema = z.object({
319
321
  export const SqlExecutionStartEventSchema = z.object({
320
322
  Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLExecutionStart'),
321
323
  executionId: z.number(),
324
+ rootExecutionId: z.number().optional(),
322
325
  description: z.string().optional(),
326
+ physicalPlanDescription: z.string().optional(),
323
327
  time: z.number(),
324
328
  sparkPlanInfo: SparkPlanInfoFieldSchema,
325
329
  });
326
330
 
327
331
  // applyAdaptiveExecutionUpdate: AQE re-plans mid-query and re-emits sparkPlanInfo for the same
328
- // executionId; last write wins. Neither SQL schema declares physicalPlanDescription: nothing reads
332
+ // executionId; last write wins. This schema does not declare physicalPlanDescription: nothing reads
329
333
  // it, so zod strips it (dispatchLine already empties it before JSON.parse, see stripPlanDescription).
330
334
  export const SqlAdaptiveExecutionUpdateEventSchema = z.object({
331
335
  Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLAdaptiveExecutionUpdate'),