sparkforensics-cli 0.1.0 → 0.2.2

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 (361) 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.DQTZyGL1.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.DppnXnDE.js +1 -0
  12. package/export-template/docs/assets/chunks/VPLocalSearchBox.BkBIPFs6.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.DP0u1AUq.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.CWpj01WU.js +1 -0
  24. package/export-template/docs/assets/contributor-guide_architecture_board-widgets.md.CWpj01WU.lean.js +1 -0
  25. package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.CgzUsQ6W.js +1 -0
  26. package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.CgzUsQ6W.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.CooslVJt.js +1 -0
  30. package/export-template/docs/assets/contributor-guide_architecture_impact-estimation.md.CooslVJt.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.C-xxn0q7.js +1 -0
  36. package/export-template/docs/assets/contributor-guide_architecture_state-and-history.md.C-xxn0q7.lean.js +1 -0
  37. package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.R27gQrgY.js +1 -0
  38. package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.R27gQrgY.lean.js +1 -0
  39. package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.IbnfNrV3.js +6 -0
  40. package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.IbnfNrV3.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.DSixAiZE.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.B4tPGIal.js +1 -0
  151. package/export-template/docs/assets/user-guide_alternative-log-retrieval.md.B4tPGIal.lean.js +1 -0
  152. package/export-template/docs/assets/user-guide_getting-started.md.BJvwLEIM.js +3 -0
  153. package/export-template/docs/assets/user-guide_getting-started.md.BJvwLEIM.lean.js +1 -0
  154. package/export-template/docs/assets/user-guide_mcp-tools.md.Vi3RoflJ.js +125 -0
  155. package/export-template/docs/assets/user-guide_mcp-tools.md.Vi3RoflJ.lean.js +1 -0
  156. package/export-template/docs/assets/user-guide_run-comparison.md.CQc1aoU8.js +1 -0
  157. package/export-template/docs/assets/user-guide_run-comparison.md.CQc1aoU8.lean.js +1 -0
  158. package/export-template/docs/assets/user-guide_understanding-findings.md.DL1UDhvR.js +1 -0
  159. package/export-template/docs/assets/user-guide_understanding-findings.md.DL1UDhvR.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 +111 -0
  216. package/export-template/parser-worker-DyjiQvfP.js +112 -0
  217. package/export-template/sample-runs/sample-run.ndjson.gz +0 -0
  218. package/package.json +20 -6
  219. package/vendor-core/analyzer.js +74 -74
  220. package/vendor-core/cli/budgets.js +13 -27
  221. package/vendor-core/cli/collect-run.js +43 -19
  222. package/vendor-core/core-count.js +25 -27
  223. package/vendor-core/core-locality-ratio.js +4 -11
  224. package/vendor-core/core-time-series.js +6 -12
  225. package/vendor-core/core-usage-locality.js +3 -4
  226. package/vendor-core/detectors.js +395 -389
  227. package/vendor-core/docs-config.js +69 -21
  228. package/vendor-core/docs-content/chapters/01-intro.md +32 -0
  229. package/vendor-core/docs-content/chapters/02-spark-architecture.md +76 -0
  230. package/vendor-core/docs-content/chapters/03-memory-model.md +73 -0
  231. package/vendor-core/docs-content/chapters/04-partitioning.md +65 -0
  232. package/vendor-core/docs-content/chapters/05-joins.md +62 -0
  233. package/vendor-core/docs-content/chapters/06-shuffle.md +59 -0
  234. package/vendor-core/docs-content/chapters/07-data-formats.md +81 -0
  235. package/vendor-core/docs-content/chapters/07b-table-formats.md +56 -0
  236. package/vendor-core/docs-content/chapters/08-caching.md +58 -0
  237. package/vendor-core/docs-content/chapters/09-pyspark.md +78 -0
  238. package/vendor-core/docs-content/chapters/10-aqe.md +167 -0
  239. package/vendor-core/docs-content/chapters/11-cluster-config.md +170 -0
  240. package/vendor-core/docs-content/chapters/12-anti-patterns.md +171 -0
  241. package/vendor-core/docs-content/chapters/14-metrics.md +87 -0
  242. package/vendor-core/docs-content/chapters/15-config.md +93 -0
  243. package/vendor-core/docs-content/chapters/nav-index.json +370 -0
  244. package/vendor-core/docs-content/detection/cache.md +7 -0
  245. package/vendor-core/docs-content/detection/cfg.md +15 -0
  246. package/vendor-core/docs-content/detection/chrn.md +9 -0
  247. package/vendor-core/docs-content/detection/cold.md +4 -0
  248. package/vendor-core/docs-content/detection/cstor.md +4 -0
  249. package/vendor-core/docs-content/detection/fail.md +5 -0
  250. package/vendor-core/docs-content/detection/gc.md +4 -0
  251. package/vendor-core/docs-content/detection/host.md +5 -0
  252. package/vendor-core/docs-content/detection/incmp.md +6 -0
  253. package/vendor-core/docs-content/detection/jobs.md +4 -0
  254. package/vendor-core/docs-content/detection/local.md +6 -0
  255. package/vendor-core/docs-content/detection/mem.md +10 -0
  256. package/vendor-core/docs-content/detection/part.md +5 -0
  257. package/vendor-core/docs-content/detection/plan.md +14 -0
  258. package/vendor-core/docs-content/detection/retry.md +4 -0
  259. package/vendor-core/docs-content/detection/sfail.md +5 -0
  260. package/vendor-core/docs-content/detection/shape.md +5 -0
  261. package/vendor-core/docs-content/detection/shfl.md +4 -0
  262. package/vendor-core/docs-content/detection/skew.md +6 -0
  263. package/vendor-core/docs-content/detection/slow.md +6 -0
  264. package/vendor-core/docs-content/detection/spec.md +8 -0
  265. package/vendor-core/docs-content/detection/spill.md +7 -0
  266. package/vendor-core/docs-content/detection/strag.md +5 -0
  267. package/vendor-core/docs-content/detection/tiny.md +4 -0
  268. package/vendor-core/docs-content/detection/util.md +4 -0
  269. package/vendor-core/docs-content/diagrams/aqe-loop.dark.svg +1 -0
  270. package/vendor-core/docs-content/diagrams/aqe-loop.svg +1 -0
  271. package/vendor-core/docs-content/diagrams/broadcast-vs-shuffle.dark.svg +1 -0
  272. package/vendor-core/docs-content/diagrams/broadcast-vs-shuffle.svg +1 -0
  273. package/vendor-core/docs-content/diagrams/cache-lifecycle.dark.svg +1 -0
  274. package/vendor-core/docs-content/diagrams/cache-lifecycle.svg +1 -0
  275. package/vendor-core/docs-content/diagrams/cold-start-timeline.dark.svg +1 -0
  276. package/vendor-core/docs-content/diagrams/cold-start-timeline.svg +1 -0
  277. package/vendor-core/docs-content/diagrams/columnar-layout.dark.svg +1 -0
  278. package/vendor-core/docs-content/diagrams/columnar-layout.svg +1 -0
  279. package/vendor-core/docs-content/diagrams/container-memory.dark.svg +1 -0
  280. package/vendor-core/docs-content/diagrams/container-memory.svg +1 -0
  281. package/vendor-core/docs-content/diagrams/dag-stages.dark.svg +1 -0
  282. package/vendor-core/docs-content/diagrams/dag-stages.svg +1 -0
  283. package/vendor-core/docs-content/diagrams/driver-executor.dark.svg +1 -0
  284. package/vendor-core/docs-content/diagrams/driver-executor.svg +1 -0
  285. package/vendor-core/docs-content/diagrams/duplicate-plan-subtree.dark.svg +1 -0
  286. package/vendor-core/docs-content/diagrams/duplicate-plan-subtree.svg +1 -0
  287. package/vendor-core/docs-content/diagrams/join-strategy.dark.svg +1 -0
  288. package/vendor-core/docs-content/diagrams/join-strategy.svg +1 -0
  289. package/vendor-core/docs-content/diagrams/memory-borrowing.dark.svg +1 -0
  290. package/vendor-core/docs-content/diagrams/memory-borrowing.svg +1 -0
  291. package/vendor-core/docs-content/diagrams/memory-regions.dark.svg +1 -0
  292. package/vendor-core/docs-content/diagrams/memory-regions.svg +1 -0
  293. package/vendor-core/docs-content/diagrams/repartition-vs-coalesce.dark.svg +1 -0
  294. package/vendor-core/docs-content/diagrams/repartition-vs-coalesce.svg +1 -0
  295. package/vendor-core/docs-content/diagrams/retry-escalation-ladder.dark.svg +1 -0
  296. package/vendor-core/docs-content/diagrams/retry-escalation-ladder.svg +1 -0
  297. package/vendor-core/docs-content/diagrams/shuffle-map-reduce.dark.svg +1 -0
  298. package/vendor-core/docs-content/diagrams/shuffle-map-reduce.svg +1 -0
  299. package/vendor-core/docs-content/diagrams/spill-classification.dark.svg +1 -0
  300. package/vendor-core/docs-content/diagrams/spill-classification.svg +1 -0
  301. package/vendor-core/docs-content/diagrams/udf-execution-models.dark.svg +1 -0
  302. package/vendor-core/docs-content/diagrams/udf-execution-models.svg +1 -0
  303. package/vendor-core/docs-content/tuning/broadcast-sizing.md +78 -0
  304. package/vendor-core/docs-content/tuning/cold-start.md +81 -0
  305. package/vendor-core/docs-content/tuning/duplicate-plan-subtree.md +45 -0
  306. package/vendor-core/docs-content/tuning/failures.md +124 -0
  307. package/vendor-core/docs-content/tuning/gc.md +110 -0
  308. package/vendor-core/docs-content/tuning/job-failure-rate.md +101 -0
  309. package/vendor-core/docs-content/tuning/memory-utilization.md +58 -0
  310. package/vendor-core/docs-content/tuning/retry-waste.md +90 -0
  311. package/vendor-core/docs-content/tuning/shuffle.md +154 -0
  312. package/vendor-core/docs-content/tuning/skew.md +123 -0
  313. package/vendor-core/docs-content/tuning/slow-host.md +117 -0
  314. package/vendor-core/docs-content/tuning/small-files.md +99 -0
  315. package/vendor-core/docs-content/tuning/spill.md +114 -0
  316. package/vendor-core/docs-content/tuning/straggler.md +103 -0
  317. package/vendor-core/docs-content/tuning/tiny-tasks.md +94 -0
  318. package/vendor-core/docs-content/tuning/utilization.md +90 -0
  319. package/vendor-core/docs-site-config.js +10 -17
  320. package/vendor-core/efficiency-model.js +7 -13
  321. package/vendor-core/etl-phases.js +3 -5
  322. package/vendor-core/event-handlers.js +232 -134
  323. package/vendor-core/event-schemas.js +48 -114
  324. package/vendor-core/evidence-availability.js +5 -10
  325. package/vendor-core/evidence-report.js +73 -123
  326. package/vendor-core/export-data.js +48 -0
  327. package/vendor-core/finding-action-label.js +4 -10
  328. package/vendor-core/finding-filter-predicate.js +3 -7
  329. package/vendor-core/finding-generic-recommendation.js +112 -0
  330. package/vendor-core/finding-names.js +51 -0
  331. package/vendor-core/format-utils.js +112 -38
  332. package/vendor-core/impact-band.js +18 -24
  333. package/vendor-core/impact-estimator.js +38 -74
  334. package/vendor-core/ingest.js +7 -13
  335. package/vendor-core/job-groups.js +3 -6
  336. package/vendor-core/list-runs.js +278 -0
  337. package/vendor-core/load-vendored.js +6 -12
  338. package/vendor-core/log-header-peek.js +81 -0
  339. package/vendor-core/lz4-block.js +4 -6
  340. package/vendor-core/mcp-server-factory.js +38 -8
  341. package/vendor-core/mcp-tools.js +105 -76
  342. package/vendor-core/model-assembler.js +8 -16
  343. package/vendor-core/occupancy.js +5 -9
  344. package/vendor-core/parser-worker.js +20 -29
  345. package/vendor-core/plan-dot.js +2 -5
  346. package/vendor-core/plan-duration-attribution.js +78 -29
  347. package/vendor-core/plan-graph-model.js +126 -69
  348. package/vendor-core/plan-node-detail.js +31 -17
  349. package/vendor-core/plan-summary.js +19 -8
  350. package/vendor-core/recommendation-rollup.js +35 -39
  351. package/vendor-core/redact.js +72 -16
  352. package/vendor-core/rolling-log-reassembly.js +4 -6
  353. package/vendor-core/run-comparison.js +86 -72
  354. package/vendor-core/scaling-sim.js +5 -7
  355. package/vendor-core/session-snapshot.js +1 -1
  356. package/vendor-core/shs-fetch.js +4 -6
  357. package/vendor-core/shs-load.js +9 -13
  358. package/vendor-core/shs-request.js +1 -1
  359. package/vendor-core/stage-quantiles.js +14 -0
  360. package/vendor-core/types.js +78 -18
  361. package/vendor-core/wasted-core-hours.js +7 -12
