sparkforensics-server 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/package.json +1 -1
  2. package/public/assets/AutoscalingChurn-CRA2Mb7F.js +1 -0
  3. package/public/assets/CacheUtilization-Bh9OY_V9.js +1 -0
  4. package/public/assets/CachingOpportunity-oCiQwmTJ.js +1 -0
  5. package/public/assets/{ColdStart-B38OiAOp.js → ColdStart-BJrtjfmn.js} +1 -1
  6. package/public/assets/ConfigAudit-DMvKxySP.js +1 -0
  7. package/public/assets/CoreUsageArea-REC2zsKw.js +1 -0
  8. package/public/assets/Dashboard-BHZ76JCu.js +2 -0
  9. package/public/assets/DisclosureContext-OO7X1HAx.js +1 -0
  10. package/public/assets/{DuplicatePlanSubtree-BqS2A2j4.js → DuplicatePlanSubtree-BgG-L-cK.js} +1 -1
  11. package/public/assets/ExecutorUtilization-D2KhSVbY.js +1 -0
  12. package/public/assets/{GcPressure-CT1yzR_s.js → GcPressure-D1BTE8qh.js} +1 -1
  13. package/public/assets/{IncompleteRun-C7Qkjtjn.js → IncompleteRun-DD40xUpy.js} +1 -1
  14. package/public/assets/{JobFailures-WHl3ELfh.js → JobFailures-YwNzCF19.js} +1 -1
  15. package/public/assets/MemoryUtilization-9b8ee_3_.js +1 -0
  16. package/public/assets/{OverBroadcast-6oSSAQ_R.js → OverBroadcast-CfctS8a8.js} +1 -1
  17. package/public/assets/PartitionSizing-VG8kBgG5.js +1 -0
  18. package/public/assets/{PlanExplorer-DcxmiQWL.js → PlanExplorer-DmubTAOP.js} +1 -1
  19. package/public/assets/PlanGraphRoute-DctPZjS0.js +1 -0
  20. package/public/assets/RetryWaste-CnpM8bB7.js +1 -0
  21. package/public/assets/{ShuffleIO-CRhrYNE0.js → ShuffleIO-D13vGmas.js} +1 -1
  22. package/public/assets/Skew-DlagstYP.js +1 -0
  23. package/public/assets/SlowHost-cSkLRZ_e.js +1 -0
  24. package/public/assets/{SmallFiles-C082c2rd.js → SmallFiles-B-kERMcq.js} +1 -1
  25. package/public/assets/{SpeculationWaste-BS2vpadr.js → SpeculationWaste-DaysAzXO.js} +1 -1
  26. package/public/assets/Spill-H44Gz4US.js +1 -0
  27. package/public/assets/{StageFailed-D3ac9Sou.js → StageFailed-DCfii9h_.js} +1 -1
  28. package/public/assets/StageFindingGroup-BXJ0Mj5n.js +1 -0
  29. package/public/assets/{StageHeader-pI1nTwVf.js → StageHeader--oN2J-oD.js} +1 -1
  30. package/public/assets/StageShape-IZ80CZiJ.js +1 -0
  31. package/public/assets/{StageSlowness-BaNyfNvL.js → StageSlowness--BRIaBc1.js} +1 -1
  32. package/public/assets/Straggler-Ctq0Df_o.js +1 -0
  33. package/public/assets/TaskFailures-Bks_gqhL.js +1 -0
  34. package/public/assets/{TinyTask-8_5lpq_2.js → TinyTask-BssfzGFB.js} +1 -1
  35. package/public/assets/{UnderBroadcast-DJ0XSwth.js → UnderBroadcast-DsScrxc-.js} +1 -1
  36. package/public/assets/{finding-anchor-Cys8Z6Yn.js → finding-anchor-BYW1oz5R.js} +1 -1
  37. package/public/assets/{impact-sort-BTQndbof.js → impact-sort-xzy0g-Vh.js} +1 -1
  38. package/public/assets/index-DhpRGMZc.css +2 -0
  39. package/public/assets/index-DwqkFUFk.js +14 -0
  40. package/public/assets/instrument-sans-latin-ext-wdth-normal-C5E2Gvlv.woff2 +0 -0
  41. package/public/assets/instrument-sans-latin-wdth-normal-BVScPF0l.woff2 +0 -0
  42. package/public/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  43. package/public/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  44. package/public/assets/{parser-worker-DmeCnLWU.js → parser-worker-CA5RxqKc.js} +8 -8
  45. package/public/assets/react-vendor-DfpuPQZf.js +9 -0
  46. package/public/assets/{select-DIYN6D3y.js → select-C8edjYTq.js} +1 -1
  47. package/public/assets/{usePagedRows-BxvdFnPk.js → usePagedRows-CsuUFeYx.js} +1 -1
  48. package/public/assets/useRecentFiles-BvkKo3JN.js +11 -0
  49. package/public/assets/{useSortMode-B8SRfAoG.js → useSortMode-joX71BZN.js} +1 -1
  50. package/public/docs/404.html +3 -7
  51. package/public/docs/assets/{app.D2GqAkhy.js → app.CnWL6bKV.js} +1 -1
  52. package/public/docs/assets/chunks/@localSearchIndexroot.tE5pz54z.js +1 -0
  53. package/public/docs/assets/chunks/{VPLocalSearchBox.BS4cSmtW.js → VPLocalSearchBox.C0IBKeRQ.js} +1 -1
  54. package/public/docs/assets/chunks/{theme.BuxVpb0H.js → theme.BH-2HuRf.js} +2 -2
  55. package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.js} +1 -1
  56. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.js +1 -0
  57. package/public/docs/assets/{contributor-guide_architecture_detector-contract.md.wj6Et6UD.js → contributor-guide_architecture_detector-contract.md.N4hhenrA.js} +1 -1
  58. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.js +1 -0
  59. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.DfvbqEUu.js +1 -0
  60. package/public/docs/assets/{contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js → contributor-guide_architecture_impact-estimation.md.DfvbqEUu.lean.js} +1 -1
  61. package/public/docs/assets/{contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js → contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.js} +1 -1
  62. package/public/docs/assets/{contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js → contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.lean.js} +1 -1
  63. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +1 -0
  64. package/public/docs/assets/{contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.lean.js → contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js} +1 -1
  65. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.js +1 -0
  66. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js → contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.js} +1 -1
  67. package/public/docs/assets/{contributor-guide_development-setup.md.D9bu4j_c.js → contributor-guide_development-setup.md.DQdzera7.js} +1 -1
  68. package/public/docs/assets/instrument-sans-latin-ext-wdth-normal.C5E2Gvlv.woff2 +0 -0
  69. package/public/docs/assets/instrument-sans-latin-wdth-normal.BVScPF0l.woff2 +0 -0
  70. package/public/docs/assets/jetbrains-mono-cyrillic-wght-normal.D73BlboJ.woff2 +0 -0
  71. package/public/docs/assets/jetbrains-mono-greek-wght-normal.Bw9x6K1M.woff2 +0 -0
  72. package/public/docs/assets/jetbrains-mono-latin-ext-wght-normal.DBQx-q_a.woff2 +0 -0
  73. package/public/docs/assets/jetbrains-mono-latin-wght-normal.B9CIFXIH.woff2 +0 -0
  74. package/public/docs/assets/jetbrains-mono-vietnamese-wght-normal.Bt-aOZkq.woff2 +0 -0
  75. package/public/docs/assets/style.CyK9Ss73.css +1 -0
  76. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.BEepbDDL.js +24 -0
  77. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.BEepbDDL.lean.js +1 -0
  78. package/public/docs/assets/{user-guide_mcp-tools_compare-runs.md.CPowlb2q.js → user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js} +3 -2
  79. package/public/docs/assets/{user-guide_mcp-tools_compare-runs.md.CPowlb2q.lean.js → user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js} +1 -1
  80. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.js → user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js} +2 -2
  81. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.lean.js → user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js} +1 -1
  82. package/public/docs/assets/{user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js → user-guide_mcp-tools_get-run-summary.md.EhaIsele.js} +1 -1
  83. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +1 -0
  84. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +1 -0
  85. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.js +1 -0
  86. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.lean.js +1 -0
  87. package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.js +1 -0
  88. package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.lean.js +1 -0
  89. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +6 -10
  90. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +4 -8
  91. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +6 -10
  92. package/public/docs/contributor-guide/architecture/board-widgets.html +4 -8
  93. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +4 -8
  94. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +6 -10
  95. package/public/docs/contributor-guide/architecture/detector-contract.html +6 -10
  96. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +4 -8
  97. package/public/docs/contributor-guide/architecture/drill-down.html +4 -8
  98. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +6 -10
  99. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +4 -8
  100. package/public/docs/contributor-guide/architecture/impact-estimation.html +6 -10
  101. package/public/docs/contributor-guide/architecture/index.html +4 -8
  102. package/public/docs/contributor-guide/architecture/overview.html +4 -8
  103. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +4 -8
  104. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +6 -10
  105. package/public/docs/contributor-guide/architecture/state-and-history.html +4 -8
  106. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +4 -8
  107. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +4 -8
  108. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +4 -8
  109. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +4 -8
  110. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +4 -8
  111. package/public/docs/contributor-guide/architecture/widget-rendering.html +4 -8
  112. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +6 -10
  113. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +4 -8
  114. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +4 -8
  115. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +6 -10
  116. package/public/docs/contributor-guide/architecture/worker-protocol.html +4 -8
  117. package/public/docs/contributor-guide/contributing.html +4 -8
  118. package/public/docs/contributor-guide/development-setup.html +6 -10
  119. package/public/docs/contributor-guide/testing.html +4 -8
  120. package/public/docs/favicon.svg +2 -2
  121. package/public/docs/hashmap.json +1 -1
  122. package/public/docs/index.html +4 -8
  123. package/public/docs/tuning-reference/anti-patterns.html +4 -8
  124. package/public/docs/tuning-reference/aqe.html +4 -8
  125. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +4 -8
  126. package/public/docs/tuning-reference/bottleneck-cold-start.html +4 -8
  127. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +4 -8
  128. package/public/docs/tuning-reference/bottleneck-failures.html +4 -8
  129. package/public/docs/tuning-reference/bottleneck-gc.html +4 -8
  130. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +4 -8
  131. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +4 -8
  132. package/public/docs/tuning-reference/bottleneck-retry-waste.html +4 -8
  133. package/public/docs/tuning-reference/bottleneck-shuffle.html +4 -8
  134. package/public/docs/tuning-reference/bottleneck-skew.html +4 -8
  135. package/public/docs/tuning-reference/bottleneck-slow-host.html +4 -8
  136. package/public/docs/tuning-reference/bottleneck-small-files.html +4 -8
  137. package/public/docs/tuning-reference/bottleneck-spill.html +4 -8
  138. package/public/docs/tuning-reference/bottleneck-straggler.html +4 -8
  139. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +4 -8
  140. package/public/docs/tuning-reference/bottleneck-utilization.html +4 -8
  141. package/public/docs/tuning-reference/caching.html +4 -8
  142. package/public/docs/tuning-reference/cluster-config.html +4 -8
  143. package/public/docs/tuning-reference/config.html +4 -8
  144. package/public/docs/tuning-reference/data-formats.html +4 -8
  145. package/public/docs/tuning-reference/index.html +5 -9
  146. package/public/docs/tuning-reference/intro.html +4 -8
  147. package/public/docs/tuning-reference/joins.html +4 -8
  148. package/public/docs/tuning-reference/memory-model.html +4 -8
  149. package/public/docs/tuning-reference/metrics.html +4 -8
  150. package/public/docs/tuning-reference/partitioning.html +4 -8
  151. package/public/docs/tuning-reference/pyspark.html +4 -8
  152. package/public/docs/tuning-reference/shuffle.html +4 -8
  153. package/public/docs/tuning-reference/spark-architecture.html +4 -8
  154. package/public/docs/tuning-reference/table-formats.html +4 -8
  155. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +4 -8
  156. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +4 -8
  157. package/public/docs/user-guide/alternative-log-retrieval.html +4 -8
  158. package/public/docs/user-guide/getting-started/ci-and-automation.html +11 -13
  159. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +4 -8
  160. package/public/docs/user-guide/getting-started/regression-metric-keys.html +4 -8
  161. package/public/docs/user-guide/getting-started/tuning-thresholds.html +4 -8
  162. package/public/docs/user-guide/getting-started.html +4 -8
  163. package/public/docs/user-guide/mcp-tools/compare-runs.html +8 -11
  164. package/public/docs/user-guide/mcp-tools/diagnose-run.html +4 -8
  165. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +6 -10
  166. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +4 -8
  167. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +4 -8
  168. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +4 -8
  169. package/public/docs/user-guide/mcp-tools/get-run-summary.html +6 -10
  170. package/public/docs/user-guide/mcp-tools/list-runs.html +4 -8
  171. package/public/docs/user-guide/mcp-tools.html +4 -8
  172. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +6 -10
  173. package/public/docs/user-guide/run-comparison.html +6 -10
  174. package/public/docs/user-guide/understanding-findings.html +6 -10
  175. package/public/export-template.html +46 -46
  176. package/public/favicon.svg +2 -2
  177. package/public/index.html +4 -7
  178. package/vendor-core/allocation.js +105 -20
  179. package/vendor-core/api-merge-writes.js +1 -0
  180. package/vendor-core/cli/budgets.js +62 -2
  181. package/vendor-core/cli/regression-budgets.js +35 -0
  182. package/vendor-core/comparison-output.js +2 -1
  183. package/vendor-core/core-source-hash.txt +1 -1
  184. package/vendor-core/delta-log-paths.js +10 -1
  185. package/vendor-core/detectors.js +290 -92
  186. package/vendor-core/docs-content/detection/chrn.md +3 -1
  187. package/vendor-core/docs-content/detection/cold.md +2 -1
  188. package/vendor-core/docs-content/detection/mem.md +1 -1
  189. package/vendor-core/docs-content/detection/plan.md +9 -2
  190. package/vendor-core/docs-content/detection/shfl.md +8 -2
  191. package/vendor-core/docs-content/detection/skew.md +12 -5
  192. package/vendor-core/docs-content/detection/slow.md +6 -2
  193. package/vendor-core/docs-content/detection/spill.md +4 -1
  194. package/vendor-core/docs-content/detection/strag.md +5 -3
  195. package/vendor-core/docs-content/detection/tiny.md +3 -2
  196. package/vendor-core/docs-content/detection/util.md +6 -2
  197. package/vendor-core/efficiency-model.js +22 -14
  198. package/vendor-core/evidence-report.js +11 -11
  199. package/vendor-core/finding-presentation.js +52 -19
  200. package/vendor-core/finding-types.js +71 -16
  201. package/vendor-core/impact-model.js +2 -2
  202. package/vendor-core/mcp-server-factory.js +6 -2
  203. package/vendor-core/mcp-tools.js +10 -1
  204. package/vendor-core/median.js +8 -0
  205. package/vendor-core/remediation.js +10 -5
  206. package/vendor-core/run-comparison.js +2 -0
  207. package/vendor-core/run-output.js +14 -3
  208. package/vendor-core/sequence-alignment.js +83 -0
  209. package/vendor-core/stage-alignment.js +387 -63
  210. package/vendor-core/stage-quantiles.js +3 -1
  211. package/vendor-core/stage-structure.js +83 -0
  212. package/vendor-core/types.js +12 -2
  213. package/vendor-core/wall-clock.js +8 -0
  214. package/vendor-core/wasted-core-hours.js +12 -11
  215. package/vendor-core/write-targets.js +32 -0
  216. package/public/assets/AutoscalingChurn-5hDGIXi4.js +0 -1
  217. package/public/assets/CacheUtilization-CT2SCnJJ.js +0 -1
  218. package/public/assets/CachingOpportunity-D_O_WkbH.js +0 -1
  219. package/public/assets/ConfigAudit-BoplryCH.js +0 -1
  220. package/public/assets/CoreUsageArea-csgwNxkl.js +0 -1
  221. package/public/assets/Dashboard-BgKodJl2.js +0 -2
  222. package/public/assets/DisclosureContext-B0siMVdg.js +0 -1
  223. package/public/assets/ExecutorUtilization-Dp52QGWo.js +0 -1
  224. package/public/assets/MemoryUtilization-D8IUDHJI.js +0 -1
  225. package/public/assets/PartitionSizing-BwdFL-mw.js +0 -1
  226. package/public/assets/PlanGraphRoute-pqnHTt4i.js +0 -1
  227. package/public/assets/RetryWaste-BTbeHkJc.js +0 -1
  228. package/public/assets/Skew-DSNXRG9V.js +0 -1
  229. package/public/assets/SlowHost-B7Lwu2EU.js +0 -1
  230. package/public/assets/Spill-B4DVKHXx.js +0 -1
  231. package/public/assets/StageFindingGroup-D9naK109.js +0 -1
  232. package/public/assets/StageShape-eO5le8iG.js +0 -1
  233. package/public/assets/Straggler-D5Fnok_y.js +0 -1
  234. package/public/assets/TaskFailures-BWnC-K_G.js +0 -1
  235. package/public/assets/index-BcqTLKFp.css +0 -2
  236. package/public/assets/index-DCeWge12.js +0 -13
  237. package/public/assets/react-vendor-DEdfrRTJ.js +0 -9
  238. package/public/assets/useRecentFiles-C3Ov47U7.js +0 -11
  239. package/public/docs/assets/chunks/@localSearchIndexroot.D4OdkQjy.js +0 -1
  240. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +0 -1
  241. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.js +0 -1
  242. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +0 -1
  243. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.js +0 -1
  244. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.js +0 -1
  245. package/public/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  246. package/public/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  247. package/public/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  248. package/public/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  249. package/public/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  250. package/public/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  251. package/public/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  252. package/public/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  253. package/public/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  254. package/public/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  255. package/public/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  256. package/public/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  257. package/public/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  258. package/public/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  259. package/public/docs/assets/style.tmDhD3c8.css +0 -1
  260. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.js +0 -22
  261. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.lean.js +0 -1
  262. package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.js +0 -1
  263. package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.lean.js +0 -1
  264. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.js +0 -1
  265. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.lean.js +0 -1
  266. package/public/docs/assets/user-guide_understanding-findings.md.Dqt2H_C1.js +0 -1
  267. package/public/docs/assets/user-guide_understanding-findings.md.Dqt2H_C1.lean.js +0 -1
  268. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.lean.js} +0 -0
  269. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js → contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.lean.js} +0 -0
  270. /package/public/docs/assets/{contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js → contributor-guide_architecture_detector-contract.md.N4hhenrA.lean.js} +0 -0
  271. /package/public/docs/assets/{contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.lean.js → contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.lean.js} +0 -0
  272. /package/public/docs/assets/{contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.lean.js → contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.lean.js} +0 -0
  273. /package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js → contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.lean.js} +0 -0
  274. /package/public/docs/assets/{contributor-guide_development-setup.md.D9bu4j_c.lean.js → contributor-guide_development-setup.md.DQdzera7.lean.js} +0 -0
  275. /package/public/docs/assets/{user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js → user-guide_mcp-tools_get-run-summary.md.EhaIsele.lean.js} +0 -0
