sparkforensics-cli 0.1.0 → 0.2.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 (360) hide show
  1. package/README.md +6 -0
  2. package/bin/sparkforensics-analyze.mjs +113 -48
  3. package/export-template/docs/404.html +25 -0
  4. package/export-template/docs/assets/app.CndaAS6v.js +1 -0
  5. package/export-template/docs/assets/aqe-loop.IwQSATHw.svg +1 -0
  6. package/export-template/docs/assets/aqe-loop.dark.DGbaxqJE.svg +1 -0
  7. package/export-template/docs/assets/broadcast-vs-shuffle.Db4WY1XK.svg +1 -0
  8. package/export-template/docs/assets/broadcast-vs-shuffle.dark.C7Bxs0mG.svg +1 -0
  9. package/export-template/docs/assets/cache-lifecycle.dark.B-hS7AgU.svg +1 -0
  10. package/export-template/docs/assets/cache-lifecycle.rEOVYQNU.svg +1 -0
  11. package/export-template/docs/assets/chunks/@localSearchIndexroot.DNY8bVcl.js +1 -0
  12. package/export-template/docs/assets/chunks/VPLocalSearchBox.yJbZbsEo.js +9 -0
  13. package/export-template/docs/assets/chunks/duplicate-plan-subtree.dark.Cdp70QhV.js +1 -0
  14. package/export-template/docs/assets/chunks/framework.DSg0KOwT.js +20 -0
  15. package/export-template/docs/assets/chunks/retry-escalation-ladder.dark.DHipdJgZ.js +1 -0
  16. package/export-template/docs/assets/chunks/theme.Df2VAG9w.js +2 -0
  17. package/export-template/docs/assets/cold-start-timeline.DxC_Sc7w.svg +1 -0
  18. package/export-template/docs/assets/cold-start-timeline.dark.CZ17YcAG.svg +1 -0
  19. package/export-template/docs/assets/columnar-layout.PghGeOEA.svg +1 -0
  20. package/export-template/docs/assets/columnar-layout.dark.BVNlz0ff.svg +1 -0
  21. package/export-template/docs/assets/container-memory.DIO0AnIm.svg +1 -0
  22. package/export-template/docs/assets/container-memory.dark.CP-5zuCl.svg +1 -0
  23. package/export-template/docs/assets/contributor-guide_architecture_board-widgets.md.B-OsL91z.js +1 -0
  24. package/export-template/docs/assets/contributor-guide_architecture_board-widgets.md.B-OsL91z.lean.js +1 -0
  25. package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.BOeH4d1J.js +1 -0
  26. package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.BOeH4d1J.lean.js +1 -0
  27. package/export-template/docs/assets/contributor-guide_architecture_drill-down.md.BtPdlM7r.js +1 -0
  28. package/export-template/docs/assets/contributor-guide_architecture_drill-down.md.BtPdlM7r.lean.js +1 -0
  29. package/export-template/docs/assets/contributor-guide_architecture_impact-estimation.md.DYCDPgkh.js +1 -0
  30. package/export-template/docs/assets/contributor-guide_architecture_impact-estimation.md.DYCDPgkh.lean.js +1 -0
  31. package/export-template/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.js +1 -0
  32. package/export-template/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.lean.js +1 -0
  33. package/export-template/docs/assets/contributor-guide_architecture_overview.md.CehiRmGn.js +1 -0
  34. package/export-template/docs/assets/contributor-guide_architecture_overview.md.CehiRmGn.lean.js +1 -0
  35. package/export-template/docs/assets/contributor-guide_architecture_state-and-history.md.m3S3UdMk.js +1 -0
  36. package/export-template/docs/assets/contributor-guide_architecture_state-and-history.md.m3S3UdMk.lean.js +1 -0
  37. package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.DbqPf2OT.js +1 -0
  38. package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.DbqPf2OT.lean.js +1 -0
  39. package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.B93qJ_tT.js +6 -0
  40. package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.B93qJ_tT.lean.js +1 -0
  41. package/export-template/docs/assets/contributor-guide_contributing.md.CvRsdr6J.js +1 -0
  42. package/export-template/docs/assets/contributor-guide_contributing.md.CvRsdr6J.lean.js +1 -0
  43. package/export-template/docs/assets/contributor-guide_development-setup.md.DvAN_9mK.js +12 -0
  44. package/export-template/docs/assets/contributor-guide_development-setup.md.DvAN_9mK.lean.js +1 -0
  45. package/export-template/docs/assets/contributor-guide_testing.md.6rIKqSyY.js +1 -0
  46. package/export-template/docs/assets/contributor-guide_testing.md.6rIKqSyY.lean.js +1 -0
  47. package/export-template/docs/assets/dag-stages.DSz_S937.svg +1 -0
  48. package/export-template/docs/assets/dag-stages.dark.F72UzxH4.svg +1 -0
  49. package/export-template/docs/assets/driver-executor.D5pQ7YN1.svg +1 -0
  50. package/export-template/docs/assets/driver-executor.dark.BmX9cPvh.svg +1 -0
  51. package/export-template/docs/assets/duplicate-plan-subtree.B4cvN6fj.svg +1 -0
  52. package/export-template/docs/assets/duplicate-plan-subtree.dark.Dw8wS0Ag.svg +1 -0
  53. package/export-template/docs/assets/index.md.CHJVslga.js +1 -0
  54. package/export-template/docs/assets/index.md.CHJVslga.lean.js +1 -0
  55. package/export-template/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  56. package/export-template/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  57. package/export-template/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  58. package/export-template/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  59. package/export-template/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  60. package/export-template/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  61. package/export-template/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  62. package/export-template/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  63. package/export-template/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  64. package/export-template/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  65. package/export-template/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  66. package/export-template/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  67. package/export-template/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  68. package/export-template/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  69. package/export-template/docs/assets/join-strategy.C_FvrCEo.svg +1 -0
  70. package/export-template/docs/assets/join-strategy.dark.ChMLnNII.svg +1 -0
  71. package/export-template/docs/assets/memory-borrowing.BqQRJg0u.svg +1 -0
  72. package/export-template/docs/assets/memory-borrowing.dark.Yhh20O9C.svg +1 -0
  73. package/export-template/docs/assets/memory-regions.XHvO7jHG.svg +1 -0
  74. package/export-template/docs/assets/memory-regions.dark.D4TP9_08.svg +1 -0
  75. package/export-template/docs/assets/repartition-vs-coalesce.BovLRrpj.svg +1 -0
  76. package/export-template/docs/assets/repartition-vs-coalesce.dark.BhAczKZQ.svg +1 -0
  77. package/export-template/docs/assets/retry-escalation-ladder.DyTKJJmZ.svg +1 -0
  78. package/export-template/docs/assets/retry-escalation-ladder.dark.BdsabtU3.svg +1 -0
  79. package/export-template/docs/assets/shuffle-map-reduce.KuOEZVmg.svg +1 -0
  80. package/export-template/docs/assets/shuffle-map-reduce.dark.BgQZnFSb.svg +1 -0
  81. package/export-template/docs/assets/spill-classification.BU2euYDO.svg +1 -0
  82. package/export-template/docs/assets/spill-classification.dark.D7i1M40d.svg +1 -0
  83. package/export-template/docs/assets/style.DXOMCXxn.css +1 -0
  84. package/export-template/docs/assets/tuning-reference_anti-patterns.md.Df1YMIHu.js +1 -0
  85. package/export-template/docs/assets/tuning-reference_anti-patterns.md.Df1YMIHu.lean.js +1 -0
  86. package/export-template/docs/assets/tuning-reference_aqe.md.BIsCtLzm.js +1 -0
  87. package/export-template/docs/assets/tuning-reference_aqe.md.BIsCtLzm.lean.js +1 -0
  88. package/export-template/docs/assets/tuning-reference_bottleneck-broadcast-sizing.md.CEstB3Ia.js +1 -0
  89. package/export-template/docs/assets/tuning-reference_bottleneck-broadcast-sizing.md.CEstB3Ia.lean.js +1 -0
  90. package/export-template/docs/assets/tuning-reference_bottleneck-cold-start.md.CEuy-72y.js +7 -0
  91. package/export-template/docs/assets/tuning-reference_bottleneck-cold-start.md.CEuy-72y.lean.js +1 -0
  92. package/export-template/docs/assets/tuning-reference_bottleneck-duplicate-plan-subtree.md.CIohQDfn.js +1 -0
  93. package/export-template/docs/assets/tuning-reference_bottleneck-duplicate-plan-subtree.md.CIohQDfn.lean.js +1 -0
  94. package/export-template/docs/assets/tuning-reference_bottleneck-failures.md.4z5BXGJ2.js +6 -0
  95. package/export-template/docs/assets/tuning-reference_bottleneck-failures.md.4z5BXGJ2.lean.js +1 -0
  96. package/export-template/docs/assets/tuning-reference_bottleneck-gc.md.DSxzZRK7.js +6 -0
  97. package/export-template/docs/assets/tuning-reference_bottleneck-gc.md.DSxzZRK7.lean.js +1 -0
  98. package/export-template/docs/assets/tuning-reference_bottleneck-job-failure-rate.md.BaJl__1W.js +8 -0
  99. package/export-template/docs/assets/tuning-reference_bottleneck-job-failure-rate.md.BaJl__1W.lean.js +1 -0
  100. package/export-template/docs/assets/tuning-reference_bottleneck-memory-utilization.md.DbP-SJZc.js +7 -0
  101. package/export-template/docs/assets/tuning-reference_bottleneck-memory-utilization.md.DbP-SJZc.lean.js +1 -0
  102. package/export-template/docs/assets/tuning-reference_bottleneck-retry-waste.md.D5JMjVOt.js +1 -0
  103. package/export-template/docs/assets/tuning-reference_bottleneck-retry-waste.md.D5JMjVOt.lean.js +1 -0
  104. package/export-template/docs/assets/tuning-reference_bottleneck-shuffle.md.CM-nTmIH.js +12 -0
  105. package/export-template/docs/assets/tuning-reference_bottleneck-shuffle.md.CM-nTmIH.lean.js +1 -0
  106. package/export-template/docs/assets/tuning-reference_bottleneck-skew.md.BdUwiDhn.js +14 -0
  107. package/export-template/docs/assets/tuning-reference_bottleneck-skew.md.BdUwiDhn.lean.js +1 -0
  108. package/export-template/docs/assets/tuning-reference_bottleneck-slow-host.md.BlIo6UDW.js +7 -0
  109. package/export-template/docs/assets/tuning-reference_bottleneck-slow-host.md.BlIo6UDW.lean.js +1 -0
  110. package/export-template/docs/assets/tuning-reference_bottleneck-small-files.md.B8kloyx8.js +5 -0
  111. package/export-template/docs/assets/tuning-reference_bottleneck-small-files.md.B8kloyx8.lean.js +1 -0
  112. package/export-template/docs/assets/tuning-reference_bottleneck-spill.md.PNH7mITt.js +6 -0
  113. package/export-template/docs/assets/tuning-reference_bottleneck-spill.md.PNH7mITt.lean.js +1 -0
  114. package/export-template/docs/assets/tuning-reference_bottleneck-straggler.md.DY36fHN5.js +7 -0
  115. package/export-template/docs/assets/tuning-reference_bottleneck-straggler.md.DY36fHN5.lean.js +1 -0
  116. package/export-template/docs/assets/tuning-reference_bottleneck-tiny-tasks.md.QTV7O8kU.js +8 -0
  117. package/export-template/docs/assets/tuning-reference_bottleneck-tiny-tasks.md.QTV7O8kU.lean.js +1 -0
  118. package/export-template/docs/assets/tuning-reference_bottleneck-utilization.md.DTiueZC3.js +5 -0
  119. package/export-template/docs/assets/tuning-reference_bottleneck-utilization.md.DTiueZC3.lean.js +1 -0
  120. package/export-template/docs/assets/tuning-reference_caching.md.B7aQ8asB.js +1 -0
  121. package/export-template/docs/assets/tuning-reference_caching.md.B7aQ8asB.lean.js +1 -0
  122. package/export-template/docs/assets/tuning-reference_cluster-config.md.ZVmDGsQ3.js +1 -0
  123. package/export-template/docs/assets/tuning-reference_cluster-config.md.ZVmDGsQ3.lean.js +1 -0
  124. package/export-template/docs/assets/tuning-reference_config.md.UvveiWG3.js +1 -0
  125. package/export-template/docs/assets/tuning-reference_config.md.UvveiWG3.lean.js +1 -0
  126. package/export-template/docs/assets/tuning-reference_data-formats.md.bjCAWH3N.js +1 -0
  127. package/export-template/docs/assets/tuning-reference_data-formats.md.bjCAWH3N.lean.js +1 -0
  128. package/export-template/docs/assets/tuning-reference_index.md.BQ_NooMV.js +1 -0
  129. package/export-template/docs/assets/tuning-reference_index.md.BQ_NooMV.lean.js +1 -0
  130. package/export-template/docs/assets/tuning-reference_intro.md.CobD-lGB.js +1 -0
  131. package/export-template/docs/assets/tuning-reference_intro.md.CobD-lGB.lean.js +1 -0
  132. package/export-template/docs/assets/tuning-reference_joins.md.BtKs_CuW.js +1 -0
  133. package/export-template/docs/assets/tuning-reference_joins.md.BtKs_CuW.lean.js +1 -0
  134. package/export-template/docs/assets/tuning-reference_memory-model.md.DhT-n4y3.js +1 -0
  135. package/export-template/docs/assets/tuning-reference_memory-model.md.DhT-n4y3.lean.js +1 -0
  136. package/export-template/docs/assets/tuning-reference_metrics.md.mLOh7Apj.js +1 -0
  137. package/export-template/docs/assets/tuning-reference_metrics.md.mLOh7Apj.lean.js +1 -0
  138. package/export-template/docs/assets/tuning-reference_partitioning.md.q0zKF_8X.js +1 -0
  139. package/export-template/docs/assets/tuning-reference_partitioning.md.q0zKF_8X.lean.js +1 -0
  140. package/export-template/docs/assets/tuning-reference_pyspark.md.DDCfvN9t.js +6 -0
  141. package/export-template/docs/assets/tuning-reference_pyspark.md.DDCfvN9t.lean.js +1 -0
  142. package/export-template/docs/assets/tuning-reference_shuffle.md.BZZ7R4Ix.js +1 -0
  143. package/export-template/docs/assets/tuning-reference_shuffle.md.BZZ7R4Ix.lean.js +1 -0
  144. package/export-template/docs/assets/tuning-reference_spark-architecture.md.Dwzm5avO.js +1 -0
  145. package/export-template/docs/assets/tuning-reference_spark-architecture.md.Dwzm5avO.lean.js +1 -0
  146. package/export-template/docs/assets/tuning-reference_table-formats.md.D6wj-2dX.js +1 -0
  147. package/export-template/docs/assets/tuning-reference_table-formats.md.D6wj-2dX.lean.js +1 -0
  148. package/export-template/docs/assets/udf-execution-models.BUFDICuG.svg +1 -0
  149. package/export-template/docs/assets/udf-execution-models.dark.YTNS6GDq.svg +1 -0
  150. package/export-template/docs/assets/user-guide_alternative-log-retrieval.md.sU3KGarf.js +1 -0
  151. package/export-template/docs/assets/user-guide_alternative-log-retrieval.md.sU3KGarf.lean.js +1 -0
  152. package/export-template/docs/assets/user-guide_getting-started.md.DtEM37MK.js +3 -0
  153. package/export-template/docs/assets/user-guide_getting-started.md.DtEM37MK.lean.js +1 -0
  154. package/export-template/docs/assets/user-guide_mcp-tools.md.C8MiIu7F.js +125 -0
  155. package/export-template/docs/assets/user-guide_mcp-tools.md.C8MiIu7F.lean.js +1 -0
  156. package/export-template/docs/assets/user-guide_run-comparison.md.S0TWWmLY.js +1 -0
  157. package/export-template/docs/assets/user-guide_run-comparison.md.S0TWWmLY.lean.js +1 -0
  158. package/export-template/docs/assets/user-guide_understanding-findings.md.D0R_Y-R2.js +1 -0
  159. package/export-template/docs/assets/user-guide_understanding-findings.md.D0R_Y-R2.lean.js +1 -0
  160. package/export-template/docs/contributor-guide/architecture/board-widgets.html +25 -0
  161. package/export-template/docs/contributor-guide/architecture/detector-contract.html +25 -0
  162. package/export-template/docs/contributor-guide/architecture/drill-down.html +25 -0
  163. package/export-template/docs/contributor-guide/architecture/impact-estimation.html +25 -0
  164. package/export-template/docs/contributor-guide/architecture/index.html +25 -0
  165. package/export-template/docs/contributor-guide/architecture/overview.html +25 -0
  166. package/export-template/docs/contributor-guide/architecture/state-and-history.html +25 -0
  167. package/export-template/docs/contributor-guide/architecture/widget-rendering.html +25 -0
  168. package/export-template/docs/contributor-guide/architecture/worker-protocol.html +30 -0
  169. package/export-template/docs/contributor-guide/contributing.html +25 -0
  170. package/export-template/docs/contributor-guide/development-setup.html +36 -0
  171. package/export-template/docs/contributor-guide/testing.html +25 -0
  172. package/export-template/docs/favicon.svg +4 -0
  173. package/export-template/docs/hashmap.json +1 -0
  174. package/export-template/docs/index.html +25 -0
  175. package/export-template/docs/package.json +1 -0
  176. package/export-template/docs/tuning-reference/anti-patterns.html +25 -0
  177. package/export-template/docs/tuning-reference/aqe.html +25 -0
  178. package/export-template/docs/tuning-reference/bottleneck-broadcast-sizing.html +25 -0
  179. package/export-template/docs/tuning-reference/bottleneck-cold-start.html +31 -0
  180. package/export-template/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +25 -0
  181. package/export-template/docs/tuning-reference/bottleneck-failures.html +30 -0
  182. package/export-template/docs/tuning-reference/bottleneck-gc.html +30 -0
  183. package/export-template/docs/tuning-reference/bottleneck-job-failure-rate.html +32 -0
  184. package/export-template/docs/tuning-reference/bottleneck-memory-utilization.html +31 -0
  185. package/export-template/docs/tuning-reference/bottleneck-retry-waste.html +25 -0
  186. package/export-template/docs/tuning-reference/bottleneck-shuffle.html +36 -0
  187. package/export-template/docs/tuning-reference/bottleneck-skew.html +38 -0
  188. package/export-template/docs/tuning-reference/bottleneck-slow-host.html +31 -0
  189. package/export-template/docs/tuning-reference/bottleneck-small-files.html +29 -0
  190. package/export-template/docs/tuning-reference/bottleneck-spill.html +30 -0
  191. package/export-template/docs/tuning-reference/bottleneck-straggler.html +31 -0
  192. package/export-template/docs/tuning-reference/bottleneck-tiny-tasks.html +32 -0
  193. package/export-template/docs/tuning-reference/bottleneck-utilization.html +29 -0
  194. package/export-template/docs/tuning-reference/caching.html +25 -0
  195. package/export-template/docs/tuning-reference/cluster-config.html +25 -0
  196. package/export-template/docs/tuning-reference/config.html +25 -0
  197. package/export-template/docs/tuning-reference/data-formats.html +25 -0
  198. package/export-template/docs/tuning-reference/index.html +25 -0
  199. package/export-template/docs/tuning-reference/intro.html +25 -0
  200. package/export-template/docs/tuning-reference/joins.html +25 -0
  201. package/export-template/docs/tuning-reference/memory-model.html +25 -0
  202. package/export-template/docs/tuning-reference/metrics.html +25 -0
  203. package/export-template/docs/tuning-reference/partitioning.html +25 -0
  204. package/export-template/docs/tuning-reference/pyspark.html +30 -0
  205. package/export-template/docs/tuning-reference/shuffle.html +25 -0
  206. package/export-template/docs/tuning-reference/spark-architecture.html +25 -0
  207. package/export-template/docs/tuning-reference/table-formats.html +25 -0
  208. package/export-template/docs/user-guide/alternative-log-retrieval.html +25 -0
  209. package/export-template/docs/user-guide/getting-started.html +27 -0
  210. package/export-template/docs/user-guide/mcp-tools.html +149 -0
  211. package/export-template/docs/user-guide/run-comparison.html +25 -0
  212. package/export-template/docs/user-guide/understanding-findings.html +25 -0
  213. package/export-template/docs/vp-icons.css +0 -0
  214. package/export-template/favicon.svg +4 -0
  215. package/export-template/index.html +115 -0
  216. package/export-template/parser-worker-QqyEE4m9.js +64 -0
  217. package/package.json +16 -3
  218. package/vendor-core/analyzer.js +74 -74
  219. package/vendor-core/cli/budgets.js +13 -27
  220. package/vendor-core/cli/collect-run.js +43 -19
  221. package/vendor-core/core-count.js +25 -27
  222. package/vendor-core/core-locality-ratio.js +4 -11
  223. package/vendor-core/core-time-series.js +6 -12
  224. package/vendor-core/core-usage-locality.js +3 -4
  225. package/vendor-core/detectors.js +256 -375
  226. package/vendor-core/docs-config.js +69 -21
  227. package/vendor-core/docs-content/chapters/01-intro.md +32 -0
  228. package/vendor-core/docs-content/chapters/02-spark-architecture.md +76 -0
  229. package/vendor-core/docs-content/chapters/03-memory-model.md +73 -0
  230. package/vendor-core/docs-content/chapters/04-partitioning.md +65 -0
  231. package/vendor-core/docs-content/chapters/05-joins.md +62 -0
  232. package/vendor-core/docs-content/chapters/06-shuffle.md +59 -0
  233. package/vendor-core/docs-content/chapters/07-data-formats.md +81 -0
  234. package/vendor-core/docs-content/chapters/07b-table-formats.md +56 -0
  235. package/vendor-core/docs-content/chapters/08-caching.md +58 -0
  236. package/vendor-core/docs-content/chapters/09-pyspark.md +78 -0
  237. package/vendor-core/docs-content/chapters/10-aqe.md +167 -0
  238. package/vendor-core/docs-content/chapters/11-cluster-config.md +170 -0
  239. package/vendor-core/docs-content/chapters/12-anti-patterns.md +171 -0
  240. package/vendor-core/docs-content/chapters/14-metrics.md +87 -0
  241. package/vendor-core/docs-content/chapters/15-config.md +93 -0
  242. package/vendor-core/docs-content/chapters/nav-index.json +370 -0
  243. package/vendor-core/docs-content/detection/cache.md +6 -0
  244. package/vendor-core/docs-content/detection/cfg.md +15 -0
  245. package/vendor-core/docs-content/detection/chrn.md +7 -0
  246. package/vendor-core/docs-content/detection/cold.md +4 -0
  247. package/vendor-core/docs-content/detection/cstor.md +4 -0
  248. package/vendor-core/docs-content/detection/fail.md +5 -0
  249. package/vendor-core/docs-content/detection/gc.md +4 -0
  250. package/vendor-core/docs-content/detection/host.md +5 -0
  251. package/vendor-core/docs-content/detection/incmp.md +6 -0
  252. package/vendor-core/docs-content/detection/jobs.md +4 -0
  253. package/vendor-core/docs-content/detection/local.md +7 -0
  254. package/vendor-core/docs-content/detection/mem.md +10 -0
  255. package/vendor-core/docs-content/detection/part.md +5 -0
  256. package/vendor-core/docs-content/detection/plan.md +14 -0
  257. package/vendor-core/docs-content/detection/retry.md +4 -0
  258. package/vendor-core/docs-content/detection/sfail.md +5 -0
  259. package/vendor-core/docs-content/detection/shape.md +5 -0
  260. package/vendor-core/docs-content/detection/shfl.md +4 -0
  261. package/vendor-core/docs-content/detection/skew.md +6 -0
  262. package/vendor-core/docs-content/detection/slow.md +6 -0
  263. package/vendor-core/docs-content/detection/spec.md +7 -0
  264. package/vendor-core/docs-content/detection/spill.md +7 -0
  265. package/vendor-core/docs-content/detection/strag.md +5 -0
  266. package/vendor-core/docs-content/detection/tiny.md +4 -0
  267. package/vendor-core/docs-content/detection/util.md +4 -0
  268. package/vendor-core/docs-content/diagrams/aqe-loop.dark.svg +1 -0
  269. package/vendor-core/docs-content/diagrams/aqe-loop.svg +1 -0
  270. package/vendor-core/docs-content/diagrams/broadcast-vs-shuffle.dark.svg +1 -0
  271. package/vendor-core/docs-content/diagrams/broadcast-vs-shuffle.svg +1 -0
  272. package/vendor-core/docs-content/diagrams/cache-lifecycle.dark.svg +1 -0
  273. package/vendor-core/docs-content/diagrams/cache-lifecycle.svg +1 -0
  274. package/vendor-core/docs-content/diagrams/cold-start-timeline.dark.svg +1 -0
  275. package/vendor-core/docs-content/diagrams/cold-start-timeline.svg +1 -0
  276. package/vendor-core/docs-content/diagrams/columnar-layout.dark.svg +1 -0
  277. package/vendor-core/docs-content/diagrams/columnar-layout.svg +1 -0
  278. package/vendor-core/docs-content/diagrams/container-memory.dark.svg +1 -0
  279. package/vendor-core/docs-content/diagrams/container-memory.svg +1 -0
  280. package/vendor-core/docs-content/diagrams/dag-stages.dark.svg +1 -0
  281. package/vendor-core/docs-content/diagrams/dag-stages.svg +1 -0
  282. package/vendor-core/docs-content/diagrams/driver-executor.dark.svg +1 -0
  283. package/vendor-core/docs-content/diagrams/driver-executor.svg +1 -0
  284. package/vendor-core/docs-content/diagrams/duplicate-plan-subtree.dark.svg +1 -0
  285. package/vendor-core/docs-content/diagrams/duplicate-plan-subtree.svg +1 -0
  286. package/vendor-core/docs-content/diagrams/join-strategy.dark.svg +1 -0
  287. package/vendor-core/docs-content/diagrams/join-strategy.svg +1 -0
  288. package/vendor-core/docs-content/diagrams/memory-borrowing.dark.svg +1 -0
  289. package/vendor-core/docs-content/diagrams/memory-borrowing.svg +1 -0
  290. package/vendor-core/docs-content/diagrams/memory-regions.dark.svg +1 -0
  291. package/vendor-core/docs-content/diagrams/memory-regions.svg +1 -0
  292. package/vendor-core/docs-content/diagrams/repartition-vs-coalesce.dark.svg +1 -0
  293. package/vendor-core/docs-content/diagrams/repartition-vs-coalesce.svg +1 -0
  294. package/vendor-core/docs-content/diagrams/retry-escalation-ladder.dark.svg +1 -0
  295. package/vendor-core/docs-content/diagrams/retry-escalation-ladder.svg +1 -0
  296. package/vendor-core/docs-content/diagrams/shuffle-map-reduce.dark.svg +1 -0
  297. package/vendor-core/docs-content/diagrams/shuffle-map-reduce.svg +1 -0
  298. package/vendor-core/docs-content/diagrams/spill-classification.dark.svg +1 -0
  299. package/vendor-core/docs-content/diagrams/spill-classification.svg +1 -0
  300. package/vendor-core/docs-content/diagrams/udf-execution-models.dark.svg +1 -0
  301. package/vendor-core/docs-content/diagrams/udf-execution-models.svg +1 -0
  302. package/vendor-core/docs-content/tuning/broadcast-sizing.md +78 -0
  303. package/vendor-core/docs-content/tuning/cold-start.md +81 -0
  304. package/vendor-core/docs-content/tuning/duplicate-plan-subtree.md +45 -0
  305. package/vendor-core/docs-content/tuning/failures.md +124 -0
  306. package/vendor-core/docs-content/tuning/gc.md +110 -0
  307. package/vendor-core/docs-content/tuning/job-failure-rate.md +101 -0
  308. package/vendor-core/docs-content/tuning/memory-utilization.md +58 -0
  309. package/vendor-core/docs-content/tuning/retry-waste.md +90 -0
  310. package/vendor-core/docs-content/tuning/shuffle.md +154 -0
  311. package/vendor-core/docs-content/tuning/skew.md +123 -0
  312. package/vendor-core/docs-content/tuning/slow-host.md +117 -0
  313. package/vendor-core/docs-content/tuning/small-files.md +99 -0
  314. package/vendor-core/docs-content/tuning/spill.md +114 -0
  315. package/vendor-core/docs-content/tuning/straggler.md +103 -0
  316. package/vendor-core/docs-content/tuning/tiny-tasks.md +94 -0
  317. package/vendor-core/docs-content/tuning/utilization.md +90 -0
  318. package/vendor-core/docs-site-config.js +10 -17
  319. package/vendor-core/efficiency-model.js +7 -13
  320. package/vendor-core/etl-phases.js +3 -5
  321. package/vendor-core/event-handlers.js +232 -134
  322. package/vendor-core/event-schemas.js +48 -114
  323. package/vendor-core/evidence-availability.js +5 -10
  324. package/vendor-core/evidence-report.js +72 -122
  325. package/vendor-core/export-data.js +48 -0
  326. package/vendor-core/finding-action-label.js +4 -10
  327. package/vendor-core/finding-filter-predicate.js +3 -7
  328. package/vendor-core/finding-generic-recommendation.js +112 -0
  329. package/vendor-core/finding-names.js +51 -0
  330. package/vendor-core/format-utils.js +112 -38
  331. package/vendor-core/impact-band.js +18 -24
  332. package/vendor-core/impact-estimator.js +38 -74
  333. package/vendor-core/ingest.js +7 -13
  334. package/vendor-core/job-groups.js +3 -6
  335. package/vendor-core/list-runs.js +278 -0
  336. package/vendor-core/load-vendored.js +6 -12
  337. package/vendor-core/log-header-peek.js +81 -0
  338. package/vendor-core/lz4-block.js +4 -6
  339. package/vendor-core/mcp-server-factory.js +38 -8
  340. package/vendor-core/mcp-tools.js +105 -76
  341. package/vendor-core/model-assembler.js +8 -16
  342. package/vendor-core/occupancy.js +5 -9
  343. package/vendor-core/parser-worker.js +18 -27
  344. package/vendor-core/plan-dot.js +2 -5
  345. package/vendor-core/plan-duration-attribution.js +78 -29
  346. package/vendor-core/plan-graph-model.js +126 -69
  347. package/vendor-core/plan-node-detail.js +31 -17
  348. package/vendor-core/plan-summary.js +19 -8
  349. package/vendor-core/recommendation-rollup.js +35 -39
  350. package/vendor-core/redact.js +72 -16
  351. package/vendor-core/rolling-log-reassembly.js +4 -6
  352. package/vendor-core/run-comparison.js +65 -70
  353. package/vendor-core/scaling-sim.js +5 -7
  354. package/vendor-core/session-snapshot.js +1 -1
  355. package/vendor-core/shs-fetch.js +4 -6
  356. package/vendor-core/shs-load.js +9 -13
  357. package/vendor-core/shs-request.js +1 -1
  358. package/vendor-core/stage-quantiles.js +14 -0
  359. package/vendor-core/types.js +78 -18
  360. package/vendor-core/wasted-core-hours.js +7 -12