@@ -9,9 +9,7 @@ import { IMPACT_BAND_ORDER } from './format-utils.js';
9
9
 
10
10
 
11
11
 
12
- // FNV-1a 32-bit: a small, dependency-free stable string hash. Used to derive
13
- // a deterministic finding `id` at the single choke point below so the same
14
- // logical finding keeps the same id across runs (no timestamps, no randomness).
12
+ // FNV-1a 32-bit stable string hash: deterministic finding id across runs, no timestamps/randomness.
15
13
  function fnv1a(str ) {
16
14
  let h = 0x811c9dc5;
17
15
  for (let i = 0; i < str.length; i++) {
@@ -22,36 +20,19 @@ function fnv1a(str ) {
22
20
  }
23
21
 
24
22
  export function findingId(f ) {
25
- // Location key mirrors the report's stable identity: stage, else SQL
26
- // execution, else the audited config property.
23
+ // Location key: stage, else SQL execution, else audited config property.
27
24
  const locKey = f.stageId ?? f.executionId ?? f.property ?? '';
28
- // Discriminators for detectors that intentionally emit multiple findings on
29
- // the same location+metric; without them these siblings hash to one id:
30
- // stage-scope: slowHost (host), memoryUtilization (executorId + dimension),
31
- // partitionSizing (rule);
32
- // app-scope: cacheUtilization (rddId + variant, stageId is always null),
33
- // cachingOpportunity (stageId/executionId both null too: `relation`/
34
- // `format` distinguish leaf findings, `operator`+`relation` distinguish
35
- // composite findings, `executionIds` as a last resort when everything
36
- // else matches);
37
- // SQL plan-advisor: smallFiles (direction/nodeName),
38
- // duplicatePlanSubtree (rootName/subtreeSize/groupIndex: two unrelated
39
- // duplicate-subtree groups can share rootName+subtreeSize, e.g. the same
40
- // "BroadcastExchange over a Project/Filter/Scan" shape repeated once per
41
- // dimension table; groupIndex (the group's deterministic position within
42
- // one execution's plan) is the actual uniqueness guarantee, since the
43
- // informational `sampleRelation` field can be null or coincide for two
44
- // groups),
45
- // broadcastSizing (per node/side, distinguished by value + largerSideBytes).
46
- // The metric `value` folds in the per-node magnitude that broadcastSizing
47
- // siblings carry no other field for; it is deterministic for a given log, so
48
- // the id stays stable across re-parses (no timestamp/randomness).
49
- // memoryUtilization's `rule` (heapNearCapacity/heapOverProvisioned) is excluded here: an
50
- // executor falls in exactly one heap band, so `executorId` alone already guarantees
51
- // uniqueness and folding `rule` in too would just add gratuitous id-rotation risk if the
52
- // band logic ever changes. partitionSizing's `rule` (maxPartitionTooBig/
53
- // shufflePartitionSkew/lowShuffleParallelism) IS load-bearing here: a stage can emit more
54
- // than one of those rules at once, sharing the same stageId+metric.
25
+ // Discriminators for detectors that emit multiple findings on the same
26
+ // location+metric; without them these siblings hash to one id:
27
+ // slowHost (host), memoryUtilization (executorId+dimension), partitionSizing (rule);
28
+ // cacheUtilization (rddId+variant), cachingOpportunity (relation/format for leaf,
29
+ // operator+relation for composite, executionIds as last resort);
30
+ // smallFiles (direction/nodeName), duplicatePlanSubtree (groupIndex is the real
31
+ // uniqueness guarantee: rootName+subtreeSize can collide across groups),
32
+ // broadcastSizing (value+largerSideBytes per node/side).
33
+ // memoryUtilization excludes `rule`: one heap band per executor, so executorId is already
34
+ // unique and folding rule in risks id churn if band logic changes. partitionSizing keeps
35
+ // `rule`: a stage can emit several rules at once sharing stageId+metric.
55
36
  const rule = f.type === 'memoryUtilization' ? undefined : f.rule;
56
37
  const disc = [
57
38
  f.host, f.executorId, rule, f.variant, f.dimension,
@@ -62,6 +43,35 @@ export function findingId(f ) {
62
43
  return fnv1a(`${f.type}|${locKey}|${f.metric ?? ''}|${f.value ?? ''}|${disc}`);
63
44
  }
64
45
 
46
+ // skew's max/median branch (stage.taskCount below minTasksForP95) and straggler are both driven
47
+ // by the identical (taskDurationMax - taskDurationP50) delta on the same stage: the same
48
+ // dominant outlier task reported by two detectors, each independently clipped (see "Overlap
49
+ // caveat: skew / straggler" in impact-estimation.md). skew's P95/median branch samples a
50
+ // different task and stays independent. Flags both sides via validationRequired (rather than
51
+ // suppressing either) so neither finding's own diagnostic value is lost; the flag rides the same
52
+ // confidence-caveat UI a reader already sees before trusting either finding's magnitude.
53
+ function overlapNote(otherType ) {
54
+ return `This overlaps with the ${otherType} finding on this stage: both are driven by the same dominant outlier task, so don't add their recoverable-time figures together.`;
55
+ }
56
+
57
+ function flagSkewStragglerOverlap(findings ) {
58
+ const maxMedianSkewStages = new Set(
59
+ findings.filter((f) => f.type === 'skew' && f.metric === 'max/median' && f.stageId != null).map((f) => f.stageId),
60
+ );
61
+ if (maxMedianSkewStages.size === 0) return;
62
+ const stragglerStages = new Set(
63
+ findings.filter((f) => f.type === 'straggler' && f.stageId != null).map((f) => f.stageId),
64
+ );
65
+ const overlapStages = new Set([...maxMedianSkewStages].filter((id) => stragglerStages.has(id)));
66
+ if (overlapStages.size === 0) return;
67
+ for (const f of findings) {
68
+ if (f.stageId == null || !overlapStages.has(f.stageId)) continue;
69
+ const note = f.type === 'skew' ? overlapNote('straggler') : f.type === 'straggler' ? overlapNote('skew') : null;
70
+ if (!note) continue;
71
+ f.validationRequired = f.validationRequired ? `${f.validationRequired} ${note}` : note;
72
+ }
73
+ }
74
+
65
75
  function push(out , entry , result ) {
66
76
  if (!result) return;
67
77
  const detectorVersion = entry.version ?? 1;
@@ -70,28 +80,15 @@ function push(out , entry , result
70
80
  if (entry.suppressWhen && entry.suppressWhen(f, out)) continue;
71
81
  const stamped = { ...f, docAnchor: f.docAnchor ?? entry.docAnchor, detectorVersion };
72
82
  const id = findingId(stamped);
73
- // Guardrail against any detector (this one or a future one) emitting two
74
- // structurally-identical findings for what should be one logical
75
- // occurrence: same id => same finding, keep only the first. Added after a
76
- // real-log bug where duplicatePlanSubtree emitted two distinct findings
77
- // sharing one id (see findingId's groupIndex discriminator above and
78
- // tests/detectors-plan.test.js's duplicatePlanSubtree regression tests);
79
- // this guard's correctness depends on findingId's discriminator list
80
- // actually being unique per distinct finding, not on this line itself.
83
+ // Dedup guard: same id => same finding, keep first. Correctness depends on
84
+ // findingId's discriminators being unique per distinct finding, not on this line.
81
85
  if (out.some((existing) => existing.id === id)) continue;
82
86
  out.push({ ...stamped, id });
83
87
  }
84
88
  }
85
89
 
86
- // `app` is widened to `SparkAppInfo | null` rather than the plan's literal
87
- // non-nullable `SparkAppInfo`: real callers (evidence-report.ts's
88
- // `buildJson`, useIngest.ts's `runDone`/`snapshotParsedRun`) pass
89
- // `AppModel.app`, which is genuinely `SparkAppInfo | null` at the type
90
- // level even though by the time `analyze()` runs in practice a full parse
91
- // has completed and `app` is always populated (the same reasoning
92
- // detectors.ts's `DetectorCtx.app` comment gives for treating its own,
93
- // separate `app` field as non-nullable). Widened to match the real caller
94
- // type rather than forcing a cast at every call site.
90
+ // `app` widened to `SparkAppInfo | null` to match real callers (AppModel.app is
91
+ // nullable at the type level); every detector below tolerates a null app.
95
92
  export function analyze(
96
93
  app ,
97
94
  stages ,
@@ -101,29 +98,18 @@ export function analyze(
101
98
  sql = new Map(),
102
99
  runAggregates = null,
103
100
  ) {
104
- // `app ?? {}`: every detector below tolerates a null `app` (malformed logs
105
- // with no ApplicationStart), so this call must too; computePeakConcurrentCores
106
- // just falls back to the executor-derived core sum when `resources` is absent.
107
- // computePeakConcurrentCores (not computeTotalCores) here specifically: the
108
- // ceiling this feeds (src/occupancy.ts's computeCeiling) needs a genuine
109
- // concurrent-capacity bound, and computeTotalCores's cumulative sum over
110
- // every addition overstates that under dynamic allocation/executor
111
- // replacement (a churned-through executor's cores were never actually
112
- // concurrent with its replacement's).
113
- // `executorsAdded`/`executorsRemoved` casts: computePeakConcurrentCores only
114
- // reads `executorId`/`timestamp`/`totalCores`, all present on
115
- // ExecutorAddedEvent/ExecutorRemovedEvent, but `totalCores` isn't on
116
- // ExecutorRemovedEvent, so the `ExecutorEvent` union as a whole is a
117
- // structural mismatch against its parameter shapes.
101
+ // `app ?? {}`: detectors tolerate a null app (malformed logs), so this must too.
102
+ // computePeakConcurrentCores (not computeTotalCores): the occupancy ceiling needs a
103
+ // concurrent-capacity bound; a cumulative sum overstates it under dynamic allocation.
104
+ // Casts: the function reads only executorId/timestamp/totalCores, but totalCores isn't
105
+ // on ExecutorRemovedEvent, so the ExecutorEvent union mismatches its parameter shapes.
118
106
  const totalCores = computePeakConcurrentCores(
119
107
  app ?? {},
120
108
  executorsAdded ,
121
109
  executorsRemoved ,
122
110
  );
123
- // Computed once, up front, so detectors can gate impact band on the same
124
- // occupancy-clipped waste figure estimateImpact() below displays as that
125
- // finding's savings (src/detectors.ts's `clippedWasteMs`), not a raw
126
- // pre-clip delta the two passes would otherwise disagree on.
111
+ // Computed once so detectors gate impact band on the same occupancy-clipped waste
112
+ // estimateImpact displays as savings, not a raw pre-clip delta the two passes would disagree on.
127
113
  const occupancy = computeOccupancy(stages , totalCores);
128
114
  const ctx = {
129
115
  app, stages, executorsAdded, executorsRemoved, jobs, sql, runAggregates, occupancy,
@@ -148,20 +134,34 @@ export function analyze(
148
134
  }
149
135
  estimateImpact(out, stages, totalCores);
150
136
  deriveImpactBand(out, app);
151
- // Ascending IMPACT_BAND_ORDER (critical 0 → info 2) puts the worst band
152
- // first, matching the old descending 3/2/1 rank this replaced; `sort` is
153
- // stable, so findings sharing a band keep their DETECTORS declaration order.
137
+ flagSkewStragglerOverlap(out);
138
+ // Ascending IMPACT_BAND_ORDER (critical 0 -> info 2) puts the worst band first;
139
+ // stable sort keeps DETECTORS declaration order within a band.
154
140
  out.sort((a, b) => IMPACT_BAND_ORDER[a.impactBand] - IMPACT_BAND_ORDER[b.impactBand]);
155
141
  return out;
156
142
  }
157
143
 
158
- export function auditConfig(app ) {
144
+ // Memoizes auditConfig by `app` identity (like evidence-report.ts's jsonCache) so config detectors
145
+ // don't re-run when both the export and the report path audit the same app. WeakMap can't key on
146
+ // `null`, so that case skips the cache. Returns a fresh copy each call so a caller's in-place
147
+ // mutation (e.g. `.sort()`) can't corrupt the cached array.
148
+ const auditConfigCache = new WeakMap ();
149
+
150
+ function computeAuditConfig(app ) {
159
151
  const out = [];
160
152
  for (const d of DETECTORS) if (d.scope === 'config') push(out, d, d.detect({ app }));
161
- // configAudit's own impact-estimator case (src/impact-estimator.ts) needs neither
162
- // `stages` nor `totalCores`: it's unconditionally `costOnly('none')`. An empty stages
163
- // map is enough for parity with the same finding type produced via analyze().
153
+ // configAudit's impact case is unconditionally costOnly('none'): needs no stages/totalCores,
154
+ // an empty stages map gives parity with analyze().
164
155
  estimateImpact(out, new Map());
165
156
  deriveImpactBand(out, app);
166
157
  return out.map((f) => ({ ...f, stageId: f.stageId ?? null }));
167
158
  }
159
+
160
+ export function auditConfig(app ) {
161
+ if (app === null) return computeAuditConfig(app);
162
+ const cached = auditConfigCache.get(app);
163
+ if (cached) return cached.slice();
164
+ const result = computeAuditConfig(app);
165
+ auditConfigCache.set(app, result);
166
+ return result.slice();
167
+ }
@@ -26,10 +26,8 @@ function taskDataTrusted(appModel ) {
26
26
  return entry?.state === 'present';
27
27
  }
28
28
 
29
- // `Finding.value` is `number | string` (some detectors, e.g. stageFailed/
30
- // configAudit, put human-readable text there instead of a magnitude; see
31
- // types.ts). The 'spill' findings this is used for are always numeric; the
32
- // typeof guard below narrows without changing behavior for real input.
29
+ // Finding.value is number|string (some detectors put text there); spill findings are
30
+ // always numeric, so the typeof guard narrows without changing behavior for real input.
33
31
  function maxFindingValue(catalog , type ) {
34
32
  const values = catalog
35
33
  .filter((f) => f.type === type)
@@ -68,10 +66,8 @@ function checkSkew(appModel , maxSkewRatio ) {
68
66
  if (stages.length === 0) {
69
67
  return { name: 'max-skew', status: 'inconclusive', detail: 'No stage data observed in this event log.' };
70
68
  }
71
- // Recompute the true ratio per stage (not from the finding catalog): the
72
- // skew detector floors its findings at thresholds.ratioWarn (3), so a
73
- // budget stricter than that floor could never be enforced by reading
74
- // catalog findings alone.
69
+ // Recompute the true ratio per stage: the skew detector floors findings at
70
+ // thresholds.ratioWarn (3), so a stricter budget can't be enforced from catalog alone.
75
71
  const ratios = stages
76
72
  .map((stage) => computeSkewRatio(stage, SKEW_MIN_TASKS_FOR_P95))
77
73
  .filter((r) => r !== null)
@@ -96,10 +92,8 @@ function checkFailedTaskRate(appModel , catalog , maxPct
96
92
  if ((appModel.jobs?.size ?? 0) === 0) {
97
93
  return { name: 'max-failed-task-rate', status: 'inconclusive', detail: 'No job data observed in this event log.' };
98
94
  }
99
- // Jobs completed but the jobFailureRate detector never fired: job failure
100
- // rate is below its own 10% info floor (src/detectors.js), so the task
101
- // failure rate is implicitly low too. Known v1 limitation: a budget
102
- // stricter than that floor cannot be enforced.
95
+ // jobFailureRate never fired => rate below its 10% info floor, so task failure rate is
96
+ // implicitly low. v1 limitation: a budget stricter than that floor can't be enforced.
103
97
  return { name: 'max-failed-task-rate', status: 'pass', detail: `No job-failure-rate finding: task failure rate is below the detector's reporting floor.` };
104
98
  }
105
99
 
@@ -125,10 +119,8 @@ function checkRegression(comparison , maxRegressionPct
125
119
  if (!row || row.direction === 'unavailable' || row.baseline == null || row.delta == null) {
126
120
  return { name: 'max-regression', status: 'inconclusive', detail: `Metric "${regressionMetric}" is unavailable for this comparison.` };
127
121
  }
128
- // A neutral-direction metric (inputBytes, outputBytes, taskCount,
129
- // executorsAdded see NEUTRAL_METRIC_KEYS in run-comparison.ts) measures
130
- // workload volume, not performance: an increase isn't a regression, so a
131
- // regression budget can't be meaningfully evaluated against it either way.
122
+ // A neutral-direction metric (inputBytes/outputBytes/taskCount/executorsAdded) measures
123
+ // workload volume, not performance: an increase isn't a regression, so no direction to check.
132
124
  if (row.direction === 'neutral') {
133
125
  return { name: 'max-regression', status: 'inconclusive', detail: `Metric "${regressionMetric}" measures workload volume, not performance: it has no regression direction to check.` };
134
126
  }
@@ -156,8 +148,7 @@ function checkFailOnIntroduced(comparison , band )
156
148
  : { name: 'fail-on-introduced', status: 'pass', detail: `No introduced findings match "${band}".` };
157
149
  }
158
150
 
159
- // Shared by both comparison-dependent budgets below: each needs the same
160
- // "no comparison yet -> inconclusive" fallback instead of actually checking.
151
+ // Both comparison-dependent budgets share the "no comparison yet -> inconclusive" fallback.
161
152
  function pushComparisonBudget(
162
153
  results ,
163
154
  comparison ,
@@ -176,18 +167,13 @@ export function evaluateBudgets({ appModel, catalog, budgets, comparison }
176
167
  if (Number.isFinite(budgets.maxSkewRatio)) results.push(checkSkew(appModel, budgets.maxSkewRatio ));
177
168
  if (Number.isFinite(budgets.maxFailedTaskRatePct)) results.push(checkFailedTaskRate(appModel, catalog, budgets.maxFailedTaskRatePct ));
178
169
  if (Number.isFinite(budgets.minEfficiencyPct)) results.push(checkEfficiency(appModel, budgets.minEfficiencyPct ));
179
- // Guarded here (not just at the CLI/mcp-tools call sites) so any caller of
180
- // evaluateBudgets() gets this for free: regressionMetric with no
181
- // maxRegressionPct would otherwise skip the whole `if` below silently,
182
- // reporting nothing at all instead of a visible inconclusive result.
170
+ // Guarded here so any evaluateBudgets caller benefits: regressionMetric without
171
+ // maxRegressionPct would otherwise skip the `if` silently, reporting nothing.
183
172
  if (budgets.regressionMetric !== undefined && budgets.maxRegressionPct === undefined) {
184
173
  results.push({ name: 'max-regression', status: 'inconclusive', detail: `regressionMetric "${budgets.regressionMetric}" was set without maxRegressionPct; the regression budget was not evaluated.` });
185
174
  } else if (budgets.maxRegressionPct !== undefined) {
186
- // `!== undefined`, not `Number.isFinite`: a zero-baseline regression's pct
187
- // is itself `Infinity` (see checkRegression), so an "unlimited" budget is a
188
- // legitimate finite-typed-as-number input here (CLI's own flag validation
189
- // already rejects non-finite --max-regression-pct input, so this only
190
- // widens what a direct evaluateBudgets caller, e.g. a test, can express).
175
+ // `!== undefined`, not Number.isFinite: a zero-baseline regression's pct is Infinity,
176
+ // so an "unlimited" budget is a legitimate input (the CLI already rejects non-finite flags).
191
177
  pushComparisonBudget(results, comparison, 'max-regression',
192
178
  (c) => checkRegression(c, budgets.maxRegressionPct , budgets.regressionMetric ?? 'wallClock'));
193
179
  }
@@ -1,4 +1,4 @@
1
- import { readFileSync, readdirSync, statSync } from 'node:fs';
1
+ import { readFileSync, readdirSync, statSync, openSync, readSync, closeSync } from 'node:fs';
2
2
  import { join, basename } from 'node:path';
3
3
  import { createState, runParse, runParseFiles, reassembleRollingEntries } from '../parser-worker.js';
4
4
  import { createModelCallbacks } from '../model-assembler.js';
@@ -23,8 +23,7 @@ export function emptyAppModel() {
23
23
  };
24
24
  }
25
25
 
26
- // Mirrors tests/parser-worker.test.js's fakeFile: the proven File-like shape
27
- // runParse/runParseFiles need (name, size, slice().arrayBuffer(), arrayBuffer()).
26
+ // File-like shape runParse/runParseFiles need: name, size, slice().arrayBuffer(), arrayBuffer().
28
27
  export function nodeFileFromPath(path ) {
29
28
  const bytes = readFileSync(path);
30
29
  const u8 = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
@@ -39,27 +38,53 @@ export function nodeFileFromPath(path ) {
39
38
  };
40
39
  }
41
40
 
42
- // Delegates to src/ingest.js's routeMessage: the single source of truth for
43
- // worker-message-type -> handler-callback wiring, shared by the browser
44
- // Worker context and this synchronous-in-Node CLI path. No pendingTaskRequests
45
- // map: the CLI never sends a 'getTaskData' request, so it never receives a
46
- // 'taskData' message back.
41
+ // Lazy-loading variant for peekLogHeader: only reads the requested byte range from disk,
42
+ // avoiding eager full-file slurps for large logs during directory scans.
43
+ // Used by list_runs local-mode scanning; does NOT have arrayBuffer() since peekLogHeader
44
+ // only needs slice() for bounded reads of the file header.
45
+ export function nodeLazyPeekableFromPath(path )
46
+
47
+
48
+
49
+ {
50
+ const size = statSync(path).size;
51
+ return {
52
+ name: basename(path),
53
+ size,
54
+ slice(start , end ) {
55
+ return {
56
+ async arrayBuffer() {
57
+ const len = Math.max(0, Math.min(end, size) - start);
58
+ if (len === 0) return new ArrayBuffer(0);
59
+ const buf = Buffer.alloc(len);
60
+ const fd = openSync(path, 'r');
61
+ try {
62
+ readSync(fd, buf, 0, len, start);
63
+ } finally {
64
+ closeSync(fd);
65
+ }
66
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
67
+ },
68
+ };
69
+ },
70
+ };
71
+ }
72
+
73
+ // routeMessage is the single source of truth for worker-message -> handler wiring, shared by
74
+ // the browser Worker and this Node CLI path. No pendingTaskRequests: the CLI never sends
75
+ // 'getTaskData', so never receives 'taskData' back.
47
76
  export function dispatch(msg , handlers ) {
48
77
  routeMessage(msg , handlers);
49
78
  }
50
79
 
51
- function isRollingLogDirectory(dirPath ) {
80
+ export function isRollingLogDirectory(dirPath ) {
52
81
  const names = readdirSync(dirPath);
53
82
  return names.some((n ) => /^events_\d+_/.test(n));
54
83
  }
55
84
 
56
- // Shared ingest scaffold for both consumers of routeMessage's onDone/onError
57
- // dispatch pattern (this file's collectRun, over a local file/dir, and
58
- // shs-load.ts's collectShsAppModel, over fetched archive bytes): builds a
59
- // fresh AppModel wired to the shared handlers, then hands `run` a
60
- // (state, emit, reject) triple so each caller only supplies its own decode
61
- // step (and its own error type via `onDecodeError` — a plain Error here, an
62
- // mcpError over in shs-load.ts).
85
+ // Shared ingest scaffold for both routeMessage consumers (this file's collectRun over a
86
+ // local file/dir, shs-load.ts's collectShsAppModel over fetched bytes): each caller supplies
87
+ // its own decode step and error type via onDecodeError.
63
88
  export function collectViaDispatch(
64
89
  run ,
65
90
  onDecodeError ,
@@ -96,9 +121,8 @@ export async function collectRun(inputPath )
96
121
  return;
97
122
  }
98
123
  const files = ordered.map((name) => nodeFileFromPath(join(inputPath, name)));
99
- // .catch(reject), not void: an unexpected throw past the parser's own
100
- // guards (e.g. in decoder.flush) would otherwise leave this Promise
101
- // pending forever and surface only as an unhandled rejection.
124
+ // .catch(reject), not void: a throw past the parser's guards would otherwise leave
125
+ // this Promise pending forever, surfacing only as an unhandled rejection.
102
126
  runParseFiles(files, state, { emit }).catch(reject);
103
127
  } else {
104
128
  runParse(nodeFileFromPath(inputPath), state, { emit }).catch(reject);
@@ -1,6 +1,4 @@
1
- // Total cores computation: sum real executor totalCores, or fallback to
2
- // peakExecutors × configured cores. Used by efficiency model, scaling simulator,
3
- // wasted core-hours calculation, and utilization/memory-utilization detectors.
1
+ // Total cores: sum executor totalCores, else peakExecutors x configured cores.
4
2
  export function computeTotalCores(app , executorsAdded ) {
5
3
  let total = executorsAdded.reduce((s, e) => s + (e.totalCores ?? 0), 0);
6
4
  if (total <= 0) {
@@ -10,19 +8,11 @@ export function computeTotalCores(app
10
8
  return total;
11
9
  }
12
10
 
13
- // Peak concurrently-alive core count: sweeps add/remove events by timestamp
14
- // (add contributes +totalCores at its own executorId, remove contributes
15
- // -totalCores looked up from that same executorId) and tracks the running
16
- // total's max, rather than summing every addition regardless of overlap.
17
- // computeTotalCores's cumulative sum overstates capacity under dynamic
18
- // allocation/executor replacement, since a churned-through executor's cores
19
- // are never actually concurrent with its replacement's; this is the
20
- // concurrency-aware sibling used to bound the impact estimator's ceiling
21
- // (src/occupancy.ts's computeCeiling) against that inflation.
22
- // Tie-break same-timestamp events by delta ascending, so a removal is applied
23
- // before a same-instant replacement's addition: without this, a seamless swap
24
- // (old executor gone exactly when its replacement joins) would momentarily
25
- // double-count both as concurrent.
11
+ // Peak concurrently-alive core count: sweep add/remove events by timestamp, track running max,
12
+ // rather than summing every addition. A cumulative sum overstates capacity under dynamic
13
+ // allocation (a churned-through executor's cores were never concurrent with its replacement's).
14
+ // Tie-break same-timestamp events by delta ascending so a removal applies before a same-instant
15
+ // addition; else a seamless swap would momentarily double-count both as concurrent.
26
16
  function sweepPeak(events ) {
27
17
  const sorted = [...events].sort((a, b) => a.time - b.time || a.delta - b.delta);
28
18
  let running = 0;
@@ -41,28 +31,36 @@ export function computePeakConcurrentCores(
41
31
  ) {
42
32
  const coresByExecutor = new Map ();
43
33
  const coreEvents = [];
44
- const countEvents = [];
45
34
  for (const e of executorsAdded) {
46
35
  const cores = e.totalCores ?? 0;
47
36
  coresByExecutor.set(e.executorId, cores);
48
37
  coreEvents.push({ time: e.timestamp, delta: cores });
49
- countEvents.push({ time: e.timestamp, delta: 1 });
50
38
  }
51
39
  for (const e of executorsRemoved) {
52
40
  const cores = coresByExecutor.get(e.executorId) ?? 0;
53
41
  coreEvents.push({ time: e.timestamp, delta: -cores });
54
- countEvents.push({ time: e.timestamp, delta: -1 });
55
42
  }
56
43
  const peak = sweepPeak(coreEvents);
57
44
  if (peak > 0) return peak;
58
- // Real totalCores data is missing/zero for every add event, so the cores
59
- // sweep above can't tell us anything. Falling back to
60
- // `executorsAdded.length * cores` here would reintroduce the exact
61
- // cumulative-historical-additions overcount this function exists to avoid
62
- // (a churned-through executor and its replacement both counted, even
63
- // though they were never alive at once). Sweep peak *executor count*
64
- // instead: still concurrency-aware, just cores-blind.
65
- const peakExecutorCount = sweepPeak(countEvents);
45
+ // totalCores missing/zero for every add, so the cores sweep is blind. Falling back to
46
+ // executorsAdded.length * cores would reintroduce the cumulative overcount this avoids;
47
+ // sweep peak executor count instead: concurrency-aware, cores-blind.
48
+ const peakExecutorCount = computePeakConcurrentExecutorCount(executorsAdded, executorsRemoved);
66
49
  const cores = app.resources?.executor?.cores ?? null;
67
50
  return cores != null ? peakExecutorCount * cores : 0;
68
51
  }
52
+
53
+ // Peak concurrently-alive executor COUNT (not cores): the denominator memoryUtilization's
54
+ // per-executor waste math (allocatedMB x executor count) needs. Same sweep as
55
+ // computePeakConcurrentCores, over add/remove events rather than cores, so a churned-through
56
+ // executor's slot is never double-counted against its replacement's.
57
+ export function computePeakConcurrentExecutorCount(
58
+ executorsAdded ,
59
+ executorsRemoved ,
60
+ ) {
61
+ const countEvents = [
62
+ ...executorsAdded.map((e) => ({ time: e.timestamp, delta: 1 })),
63
+ ...executorsRemoved.map((e) => ({ time: e.timestamp, delta: -1 })),
64
+ ];
65
+ return sweepPeak(countEvents);
66
+ }
@@ -1,14 +1,7 @@
1
- // Whole-run core-usage-locality ratio: non-local task share across every
2
- // stage's `stage.localityStats`. Mirrors wasted-core-hours.js's shape (pure
3
- // reducer, no detector or view coupling), importable by both the
4
- // `coreLocality` detector (src/detectors.js) and CoreUsageArea.tsx's
5
- // stage-breakdown list.
6
- //
7
- // Locality classification (Spark, best to worst): PROCESS_LOCAL > NODE_LOCAL
8
- // > NO_PREF > RACK_LOCAL > ANY. NO_PREF is not a locality failure; it's what
9
- // shuffle-read stages report because there's no location-preference concept
10
- // for a shuffle fetch, so it stays in the denominator (diluting the ratio for
11
- // shuffle-heavy stages, which is correct) but never in the numerator.
1
+ // Whole-run core-usage-locality ratio: non-local task share across every stage's localityStats.
2
+ // Locality tiers (Spark, best to worst): PROCESS_LOCAL > NODE_LOCAL > NO_PREF > RACK_LOCAL > ANY.
3
+ // NO_PREF is not a locality failure (shuffle-read stages report it, no location preference for a
4
+ // shuffle fetch), so it stays in the denominator but never the numerator.
12
5
  const NON_LOCAL_TIERS = new Set(['RACK_LOCAL', 'ANY']);
13
6
  const TOP_N = 5;
14
7
  const MIN_TASKS_PER_STAGE = 10;
@@ -1,11 +1,7 @@
1
- // Sweep-line "busy cores over time" computation. Pure and side-effect-free so
2
- // it can run inside parser-worker.js (over retained task launch/finish
3
- // timestamps) or on the main thread (scaling simulator). One core per task:
4
- // Spark's default task-to-core mapping.
5
- //
6
- // `hypotheticalCores` clamps the concurrent busy-core count to N (the
7
- // deterministic "utilization at N cores" curve). It does NOT re-schedule tasks;
8
- // makespan re-estimation is the scaling simulator's job, layered on this signal.
1
+ // Sweep-line "busy cores over time". Pure so it runs in parser-worker (over retained task
2
+ // timestamps) or on the main thread (scaling simulator). One core per task: Spark's default.
3
+ // `hypotheticalCores` clamps the concurrent busy-core count to N; it does NOT re-schedule
4
+ // tasks (makespan re-estimation is the scaling simulator's job, layered on this signal).
9
5
 
10
6
 
11
7
 
@@ -19,18 +15,16 @@
19
15
 
20
16
 
21
17
  function buildStepFunction(intervals , hypotheticalCores ) {
22
- // Emit +1 at launch, -1 at finish. Skip empty/negative intervals.
23
18
  const events = [];
24
19
  for (const { launch, finish } of intervals) {
25
20
  if (!(finish > launch)) continue;
26
21
  events.push({ t: launch, delta: 1 });
27
22
  events.push({ t: finish, delta: -1 });
28
23
  }
29
- // Sort by time; at equal time process -1 before +1 so [launch, finish) is
30
- // half-open (a task finishing exactly as another launches does not overlap).
24
+ // At equal time process -1 before +1 so [launch, finish) is half-open (a task finishing
25
+ // exactly as another launches does not overlap).
31
26
  events.sort((a, b) => (a.t - b.t) || (a.delta - b.delta));
32
27
 
33
- // Walk consecutive timestamps, emitting the busy level held on each interval.
34
28
  const segments = [];
35
29
  let busy = 0;
36
30
  for (let i = 0; i < events.length; i++) {
@@ -1,7 +1,6 @@
1
- // Stage-granular approximation of core-usage-by-locality over time. See the
2
- // plan's Task 6 DEVIATION note: per-task locality is not retained, so this
3
- // distributes each stage's executorRunTime across its wall-clock window,
4
- // split by localityStats proportions. Not exact; approximate by construction.
1
+ // Stage-granular approximation of core-usage-by-locality over time. Per-task locality is not
2
+ // retained, so this distributes each stage's executorRunTime across its wall-clock window,
3
+ // split by localityStats proportions. Approximate by construction.
5
4
  export const LOCALITY_TIERS = ['PROCESS_LOCAL', 'NODE_LOCAL', 'RACK_LOCAL', 'NO_PREF', 'ANY'];
6
5
 
7
6