@@ -1,4 +1,4 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
2
- <rect width="32" height="32" rx="7" fill="#181614"/>
3
- <path d="M17.5 5 8 18h6.5l-1 9L24 14h-6.5l1-9z" fill="#ff6b2c"/>
2
+ <rect width="32" height="32" rx="6" fill="#4f46e5"/>
3
+ <path d="M17.5 5 8 18h6.5l-1 9L24 14h-6.5l1-9z" fill="#eceafd"/>
4
4
  </svg>
package/public/index.html CHANGED
@@ -10,9 +10,6 @@
10
10
  <meta property="og:url" content="https://shuffle-works.github.io/sparkforensics/">
11
11
  <meta property="og:title" content="SparkForensics">
12
12
  <meta property="og:description" content="Zero-backend browser dashboard that parses Apache Spark event logs to diagnose performance bottlenecks: skew, spill, GC pressure, stragglers, cache utilization, and more.">
13
- <link rel="preconnect" href="https://fonts.googleapis.com">
14
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15
- <link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL@400..700,0..1&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
16
13
  <script>
17
14
  try {
18
15
  var stored = localStorage.getItem('shuffle-works-theme');
@@ -23,14 +20,14 @@
23
20
  document.documentElement.setAttribute('data-theme', 'light');
24
21
  } catch (_) {}