@@ -11,15 +11,11 @@ const BOTTLENECK_WIDGET = {
11
11
  coldStart: 'executor-timeline', utilization: 'executor-timeline', speculationWaste: 'executor-timeline',
12
12
  };
13
13
 
14
- // Cross-widget stage recurrence: how many *distinct board widgets* (not raw
15
- // catalog entries: skew+straggler on one stage still count as one widget,
16
- // task-skew) flag a given stage. Computed once from the full catalog each
17
- // widget already receives, so no new parameter threads through the
18
- // dashboard-renderer render loop.
19
- // `stageId` widened to also accept `undefined` (Task 22, src/detectors.ts):
20
- // sql/config-scope findings never set `stageId` at all, so `Finding.stageId`
21
- // is now `number | null | undefined`, and this `catalog` param is usually a
22
- // live `Finding[]`. `stageId == null` below already treats both the same.
14
+ // Cross-widget stage recurrence: how many distinct board widgets (skew+straggler on one stage
15
+ // still count as one, task-skew) flag a stage. Computed once from the full catalog each widget
16
+ // already receives.
17
+ // `stageId` may be number|null|undefined: sql/config-scope findings never set it; `stageId == null`
18
+ // below treats both the same.
23
19
  export function stageWidgetFrequency(catalog ) {
24
20
  const perStage = new Map();
25
21
  for (const b of catalog) {
@@ -34,12 +30,9 @@ export function stageWidgetFrequency(catalog
34
30
  return freq;
35
31
  }
36
32
 
37
- // Canonical detector type ALL-CAPS board tag vocabulary (AGENTS.md "Problem
38
- // flagging"). Single source of truth for every widget that needs to render a
39
- // catalog entry's tag outside its own dedicated board card, e.g. Bottleneck
40
- // Alerts, which lists findings across every detector type.
41
- // Exported (not just used internally by typeTag) so doc-sync checks can
42
- // enumerate every tag value without hand-duplicating this list elsewhere.
33
+ // Canonical detector type -> ALL-CAPS board tag vocabulary. Single source of truth for every
34
+ // widget that renders a catalog entry's tag outside its own card (e.g. Bottleneck Alerts).
35
+ // Exported so doc-sync checks can enumerate every tag without hand-duplicating this list.
43
36
  export const TYPE_TAG_MAP = {
44
37
  skew: 'SKEW', shuffle: 'SHFL', spill: 'SPILL', gc: 'GC',
45
38
  coldStart: 'COLD', utilization: 'UTIL', memoryUtilization: 'MEM',
@@ -58,23 +51,34 @@ export function typeTag(type ) {
58
51
  return TYPE_TAG_MAP[type] ?? type.toUpperCase();
59
52
  }
60
53
 
61
- // Human-readable tooltips for the terse spill-classification badges
62
- // (skew | vol | ?), surfaced via title= so the vocabulary is decipherable
63
- // without an external key. Kept domain-agnostic (Spark-internal terms only).
54
+ // Human-readable tooltips for the terse spill-classification badges (skew | vol | ?), surfaced via
55
+ // title=. Kept domain-agnostic (Spark-internal terms only).
64
56
  export const SPILL_CLASS_TITLE = {
65
57
  skew: 'Skew spill: a few heavy tasks spill while most do not; rebalance partitioning',
66
58
  volume: 'Volume spill: most tasks spill because data exceeds memory; add partitions',
67
59
  unclassified: 'Spill cause could not be classified',
68
60
  };
69
61
 
70
- /**
71
- * Worst (lowest IMPACT_BAND_ORDER) impact band across a list of findings,
72
- * `undefined` when the list is empty: shared by every widget that needs a
73
- * single band to badge/color a group of findings (Topbar, ShuffleIO,
74
- * GcPressure, TaskSkew, Failures, ConfigAudit, Alerts).
62
+ // Short badge text, kept distinct from the long-form SPILL_CLASS_TITLE tooltip. Shared by
63
+ // Spill.tsx's per-row badge and StageTable.tsx's spill-column cell.
64
+ export const SPILL_CLASS_SHORT = {
65
+ skew: 'skew', volume: 'vol', unclassified: '?',
66
+ };
67
+
68
+ const DEFAULT_MAX_STAGE_IDS_SHOWN = 8;
69
+
70
+ // Caps a stage-id list's printed length: a sql-scope finding's stageIds can run into the hundreds,
71
+ // and joined raw that length balloons an auto-layout table's column width. Shared by FixTheseFirst
72
+ // and PlanFindings.
73
+ export function formatStageIdsLabel(stageIds , maxShown = DEFAULT_MAX_STAGE_IDS_SHOWN) {
74
+ if (stageIds.length <= maxShown) return stageIds.join(', ');
75
+ return `${stageIds.slice(0, maxShown).join(', ')}, +${stageIds.length - maxShown} more`;
76
+ }
77
+
78
+ /** Worst (lowest IMPACT_BAND_ORDER) impact band across findings, undefined when empty. Shared by
79
+ * every widget that badges/colors a group of findings.
75
80
  * @param {{ impactBand: 'critical' | 'warning' | 'info' }[]} findings
76
- * @returns {'critical' | 'warning' | 'info' | undefined}
77
- */
81
+ * @returns {'critical' | 'warning' | 'info' | undefined} */
78
82
  export function worstImpactBand(
79
83
  findings ,
80
84
  ) {
@@ -89,12 +93,8 @@ export function escHtml(str ) {
89
93
  return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
90
94
  }
91
95
 
92
- // Reduces an embedded HDFS/S3/absolute-path fragment in a string to its
93
- // basename (e.g. "Scan ExistingRDD ... - hdfs://host/a/b/c" -> "c"). Strings
94
- // with no such fragment pass through unchanged; this is NOT a general
95
- // truncator. Shared by cache-utilization.js (RDD names), detectors.js (plan
96
- // node names embedded in recommendation text), and stage-detail.js (plan
97
- // node detail labels).
96
+ // Reduces an embedded HDFS/S3/absolute-path fragment to its basename (e.g. "... hdfs://host/a/b/c"
97
+ // -> "c"). Strings with no such fragment pass through unchanged; NOT a general truncator.
98
98
  export function pathBasename(str ) {
99
99
  const s = String(str);
100
100
  const m = s.match(/((?:hdfs?|s3[an]?):\/\/\S+|\/[^\s,)]+)/i);
@@ -103,10 +103,9 @@ export function pathBasename(str ) {
103
103
  return segs[segs.length - 1] || s;
104
104
  }
105
105
 
106
- // "collect at /u02/hadoop/.../utils.py:1869" -> "collect at utils.py:1869". Splits
107
- // on the last " at " (Spark's own callsite format is "<operation> at <location>"),
108
- // then reduces the location to its basename via pathBasename (whose match already
109
- // includes a trailing ":<line>", since colons aren't excluded from its regex).
106
+ // "collect at /u02/.../utils.py:1869" -> "collect at utils.py:1869". Splits on the last " at "
107
+ // (Spark's callsite format), then reduces the location to its basename via pathBasename (whose
108
+ // match keeps a trailing ":<line>").
110
109
  export function trimCallsite(callsite ) {
111
110
  const idx = callsite.lastIndexOf(' at ');
112
111
  if (idx === -1) return callsite;
@@ -115,9 +114,8 @@ export function trimCallsite(callsite ) {
115
114
  return `${operation} at ${pathBasename(location)}`;
116
115
  }
117
116
 
118
- // recent-files ids are `${name}::${size}::${lastModified}`: show just the
119
- // name. Preferred over `app.name`, which is usually identical across the two
120
- // runs being compared and so wouldn't distinguish baseline from candidate.
117
+ // recent-files ids are `${name}::${size}::${lastModified}`: show just the name. Preferred over
118
+ // app.name, which is usually identical across the two compared runs.
121
119
  export function runLabel(id ) {
122
120
  return id.split('::')[0] || id;
123
121
  }
@@ -136,6 +134,82 @@ export function nsToMs(ns ) {
136
134
  return ns / 1e6;
137
135
  }
138
136
 
137
+ // Finding.value is number|string only because a couple of detectors (configAudit, stageFailed)
138
+ // report a string; every other row formatter needs the numeric case and falls back to 0.
139
+ export function numericValue(f ) {
140
+ return typeof f.value === 'number' ? f.value : 0;
141
+ }
142
+
143
+ // Low-level "number -> display string" step shared by every widget's per-finding label resolver:
144
+ // each keeps its own type/metric routing and calls this only for the final value-to-string step,
145
+ // so the same number renders identically ("42%", "2.3×") everywhere.
146
+ export function formatMetricValue(unit , value ) {
147
+ switch (unit) {
148
+ case 'pct': return `${value}%`;
149
+ // 'pct' expects an already-scaled 0-100 value (every detector's Finding.value convention); this
150
+ // variant is for the rare metric (slowHost's hostDurationShare) whose value is a raw 0-1 fraction.
151
+ case 'pctFraction': return `${Math.round(value * 100)}%`;
152
+ case 'ratio': return `${value}×`;
153
+ case 'ms': return formatDuration(value);
154
+ case 'count': return `${value}`;
155
+ }
156
+ }
157
+
158
+ // Finding.metric -> display unit, for the compact plan-graph finding chip. Keyed by the exact
159
+ // `metric` string each per-stage/plan-advisor detector writes (see detectors.ts); a metric this map
160
+ // doesn't cover, or a non-numeric `value` (stageFailed's failure reason, configAudit's config text),
161
+ // yields no magnitude. 'bytes' handles the sub-KB tier formatBytes deliberately lacks; 'minutes'
162
+ // converts to ms so it shares formatDuration's phrasing.
163
+ const FINDING_METRIC_UNIT = {
164
+ shuffleReadBytes: 'bytes', memoryBytesSpilled: 'bytes', shuffleReadMax: 'bytes',
165
+ avgFileSizeBytes: 'bytes', smallerSideBytes: 'bytes', broadcastBytes: 'bytes',
166
+ speculationWasteMs: 'ms', retryWasteMs: 'ms', taskDurationP50: 'ms',
167
+ stageDurationMinutes: 'minutes',
168
+ 'P95/median': 'ratio', 'max/median': 'ratio', pRatio: 'ratio', oiRatio: 'ratio',
169
+ taskStageSkew: 'ratio', hostMeanRatio: 'ratio', execMaxMedianRatio: 'ratio',
170
+ gcPct: 'pct', failureRate: 'pct', stragglerShare: 'pct',
171
+ hostDurationShare: 'pctFraction',
172
+ taskCount: 'count', speculativeTasks: 'count', subtreeOccurrences: 'count',
173
+ };
174
+
175
+ // formatBytes floors at KB (its guarded contract, see partition-sizing.test.tsx); the plan-graph
176
+ // chip is the one place a sub-KB magnitude is meaningful (a smallFiles avgFileSizeBytes can be a
177
+ // few hundred bytes), so render that tier here rather than widening the shared formatter.
178
+ function formatChipBytes(bytes ) {
179
+ if (bytes > 0 && bytes < 1000) return `${Math.round(bytes)} B`;
180
+ return formatBytes(bytes);
181
+ }
182
+
183
+ /** Compact magnitude for a finding's plan-graph chip (e.g. "4.2 GB", "3.2×", "45%"), taken from the
184
+ * finding's own `value` rendered in its `metric`'s unit. Returns null when `value` is non-numeric
185
+ * (stageFailed/configAudit reuse it for text) or the metric isn't in FINDING_METRIC_UNIT. */
186
+ export function formatFindingMagnitude(finding ) {
187
+ const unit = finding.metric ? FINDING_METRIC_UNIT[finding.metric] : undefined;
188
+ if (!unit || typeof finding.value !== 'number') return null;
189
+ switch (unit) {
190
+ case 'bytes': return formatChipBytes(finding.value);
191
+ case 'minutes': return formatDuration(finding.value * 60000);
192
+ case 'ms': return formatMetricValue('ms', finding.value);
193
+ case 'ratio': return formatMetricValue('ratio', finding.value);
194
+ case 'pct': return formatMetricValue('pct', finding.value);
195
+ case 'pctFraction': return formatMetricValue('pctFraction', finding.value);
196
+ case 'count': return formatMetricValue('count', finding.value);
197
+ }
198
+ }
199
+
200
+ /** The plan-graph finding chip's detail text: the finding's magnitude and, when a wall-clock
201
+ * recovery is claimed, "~<time>" (the optimistic `high` bound, matching formatImpactEstimateCompact),
202
+ * joined by " · " (e.g. "4.2 GB · ~38.0s"). null when neither part is available. */
203
+ export function formatFindingChipDetail(
204
+ finding ,
205
+ ) {
206
+ const magnitude = formatFindingMagnitude(finding);
207
+ const wallClock = finding.impactEstimate?.wallClock;
208
+ const recoverable = wallClock ? `~${formatDuration(wallClock.high)}` : null;
209
+ const parts = [magnitude, recoverable].filter(Boolean);
210
+ return parts.length > 0 ? parts.join(' · ') : null;
211
+ }
212
+
139
213
  export function formatDuration(ms ) {
140
214
  if (!ms || ms <= 0) return '—';
141
215
  if (ms < 1000) return `${Math.round(ms)}ms`;
@@ -1,11 +1,8 @@
1
1
 
2
2
  import { STRAGGLER_FLOOR_PCT_WARN, STRAGGLER_FLOOR_PCT_CRIT } from './detectors.js';
3
3
 
4
- // Reused from `straggler`'s own thresholds (detectors.ts's
5
- // `floorPctWarn`/`floorPctCrit`, marked `NOT SOURCED: unvalidated` there
6
- // already): applying the same, already-accepted noise floor globally rather
7
- // than inventing a second, unrelated cutoff. Imported (not re-literaled) so
8
- // the two can't drift out of sync.
4
+ // Reused from straggler's own thresholds (marked NOT SOURCED: unvalidated there): apply the same
5
+ // accepted noise floor globally rather than inventing a second cutoff. Imported so they can't drift.
9
6
  const IMPACT_FLOOR_PCT_WARN = STRAGGLER_FLOOR_PCT_WARN;
10
7
  const IMPACT_FLOOR_PCT_CRIT = STRAGGLER_FLOOR_PCT_CRIT;
11
8
 
@@ -16,31 +13,28 @@ function appDurationMs(app ) {
16
13
  }
17
14
 
18
15
  /**
19
- * Overwrites `.impactBand` for any finding with a quantified wall-clock
20
- * estimate, grading it purely by recoverable time as a fraction of the
21
- * run's total duration: a full replace (can move a finding's fixed fallback
22
- * band in either direction once real recoverable time is known), not a
23
- * promote-only floor. Findings with no `wallClock` estimate
24
- * (`resourceOnly`/`informational` basis) or an unknown/zero app duration are
25
- * left exactly as the detector set them: there is no comparable figure to
26
- * grade them by, so the detector's own fixed classification stands as the
27
- * final value. Mutates in place and returns the same array, matching
28
- * `estimateImpact`'s own signature style.
16
+ * Overwrites `.impactBand` for any finding with a quantified wall-clock estimate, grading it purely
17
+ * by recoverable time as a fraction of the run's duration: a full replace (either direction), not a
18
+ * promote-only floor. Findings with no wallClock estimate (resourceOnly/informational), an
19
+ * unknown/zero app duration, or an explicit safety-signal exemption (partitionSizing's
20
+ * maxPartitionTooBig rule) are left as the detector set them. Mutates in place and returns the array.
29
21
  *
30
- * Grades `wallClock.high`, matching `triage-target.ts`/`FixTheseFirst.tsx`/
31
- * `formatImpactEstimateCompact`, which already rank and display severity by
32
- * the same optimistic figure. This is a deliberate split from
33
- * `view/impact-sort.ts`'s `'impact'` sort mode, which ranks by `.low`
34
- * instead: severity grading (this function) wants the best-case number a fix
35
- * could realistically claim, while the sort wants the guaranteed floor so an
36
- * optimistic-but-contended finding never outranks a smaller but certain one.
37
- * Same range, two different fields for two different questions — not an
38
- * inconsistency to reconcile.
22
+ * Grades wallClock.high (matching triage-target/FixTheseFirst, which rank by the same optimistic
23
+ * figure), a deliberate split from view/impact-sort.ts's 'impact' sort, which ranks by .low so an
24
+ * optimistic-but-contended finding never outranks a smaller certain one. Same range, two fields for
25
+ * two questions.
39
26
  */
40
27
  export function deriveImpactBand(findings , app ) {
41
28
  const durationMs = appDurationMs(app);
42
29
  if (durationMs == null) return findings;
43
30
  for (const finding of findings) {
31
+ // maxPartitionTooBig is a hardcoded-critical OOM/crash-risk safety signal, not a
32
+ // time-recovery one, yet it carries a real wallClock estimate (unlike the other hardcoded-
33
+ // critical findings, which stay costOnly/informational and are exempted below by having no
34
+ // wallClock at all). Exempt it explicitly so a long-running job never demotes an active
35
+ // crash risk down to 'info' just because fixing it saves little wall-clock time relative to
36
+ // the run.
37
+ if (finding.type === 'partitionSizing' && finding.rule === 'maxPartitionTooBig') continue;
44
38
  const recoverableMs = finding.impactEstimate?.wallClock?.high;
45
39
  if (recoverableMs == null) continue;
46
40
  const pct = recoverableMs / durationMs;
@@ -2,8 +2,7 @@
2
2
  import { computeOccupancy, estimateSingleStage, estimateMultiStage, } from './occupancy.js';
3
3
  import { detectorCatalog } from './detectors.js';
4
4
 
5
- // Assumed shuffle-network throughput, ~1 Gbps. Starting assumption; tune against the
6
- // Task 10 real-log spot-check (docs/architecture.md's Impact estimation section).
5
+ // Assumed shuffle-network throughput, ~1 Gbps. Starting assumption, unvalidated.
7
6
  const SHUFFLE_THROUGHPUT_BPS = 125_000_000;
8
7
  // Assumed disk I/O throughput for spilled data, ~200 MB/s (conservative HDD/SSD blend).
9
8
  const SPILL_IO_THROUGHPUT_BPS = 200_000_000;
@@ -22,11 +21,8 @@ const EXECUTOR_STARTUP_OVERHEAD_MS = 15000;
22
21
  // Assumed re-read throughput, shared by cachingOpportunity and cacheUtilization.
23
22
  const RE_READ_THROUGHPUT_BPS = 125_000_000;
24
23
 
25
- // The stageSlowness detector (src/detectors.ts) flags a stage once its wall-clock
26
- // duration reaches `infoMin` minutes; that's the point beyond which the stage's time
27
- // stops being "normal", so it's also the floor for the waste this estimate reports.
28
- // Read from the detector's own catalog entry (not a duplicated literal) so the two
29
- // stay in sync automatically if the detector's threshold ever changes.
24
+ // stageSlowness flags a stage at `infoMin` minutes; that's the floor for the waste this estimate
25
+ // reports. Read from the detector's catalog entry so the two stay in sync automatically.
30
26
  const STAGE_SLOWNESS_THRESHOLD_MINUTES = (() => {
31
27
  const infoMin = detectorCatalog().find((d) => d.type === 'stageSlowness')?.thresholds?.infoMin;
32
28
  if (typeof infoMin !== 'number') {
@@ -35,9 +31,8 @@ const STAGE_SLOWNESS_THRESHOLD_MINUTES = (() => {
35
31
  return infoMin;
36
32
  })();
37
33
 
38
- // A finding with no quantifiable magnitude falls back to 'informational'; one that
39
- // still yields a rawWaste figure (independent of any stage window) falls back to
40
- // 'resourceOnly'. Never a fake {low: 0, high: 0}: wallClock is null in both cases.
34
+ // No quantifiable magnitude -> 'informational'; a rawWaste figure with no stage window ->
35
+ // 'resourceOnly'. Never a fake {low:0, high:0}: wallClock is null in both cases.
41
36
  function costOnly(estimateMethod , rawWaste ) {
42
37
  return rawWaste
43
38
  ? { basis: 'resourceOnly', wallClock: null, estimateMethod, rawWaste }
@@ -67,9 +62,8 @@ function stageMappableWasteOrCostOnly(
67
62
  if (!stageIds || stageIds.length === 0) {
68
63
  return costOnly('modeled', rawWaste);
69
64
  }
70
- // One waste event spread over a span of stages, not N independent wastes:
71
- // apportion it evenly so estimateMultiStage's union cap doesn't have to
72
- // absorb the same amount claimed once per stage.
65
+ // One waste event spread over a span of stages, not N independent wastes: apportion evenly so
66
+ // estimateMultiStage's union cap doesn't absorb the same amount claimed once per stage.
73
67
  const perStageWasteMs = wasteMs / stageIds.length;
74
68
  const wasteMsByStage = new Map(stageIds.map((id) => [id, perStageWasteMs]));
75
69
  const est = estimateMultiStage(stageIds, wasteMsByStage, stages , occupancy);
@@ -77,13 +71,8 @@ function stageMappableWasteOrCostOnly(
77
71
  return { basis: est.basis, wallClock: est.wallClock, estimateMethod: 'modeled', rawWaste };
78
72
  }
79
73
 
80
- /**
81
- * Per-finding-type dispatch, populated incrementally: each formula task adds one
82
- * case here. A type with no case stays uncovered (no impactEstimate attached),
83
- * every type this design covers must eventually get one, per the Global Constraints'
84
- * "no silent omissions" rule; docs/architecture.md's Impact estimation table is the
85
- * authoritative completeness check, not this switch by itself.
86
- */
74
+ /** Per-finding-type dispatch. A type with no case stays uncovered (no impactEstimate attached);
75
+ * docs/architecture.md's Impact estimation table is the authoritative completeness check, not this switch. */
87
76
  function computeEstimateForFinding(
88
77
  finding ,
89
78
  stages ,
@@ -91,8 +80,7 @@ function computeEstimateForFinding(
91
80
  ) {
92
81
  switch (finding.type) {
93
82
  case 'retryWaste': {
94
- // The waste figure lives on the Stage, not the Finding: the detector only
95
- // re-publishes it as `metric`/`value` (src/detectors.ts's retryWaste entry).
83
+ // The waste figure lives on the Stage, not the Finding: the detector only re-publishes it as metric/value.
96
84
  if (finding.stageId == null) return null;
97
85
  const stage = stages.get(finding.stageId);
98
86
  if (!stage) return null;
@@ -110,9 +98,8 @@ function computeEstimateForFinding(
110
98
  // The detector reports the gap as `metric: 'startupGapSeconds', value: <seconds>`.
111
99
  if (typeof finding.value !== 'number') return null;
112
100
  const wasteMs = finding.value * 1000;
113
- // Time before any task starts can never overlap any stage; a genuine,
114
- // unclipped point estimate, not tied to any stage's own gate (coldStart is
115
- // app-scoped, stageId: null): see Non-goals in the original design spec.
101
+ // Time before any task starts can never overlap any stage; a genuine unclipped point estimate,
102
+ // not tied to any stage's gate (coldStart is app-scoped, stageId: null).
116
103
  return { basis: 'serial', wallClock: { low: wasteMs, high: wasteMs }, estimateMethod: 'measured' };
117
104
  }
118
105
  case 'gc': {
@@ -129,11 +116,8 @@ function computeEstimateForFinding(
129
116
  return costOnly('modeled', rawWaste);
130
117
  }
131
118
  const avgConcurrency = executorRunTime / stageDurationMs;
132
- // jvmGCTime is a cross-task core-time sum, the same aggregation shape as
133
- // executorRunTime; dividing by the stage's own average concurrency converts it
134
- // to an approximate wall-clock figure. This rides on measured inputs but is a
135
- // modeled approximation, not an exact reconstruction: hence 'modeled', and see
136
- // docs/architecture.md's spot-check note next to this formula.
119
+ // jvmGCTime is a cross-task core-time sum (same shape as executorRunTime); dividing by the
120
+ // stage's average concurrency converts it to an approximate wall-clock figure. Modeled, not exact.
137
121
  const wasteMs = jvmGCTime / avgConcurrency;
138
122
  return singleStageImpact(wasteMs, finding.stageId, stages, occupancy, 'modeled', rawWaste);
139
123
  }
@@ -149,13 +133,9 @@ function computeEstimateForFinding(
149
133
  }
150
134
  case 'straggler':
151
135
  case 'stageShape': {
152
- // NOTE: this is a shared case for two finding types. straggler (which has
153
- // no `rule` field to check) falls through to the max-P50 wall-clock
154
- // computation below; every stageShape rule (including taskStageSkew) gets
155
- // its own resourceOnly formula here, each returning early (not `break`,
156
- // which would fall off the end of this function's switch and implicitly
157
- // return `undefined` instead of `null` since the switch is the function's
158
- // final statement).
136
+ // Shared case for two finding types. straggler (no `rule` field) falls through to the max-P50
137
+ // computation below; every stageShape rule returns early (not `break`, which would fall off
138
+ // the switch and return undefined instead of null since the switch is the function's last statement).
159
139
  if (finding.type === 'stageShape') {
160
140
  if (finding.rule === 'lowParallelism') {
161
141
  const stage = stages.get(finding.stageId );
@@ -178,12 +158,9 @@ function computeEstimateForFinding(
178
158
  if (!stage) return null;
179
159
  const totalCores = (finding.totalCores ) ?? 0;
180
160
  const taskCount = stage.taskCount ?? 0;
181
- // Cores idle during the straggler's tail, at achieved concurrency (not full
182
- // cluster capacity, which is lowParallelism's own territory): satisfying this
183
- // rule's trigger condition mathematically forces the occupancy-clipped
184
- // wall-clock estimate to zero on every firing, so this is resourceOnly, not a
185
- // wall-clock claim (see the Overlap caveat section in
186
- // docs-site/contributor-guide/architecture/impact-estimation.md).
161
+ // Cores idle during the straggler's tail, at achieved concurrency (not full cluster
162
+ // capacity, which is lowParallelism's territory): this rule's trigger forces the
163
+ // occupancy-clipped estimate to zero on every firing, so it's resourceOnly, not a wall-clock claim.
187
164
  const idleCoreMs =
188
165
  Math.max(0, Math.min(totalCores, taskCount) - 1) *
189
166
  Math.max(0, (stage.taskDurationMax ?? 0) - (stage.taskDurationP50 ?? 0));
@@ -198,13 +175,10 @@ function computeEstimateForFinding(
198
175
  return singleStageImpact(wasteMs, finding.stageId, stages, occupancy, 'measured', { value: wasteMs, unit: 'ms' });
199
176
  }
200
177
  case 'slowHost': {
201
- // Three duration-based shapes, each carrying its absolute-ms figure under a
202
- // different field (`value` is a ratio or a share in all three, never ms):
203
- // - the per-host mean branch, discriminated by `metric` (it sets no `variant`)
204
- // - the duration-share branch, discriminated by `variant`
205
- // - the multi-dimension branch, only for its taskTime dimension
206
- // Every other shape (the byte-based multiDim dimensions) has no absolute
207
- // figure today, so it stays informational.
178
+ // Three duration-based shapes, each carrying its absolute-ms figure under a different field
179
+ // (`value` is always a ratio/share, never ms): the per-host mean branch (discriminated by
180
+ // `metric`), the duration-share branch (`variant`), and the multiDim taskTime dimension. Every
181
+ // byte-based multiDim dimension has no absolute figure today, so it stays informational.
208
182
  const absoluteMs =
209
183
  finding.metric === 'hostMeanRatio' || finding.variant === 'durationShare'
210
184
  ? (finding.hostMeanMs )
@@ -223,13 +197,10 @@ function computeEstimateForFinding(
223
197
  case 'duplicatePlanSubtree': {
224
198
  const stageIds = finding.stageIds ;
225
199
  if (!stageIds || stageIds.length === 0) return null;
226
- // The detector reports metric: 'subtreeOccurrences', value: <occurrences>, always
227
- // >= 2 (its own `minOccurrences` threshold). Only the repeats past the first are
228
- // redundant: computing the subtree once is real work, so the waste is
229
- // (occurrences - 1) / occurrences of the contributing stages' time, not all of it.
200
+ // The detector reports subtreeOccurrences >= 2. Only repeats past the first are redundant:
201
+ // computing the subtree once is real work, so waste is (occurrences-1)/occurrences of the stages' time.
230
202
  const occurrences = typeof finding.value === 'number' ? finding.value : 0;
231
- // Defensive only: the detector's own `minOccurrences` threshold guarantees occurrences
232
- // >= 2 on real data, so this is a malformed-`value` fallback, not a real formula run.
203
+ // Defensive only: minOccurrences guarantees occurrences >= 2 on real data; a malformed-value fallback.
233
204
  if (occurrences < 2) return costOnly('none');
234
205
  const redundantFraction = (occurrences - 1) / occurrences;
235
206
  const wasteMsByStage = new Map ();
@@ -261,8 +232,7 @@ function computeEstimateForFinding(
261
232
  if (!stage) return null;
262
233
  const diskBytesSpilled = stage.diskBytesSpilled ?? 0;
263
234
  const wasteMs = (diskBytesSpilled / SPILL_IO_THROUGHPUT_BPS) * 1000;
264
- // Surfaces the number the formula actually uses: the finding's displayed `metric`
265
- // is memoryBytesSpilled, but disk spill is what costs I/O time.
235
+ // Surfaces the number the formula uses: the displayed metric is memoryBytesSpilled, but disk spill costs the I/O time.
266
236
  return singleStageImpact(wasteMs, finding.stageId, stages, occupancy, 'modeled', { value: diskBytesSpilled, unit: 'bytes' });
267
237
  }
268
238
  case 'stageSlowness': {
@@ -288,12 +258,10 @@ function computeEstimateForFinding(
288
258
  const taskCount = stage.taskCount ?? 0;
289
259
  if (targetTaskCount > taskCount && taskCount > 0) {
290
260
  const stageDurationMs = Math.max(0, (stage.completedAt ?? 0) - (stage.submittedAt ?? 0));
291
- // Too few shuffle partitions means each task processes more bytes than the ideal
292
- // target, doing serially what more partitions would let run concurrently: the waste
293
- // is that serialized work, not the scheduling cost of the tasks you'd add to fix it
294
- // (adding tasks INCURS overhead, it doesn't recover any). Model the achievable
295
- // duration at target parallelism by scaling down proportionally to the partition
296
- // shortfall, and claim the difference.
261
+ // Too few shuffle partitions means each task processes more than the ideal bytes,
262
+ // serializing work more partitions would run concurrently: the waste is that serialized
263
+ // work, not the scheduling cost of tasks you'd add (adding tasks incurs overhead, recovers
264
+ // nothing). Model the achievable duration at target parallelism by scaling down proportionally.
297
265
  wasteMs = stageDurationMs * (1 - taskCount / targetTaskCount);
298
266
  }
299
267
  } else {
@@ -330,8 +298,7 @@ function computeEstimateForFinding(
330
298
  return costOnly('measured', { value: finding.value, unit: 'mbSeconds' });
331
299
  }
332
300
  if (finding.variant === 'idleCores') {
333
- // Idle core-time priced as memory held but unused: the same MB-seconds unit
334
- // the wasteModel variant reports, so the two are comparable.
301
+ // Idle core-time priced as memory held but unused: the same MB-seconds unit as wasteModel, so comparable.
335
302
  const idleRateFraction = finding.idleRateFraction ;
336
303
  const allocatedMB = finding.allocatedMB ;
337
304
  const peakExecutors = finding.peakExecutors ;
@@ -342,9 +309,8 @@ function computeEstimateForFinding(
342
309
  }
343
310
  return costOnly('modeled');
344
311
  }
345
- // Only the over-provisioned band is a waste; the near-capacity band is an OOM-risk
346
- // signal with no magnitude to report, and the dataUnavailable shape has no inputs
347
- // at all: both stay informational.
312
+ // Only the over-provisioned band is a waste; the near-capacity band is an OOM-risk signal with
313
+ // no magnitude, and the dataUnavailable shape has no inputs: both stay informational.
348
314
  if (finding.variant === 'memoryBand' && finding.rule === 'heapOverProvisioned') {
349
315
  const allocatedBytes = finding.allocatedBytes ;
350
316
  const heap = finding.heap ;
@@ -398,11 +364,9 @@ function computeEstimateForFinding(
398
364
  const numPartitions = (finding.numPartitions ) ?? 0;
399
365
  const numUncachedPartitions = Math.max(0, numPartitions - numCachedPartitions);
400
366
  const cachedBytes = memorySize + diskSize;
401
- // Extrapolate the never-cached partitions' size from the CACHED partitions' own
402
- // average size (uncached/cached, not uncached/total: `numCachedPartitions` partitions
403
- // produced `cachedBytes`, not all `numPartitions` of them). `diskSize` is added once
404
- // more on its own: those bytes are already cached, but on disk rather than memory, so
405
- // re-reading them still costs I/O the way a genuinely-uncached partition would.
367
+ // Extrapolate never-cached partitions' size from the CACHED partitions' average (uncached/
368
+ // cached, not uncached/total: numCachedPartitions produced cachedBytes). diskSize is added
369
+ // once more: those bytes are cached but on disk, so re-reading them still costs I/O like an uncached partition.
406
370
  const uncachedBytes = numCachedPartitions > 0 ? (cachedBytes / numCachedPartitions) * numUncachedPartitions : 0;
407
371
  const uncachedOrSpilledBytes = uncachedBytes + diskSize;
408
372
  const wasteMs = (uncachedOrSpilledBytes / RE_READ_THROUGHPUT_BPS) * 1000;
@@ -1,11 +1,7 @@
1
- // Worker/CLI-shared message router: maps a worker (or Node-synchronous,
2
- // per src/cli/collect-run.js's dispatch()) message to its handler callback.
3
- // Exported so both contexts run one switch instead of hand-kept-in-sync
4
- // copies. `pendingTaskRequests` is optional: the CLI path never sends a
5
- // `getTaskData` request, so it never receives a `taskData` message back.
6
- // Handler dispatch is optional-chained (`handlers.onXxx?.(...)`), so a
7
- // caller that adds a new message type here without wiring its handler
8
- // fails silently (a no-op) rather than throwing.
1
+ // Worker/CLI-shared message router: maps a worker (or Node-synchronous) message to its handler.
2
+ // Exported so both contexts run one switch. `pendingTaskRequests` is optional: the CLI path never
3
+ // sends a `getTaskData` request, so never receives `taskData` back. Handler dispatch is
4
+ // optional-chained, so a caller that adds a message type without wiring its handler no-ops rather than throws.
9
5
 
10
6
 
11
7
 
@@ -77,11 +73,9 @@ export function createIngestClient()
77
73
 
78
74
  let handlers = {};
79
75
 
80
- // The `new Worker(new URL(...), ...)` expression must appear inline, exactly
81
- // like this, for Vite's worker plugin to statically detect and bundle it
82
- // (including its own imports, e.g. vendor/fflate.js): routing the URL
83
- // through an intermediate variable defeats that detection and leaves the
84
- // worker's dependencies unbundled, 404ing at runtime in a production build.
76
+ // The `new Worker(new URL(...), ...)` expression must appear inline for Vite's worker plugin to
77
+ // statically detect and bundle it (with its own imports); routing the URL through a variable
78
+ // defeats detection and leaves the worker's deps unbundled, 404ing in a production build.
85
79
  function makeWorker() {
86
80
  worker = new Worker(new URL('./parser-worker.js', import.meta.url), { type: 'module' });
87
81
  worker.onmessage = ({ data }) => routeMessage(data, handlers, pendingTaskRequests);
@@ -1,9 +1,6 @@
1
- // §8 Concurrent-job-group reliability guard (SparkLens JobOverlapAnalyzer).
2
- // Groups jobs by SQL execution id; jobs with none form their own singleton
3
- // group. If two DIFFERENT groups' [submissionTime, completionTime] intervals
4
- // overlap, wall-clock-based estimates (scaling simulator, efficiency model)
5
- // become unreliable. Jobs WITHIN a group overlapping (AQE/multi-stage) is
6
- // normal and not the signal.
1
+ // §8 Concurrent-job-group reliability guard. Groups jobs by SQL
2
+ // execution id (jobs with none form singleton groups). If two DIFFERENT groups' intervals overlap,
3
+ // wall-clock-based estimates become unreliable. Jobs WITHIN a group overlapping (AQE) is normal.
7
4
  export function checkConcurrentJobGroups(jobs ) {
8
5
  const list = jobs ? [...jobs.values()] : [];
9
6
  // Build one interval per group: [min submission, max completion].