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
@@ -1,11 +1,10 @@
1
- // Portable, redacted evidence report (ELV-002). Pure builder over an appModel:
2
- // runs the detectors, then serializes a run summary + every finding into a
3
- // deterministic, byte-stable JSON document plus a human-readable Markdown
4
- // rendering. Raw task records are never included (privacy baseline); identifier
5
- // redaction is opt-in via { redact: true }.
1
+ // Portable, redacted evidence report. Pure builder over an appModel: runs the detectors,
2
+ // then serializes a run summary + findings into a deterministic, byte-stable JSON + Markdown.
3
+ // Raw task records are never included (privacy baseline); redaction is opt-in via { redact: true }.
6
4
  import { analyze, auditConfig } from './analyzer.js';
7
5
  import { detectorCatalog } from './detectors.js';
8
6
  import { typeTag, formatBytes, formatDuration, IMPACT_BAND_ORDER } from './format-utils.js';
7
+ import { FINDING_NAMES, titleCase } from './finding-names.js';
9
8
  import { redactReport } from './redact.js';
10
9
  import { coreFindingActionLabel } from './finding-action-label.js';
11
10
  import { matchesFindingFilterCriteria } from './finding-filter-predicate.js';
@@ -17,42 +16,28 @@ import { getThresholdSummary } from './threshold-summary.js';
17
16
 
18
17
  export const EVIDENCE_SCHEMA_VERSION = 3;
19
18
 
20
- // NOTE on deviations from the plan's literal interface text: `findingRow`
21
- // below always sets `id`/`metric`/`value`/`recommendation` via `?? null`
22
- // (never omits the key), and `buildJson` always sets `evidenceAvailability`
23
- // and `summary.app.{id,name,sparkVersion}` via `?? null` too (AppModel's own
24
- // `evidenceAvailability` field is `EvidenceAvailability | null`, `app` is
25
- // `SparkAppInfo | null`, and `SparkAppInfo.sparkVersion` documents `null` as
26
- // a deliberate "unknown version" sentinel distinct from absence). So these
27
- // can genuinely be `null` at runtime even though the plan pins them as
28
- // plain/optional non-null types. Widened here to match reality rather than
29
- // masking it with a cast, and kept as `?? null`, not `?? undefined`: since
30
- // JSON.stringify drops `undefined` keys but keeps `null`, using `undefined`
31
- // here would silently strip these fields from the serialized report.
19
+ // findingRow always sets id/metric/value/recommendation via `?? null` (never omits the key), and
20
+ // buildJson does the same for evidenceAvailability and summary.app.{id,name,sparkVersion}: these
21
+ // are genuinely nullable at runtime (AppModel.app is nullable, sparkVersion documents null as an
22
+ // "unknown version" sentinel). Kept as `?? null`, not `?? undefined`: JSON.stringify drops
23
+ // undefined keys but keeps null, so undefined would silently strip these from the report.
32
24
  //
33
- // `value` is further widened to `number | string | null` (Task 22,
34
- // src/detectors.ts): `stageFailed` and the four `configAudit` entries put
35
- // human-readable text in `Finding.value` instead of a magnitude, and this
36
- // row shape carries that value through unchanged.
25
+ // `value` is number|string|null: stageFailed and the configAudit entries put text in Finding.value
26
+ // instead of a magnitude, carried through unchanged.
37
27
 
38
-
28
+
39
29
 
40
30
 
41
-
42
-
43
-
44
-
45
-
31
+
32
+
33
+
46
34
 
47
35
 
48
36
 
49
37
 
50
38
 
51
- // The `Fix these first` rollup row (ELV-002 extension): one entry per
52
- // `buildRecommendationRollup` group, carrying the same impact-ranked
53
- // aggregation the dashboard's `FixTheseFirst.tsx` widget renders, so the
54
- // CLI/MCP/download paths get the "what's the highest-leverage fix" ranking
55
- // too, not just the flat impact-sorted `findings` list above.
39
+ // The `Fix these first` rollup row: one entry per buildRecommendationRollup
40
+ // group, so the CLI/MCP/download paths get the same impact-ranked aggregation the dashboard shows.
56
41
 
57
42
 
58
43
 
@@ -68,9 +53,8 @@ export const EVIDENCE_SCHEMA_VERSION = 3;
68
53
 
69
54
 
70
55
 
71
- // One line per detector `type` that fired zero findings this run, so a flat
72
- // evidence report can state "these were checked and came back clean" the
73
- // same way the dashboard's clean-checks table does.
56
+ // One line per detector `type` that fired zero findings, so a flat report can state "these were
57
+ // checked and came back clean" like the dashboard's clean-checks table.
74
58
 