25
22
  </script>
26
- <script type="module" crossorigin src="./assets/index-DCeWge12.js"></script>
23
+ <script type="module" crossorigin src="./assets/index-DwqkFUFk.js"></script>
27
24
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-hePW80VL.js">
28
25
  <link rel="modulepreload" crossorigin href="./assets/charts-vendor-0VMaLVLN.js">
29
26
  <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">
27
+ <link rel="modulepreload" crossorigin href="./assets/react-vendor-DfpuPQZf.js">
28
+ <link rel="modulepreload" crossorigin href="./assets/DisclosureContext-OO7X1HAx.js">
32
29
  <link rel="stylesheet" crossorigin href="./assets/plan-graph-vendor-DLioOiRN.css">
33
- <link rel="stylesheet" crossorigin href="./assets/index-BcqTLKFp.css">
30
+ <link rel="stylesheet" crossorigin href="./assets/index-DhpRGMZc.css">
34
31
  </head>
35
32
  <body>
36
33
  <div id="root"></div>
@@ -1,7 +1,9 @@
1
+ import { computePeakConcurrentExecutorCount } from './core-count.js';
1
2
  import { parseSparkMemoryMB } from './spark-memory.js';
2
-
3
+
3
4
 
4
5
  const MS_PER_HOUR = 3_600_000;
6
+ const MS_PER_SECOND = 1000;
5
7
  const MIB_PER_GIB = 1024;
6
8
  // Spark's documented floor and factor for the default executor memory overhead
7
9
  // (spark.executor.memoryOverhead = max(factor * executor memory, 384 MiB)).
@@ -14,12 +16,29 @@ const DEFAULT_OVERHEAD_FACTOR = 0.1;
14
16
   
15
17
 
16
18
 
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+  
30
+
17
31
 
18
32
 
33
+ // The fields the allocation reads from the parsed model's executor events, so a detector's
34
+ // narrower event shapes qualify too.
35
+
36
+
37
+
19
38
 
20
-
21
-
39
+
22
40
 
41
+
23
42
 
24
43
 
25
44
  /** The latest timestamp the log records: the application start and end and every stage and
@@ -43,7 +62,7 @@ const DEFAULT_EXECUTOR_MEMORY_MIB = 1024;
43
62
  // plus overhead, plus off-heap and PySpark worker memory when configured. Null when the log
44
63
  // records no Spark properties (nothing to tell a default from a missing config) or a memory key it
45
64
  // does record cannot be read.
46
- function executorMemoryMiB(app ) {
65
+ function executorMemoryMiB(app ) {
47
66
  const config = app?.config;
48
67
  if (config == null) return null;
49
68
  // undefined: key absent; null: present but unreadable.
@@ -63,6 +82,11 @@ function executorMemoryMiB(app ) {
63
82
  return heap + overhead + offHeap + pyspark;
64
83
  }
65
84
 
85
+ function loggedDynamicAllocation(app ) {
86
+ const raw = app?.config?.['spark.dynamicAllocation.enabled'];
87
+ return raw == null ? null : raw.trim().toLowerCase() === 'true' ? 'on' : 'off';
88
+ }
89
+
66
90
  /** Allocated core-hours and memory GiB-hours from the executor lifecycle: each executor counts
67
91
  * from its ExecutorAdded timestamp to its first later ExecutorRemoved timestamp. One with no
68
92
  * removal closes at the application end when the log has one, else at the last timestamp the log
@@ -72,35 +96,96 @@ function executorMemoryMiB(app ) {
72
96
  * else the larger of 384 MiB and spark.executor.memoryOverheadFactor, default 0.1, times the
73
97
  * memory), plus spark.memory.offHeap.size when spark.memory.offHeap.enabled is true, plus
74
98
  * spark.executor.pyspark.memory.
99
+ * The executor counts and seconds come from the same alive intervals.
75
100
  * Null, never 0, for a figure whose inputs the log lacks. */