75
59
 
76
60
 
@@ -94,19 +78,27 @@ export const EVIDENCE_SCHEMA_VERSION = 3;
94
78
  // Fields surfaced as first-class report columns. Everything else on a finding
95
79
  // becomes its `evidence` payload (sorted for stable key order).
96
80
  const CORE_KEYS = new Set([
97
- 'id', 'type', 'impactBand', 'stageId', 'metric', 'value',
81
+ 'id', 'type', 'name', 'impactBand', 'stageId', 'metric', 'value',
98
82
  'recommendation', 'detectorVersion', 'confidence', 'validationRequired', 'docAnchor', 'impactEstimate',
99
83
  'actionLabel',
100
84
  ]);
101
85
 
86
+ // Internal-only fields with no meaning to a human reading this report: never surfaced as a core
87
+ // column, and also excluded from the generic evidence dump (unlike stageIds, which IS actionable
88
+ // to a reader). `planNodeIds` is view-layer plan-graph node ids (Plan Advisor detectors, see
89
+ // plan-graph-model.ts): on a real log it can carry a hundred-plus ids, which would otherwise print
90
+ // as one unreadable `- planNodeIds: [...]` line and bloat the report for no reader benefit.
91
+ const NON_EVIDENCE_KEYS = new Set(['planNodeIds']);
92
+
102
93
  function findingRow(f ) {
103
94
  const evidence = {};
104
95
  for (const k of Object.keys(f).sort()) {
105
- if (!CORE_KEYS.has(k)) evidence[k] = (f )[k];
96
+ if (!CORE_KEYS.has(k) && !NON_EVIDENCE_KEYS.has(k)) evidence[k] = (f )[k];
106
97
  }
107
98
  const row = {
108
99
  id: f.id ?? null,
109
100
  type: f.type,
101
+ name: titleCase(FINDING_NAMES[f.type] ?? f.type),
110
102
  tag: typeTag(f.type),
111
103
  impactBand: f.impactBand,
112
104
  stageId: f.stageId ?? null,
@@ -115,11 +107,9 @@ function findingRow(f ) {
115
107
  recommendation: f.recommendation ?? null,
116
108
  detectorVersion: f.detectorVersion ?? 1,
117
109
  evidence,
118
- // Deliberate simplification vs. the view layer's `REGISTRY[type]?.findingLabel`
119
- // fallback (src/view/finding-action-label.ts): there's no widget registry at
120
- // this layer, and falling back to the finding's own `type` is reasonable
121
- // since coreFindingActionLabel's switch already covers every type the real
122
- // detectors emit; only obscure/future unmatched sub-variants hit this fallback.
110
+ // Deliberate simplification vs the view layer's REGISTRY fallback: no widget registry here, and
111
+ // falling back to the finding's own `type` is fine since coreFindingActionLabel already covers
112
+ // every emitted type; only obscure/future sub-variants hit this fallback.
123
113
  actionLabel: coreFindingActionLabel(f) ?? f.type,
124
114
  };
125
115
  // Threshold/confidence provenance, only when the detector emitted it.
@@ -130,8 +120,8 @@ function findingRow(f ) {
130
120
  return row;
131
121
  }
132
122
 
133
- // Deterministic finding order: impact band, then type, then stage, then id, so a
134
- // fixed appModel always serializes byte-for-byte identically.
123
+ // Deterministic finding order: impact band, then type, then stage, then id, so a fixed appModel
124
+ // always serializes byte-for-byte identically.
135
125
  function sortFindings(rows ) {
136
126
  return [...rows].sort((a, b) => {
137
127
  const s = (IMPACT_BAND_ORDER[a.impactBand] ?? 9) - (IMPACT_BAND_ORDER[b.impactBand] ?? 9);
@@ -144,20 +134,13 @@ function sortFindings(rows ) {
144
134
  });
145
135
  }
146
136
 
147
- // `isEligible`/`rankFindings` are shared with FixTheseFirst.tsx via
148
- // src/recommendation-rollup.ts (both files used to duplicate this logic
149
- // near-verbatim); this file's own `isEligible` call site omits
150
- // FixTheseFirst.tsx's extra `REGISTRY[finding.type] != null` check: every
151
- // real finding reaching this file already came out of `analyze()`/
152
- // `auditConfig()`, so it's always a known type, and REGISTRY (a `.tsx` file)
153
- // isn't importable from this core module anyway.
137
+ // isEligible/rankFindings are shared with FixTheseFirst.tsx via recommendation-rollup.ts; this
138
+ // file's isEligible call omits FixTheseFirst's REGISTRY check: every finding here already came out
139
+ // of analyze()/auditConfig() so it's a known type, and REGISTRY (.tsx) isn't importable here anyway.
154
140
 
155
- // The impact-ranked "what's the highest-leverage fix" rollup, ported from
156
- // FixTheseFirst.tsx so the CLI/MCP/download paths get the same ranking the
157
- // dashboard shows. `buildRecommendationRollup` already returns groups in the
158
- // correct cross-group order (time groups by recoverable ms descending, then
159
- // resource/count groups by worst impact band), so this only maps each group to
160
- // its JSON row shape without re-sorting.
141
+ // The impact-ranked "highest-leverage fix" rollup, ported from FixTheseFirst.tsx so CLI/MCP/download
142
+ // get the same ranking. buildRecommendationRollup already returns groups in the correct cross-group
143
+ // order, so this only maps each group to its JSON row without re-sorting.
161
144
  function buildRecommendations(
162
145
  findings ,
163
146
  stages ,
@@ -181,10 +164,8 @@ function buildRecommendations(
181
164
  kind: 'time',
182
165
  stageCount: group.stageCount,
183
166
  recoverableMsHigh: group.recoverableMsHigh,
184
- // A point estimate, not a range: matches FixTheseFirst.tsx's own
185
- // `trailingStat` for time groups, which prints the same figure twice
186
- // rather than the finding-level low-high spread this group already
187
- // collapsed away via computeStageUnionMs.
167
+ // A point estimate, not a range: matches FixTheseFirst.tsx's trailingStat for time groups,
168
+ // which prints the same figure twice rather than the finding-level spread computeStageUnionMs collapsed.
188
169
  impact: formatWallClockRange(group.recoverableMsHigh, group.recoverableMsHigh),
189
170
  };
190
171
  }
@@ -208,25 +189,16 @@ function buildRecommendations(
208
189
  });
209
190
  }
210
191
 
211
- // Detector types that fired zero findings this run, so a flat evidence
212
- // report can state "these were checked and came back clean" the same way
213
- // the dashboard's clean-checks table does. Deliberately differs from the
214
- // dashboard's Alerts.tsx "Clean checks" table, which excludes
215
- // memoryUtilization/utilization/coreLocality (the "always-mounted reference"
216
- // widgets; see isAlwaysMountedType in src/view/detector-registry.tsx)
217
- // because those are already always shown as their own widget elsewhere on
218
- // the board. A flat evidence report has no such separate always-visible
219
- // surface for them, so this list intentionally includes them when they have
220
- // zero findings, rather than mirroring the UI's display-consolidation
221
- // exclusion.
192
+ // Detector types that fired zero findings, so a flat report can state "checked and clean". Differs
193
+ // from the dashboard's Alerts.tsx "Clean checks", which excludes coreLocality (the one remaining
194
+ // always-mounted reference widget, shown elsewhere); a flat report has no such separate surface,
195
+ // so this includes it too when it has zero findings.
222
196
  function buildCleanChecks(findings ) {
223
197
  const firedTypes = new Set(findings.map((f) => f.type));
224
198
  const seen = new Set ();
225
199
  const entries = [];
226
- // detectorCatalog() can list the same `type` more than once (configAudit
227
- // has 4 separate DETECTORS entries, all `type: 'configAudit'`); dedupe by
228
- // type, keeping first occurrence, so a type with several sibling detector
229
- // entries still contributes exactly one clean-check line.
200
+ // detectorCatalog() can list the same type more than once (configAudit has 4 entries); dedupe by
201
+ // type, keeping first, so a type with sibling entries contributes exactly one clean-check line.
230
202
  for (const d of detectorCatalog() ) {
231
203
  if (firedTypes.has(d.type) || seen.has(d.type)) continue;
232
204
  seen.add(d.type);
@@ -235,15 +207,10 @@ function buildCleanChecks(findings ) {
235
207
  return entries;
236
208
  }
237
209
 
238
- // Keyed by appModel object identity, not runId: mcp-tools.ts caches one fixed
239
- // appModel per runId for the run's whole cached lifetime (resolveOrCreateRun
240
- // never mutates a cached entry's appModel), so re-running analyze()/
241
- // auditConfig() for the same appModel always reproduces the same catalog.
242
- // getFindingEvidence in particular calls buildEvidenceReport once per
243
- // drill-down on an already-diagnosed run; without this, N findings looked up
244
- // on one run meant N full detector re-runs. A WeakMap needs no explicit
245
- // invalidation: once mcp-tools.ts evicts the appModel, this entry is
246
- // unreachable and collectible too.
210
+ // Keyed by appModel object identity: mcp-tools.ts caches one fixed appModel per runId (never
211
+ // mutated), so re-running analyze()/auditConfig() reproduces the same catalog. getFindingEvidence
212
+ // calls buildEvidenceReport once per drill-down; without this, N lookups meant N detector re-runs.
213
+ // A WeakMap needs no invalidation: once mcp-tools.ts evicts the appModel, this entry is collectible.
247
214
  const jsonCache = new WeakMap ();
248
215
 
249
216
  function buildJson(appModel ) {
@@ -268,12 +235,8 @@ function buildJson(appModel ) {
268
235
  schemaVersion: EVIDENCE_SCHEMA_VERSION,
269
236
  summary: {
270
237
  app: {
271
- // `?? null`, not `?? undefined`: pre-migration behavior (and
272
- // SparkAppInfo.sparkVersion's own doc comment) treats `null` as a
273
- // deliberate "unknown/absent" sentinel distinct from an omitted key.
274
- // JSON.stringify drops `undefined` keys but keeps `null`, so using
275
- // `?? undefined` here would have silently dropped these fields from
276
- // the serialized report whenever `app` is null or its fields unset.
238
+ // `?? null`, not `?? undefined`: JSON.stringify drops undefined keys but keeps null, and
239
+ // sparkVersion's null is a deliberate "unknown/absent" sentinel; undefined would drop these.
277
240
  id: app?.id ?? null,
278
241
  name: app?.name ?? null,
279
242
  sparkVersion: app?.sparkVersion ?? null,
@@ -285,9 +248,8 @@ function buildJson(appModel ) {
285
248
  impactBandCounts,
286
249
  },
287
250
  evidenceAvailability: evidenceAvailability ?? null,
288
- // Detector metadata (type/version/scope/thresholds/docAnchor) so the
289
- // threshold set that produced each finding travels with the evidence.
290
- // Order follows DETECTORS, which is stable => byte-stable serialization.
251
+ // Detector metadata so the threshold set that produced each finding travels with the evidence.
252
+ // Order follows DETECTORS (stable) => byte-stable serialization.
291
253
  detectors: detectorCatalog(),
292
254
  findings: rows,
293
255
  recommendations,
@@ -297,9 +259,8 @@ function buildJson(appModel ) {
297
259
  return result;
298
260
  }
299
261
 
300
- // Human-readable rendering of an evidence value. Byte-magnitude keys are
301
- // humanized (avgFileSizeBytes: 3.1 MB); objects/arrays serialize compactly so
302
- // no payload is silently dropped from the Markdown.
262
+ // Human-readable rendering of an evidence value. Byte-magnitude keys are humanized; objects/arrays
263
+ // serialize compactly so no payload is silently dropped from the Markdown.
303
264
  function renderEvidenceValue(key , value ) {
304
265
  if (typeof value === 'number' && /bytes$/i.test(key)) return formatBytes(value);
305
266
  if (value !== null && typeof value === 'object') return JSON.stringify(value);
@@ -359,7 +320,7 @@ function renderMarkdown(json ) {
359
320
  lines.push('');
360
321
  for (const r of findings) {
361
322
  const where = r.stageId != null ? ` (stage ${r.stageId})` : '';
362
- lines.push(`### [${r.tag}] ${r.type} · ${r.impactBand}${where}`);
323
+ lines.push(`### ${r.name} · ${r.impactBand}${where}`);
363
324
  lines.push(`- action: ${r.actionLabel}`);
364
325
  if (r.metric != null) lines.push(`- ${r.metric}: ${r.value}`);
365
326
  if (r.recommendation) lines.push(`- ${r.recommendation}`);
@@ -368,9 +329,8 @@ function renderMarkdown(json ) {
368
329
  const impactText = r.impactEstimate ? renderImpactEstimate(r.impactEstimate) : null;
369
330
  if (impactText) lines.push(`- impact: ${impactText}`);
370
331
  lines.push(`- detector version: ${r.detectorVersion}`);
371
- // Evidence payload (sorted for stable order) so two rows differing only by
372
- // evidence (two smallFiles by direction, two partitionSizing by rule)
373
- // render distinctly and carry the full AC3 field set into the Markdown.
332
+ // Evidence payload (sorted for stable order) so two rows differing only by evidence (two
333
+ // smallFiles by direction, two partitionSizing by rule) render distinctly.
374
334
  const evidence = Object.entries(r.evidence ?? {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
375
335
  if (evidence.length) {
376
336
  lines.push('- evidence:');
@@ -386,8 +346,7 @@ function renderMarkdown(json ) {
386
346
  }
387
347
  lines.push('');
388
348
  }
389
- // Detector catalog: the version + threshold set that produced each finding,
390
- // so the Markdown (the format pasted into a ticket) carries provenance too.
349
+ // Detector catalog: the version + threshold set that produced each finding, so the Markdown carries provenance too.
391
350
  if (Array.isArray(detectors) && detectors.length) {
392
351
  lines.push('## Detectors');
393
352
  lines.push('');
@@ -413,19 +372,14 @@ function renderMarkdown(json ) {
413
372
 
414
373
 
415
374
 
416
- // CLI/MCP-facing filter over FindingRow (post-row-transformation), delegating
417
- // to the shared core predicate (src/finding-filter-predicate.ts) that also
418
- // backs the dashboard's src/view/finding-filter.ts.
375
+ // CLI/MCP-facing filter over FindingRow, delegating to the shared core predicate that also backs
376
+ // the dashboard's finding-filter.
419
377
  function matchesFindingsFilter(row , filter ) {
420
378
  return matchesFindingFilterCriteria(row, filter);
421
379
  }
422
380
 
423
- /**
424
- * Build a `FindingsFilter` from the three optional CLI/MCP filter dimensions,
425
- * or `undefined` when none were passed (the "is anything actually set" gate
426
- * both `bin/sparkforensics-analyze.mjs` and `diagnoseRun` need before calling
427
- * `buildEvidenceReport`).
428
- */
381
+ /** Build a FindingsFilter from the three optional CLI/MCP filter dimensions, or undefined when
382
+ * none were passed (the "is anything set" gate before calling buildEvidenceReport). */
429
383
  export function toFindingsFilter(
430
384
  impactBand , type , stageId ,
431
385
  ) {
@@ -434,13 +388,10 @@ export function toFindingsFilter(
434
388
 
435
389
  /**
436
390
  * Build a portable evidence report from an appModel.
437
- * @param appModel
438
- * @param opts redact=true pseudonymizes app ids / hosts; markdown=false skips
439
- * rendering the Markdown string (returned as '' instead) for callers that
440
- * only need `json`; findingsFilter narrows `json.findings` (and the
441
- * rendered Markdown's Findings section) only — `summary`, `recommendations`,
442
- * and `cleanChecks` stay computed from the full, unfiltered set, so a
443
- * narrow filter never hides that other checks passed or other fixes exist.
391
+ * @param opts redact=true pseudonymizes app ids / hosts; markdown=false skips the Markdown string;
392
+ * findingsFilter narrows json.findings (and the Markdown Findings section) only, summary,
393
+ * recommendations, and cleanChecks stay computed from the full set, so a narrow filter never
394
+ * hides that other checks passed or other fixes exist.
444
395
  */
445
396
  export function buildEvidenceReport(
446
397
  appModel ,
@@ -450,9 +401,8 @@ export function buildEvidenceReport(
450
401
  ) {
451
402
  let json = buildJson(appModel);
452
403
  if (redact) json = redactReport(json);
453
- // Filter after redact, not before: redaction only replaces string values
454
- // on rows that survive (app id / host tokens), it never adds/removes rows,
455
- // so the two orderings produce identical final content either way.
404
+ // Filter after redact, not before: redaction only replaces string values on surviving rows,
405
+ // never adds/removes rows, so the two orderings produce identical final content.
456
406
  if (findingsFilter) json = { ...json, findings: json.findings.filter((row) => matchesFindingsFilter(row, findingsFilter)) };
457
407
  const markdown = computeMarkdown ? renderMarkdown(json) : '';
458
408
  return { markdown, json };
@@ -0,0 +1,48 @@
1
+
2
+
3
+
4
+
5
+
6
+ export const EXPORT_DATA_SCHEMA_VERSION = 1;
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+ /** Converts an in-memory AppModel (Map-based, as collectRun()/the browser
23
+ * worker produce it) plus the CLI's already-computed catalog/configFindings
24
+ * into a plain, JSON-serializable object for the HTML export's data.js.
25
+ * Maps become arrays; main-export.tsx rebuilds them client-side keyed the
26
+ * same way the store already expects (Stage.id, Job.id,
27
+ * SqlExecution.id). Deliberately excludes raw per-task TaskData, which is far
28
+ * too large to inline; task-detail widgets degrade gracefully in export mode. */
29
+ export function buildExportRunData(
30
+ appModel ,
31
+ catalog ,
32
+ configFindings ,
33
+ skippedLines ,
34
+ ) {
35
+ return {
36
+ schemaVersion: EXPORT_DATA_SCHEMA_VERSION,
37
+ app: appModel.app,
38
+ stages: [...appModel.stages.values()],
39
+ jobs: [...appModel.jobs.values()],
40
+ sql: [...appModel.sql.values()],
41
+ executors: appModel.executors,
42
+ runAggregates: appModel.runAggregates,
43
+ evidenceAvailability: appModel.evidenceAvailability,
44
+ catalog,
45
+ configFindings,
46
+ skippedLines,
47
+ };
48
+ }
@@ -1,15 +1,9 @@
1
1
 
2
2
 
3
- /** A short, imperative action label for a finding's row (e.g. "Reduce
4
- * shuffle size"). Keyed off `finding.type` plus whichever discriminant field
5
- * that detector uses for its sub-variants (`rule`, `direction`, `variant`, or
6
- * `property`: there's no single field name shared across detectors; see
7
- * detectors.ts). Core-safe: no `src/view/**` import, so both the dashboard
8
- * (`src/view/finding-action-label.ts`, which layers the REGISTRY fallback on
9
- * top of this) and `src/evidence-report.ts` (CLI/MCP path, which cannot
10
- * import React-only code) share the exact same switch logic instead of
11
- * forking it. Returns `undefined` for any (type, variant) combination this
12
- * switch doesn't cover: the caller decides what to fall back to. */
3
+ /** A short, imperative action label for a finding's row. Keyed off finding.type plus whichever
4
+ * discriminant field that detector uses (rule/direction/variant/property). Core-safe (no view
5
+ * import) so the dashboard's wrapper and evidence-report.ts share the same switch. Returns
6
+ * undefined for any combination this switch doesn't cover; the caller decides the fallback. */
13
7
  export function coreFindingActionLabel(finding ) {
14
8
  switch (finding.type) {
15
9
  case 'skew':
@@ -1,10 +1,6 @@
1
- // Core-owned finding-filter predicate, shared by the CLI/MCP evidence report
2
- // (src/evidence-report.ts's FindingsFilter) and the dashboard's finding
3
- // filter bar (src/view/finding-filter.ts's FilterSelection): same precedent
4
- // as src/finding-action-label.ts, whose view-layer counterpart wraps it
5
- // instead of reimplementing its switch. Each dimension is unconstrained when
6
- // empty/absent; a `stageId` criterion (single value or a set, for the
7
- // dashboard's multi-select) only matches a row whose own stageId is present.
1
+ // Core-owned finding-filter predicate, shared by the CLI/MCP evidence report and the dashboard's
2
+ // filter bar (its view counterpart wraps this instead of reimplementing). Each dimension is
3
+ // unconstrained when empty; a stageId criterion only matches a row whose own stageId is present.
8
4
 
9
5
 
10
6
  function isNonEmpty (m ) {
@@ -0,0 +1,112 @@
1
+
2
+
3
+ /** A generic, type-level recommendation sentence for a finding: the shape of the fix, with no
4
+ * instance data (numbers, stage ids, host names, file counts, config values). Mirrors
5
+ * coreFindingActionLabel's (type, discriminant) switch (same fields: rule/direction/variant/
6
+ * property, plus cacheUtilization's variant and memoryUtilization's dataUnavailable), but covers
7
+ * every finding type, not just the ones with a distinct action label.
8
+ *
9
+ * Used for a multi-finding group's muted description line (TypeGroupRow in FixTheseFirst.tsx),
10
+ * where the highest-impact member's own `recommendation` (real numbers, one stage) would
11
+ * misrepresent a summed-impact trailing stat covering every member. Returns undefined where a
12
+ * detector's real branch key isn't exposed as a Finding field (spill's skew/volume
13
+ * classification, tinyTask's shuffle-vs-no-shuffle fix, duplicatePlanSubtree's isExchangeRoot),
14
+ * so those combine into one sentence covering both cases, or for any (type, discriminant)
15
+ * combination this switch doesn't recognize; the caller shows no muted line rather than guess. */
16
+ export function coreFindingGenericRecommendation(finding ) {
17
+ switch (finding.type) {
18
+ case 'skew':
19
+ return 'For join-driven skew, enable AQE skew-join handling (spark.sql.adaptive.skewJoin.enabled); otherwise salt the key or repartition on a better key to reduce task skew.';
20
+ case 'stageShape':
21
+ switch (finding.rule) {
22
+ case 'lowParallelism': return 'Too few tasks run relative to the cores available, leaving cluster capacity idle: repartition to use more of it.';
23
+ case 'dataExplosion': return 'Output volume far exceeds input volume: check for an exploding join or a cross product.';
24
+ case 'taskStageSkew': return 'A single straggler task gates the whole stage\'s wall-clock duration.';
25
+ }
26
+ break;
27
+ case 'shuffle':
28
+ return 'Consider increasing spark.sql.shuffle.partitions or adding a broadcast join to shrink the shuffle.';
29
+ case 'partitionSizing':
30
+ switch (finding.rule) {
31
+ case 'shufflePartitionSkew': return 'For join skew, enable AQE skew-join handling (spark.sql.adaptive.skewJoin.enabled); otherwise salt the key or repartition on a better key.';
32
+ case 'lowShuffleParallelism': return 'Raise spark.sql.shuffle.partitions so each partition is smaller.';
33
+ case 'maxPartitionTooBig': return 'Repartition to break up the oversized partition before this stage.';
34
+ }
35
+ break;
36
+ case 'spill':
37
+ return 'If the spill is skew-driven, fix task skew first: adding memory will not help. Otherwise raise spark.sql.shuffle.partitions or increase executor memory.';
38
+ case 'gc':
39
+ return finding.direction === 'low'
40
+ ? 'Memory may be over-provisioned here: consider reducing spark.executor.memory for cost savings.'
41
+ : 'Reduce object creation, use primitive types, avoid UDFs, or increase executor memory to cut GC time.';
42
+ case 'slowHost':
43
+ if (finding.variant === 'durationShare') return 'Check for data locality or partition assignment skewing work onto one node.';
44
+ if (finding.variant === 'multiDim') return 'Investigate uneven partition assignment or a degraded executor.';
45
+ return 'Check what this host was running: it may just hold data locality for its tasks or carry one heavy stage, rather than a hardware fault. Enable spark.speculation to relaunch a lagging task automatically.';
46
+ case 'stageSlowness':
47
+ return 'Often a partition-count problem: raise parallelism via spark.sql.shuffle.partitions or spark.default.parallelism, or check for a large per-task data volume driving heavy shuffle and spill.';
48
+ case 'stageFailed':
49
+ return 'Inspect the driver log for the failure reason and the job that triggered it.';
50
+ case 'failures':
51
+ return 'Investigate driver logs for executor instability or data-driven errors.';
52
+ case 'straggler':
53
+ return 'Rule out a GC pause or a slow shuffle fetch before assuming a hardware issue. If a skewed key is the real cause, that is a candidate for AQE\'s skew-join handling.';
54
+ case 'speculationWaste':
55
+ return 'If task durations are naturally variable rather than genuine stragglers, consider tuning spark.speculation.multiplier/quantile.';
56
+ case 'retryWaste':
57
+ return 'Investigate executor loss or fetch failures behind the retried attempts.';
58
+ case 'tinyTask':
59
+ return 'Scheduler overhead may dominate: lower spark.sql.shuffle.partitions, or coalesce down to fewer, larger tasks.';
60
+ case 'coldStart':
61
+ return 'Keep a warm pool of idle executors, or if using dynamic allocation, raise the minimum/initial executor count so it does not scale up from zero.';
62
+ case 'utilization':
63
+ return 'Consider reducing cluster size or enabling dynamic allocation.';
64
+ case 'memoryUtilization':
65
+ switch (finding.variant) {
66
+ case 'idleCores': return 'Reduce cluster size or enable dynamic allocation.';
67
+ case 'wasteModel': return 'Review spark.executor.memory and executor count.';
68
+ case 'memoryBand':
69
+ if (finding.dataUnavailable) break;
70
+ return finding.rule === 'heapNearCapacity'
71
+ ? 'Memory may be too small: raise spark.executor.memory to avoid OOM/spill.'
72
+ : 'Memory may be over-provisioned: consider reducing spark.executor.memory for cost savings.';
73
+ }
74
+ break;
75
+ case 'cacheUtilization':
76
+ switch (finding.variant) {
77
+ case 'partialCache': return 'Increase executor memory or reduce the cached dataset size so more of it stays cached.';
78
+ case 'diskSpillover': return 'Executor memory may be too small for this cached dataset: increase executor memory or reduce its size.';
79
+ }
80
+ break;
81
+ case 'coreLocality':
82
+ return 'Check spark.locality.wait settings and executor/data colocation.';
83
+ case 'autoscalingChurn':
84
+ return 'This looks like wasteful re-provisioning rather than normal scale-down: consider raising spark.dynamicAllocation.executorIdleTimeout or widening the minExecutors/maxExecutors bounds to reduce flapping.';
85
+ case 'cachingOpportunity':
86
+ return finding.variant === 'composite'
87
+ ? 'Cache or persist the repeated join/union result so it is computed once instead of recomputed per query.'
88
+ : 'Cache the shared DataFrame, or broadcast it if it is a small join lookup.';
89
+ case 'jobFailureRate':
90
+ return 'Inspect the driver log for the failed job(s) and the stage failures that triggered them.';
91
+ case 'configAudit':
92
+ switch (finding.property) {
93
+ case 'spark.shuffle.service.enabled': return 'Set spark.shuffle.service.enabled=true so shuffle data survives executor removal.';
94
+ case 'spark.dynamicAllocation.minExecutors': return 'Set the minimum executor bound at or below the maximum.';
95
+ case 'spark.dynamicAllocation.maxExecutors': return 'Set spark.dynamicAllocation.maxExecutors to cap cluster growth.';
96
+ case 'spark.serializer': return 'Consider spark.serializer=org.apache.spark.serializer.KryoSerializer for faster, smaller buffers.';
97
+ case 'spark.executor.memoryOverhead': return 'Raise executor memoryOverhead above Spark\'s default floor to avoid off-heap OOM-kills.';
98
+ }
99
+ break;
100
+ case 'duplicatePlanSubtree':
101
+ return 'Check whether the repeated subtree could be computed once and reused, or cache/persist the shared computation.';
102
+ case 'smallFiles':
103
+ return finding.direction === 'write'
104
+ ? 'Repartition or coalesce before writing to raise the average file size.'
105
+ : 'Compact the upstream output so fewer, larger files are produced.';
106
+ case 'underBroadcast':
107
+ return 'This could have been a broadcast join: consider a broadcast() hint or raising spark.sql.autoBroadcastJoinThreshold.';
108
+ case 'overBroadcast':
109
+ return 'Check for a misapplied broadcast hint or a misconfigured spark.sql.autoBroadcastJoinThreshold.';
110
+ }
111
+ return undefined;
112
+ }
@@ -0,0 +1,51 @@
1
+ // Canonical detector `type` -> human-readable label (single source of truth). detector-registry
2
+ // imports this (web uses it lowercase); evidence-report.ts Title Cases it for CLI/MCP names.
3
+ //
4
+ // broadcastSizing is the DETECTORS-level type but no real Finding carries it (the detector pushes
5
+ // underBroadcast/overBroadcast). Kept as a dead key so the DETECTORS-type completeness check finds it.
6
+ export const FINDING_NAMES = {
7
+ incompleteRun: 'incomplete run',
8
+
9
+ skew: 'task skew',
10
+ stageShape: 'stage shape',
11
+ tinyTask: 'tiny tasks',
12
+
13
+ shuffle: 'shuffle I/O',
14
+ partitionSizing: 'partition sizing',
15
+
16
+ spill: 'spill',
17
+
18
+ gc: 'GC pressure',
19
+
20
+ stageFailed: 'failed stage',
21
+ failures: 'failed tasks',
22
+ retryWaste: 'retry waste',
23
+
24
+ slowHost: 'slow executor host',
25
+ stageSlowness: 'slow stage',
26
+ straggler: 'straggling task',
27
+ speculationWaste: 'speculation waste',
28
+ coldStart: 'cold start',
29
+
30
+ memoryUtilization: 'memory utilization',
31
+ utilization: 'executor utilization',
32
+ coreLocality: 'core locality',
33
+ cachingOpportunity: 'caching opportunity',
34
+ cacheUtilization: 'cache utilization',
35
+ jobFailureRate: 'job failure rate',
36
+ autoscalingChurn: 'autoscaling churn',
37
+
38
+ configAudit: 'config audit',
39
+
40
+ duplicatePlanSubtree: 'duplicate plan subtree',
41
+ smallFiles: 'small files',
42
+ broadcastSizing: 'broadcast sizing',
43
+ underBroadcast: 'missed broadcast join',
44
+ overBroadcast: 'oversized broadcast join',
45
+ };
46
+
47
+ // Capitalizes the first letter of each word, leaving other characters untouched so acronyms
48
+ // ('GC', 'I/O') survive.
49
+ export function titleCase(label ) {
50
+ return label.replace(/\b\w/g, (c) => c.toUpperCase());
51
+ }