76
101
  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]);
102
+ const dynamicAllocation = loggedDynamicAllocation(input.app);
103
+ const alive = aliveIntervals(input);
104
+ if (alive.length === 0) {
105
+ return { coreHours: null, memoryGbHours: null, dynamicAllocation, executorsPeak: null, executorsMean: null, executorCores: null, executorSeconds: null };
84
106
  }
85
107
  const closeAt = input.app?.endTime ?? lastObservedTimestamp(input);
86
- const configuredCores = Number.parseInt(input.app?.config?.['spark.executor.cores'] ?? '', 10);
87
108
  const memoryMiB = executorMemoryMiB(input.app);
88
109
 
89
110
  let coreMs = 0;
90
111
  let memoryMiBMs = 0;
112
+ let aliveMsTotal = 0;
91
113
  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;
114
+ const coreCounts = new Set ();
115
+ for (const { start, end, cores } of alive) {
116
+ const aliveMs = end - start;
117
+ aliveMsTotal += aliveMs;
118
+ if (cores == null) coresKnown = false; else { coreMs += cores * aliveMs; coreCounts.add(cores); }
100
119
  memoryMiBMs += (memoryMiB ?? 0) * aliveMs;
101
120
  }
121
+ const startTime = input.app?.startTime;
122
+ const windowMs = closeAt != null && startTime != null ? closeAt - startTime : 0;
123
+ const firstRemovals = alive.flatMap((a) => (a.removedAt == null ? [] : [{ executorId: a.executorId, timestamp: a.removedAt }]));
102
124
  return {
103
125
  coreHours: coresKnown ? coreMs / MS_PER_HOUR : null,
104
126
  memoryGbHours: memoryMiB != null ? memoryMiBMs / MIB_PER_GIB / MS_PER_HOUR : null,
127
+ dynamicAllocation,
128
+ executorsPeak: computePeakConcurrentExecutorCount(alive.map((a) => ({ executorId: a.executorId, timestamp: a.start })), firstRemovals),
129
+ executorsMean: windowMs > 0 ? aliveMsTotal / windowMs : null,
130
+ executorCores: coresKnown && coreCounts.size === 1 ? [...coreCounts][0] : null,
131
+ executorSeconds: aliveMsTotal / MS_PER_SECOND,
105
132
  };
106
133
  }
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+ // Each executor's alive interval: from its ExecutorAdded timestamp to its first later
145
+ // ExecutorRemoved timestamp, else the application end, else the last timestamp the log records.
146
+ // The one definition computeAllocation and allocatedCoreMsWithin share.
147
+ function aliveIntervals(input ) {
148
+ const removedAt = new Map ();
149
+ for (const e of input.executors.removed) {
150
+ const times = removedAt.get(e.executorId);
151
+ if (times) times.push(e.timestamp); else removedAt.set(e.executorId, [e.timestamp]);
152
+ }
153
+ const closeAt = input.app?.endTime ?? lastObservedTimestamp(input);
154
+ const configuredCores = Number.parseInt(input.app?.config?.['spark.executor.cores'] ?? '', 10);
155
+ const out = [];
156
+ const seen = new Set ();
157
+ for (const e of input.executors.added) {
158
+ if (seen.has(e.executorId)) continue; // a replayed ExecutorAdded is the same executor
159
+ seen.add(e.executorId);
160
+ const removal = (removedAt.get(e.executorId) ?? []).filter((t) => t >= e.timestamp).sort((a, b) => a - b)[0] ?? null;
161
+ out.push({
162
+ executorId: e.executorId,
163
+ start: e.timestamp,
164
+ end: Math.max(e.timestamp, removal ?? closeAt ?? e.timestamp),
165
+ removedAt: removal,
166
+ cores: e.totalCores != null && e.totalCores > 0 ? e.totalCores : Number.isFinite(configuredCores) ? configuredCores : null,
167
+ });
168
+ }
169
+ return out;
170
+ }
171
+
172
+ /** Allocated core-milliseconds: the figure behind `coreHours`, the capacity idle-core figures are
173
+ * measured against. Null when the log has no executor events or an executor's cores are unknown. */
174
+ export function allocatedCoreMs(input ) {
175
+ const { coreHours } = computeAllocation(input);
176
+ return coreHours != null && coreHours > 0 ? coreHours * MS_PER_HOUR : null;
177
+ }
178
+
179
+ /** The part of the allocated core-milliseconds held inside `windows` (disjoint, sorted
180
+ * intervals, such as the union of the stages' windows), counted from the same alive intervals as
181
+ * `allocatedCoreMs`. Null when `allocatedCoreMs` is. */
182
+ export function allocatedCoreMsWithin(input , windows ) {
183
+ if (allocatedCoreMs(input) == null) return null;
184
+ let coreMs = 0;
185
+ for (const { start, end, cores } of aliveIntervals(input)) {
186
+ for (const [from, to] of windows) {
187
+ coreMs += (cores ?? 0) * Math.max(0, Math.min(end, to) - Math.max(start, from));
188
+ }
189
+ }
190
+ return coreMs;
191
+ }
@@ -74,6 +74,7 @@ export function apiMergeWrites(sql )
74
74
  kind: resolved?.kind ?? null,
75
75
  target: resolved?.target ?? null,
76
76
  outputRows: null,
77
+ mergeRows: null,
77
78
  raw: `Delta MERGE operation, executions ${span}${group.unclean ? ` (${group.unclean})` : ''}`,
78
79
  };
79
80
  });
@@ -3,6 +3,7 @@ import { computeSkewRatio, ENTRY_BY_TYPE, } from '../det
3
3
  import { effectiveThresholds } from '../threshold-overrides.js';
4
4
  import { IMPACT_BAND_ORDER } from '../format-utils.js';
5
5
 
6
+ import { PAIR_DELTA_METRICS, } from '../stage-alignment.js';
6
7
 
7
8
 
8
9
 
@@ -14,13 +15,23 @@ const IMPACT_BANDS = Object.keys(IMPACT_BAND_ORDER) ;
14
15
 
15
16
 
16
17
 
18
+
19
+
20
+
21
+
22
+
17
23
 
24
+
25
+ export const STAGE_QUALITIES = ['exact', 'structural', 'aligned'];
26
+ export const DEFAULT_STAGE_BUDGET_QUALITIES = ['exact', 'structural'];
27
+ /** Metrics with a regression direction (higher is worse). Input and output volume are workload, not performance. */
28
+ export const NEUTRAL_PAIR_METRICS = new Set(['inputBytes', 'outputBytes']);
18
29
 
19
30
 
20
-
31
+
21
32
 
22
33
 
23
-
34
+
24
35
 
25
36
 
26
37
 
@@ -146,6 +157,50 @@ function checkRegression(comparison , maxRegressionPct
146
157
  : { name: 'max-regression', metric: regressionMetric, status: 'pass', detail: `Metric "${regressionMetric}" ${pctLabel}, within budget ${maxRegressionPct}%.` };
147
158
  }
148
159
 
160
+ function checkStageRegression(
161
+ comparison , { metric, maxPct } , qualities ,
162
+ ) {
163
+ const name = 'max-stage-regression' ;
164
+ if (!(PAIR_DELTA_METRICS ).includes(metric)) {
165
+ return { name, metric, status: 'inconclusive', detail: `Metric "${metric}" is not a paired-stage metric (expected one of: ${PAIR_DELTA_METRICS.join(', ')}).` };
166
+ }
167
+ if (NEUTRAL_PAIR_METRICS.has(metric)) {
168
+ return { name, metric, status: 'inconclusive', detail: `Metric "${metric}" measures workload volume, not performance: it has no regression direction to check.` };
169
+ }
170
+ const eligible = comparison.stagePairs.filter((p) => qualities.includes(p.quality));
171
+ if (eligible.length === 0) {
172
+ return { name, metric, status: 'inconclusive', detail: comparison.stagePairs.length === 0
173
+ ? 'No stages were paired between the two runs.'
174
+ : `None of the ${comparison.stagePairs.length} paired stage(s) has quality ${qualities.join(' or ')}.` };
175
+ }
176
+ const measured = eligible
177
+ .map((pair) => ({ pair, delta: pair.deltas[metric ] }))
178
+ .filter(({ delta }) => delta.baseline != null && delta.delta != null);
179
+ if (measured.length === 0) {
180
+ return { name, metric, status: 'inconclusive', detail: `Metric "${metric}" is unavailable for every eligible paired stage.` };
181
+ }
182
+ const regressions = measured
183
+ .filter(({ delta }) => delta.delta > 0)
184
+ .map(({ pair, delta }) => ({ pair, delta, pct: delta.baseline === 0 ? Infinity : (delta.delta / delta.baseline ) * 100 }))
185
+ .sort((a, b) => b.pct - a.pct || a.pair.pairId.localeCompare(b.pair.pairId));
186
+ const over = regressions.filter((r) => r.pct > maxPct);
187
+ const scope = `${measured.length} paired stage(s)`;
188
+ if (over.length === 0) {
189
+ const worst = regressions[0];
190
+ return { name, metric, status: 'pass', detail: worst
191
+ ? `No paired stage regressed "${metric}" beyond ${maxPct}% across ${scope}; the largest was ${formatPct(worst.pct)} (pair ${worst.pair.pairId}).`
192
+ : `No paired stage regressed "${metric}" across ${scope}.` };
193
+ }
194
+ const listed = over.slice(0, 3).map((r) => `pair ${r.pair.pairId} (${r.pair.quality}) ${formatPct(r.pct)}`).join(', ');
195
+ const more = over.length > 3 ? `, and ${over.length - 3} more` : '';
196
+ return { name, metric, status: 'violation',
197
+ detail: `${over.length} of ${scope} regressed "${metric}" beyond ${maxPct}%: ${listed}${more}.` };
198
+ }
199
+
200
+ function formatPct(pct ) {
201
+ return pct === Infinity ? 'from 0' : `+${pct.toFixed(1)}%`;
202
+ }
203
+
149
204
  function checkFailOnIntroduced(comparison , band ) {
150
205
  if (band !== 'all' && !IMPACT_BANDS.includes(band )) {
151
206
  return { name: 'fail-on-introduced', status: 'inconclusive', detail: `Impact band "${band}" is not recognized (expected "all" or one of ${IMPACT_BANDS.join(', ')}).` };
@@ -198,6 +253,11 @@ export function evaluateBudgets({ appModel, catalog, budgets, comparison, thresh
198
253
  for (const { metric, maxPct } of budgets.regressionBudgets ?? []) {
199
254
  pushComparisonBudget(results, comparison, 'max-regression', (c) => checkRegression(c, maxPct, metric), metric);
200
255
  }
256
+ const stageQualities = budgets.stageQualities ?? DEFAULT_STAGE_BUDGET_QUALITIES;
257
+ for (const budget of budgets.stageRegressionBudgets ?? []) {
258
+ pushComparisonBudget(results, comparison, 'max-stage-regression',
259
+ (c) => checkStageRegression(c, budget, stageQualities), budget.metric);
260
+ }
201
261
  if (budgets.failOnIntroduced !== undefined) {
202
262
  pushComparisonBudget(results, comparison, 'fail-on-introduced',
203
263
  (c) => checkFailOnIntroduced(c, budgets.failOnIntroduced ));
@@ -4,6 +4,9 @@
4
4
  import { readFileSync } from 'node:fs';
5
5
  import { resolve } from 'node:path';
6
6
  import { COMPARISON_METRIC_KEYS } from '../run-comparison.js';
7
+ import { PAIR_DELTA_METRICS } from '../stage-alignment.js';
8
+ import { NEUTRAL_PAIR_METRICS, STAGE_QUALITIES } from './budgets.js';
9
+
7
10
 
8
11
 
9
12
 
@@ -81,3 +84,35 @@ export function combineRegressionBudgets(sources )
81
84
  }
82
85
  return sources.map((s) => s.budget);
83
86
  }
87
+
88
+ /** Refuses a stage budget metric that is not a paired-stage metric or has no regression direction. */
89
+ export function assertStageBudgetMetric(metric , origin ) {
90
+ if (!(PAIR_DELTA_METRICS ).includes(metric)) {
91
+ throw new Error(`${origin}: unknown paired-stage metric "${metric}" (expected one of: ${PAIR_DELTA_METRICS.filter((m) => !NEUTRAL_PAIR_METRICS.has(m)).join(', ')}).`);
92
+ }
93
+ if (NEUTRAL_PAIR_METRICS.has(metric)) {
94
+ throw new Error(`${origin}: "${metric}" measures workload volume, not performance, so it has no regression direction to budget.`);
95
+ }
96
+ }
97
+
98
+ /** One `--stage-regression-budget` value, `<metric>:<pct>`, over the paired-stage metrics. */
99
+ export function parseStageRegressionBudgetFlag(spec ) {
100
+ const origin = `--stage-regression-budget "${spec}"`;
101
+ const colon = spec.indexOf(':');
102
+ if (colon === -1) throw new Error(`${origin}: expected <metric>:<pct>.`);
103
+ const metric = spec.slice(0, colon);
104
+ const pct = spec.slice(colon + 1);
105
+ assertStageBudgetMetric(metric, origin);
106
+ if (!PCT_PATTERN.test(pct)) throw new Error(`${origin}: "${pct}" is not a non-negative percentage.`);
107
+ return { metric, maxPct: Number(pct) };
108
+ }
109
+
110
+ /** The pair qualities a stage budget reads, from a comma list such as `exact,structural`. */
111
+ export function parseStageQualities(list , origin ) {
112
+ const qualities = list.split(',').map((q) => q.trim()).filter(Boolean);
113
+ const bad = qualities.find((q) => !(STAGE_QUALITIES ).includes(q));
114
+ if (qualities.length === 0 || bad !== undefined) {
115
+ throw new Error(`${origin}: ${bad === undefined ? 'no quality given' : `unknown quality "${bad}"`} (expected a list from: ${STAGE_QUALITIES.join(', ')}).`);
116
+ }
117
+ return qualities ;
118
+ }
@@ -11,7 +11,7 @@ import { redactComparison } from './redact.js';
11
11
 
12
12
 
13
13
 
14
-
14
+
15
15
 
16
16
 
17
17
  /** `full` carries every field (the CLI); `summary` leaves out `stagePairs` (MCP, which returns them
@@ -33,6 +33,7 @@ export function comparisonOutput(comparison , view
33
33
  unmatched: comparison.unmatched,
34
34
  replanned: comparison.replanned,
35
35
  bookkeepingStageIds: comparison.bookkeepingStageIds,
36
+ executionAlignment: comparison.executionAlignment,
36
37
  };
37
38
  }
38
39
 
@@ -1 +1 @@
1
- 6e0ac29f74ae092b14c5700dfa86d3273fce7d1af59005c6e2e450a91da92d5e
1
+ 9c33274bcae34f425c7d625bdea9cfacdb400f333bc3a5ced9c86a0e75094916
@@ -9,6 +9,15 @@ import { isCut, } from './write-target-args.js';
9
9
  // scans and `DeltaLogFileIndex [<path>/_delta_log/<file>, ...]`.
10
10
  const DELTA_LOG_PATH = /((?:[A-Za-z][A-Za-z0-9+.-]*:\/{1,3}|\/)[^\s,[\]()#]+?)\/_delta_log(?=[/[\],\s)]|$)/g;
11
11
 
12
+ // Hadoop prints a local path as `file:/p` and a URI as `file:///p`: one table, two spellings.
13
+ // Both read as `file:///p` so a table is not counted twice. A `file://host/p` is left alone.
14
+ const LOCAL_FILE_PREFIX = /^file:(?:\/|\/\/\/)(?=[^/])/;
15
+
16
+ /** The one spelling of a table path: the two local-file forms agree, other paths pass through. */
17
+ export function normalizeTablePath(path ) {
18
+ return path.replace(LOCAL_FILE_PREFIX, 'file:///').replace(/\/+$/, '');
19
+ }
20
+
12
21
 
13
22
 
14
23
  // Every distinct table path named in the plans; `unreliable` when a plan is missing or a path cut.
@@ -26,7 +35,7 @@ function deltaLogPaths(executions ) {
26
35
  if (matches.length < mentions) unreliable = true;
27
36
  for (const m of matches) {
28
37
  if (isCut(m[1])) unreliable = true;
29
- else paths.add(m[1].replace(/\/+$/, ''));
38
+ else paths.add(normalizeTablePath(m[1]));
30
39
  }
31
40
  }
32
41
  }, { dedupe: true });