sparkforensics-server 0.6.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/index.js +16 -7
  2. package/package.json +1 -1
  3. package/public/assets/AutoscalingChurn-DenWKTv-.js +1 -0
  4. package/public/assets/CacheUtilization-DLz7NdjV.js +1 -0
  5. package/public/assets/CachingOpportunity-zpQIqhzm.js +1 -0
  6. package/public/assets/ChartTheme-DAiARbLP.js +8 -0
  7. package/public/assets/ColdStart-C-9HjxWl.js +1 -0
  8. package/public/assets/ConfigAudit-D0V9tcrJ.js +1 -0
  9. package/public/assets/CoreUsageArea-yeerrljd.js +1 -0
  10. package/public/assets/Dashboard-DkUPrHKV.js +1 -0
  11. package/public/assets/DisclosureContext-DK1_iz6J.js +1 -0
  12. package/public/assets/DuplicatePlanSubtree-xDNDbCd3.js +1 -0
  13. package/public/assets/DurationHistogram-B6jsE0Ov.js +1 -0
  14. package/public/assets/EmptyState-C1uL40VU.js +1 -0
  15. package/public/assets/ExecutorUtilization-DXDZKmSc.js +1 -0
  16. package/public/assets/GcPressure-MRf7I4IG.js +1 -0
  17. package/public/assets/ImpactEstimate-Dm3xWbzV.js +1 -0
  18. package/public/assets/IncompleteRun-CaLOhMtf.js +1 -0
  19. package/public/assets/JobFailures-CzmzlvhO.js +1 -0
  20. package/public/assets/MemoryUtilization-AWUMUjCH.js +1 -0
  21. package/public/assets/NestedLoopJoin-C9sNr6Ss.js +1 -0
  22. package/public/assets/OverBroadcast-BOmRE6ca.js +1 -0
  23. package/public/assets/PartitionHint-BL2EmJfE.js +1 -0
  24. package/public/assets/PartitionSizing-BzBySvUq.js +1 -0
  25. package/public/assets/PlanExplorer-DdiQRcJU.js +1 -0
  26. package/public/assets/PlanGraphRoute-CIH9gBuR.js +1 -0
  27. package/public/assets/PlanView-B1hxzFRs.js +2 -0
  28. package/public/assets/PythonUdf-DvgCCueD.js +1 -0
  29. package/public/assets/RetryWaste-uXXu_CyM.js +1 -0
  30. package/public/assets/RowPagination-C-LzWkI5.js +1 -0
  31. package/public/assets/RowStatusCluster-Dbtmte7l.js +1 -0
  32. package/public/assets/ShuffleIO-nALwzC1_.js +1 -0
  33. package/public/assets/Skew-CHMEKgvk.js +1 -0
  34. package/public/assets/SlowHost-yyvjpHk6.js +1 -0
  35. package/public/assets/SmallFiles-y6odJ97n.js +1 -0
  36. package/public/assets/SpeculationWaste-C-SD2LOt.js +1 -0
  37. package/public/assets/Spill-DHTMmOup.js +1 -0
  38. package/public/assets/StageFailed-CQ3DxC9R.js +1 -0
  39. package/public/assets/StageFindingGroup-BgYLyj5q.js +1 -0
  40. package/public/assets/StageHeader-CIETkjVZ.js +1 -0
  41. package/public/assets/StagePill--P7PDd3s.js +1 -0
  42. package/public/assets/StageShape-BIv8vx_o.js +1 -0
  43. package/public/assets/StageSlowness-MMqzERxN.js +1 -0
  44. package/public/assets/Straggler-BgrY7AyW.js +1 -0
  45. package/public/assets/TaskFailures-BHHyKYTs.js +1 -0
  46. package/public/assets/TinyTask-DDScNhCl.js +1 -0
  47. package/public/assets/UnderBroadcast-CGIC04TI.js +1 -0
  48. package/public/assets/WidgetLeadSummary-CXFZP00C.js +1 -0
  49. package/public/assets/button-DbpTz2yk.js +1 -0
  50. package/public/assets/charts-vendor-CtTxwTeu.js +59 -0
  51. package/public/assets/composite-C9Nu2EI9.js +1 -0
  52. package/public/assets/d3-vendor-DNlD2-cz.js +1 -0
  53. package/public/assets/detector-registry-Bp8hrLua.js +2 -0
  54. package/public/assets/downsample-WmNM1qc9.js +1 -0
  55. package/public/assets/finding-anchor-Dr40LkS9.js +1 -0
  56. package/public/assets/findings-of-type-BL2u3RGZ.js +1 -0
  57. package/public/assets/impact-sort-mC5ISgX8.js +1 -0
  58. package/public/assets/inbox-KPU1Gppm.js +1 -0
  59. package/public/assets/index-CaRVPvAT.css +2 -0
  60. package/public/assets/index-fDiwfuoO.js +14 -0
  61. package/public/assets/inertValue-L9VxV0VE.js +1 -0
  62. package/public/assets/layout.worker-CLcBHxVp.js +3 -0
  63. package/public/assets/{parser-worker-CA5RxqKc.js → parser-worker-CRIOPMOW.js} +8 -8
  64. package/public/assets/plan-graph-vendor-Ce6d4HU6.js +7 -0
  65. package/public/assets/react-vendor-C6KcUD36.js +9 -0
  66. package/public/assets/select-BHqOqun5.js +1 -0
  67. package/public/assets/toggle-DPeN6aNa.js +1 -0
  68. package/public/assets/toggle-group-Kl3uyHSr.js +1 -0
  69. package/public/assets/usePagedRows-BafD6bEN.js +1 -0
  70. package/public/assets/useRecentFiles-DzJZAoOT.js +3 -0
  71. package/public/assets/useRegisterFieldControl-pQv-ZDoU.js +1 -0
  72. package/public/assets/useScrollLock-BCxn_dMp.js +1 -0
  73. package/public/assets/useSortMode-DjrAN-z4.js +1 -0
  74. package/public/docs/404.html +3 -3
  75. package/public/docs/assets/{app.CnWL6bKV.js → app.1gvzd5Zb.js} +1 -1
  76. package/public/docs/assets/chunks/@localSearchIndexroot.DS2TQP5_.js +1 -0
  77. package/public/docs/assets/chunks/{VPLocalSearchBox.C0IBKeRQ.js → VPLocalSearchBox.CNxT3s0I.js} +1 -1
  78. package/public/docs/assets/chunks/{theme.BH-2HuRf.js → theme.C5lLqUv7.js} +2 -2
  79. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.js +1 -0
  80. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.lean.js +1 -0
  81. package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.js} +1 -1
  82. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.js +1 -0
  83. package/public/docs/assets/{contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.js → contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.js} +1 -1
  84. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.lean.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CHHgywoP.js +1 -0
  86. package/public/docs/assets/{contributor-guide_architecture_detector-contract.md.N4hhenrA.lean.js → contributor-guide_architecture_detector-contract.md.CHHgywoP.lean.js} +1 -1
  87. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.lean.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.js +1 -0
  90. package/public/docs/assets/{contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.lean.js → contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.lean.js} +1 -1
  91. package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.js} +1 -1
  92. package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.lean.js} +1 -1
  93. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.js +1 -0
  94. package/public/docs/assets/{contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js → contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.lean.js} +1 -1
  95. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.K71YB-Rr.js +1 -0
  96. package/public/docs/assets/{contributor-guide_architecture_impact-estimation.md.DfvbqEUu.lean.js → contributor-guide_architecture_impact-estimation.md.K71YB-Rr.lean.js} +1 -1
  97. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.js +1 -0
  98. package/public/docs/assets/{contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.lean.js → contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.lean.js} +1 -1
  99. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.lean.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_overview.md.nxkpOhgf.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.js +1 -0
  103. package/public/docs/assets/{contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js → contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.lean.js} +1 -1
  104. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.js +1 -0
  105. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.js +1 -0
  106. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.lean.js +1 -0
  107. package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.js} +1 -1
  108. package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.js} +1 -1
  109. package/public/docs/assets/{contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js → contributor-guide_architecture_worker-protocol.md.Br6OE7RW.js} +1 -1
  110. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.Br6OE7RW.lean.js +1 -0
  111. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.js +1 -0
  112. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.lean.js → contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.lean.js} +1 -1
  113. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.js} +1 -1
  114. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.js} +2 -2
  115. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.lean.js} +1 -1
  116. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.js +1 -0
  117. package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.lean.js → contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.lean.js} +1 -1
  118. package/public/docs/assets/contributor-guide_contributing.md.DBW4drtZ.js +1 -0
  119. package/public/docs/assets/contributor-guide_development-setup.md.B21ZTDhn.js +15 -0
  120. package/public/docs/assets/contributor-guide_development-setup.md.B21ZTDhn.lean.js +1 -0
  121. package/public/docs/assets/contributor-guide_testing.md.DpkozNoG.js +4 -0
  122. package/public/docs/assets/{contributor-guide_testing.md.By6XGR9a.lean.js → contributor-guide_testing.md.DpkozNoG.lean.js} +1 -1
  123. package/public/docs/assets/{index.md.CHJVslga.js → index.md.lMm7SUMz.js} +1 -1
  124. package/public/docs/assets/{index.md.CHJVslga.lean.js → index.md.lMm7SUMz.lean.js} +1 -1
  125. package/public/docs/assets/{style.CyK9Ss73.css → style.Cfxqlj0y.css} +1 -1
  126. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.C0pcQoNc.js +29 -0
  127. package/public/docs/assets/{user-guide_getting-started_ci-and-automation.md.BEepbDDL.lean.js → user-guide_getting-started_ci-and-automation.md.C0pcQoNc.lean.js} +1 -1
  128. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.js +1 -0
  129. package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.js} +2 -2
  130. package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.lean.js} +1 -1
  131. package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.js → user-guide_mcp-tools.md.49r3L2YE.js} +1 -1
  132. package/public/docs/assets/{user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js → user-guide_mcp-tools_compare-runs.md.BQ_kIptC.js} +11 -6
  133. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.BQ_kIptC.lean.js +1 -0
  134. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.azCZXkH8.js +44 -0
  135. package/public/docs/assets/{user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js → user-guide_mcp-tools_diagnose-run.md.azCZXkH8.lean.js} +1 -1
  136. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.js} +2 -2
  137. package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.lean.js} +1 -1
  138. package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.js} +2 -1
  139. package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.lean.js} +1 -1
  140. package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.js +1 -0
  141. package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.lean.js +1 -0
  142. package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.js +1 -0
  143. package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.lean.js +1 -0
  144. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +6 -6
  145. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +6 -6
  146. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +6 -6
  147. package/public/docs/contributor-guide/architecture/board-widgets.html +6 -6
  148. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +6 -6
  149. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +6 -6
  150. package/public/docs/contributor-guide/architecture/detector-contract.html +6 -6
  151. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +6 -6
  152. package/public/docs/contributor-guide/architecture/drill-down.html +6 -6
  153. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +6 -6
  154. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +6 -6
  155. package/public/docs/contributor-guide/architecture/impact-estimation.html +6 -6
  156. package/public/docs/contributor-guide/architecture/index.html +5 -5
  157. package/public/docs/contributor-guide/architecture/overview.html +6 -6
  158. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +5 -5
  159. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +6 -6
  160. package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
  161. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +6 -6
  162. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +5 -5
  163. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +6 -6
  164. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +5 -5
  165. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +6 -6
  166. package/public/docs/contributor-guide/architecture/widget-rendering.html +6 -6
  167. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +6 -6
  168. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +6 -6
  169. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +7 -7
  170. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +6 -6
  171. package/public/docs/contributor-guide/architecture/worker-protocol.html +6 -6
  172. package/public/docs/contributor-guide/contributing.html +6 -6
  173. package/public/docs/contributor-guide/development-setup.html +8 -8
  174. package/public/docs/contributor-guide/testing.html +9 -6
  175. package/public/docs/hashmap.json +1 -1
  176. package/public/docs/index.html +6 -6
  177. package/public/docs/logo-dark.svg +1 -0
  178. package/public/docs/logo-light.svg +1 -0
  179. package/public/docs/tuning-reference/anti-patterns.html +5 -5
  180. package/public/docs/tuning-reference/aqe.html +5 -5
  181. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +5 -5
  182. package/public/docs/tuning-reference/bottleneck-cold-start.html +5 -5
  183. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +5 -5
  184. package/public/docs/tuning-reference/bottleneck-failures.html +5 -5
  185. package/public/docs/tuning-reference/bottleneck-gc.html +5 -5
  186. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +5 -5
  187. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +5 -5
  188. package/public/docs/tuning-reference/bottleneck-retry-waste.html +5 -5
  189. package/public/docs/tuning-reference/bottleneck-shuffle.html +5 -5
  190. package/public/docs/tuning-reference/bottleneck-skew.html +5 -5
  191. package/public/docs/tuning-reference/bottleneck-slow-host.html +5 -5
  192. package/public/docs/tuning-reference/bottleneck-small-files.html +5 -5
  193. package/public/docs/tuning-reference/bottleneck-spill.html +5 -5
  194. package/public/docs/tuning-reference/bottleneck-straggler.html +5 -5
  195. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +5 -5
  196. package/public/docs/tuning-reference/bottleneck-utilization.html +5 -5
  197. package/public/docs/tuning-reference/caching.html +5 -5
  198. package/public/docs/tuning-reference/cluster-config.html +5 -5
  199. package/public/docs/tuning-reference/config.html +5 -5
  200. package/public/docs/tuning-reference/data-formats.html +5 -5
  201. package/public/docs/tuning-reference/index.html +5 -5
  202. package/public/docs/tuning-reference/intro.html +5 -5
  203. package/public/docs/tuning-reference/joins.html +5 -5
  204. package/public/docs/tuning-reference/memory-model.html +5 -5
  205. package/public/docs/tuning-reference/metrics.html +5 -5
  206. package/public/docs/tuning-reference/partitioning.html +5 -5
  207. package/public/docs/tuning-reference/pyspark.html +5 -5
  208. package/public/docs/tuning-reference/shuffle.html +5 -5
  209. package/public/docs/tuning-reference/spark-architecture.html +5 -5
  210. package/public/docs/tuning-reference/table-formats.html +5 -5
  211. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +5 -5
  212. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +5 -5
  213. package/public/docs/user-guide/alternative-log-retrieval.html +5 -5
  214. package/public/docs/user-guide/getting-started/ci-and-automation.html +16 -11
  215. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +6 -6
  216. package/public/docs/user-guide/getting-started/regression-metric-keys.html +5 -5
  217. package/public/docs/user-guide/getting-started/tuning-thresholds.html +7 -7
  218. package/public/docs/user-guide/getting-started.html +5 -5
  219. package/public/docs/user-guide/mcp-tools/compare-runs.html +16 -11
  220. package/public/docs/user-guide/mcp-tools/diagnose-run.html +28 -7
  221. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +6 -6
  222. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +5 -5
  223. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +7 -6
  224. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +5 -5
  225. package/public/docs/user-guide/mcp-tools/get-run-summary.html +5 -5
  226. package/public/docs/user-guide/mcp-tools/list-runs.html +5 -5
  227. package/public/docs/user-guide/mcp-tools.html +6 -6
  228. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +5 -5
  229. package/public/docs/user-guide/run-comparison.html +6 -6
  230. package/public/docs/user-guide/understanding-findings.html +6 -6
  231. package/public/export-template.html +45 -45
  232. package/public/index.html +8 -7
  233. package/public/sample-runs/sample-run.ndjson.gz +0 -0
  234. package/vendor-core/allocation.js +25 -23
  235. package/vendor-core/analyzer.js +85 -58
  236. package/vendor-core/api-merge-writes.js +2 -0
  237. package/vendor-core/aqe-skew.js +347 -0
  238. package/vendor-core/cli/budgets.js +6 -4
  239. package/vendor-core/cli/collect-run-pair.js +89 -0
  240. package/vendor-core/cli/collect-run-worker.js +16 -0
  241. package/vendor-core/cli/collect-run.js +1 -2
  242. package/vendor-core/cli/native-zstd.js +12 -16
  243. package/vendor-core/comparability.js +234 -0
  244. package/vendor-core/comparison-output.js +15 -7
  245. package/vendor-core/comparison-verdict.js +105 -25
  246. package/vendor-core/core-source-hash.txt +1 -1
  247. package/vendor-core/core-time-series.js +0 -1
  248. package/vendor-core/core-usage-locality.js +1 -1
  249. package/vendor-core/detectors.js +1007 -380
  250. package/vendor-core/docs-config.js +0 -4
  251. package/vendor-core/docs-content/detection/cache.md +2 -1
  252. package/vendor-core/docs-content/detection/cfg.md +19 -11
  253. package/vendor-core/docs-content/detection/cold.md +2 -1
  254. package/vendor-core/docs-content/detection/gc.md +7 -3
  255. package/vendor-core/docs-content/detection/host.md +11 -1
  256. package/vendor-core/docs-content/detection/mem.md +17 -6
  257. package/vendor-core/docs-content/detection/part.md +3 -1
  258. package/vendor-core/docs-content/detection/plan.md +55 -13
  259. package/vendor-core/docs-content/detection/shfl.md +11 -5
  260. package/vendor-core/docs-content/detection/skew.md +47 -1
  261. package/vendor-core/docs-content/detection/slow.md +4 -2
  262. package/vendor-core/docs-content/detection/spec.md +6 -1
  263. package/vendor-core/docs-content/detection/spill.md +6 -3
  264. package/vendor-core/docs-content/detection/strag.md +19 -6
  265. package/vendor-core/docs-content/detection/tiny.md +9 -3
  266. package/vendor-core/effective-conf.js +7 -10
  267. package/vendor-core/efficiency-model.js +2 -2
  268. package/vendor-core/etl-phases.js +1 -2
  269. package/vendor-core/event-handlers.js +175 -57
  270. package/vendor-core/event-schemas.js +21 -16
  271. package/vendor-core/evidence-availability.js +9 -2
  272. package/vendor-core/evidence-report.js +118 -35
  273. package/vendor-core/executor-peaks.js +41 -0
  274. package/vendor-core/export-data.js +5 -5
  275. package/vendor-core/finding-identity.js +78 -0
  276. package/vendor-core/finding-presentation.js +116 -33
  277. package/vendor-core/finding-types.js +88 -21
  278. package/vendor-core/format-utils.js +14 -15
  279. package/vendor-core/html-export.js +1 -1
  280. package/vendor-core/impact-band.js +2 -2
  281. package/vendor-core/impact-format.js +47 -19
  282. package/vendor-core/impact-model.js +26 -15
  283. package/vendor-core/job-groups.js +4 -4
  284. package/vendor-core/mcp-server-factory.js +48 -12
  285. package/vendor-core/mcp-tools.js +179 -24
  286. package/vendor-core/median.js +2 -2
  287. package/vendor-core/nested-loop-join.js +73 -0
  288. package/vendor-core/occupancy.js +1 -2
  289. package/vendor-core/parser-worker.js +1 -1
  290. package/vendor-core/plan-graph-model.js +14 -22
  291. package/vendor-core/plan-node-detail.js +2 -3
  292. package/vendor-core/plan-summary.js +24 -19
  293. package/vendor-core/plan-tree-walk.js +6 -8
  294. package/vendor-core/python-stage.js +12 -1
  295. package/vendor-core/recommendation-rollup.js +13 -12
  296. package/vendor-core/redact.js +52 -46
  297. package/vendor-core/run-aggregates.js +6 -8
  298. package/vendor-core/run-comparison.js +226 -66
  299. package/vendor-core/run-identity.js +12 -0
  300. package/vendor-core/run-interpretation.js +21 -0
  301. package/vendor-core/run-metrics.js +2 -2
  302. package/vendor-core/run-output.js +32 -10
  303. package/vendor-core/run-totals.js +12 -0
  304. package/vendor-core/run-verdict.js +76 -3
  305. package/vendor-core/scaling-sim.js +3 -4
  306. package/vendor-core/snappy-block.js +2 -2
  307. package/vendor-core/spark-conf.js +168 -0
  308. package/vendor-core/sql-stages.js +2 -2
  309. package/vendor-core/stage-alignment.js +2 -12
  310. package/vendor-core/stage-identity.js +58 -3
  311. package/vendor-core/stage-label.js +14 -0
  312. package/vendor-core/stage-plan-nodes.js +17 -3
  313. package/vendor-core/stage-quantiles.js +141 -9
  314. package/vendor-core/stage-structure.js +17 -3
  315. package/vendor-core/threshold-overrides.js +9 -3
  316. package/vendor-core/types.js +58 -21
  317. package/vendor-core/wasted-core-hours.js +0 -1
  318. package/vendor-core/write-targets.js +114 -14
  319. package/vendor-core/zstd-worker-client.js +1 -1
  320. package/vendor-core/zstd-worker.js +1 -1
  321. package/public/assets/AutoscalingChurn-CRA2Mb7F.js +0 -1
  322. package/public/assets/CacheUtilization-Bh9OY_V9.js +0 -1
  323. package/public/assets/CachingOpportunity-oCiQwmTJ.js +0 -1
  324. package/public/assets/ColdStart-BJrtjfmn.js +0 -1
  325. package/public/assets/ConfigAudit-DMvKxySP.js +0 -1
  326. package/public/assets/CoreUsageArea-REC2zsKw.js +0 -1
  327. package/public/assets/Dashboard-BHZ76JCu.js +0 -2
  328. package/public/assets/DisclosureContext-OO7X1HAx.js +0 -1
  329. package/public/assets/DuplicatePlanSubtree-BgG-L-cK.js +0 -1
  330. package/public/assets/ExecutorUtilization-D2KhSVbY.js +0 -1
  331. package/public/assets/GcPressure-D1BTE8qh.js +0 -1
  332. package/public/assets/IncompleteRun-DD40xUpy.js +0 -1
  333. package/public/assets/JobFailures-YwNzCF19.js +0 -1
  334. package/public/assets/MemoryUtilization-9b8ee_3_.js +0 -1
  335. package/public/assets/OverBroadcast-CfctS8a8.js +0 -1
  336. package/public/assets/PartitionHint-CUvbnnxy.js +0 -1
  337. package/public/assets/PartitionSizing-VG8kBgG5.js +0 -1
  338. package/public/assets/PlanExplorer-DmubTAOP.js +0 -1
  339. package/public/assets/PlanGraphRoute-DctPZjS0.js +0 -1
  340. package/public/assets/RetryWaste-CnpM8bB7.js +0 -1
  341. package/public/assets/ShuffleIO-D13vGmas.js +0 -1
  342. package/public/assets/Skew-DlagstYP.js +0 -1
  343. package/public/assets/SlowHost-cSkLRZ_e.js +0 -1
  344. package/public/assets/SmallFiles-B-kERMcq.js +0 -1
  345. package/public/assets/SpeculationWaste-DaysAzXO.js +0 -1
  346. package/public/assets/Spill-H44Gz4US.js +0 -1
  347. package/public/assets/StageFailed-DCfii9h_.js +0 -1
  348. package/public/assets/StageFindingGroup-BXJ0Mj5n.js +0 -1
  349. package/public/assets/StageHeader--oN2J-oD.js +0 -1
  350. package/public/assets/StageShape-IZ80CZiJ.js +0 -1
  351. package/public/assets/StageSlowness--BRIaBc1.js +0 -1
  352. package/public/assets/Straggler-Ctq0Df_o.js +0 -1
  353. package/public/assets/TaskFailures-Bks_gqhL.js +0 -1
  354. package/public/assets/TinyTask-BssfzGFB.js +0 -1
  355. package/public/assets/UnderBroadcast-DsScrxc-.js +0 -1
  356. package/public/assets/charts-vendor-0VMaLVLN.js +0 -59
  357. package/public/assets/finding-anchor-BYW1oz5R.js +0 -1
  358. package/public/assets/impact-sort-xzy0g-Vh.js +0 -1
  359. package/public/assets/index-DhpRGMZc.css +0 -2
  360. package/public/assets/index-DwqkFUFk.js +0 -14
  361. package/public/assets/plan-graph-vendor-BYl-AzN5.js +0 -7
  362. package/public/assets/react-vendor-DfpuPQZf.js +0 -9
  363. package/public/assets/select-C8edjYTq.js +0 -1
  364. package/public/assets/usePagedRows-CsuUFeYx.js +0 -1
  365. package/public/assets/useRecentFiles-BvkKo3JN.js +0 -11
  366. package/public/assets/useSortMode-joX71BZN.js +0 -1
  367. package/public/docs/assets/chunks/@localSearchIndexroot.tE5pz54z.js +0 -1
  368. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +0 -1
  369. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js +0 -1
  370. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +0 -1
  371. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.lean.js +0 -1
  372. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.N4hhenrA.js +0 -1
  373. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +0 -1
  374. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +0 -1
  375. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.js +0 -1
  376. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +0 -1
  377. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.DfvbqEUu.js +0 -1
  378. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.js +0 -1
  379. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +0 -1
  380. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +0 -1
  381. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +0 -1
  382. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +0 -1
  383. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +0 -1
  384. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +0 -1
  385. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +0 -1
  386. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +0 -1
  387. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.js +0 -1
  388. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.js +0 -1
  389. package/public/docs/assets/contributor-guide_contributing.md.CQMvGwFt.js +0 -1
  390. package/public/docs/assets/contributor-guide_development-setup.md.DQdzera7.js +0 -15
  391. package/public/docs/assets/contributor-guide_development-setup.md.DQdzera7.lean.js +0 -1
  392. package/public/docs/assets/contributor-guide_testing.md.By6XGR9a.js +0 -1
  393. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.BEepbDDL.js +0 -24
  394. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +0 -1
  395. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js +0 -1
  396. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +0 -23
  397. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +0 -1
  398. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +0 -1
  399. package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.js +0 -1
  400. package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.lean.js +0 -1
  401. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.lean.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.lean.js} +0 -0
  402. /package/public/docs/assets/{contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js → contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.lean.js} +0 -0
  403. /package/public/docs/assets/{contributor-guide_architecture_overview.md.C1sSvm9i.lean.js → contributor-guide_architecture_overview.md.nxkpOhgf.lean.js} +0 -0
  404. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js → contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.lean.js} +0 -0
  405. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.lean.js} +0 -0
  406. /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.lean.js} +0 -0
  407. /package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.lean.js} +0 -0
  408. /package/public/docs/assets/{contributor-guide_contributing.md.CQMvGwFt.lean.js → contributor-guide_contributing.md.DBW4drtZ.lean.js} +0 -0
  409. /package/public/docs/assets/{user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js → user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.lean.js} +0 -0
  410. /package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.lean.js → user-guide_mcp-tools.md.49r3L2YE.lean.js} +0 -0
@@ -91,19 +91,17 @@ const SparkPlanInfoFieldSchema = z
91
91
  }
92
92
  });
93
93
 
94
- // Fully-specified event schemas.
95
-
96
94
  // SparkListenerLogStart case (inline: sets state.pendingSparkVersion).
97
95
  export const LogStartEventSchema = z.object({
98
96
  Event: z.literal('SparkListenerLogStart'),
99
97
  'Spark Version': z.string().optional(),
100
98
  });
101
99
 
102
- // startApplication.
103
100
  export const ApplicationStartEventSchema = z.object({
104
101
  Event: z.literal('SparkListenerApplicationStart'),
105
102
  'App ID': z.string().optional(),
106
103
  'App Name': z.string().optional(),
104
+ 'App Attempt ID': z.string().optional(),
107
105
  Timestamp: z.number().optional(),
108
106
  'Spark Version': z.string().optional(),
109
107
  });
@@ -121,7 +119,6 @@ export const EnvironmentUpdateEventSchema = z.object({
121
119
  ]).optional(),
122
120
  });
123
121
 
124
- // inline ApplicationEnd case.
125
122
  export const ApplicationEndEventSchema = z.object({
126
123
  Event: z.literal('SparkListenerApplicationEnd'),
127
124
  Timestamp: z.number().optional(),
@@ -150,8 +147,6 @@ export const ExecutorRemovedEventSchema = z.object({
150
147
  'Removed Reason': z.string().optional(),
151
148
  });
152
149
 
153
- // Schemas transcribed from their handlers.
154
-
155
150
  // startJob. 'spark.sql.execution.id' is read then parseInt'd, which coerces to string internally,
156
151
  // so it tolerates the raw value being a number; widened to string|number (some logs emit a JSON number).
157
152
  export const JobStartEventSchema = z.object({
@@ -164,7 +159,6 @@ export const JobStartEventSchema = z.object({
164
159
  }).optional(),
165
160
  });
166
161
 
167
- // endJob.
168
162
  export const JobEndEventSchema = z.object({
169
163
  Event: z.literal('SparkListenerJobEnd'),
170
164
  'Job ID': z.number(),
@@ -230,7 +224,6 @@ export const BlockUpdatedEventSchema = z.object({
230
224
  }),
231
225
  });
232
226
 
233
- // inline StageCompleted case.
234
227
  export const StageCompletedEventSchema = z.object({
235
228
  Event: z.literal('SparkListenerStageCompleted'),
236
229
  'Stage Info': z.object({
@@ -239,10 +232,17 @@ export const StageCompletedEventSchema = z.object({
239
232
  'Submission Time': z.number().optional(),
240
233
  'Completion Time': z.number().optional(),
241
234
  'Failure Reason': z.string().optional(),
235
+ // The stage's accumulators, read only for SQL metrics (Metadata "sql"): their Value is the
236
+ // accumulator's running total, which Spark writes as a decimal string. Value stays unknown
237
+ // because internal accumulators write arrays and other shapes there.
238
+ Accumulables: z.array(z.object({
239
+ ID: z.number(),
240
+ Value: z.unknown().optional(),
241
+ Metadata: z.string().optional(),
242
+ })).optional(),
242
243
  }),
243
244
  });
244
245
 
245
- // recordStageExecutorMetrics.
246
246
  export const StageExecutorMetricsEventSchema = z.object({
247
247
  Event: z.literal('SparkListenerStageExecutorMetrics'),
248
248
  'Stage ID': z.number(),
@@ -250,9 +250,8 @@ export const StageExecutorMetricsEventSchema = z.object({
250
250
  'Executor Metrics': z.record(z.string(), z.number()).optional(),
251
251
  });
252
252
 
253
- const MAX_ACCUMULABLES_PER_TASK = 10_000;
253
+ export const MAX_ACCUMULABLES_PER_TASK = 10_000;
254
254
 
255
- // accumulateTask.
256
255
  export const TaskEndEventSchema = z.object({
257
256
  Event: z.literal('SparkListenerTaskEnd'),
258
257
  'Stage ID': z.number(),
@@ -286,12 +285,16 @@ export const TaskEndEventSchema = z.object({
286
285
  // Bounded well above any real per-task metric count: caps how much a crafted TaskEnd can grow
287
286
  // taskAccumStages, which is never pruned for the life of the parse. Only ID is read. Update and
288
287
  // Value stay undeclared: Spark writes some as JSON arrays (internal.metrics.updatedBlockStatuses),
289
- // and a string|number check on them rejected the whole TaskEnd, dropping the task from its
290
- // stage's stats. They were also ~15% of TaskEnd validation time.
288
+ // and a string|number check on them would reject the whole TaskEnd, dropping the task from its
289
+ // stage's stats. Validating them would also add ~15% to TaskEnd validation time.
291
290
  Accumulables: z.array(z.object({
292
291
  ID: z.number(),
293
292
  })).max(MAX_ACCUMULABLES_PER_TASK).optional(),
294
293
  }).optional(),
294
+ // Spark 3.0+: the executor's metric peaks as of this task's end, one number per ExecutorMetricType
295
+ // name (the same names as SparkListenerStageExecutorMetrics). Values are read by name and kept
296
+ // only when numeric, so a stray value never drops the task.
297
+ 'Task Executor Metrics': z.record(z.string(), z.unknown()).optional(),
295
298
  'Task Metrics': z.object({
296
299
  'Peak Execution Memory': z.number().optional(),
297
300
  'JVM GC Time': z.number().optional(),
@@ -303,12 +306,14 @@ export const TaskEndEventSchema = z.object({
303
306
  'Remote Bytes Read': z.number().optional(),
304
307
  'Local Bytes Read': z.number().optional(),
305
308
  'Fetch Wait Time': z.number().optional(),
309
+ 'Total Records Read': z.number().optional(),
306
310
  }).optional(),
307
311
  'Shuffle Write Metrics': z.object({
308
312
  'Shuffle Bytes Written': z.number().optional(),
309
313
  }).optional(),
310
314
  'Input Metrics': z.object({
311
315
  'Bytes Read': z.number().optional(),
316
+ 'Records Read': z.number().optional(),
312
317
  }).optional(),
313
318
  'Output Metrics': z.object({
314
319
  'Bytes Written': z.number().optional(),
@@ -317,7 +322,6 @@ export const TaskEndEventSchema = z.object({
317
322
  }).optional(),
318
323
  });
319
324
 
320
- // startSqlExecution.
321
325
  export const SqlExecutionStartEventSchema = z.object({
322
326
  Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLExecutionStart'),
323
327
  executionId: z.number(),
@@ -326,6 +330,9 @@ export const SqlExecutionStartEventSchema = z.object({
326
330
  physicalPlanDescription: z.string().optional(),
327
331
  time: z.number(),
328
332
  sparkPlanInfo: SparkPlanInfoFieldSchema,
333
+ // Session settings of this execution that differ from the SparkContext's (Spark 3.3+; redacted
334
+ // values are masked by Spark). Anything but a string-to-string map is dropped, not a parse error.
335
+ modifiedConfigs: z.record(z.string(), z.string()).optional().catch(undefined),
329
336
  });
330
337
 
331
338
  // applyAdaptiveExecutionUpdate: AQE re-plans mid-query and re-emits sparkPlanInfo for the same
@@ -337,14 +344,12 @@ export const SqlAdaptiveExecutionUpdateEventSchema = z.object({
337
344
  sparkPlanInfo: SparkPlanInfoFieldSchema,
338
345
  });
339
346
 
340
- // endSqlExecution.
341
347
  export const SqlExecutionEndEventSchema = z.object({
342
348
  Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLExecutionEnd'),
343
349
  executionId: z.number(),
344
350
  time: z.number(),
345
351
  });
346
352
 
347
- // applyDriverAccumUpdates.
348
353
  export const DriverAccumUpdatesEventSchema = z.object({
349
354
  Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerDriverAccumUpdates'),
350
355
  executionId: z.number(),
@@ -6,7 +6,7 @@ export const EVIDENCE_AVAILABILITY_SCHEMA_VERSION = 1;
6
6
 
7
7
  const SUMMARIES = {
8
8
  observed: 'Observed in this event log.',
9
- explicitlyDisabled: 'Explicitly disabled in this event log.',
9
+ explicitlyDisabled: 'Disabled in this event log, by its configuration or by Spark\'s default.',
10
10
  noObservedExecutorMetrics: 'Not emitted by this event log.',
11
11
  noObservedStageSubmission: 'Not emitted by this event log.',
12
12
  noRddStorageSnapshot: 'Stages were submitted without RDD storage snapshots.',
@@ -66,6 +66,13 @@ function absent(key , reasonCode , trustworthy
66
66
  return trustworthy ? entry(key, reasonCode === 'noSqlExecution' ? 'notApplicable' : 'notEmitted', reasonCode) : entry(key, 'unknown', 'parseIncomplete');
67
67
  }
68
68
 
69
+ // The run's effective spark.eventLog.logStageExecutorMetrics: the parser's resolution of it (the
70
+ // logged value, else Spark's default of off) when it recorded one, else the logged value alone.
71
+ function stageExecutorMetricsLogging(app ) {
72
+ const resolved = app?.resources?.stageExecutorMetricsLogging;
73
+ return resolved != null ? resolved : app?.config?.['spark.eventLog.logStageExecutorMetrics'];
74
+ }
75
+
69
76
  export function deriveEvidenceAvailability(appModel , { skippedLines = 0 } = {}) {
70
77
  const app = appModel?.app;
71
78
  const inputs = app?.evidenceInputs ?? {};
@@ -80,7 +87,7 @@ export function deriveEvidenceAvailability(appModel , { skippedLines =
80
87
 
81
88
  const executorMetrics = metricRows > 0
82
89
  ? observed('executorMetrics', 'executorMetricRows', metricRows)
83
- : configIs(app?.config?.['spark.eventLog.logStageExecutorMetrics'], 'false')
90
+ : configIs(stageExecutorMetricsLogging(app), 'false')
84
91
  ? entry('executorMetrics', 'disabled', 'explicitlyDisabled')
85
92
  : absent('executorMetrics', 'noObservedExecutorMetrics', trustworthy);
86
93
  const rddStorageSnapshots = snapshots > 0
@@ -14,15 +14,16 @@ import { findingName, titleCase } from './finding-names.js';
14
14
  import { redactReport, redactRunModel } from './redact.js';
15
15
  import { formatTaskFailureHeadline, } from './task-failure.js';
16
16
  import { findingActionLabel } from './finding-action-label.js';
17
+ import { findingKeyer } from './run-comparison.js';
17
18
  import { matchesFindingFilterCriteria, singleStageId } from './finding-filter-predicate.js';
18
19
  import { buildRecommendationRollup, isEligible, isRealFinding, rankFindings, resourceGroupTotal, } from './recommendation-rollup.js';
19
20
  import { checkCoverage, isCleanRun } from './check-coverage.js';
20
- import { buildRunVerdict, stepCopyRecommendation, stepCopyText, } from './run-verdict.js';
21
+ import { buildRunVerdict, stepCopyRecommendation, stepCopyText, } from './run-verdict.js';
21
22
  import {
22
- estimateProvenance, impactEstimateFigure, impactFigure, rawWasteMeaning, savingsMeaning,
23
+ estimateProvenance, impactEstimateFigure, impactFigure, savingsMeaning,
23
24
  } from './impact-format.js';
24
25
  import { computeRunShape, } from './run-shape.js';
25
- import { extractWriteTargets, } from './write-targets.js';
26
+ import { extractWriteTargets, isPlainWrite, } from './write-targets.js';
26
27
  import { detectorInfoByType } from './detector-docs.js';
27
28
 
28
29
 
@@ -41,7 +42,11 @@ export const EVIDENCE_SCHEMA_VERSION = 5;
41
42
  // configAudit's current setting, incompleteRun's 'missing') carry theirs in `valueText` instead,
42
43
  // present only on those rows.
43
44
 
44
-
45
+
46
+
47
+
48
+
49
+
45
50
 
46
51
 
47
52
 
@@ -56,6 +61,9 @@ export const EVIDENCE_SCHEMA_VERSION = 5;
56
61
 
57
62
 
58
63
 
64
+
65
+
66
+
59
67
 
60
68
 
61
69
 
@@ -128,12 +136,29 @@ export const EVIDENCE_SCHEMA_VERSION = 5;
128
136
 
129
137
 
130
138
 
139
+ // One cost-lane entry: a finding ranked by the measured busy core time its fix removes.
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
131
152
  // The dashboard's run verdict (run-verdict.ts): title, summary sentences, the first steps in the
132
- // same order, how many more places the full list holds, and the "Copy next steps" text.
153
+ // same order, how many more places the full list holds, and the "Copy next steps" text. `steps`
154
+ // rank by wall clock; `costSteps` rank by measured busy core time and list only findings that
155
+ // carry one; `idle` is the run's allocated core time that ran no task.
133
156
 
134
157
 
135
158
 
136
159
 
160
+
161
+
137
162
 
138
163
 
139
164
 
@@ -180,17 +205,17 @@ export const EVIDENCE_SCHEMA_VERSION = 5;
180
205
  // module (stageShape's totalCores, utilization's unrounded fraction) is left off both, so it never
181
206
  // reaches the report; adding, renaming or dropping a key here changes the report contract.
182
207
  const EVIDENCE_KEYS = {
183
- skew: ['origin'],
208
+ skew: ['origin', 'aqeSkew', 'cause', 'dataRatio'],
184
209
  stageShape: ['rule'],
185
210
  shuffle: ['partitions'],
186
- partitionSizing: ['rule', 'origin', 'partitions'],
211
+ partitionSizing: ['rule', 'origin', 'aqeSkew', 'partitions'],
187
212
  spill: ['spillMagnitude', 'reads'],
188
213
  gc: ['direction'],
189
- slowHost: ['variant', 'host', 'hostTaskShare', 'hostMeanMs', 'dimension', 'executorId', 'execMaxValue'],
214
+ slowHost: ['variant', 'host', 'hostTaskShare', 'hostMeanMs', 'dimension', 'executorId', 'execMaxValue', 'executorCount', 'taskCount'],
190
215
  stageSlowness: ['reads'],
191
216
  stageFailed: ['variant', 'numTasks', 'memoryBytesSpilled', 'failedTaskDetails'],
192
217
  failures: ['failedTasks', 'dominantReason', 'dominantError', 'failureGroups', 'otherFailedTasks'],
193
- straggler: ['unit', 'speculativeTasks', 'stragglerCount', 'origin'],
218
+ straggler: ['unit', 'speculativeTasks', 'stragglerCount', 'origin', 'aqeSkew', 'cause', 'causeSharePct', 'host', 'hostTasks', 'cpuPct'],
194
219
  speculationWaste: [],
195
220
  retryWaste: ['numTasks', 'memoryBytesSpilled', 'retriedTaskDetails'],
196
221
  tinyTask: ['reads'],
@@ -211,7 +236,9 @@ const EVIDENCE_KEYS = {
211
236
  'groupIndex',
212
237
  ],
213
238
  smallFiles: ['executionId', 'stageIds', 'fileCount', 'direction', 'nodeName'],
214
- underBroadcast: ['executionId', 'stageIds', 'largerSideBytes', 'broadcastThreshold'],
239
+ pythonUdf: ['executionId', 'stageIds', 'dataSentBytes', 'dataReturnedBytes', 'stageDurationMs'],
240
+ nestedLoopJoin: ['executionId', 'stageIds', 'nodeName', 'joinType', 'condition', 'outputRows', 'leftRows', 'rightRows'],
241
+ underBroadcast: ['executionId', 'stageIds', 'largerSideBytes', 'joinType', 'buildSide', 'broadcastThreshold'],
215
242
  overBroadcast: ['executionId', 'stageIds', 'broadcastThreshold'],
216
243
  } ;
217
244
 
@@ -233,11 +260,12 @@ function projectEvidence(f ) {
233
260
  return evidence;
234
261
  }
235
262
 
236
- function findingRow(f ) {
263
+ function findingRow(f , key ) {
237
264
  // Cast: projectEvidence's keys come from EVIDENCE_KEYS[f.type], so `evidence` is that type's
238
265
  // FindingEvidenceMap entry, which TypeScript can't correlate with `type` on its own.
239
266
  const row = {
240
267
  id: f.id ?? null,
268
+ key,
241
269
  type: f.type,
242
270
  name: titleCase(findingName(f.type)),
243
271
  tag: typeTag(f.type),
@@ -262,6 +290,8 @@ function findingRow(f ) {
262
290
  row.impact = figure.text;
263
291
  row.impactMeaning = figure.meaning;
264
292
  }
293
+ const provenance = estimateProvenance(f);
294
+ if (provenance) row.estimateProvenance = provenance;
265
295
  if (f.tunedThresholds != null) row.tunedThresholds = f.tunedThresholds;
266
296
  return row;
267
297
  }
@@ -284,8 +314,8 @@ function sortFindings(rows ) {
284
314
  // file's isEligible call omits FixTheseFirst's REGISTRY check: every finding here already came out
285
315
  // of analyze()/auditConfig() so it's a known type, and REGISTRY (.tsx) isn't importable here anyway.
286
316
 
287
- // The impact-ranked "highest-leverage fix" rollup, ported from FixTheseFirst.tsx so CLI/MCP/download
288
- // get the same ranking. buildRecommendationRollup already returns groups in the correct cross-group
317
+ // The impact-ranked "highest-leverage fix" rollup, so CLI, MCP and download share the dashboard's
318
+ // ranking. buildRecommendationRollup already returns groups in the correct cross-group
289
319
  // order, so this only maps each group to its JSON row without re-sorting.
290
320
  function buildRecommendations(
291
321
  findings ,
@@ -324,7 +354,7 @@ function buildRecommendations(
324
354
  unit: group.unit,
325
355
  total: group.total,
326
356
  impact: shown,
327
- impactMeaning: shown ? rawWasteMeaning(representative.impactEstimate?.rawWaste) : null,
357
+ impactMeaning: shown ? savingsMeaning(representative) : null,
328
358
  };
329
359
  }
330
360
  return {
@@ -368,6 +398,26 @@ function buildCheckLists(
368
398
  return { cleanChecks, notRunChecks };
369
399
  }
370
400
 
401
+ // A plain write with no output row count (no node metric, and its root execution's stages report no
402
+ // rows written or the root holds another write) is not a check the log could run, so a consumer that
403
+ // compares row counts sees it listed here instead of reading the null as a match.
404
+ const WRITE_FIGURES_CHECK = 'writeOutputRows';
405
+ const LISTED_WRITES = 5;
406
+
407
+ function notRunWriteFigures(report ) {
408
+ const missing = report.writes.filter((w) => isPlainWrite(w) && w.outputRows === null);
409
+ if (missing.length === 0) return null;
410
+ const named = missing.slice(0, LISTED_WRITES)
411
+ .map((w) => `execution ${w.sqlExecutionId} ${w.command}`).join(', ');
412
+ const more = missing.length > LISTED_WRITES ? ` and ${missing.length - LISTED_WRITES} more` : '';
413
+ return {
414
+ type: WRITE_FIGURES_CHECK,
415
+ tag: 'INCMP',
416
+ thresholdSummary: 'an output row count for every plain write, from its node metric or from the stages of its root execution',
417
+ reason: `${missing.length} write${missing.length === 1 ? ' has' : 's have'} no output row count (${named}${more}): the write node has no row metric and the stages under its root execution report no rows written, or that root holds more than one write.`,
418
+ };
419
+ }
420
+
371
421
  function countByImpactBand(findings ) {
372
422
  const counts = { critical: 0, warning: 0, info: 0 };
373
423
  for (const f of findings) if (f.impactBand in counts) counts[f.impactBand ] += 1;
@@ -394,6 +444,16 @@ function verdictJson(model ) {
394
444
  text: stepCopyText(step.lead, recommendation, step.stageId),
395
445
  };
396
446
  }),
447
+ costSteps: model.costLane.map(({ finding, coreTimeMs, figure }) => ({
448
+ stageId: singleStageId(finding),
449
+ type: finding.type,
450
+ tag: typeTag(finding.type),
451
+ leadFindingId: finding.id ?? null,
452
+ actionLabel: findingActionLabel(finding),
453
+ coreTimeMs,
454
+ coreTime: figure,
455
+ })),
456
+ idle: model.idle,
397
457
  remainingPlaces: model.remaining,
398
458
  copyText: model.copyText,
399
459
  };
@@ -401,17 +461,17 @@ function verdictJson(model ) {
401
461
 
402
462
  // Keyed by appModel object identity: mcp-tools.ts caches one fixed appModel per runId (never
403
463
  // mutated), so re-running analyze()/auditConfig() reproduces the same catalog. getFindingEvidence
404
- // calls buildEvidenceReport once per drill-down; without this, N lookups meant N detector re-runs.
464
+ // calls buildEvidenceReport once per drill-down; the cache spares each lookup a detector re-run.
405
465
  // A WeakMap needs no invalidation: once mcp-tools.ts evicts the appModel, this entry is collectible.
406
466
  // Each cache is split first by the overrides object the report ran under (one fixed, frozen object
407
467
  // per CLI invocation or MCP server process; DEFAULT_THRESHOLDS for the specification's).
408
-
468
+
409
469
  const DEFAULT_THRESHOLDS = {};
410
470
  const jsonCache = new WeakMap();
411
471
  // The redacted report, keyed by the unredacted appModel it was built from.
412
472
  const redactedJsonCache = new WeakMap();
413
473
 
414
- function cacheFor (cache , thresholds ) {
474
+ export function cacheFor (cache , thresholds ) {
415
475
  const key = thresholds ?? DEFAULT_THRESHOLDS;
416
476
  let byModel = cache.get(key);
417
477
  if (!byModel) {
@@ -435,7 +495,7 @@ export function runFindings(appModel , thresholds
435
495
  jobs ?? new Map(), sql ?? new Map(),
436
496
  runAggregates ?? null, { thresholds },
437
497
  );
438
- const result = { catalog, config: auditConfig(app) };
498
+ const result = { catalog, config: auditConfig(app, stages) };
439
499
  cache.set(appModel, result);
440
500
  return result;
441
501
  }
@@ -449,8 +509,8 @@ function buildRedactedJson(appModel , thresholds
449
509
  if (cached) return cached;
450
510
  const { catalog, config } = runFindings(appModel, thresholds);
451
511
  const run = redactRunModel(appModel, catalog, config);
452
- // redactReport stays as a last pass: idempotent over pseudonyms, and it covers the report's own
453
- // structured fields (summary.app.id) the same way it always has.
512
+ // redactReport runs as a last pass: idempotent over pseudonyms, and it covers the report's own
513
+ // structured fields (summary.app.id).
454
514
  const result = redactReport(buildJson(run.appModel, thresholds, { catalog: run.catalog, config: run.configFindings }));
455
515
  cache.set(appModel, result);
456
516
  return result;
@@ -465,9 +525,15 @@ function buildJson(
465
525
  const { app, stages, executors, sql, jobs, evidenceAvailability } = appModel;
466
526
  const { catalog, config } = findings ?? runFindings(appModel, thresholds);
467
527
  const allFindings = [...catalog, ...config];
468
- const rows = sortFindings(allFindings.map(findingRow));
528
+ const keyOf = findingKeyer({ stages: stages ?? new Map(), sql: sql ?? new Map() });
529
+ const rows = sortFindings(allFindings.map((f) => findingRow(f, keyOf(f))));
469
530
  const recommendations = buildRecommendations(allFindings, stages ?? new Map());
470
531
  const { cleanChecks, notRunChecks } = buildCheckLists(allFindings, stages ?? new Map(), thresholds);
532
+ const writeTargets = extractWriteTargets(sql ?? new Map(), {
533
+ stages, skippedLines: appModel.skippedLines, unreadableSqlExecutions: appModel.unreadableSqlExecutions,
534
+ });
535
+ const unfiguredWrites = notRunWriteFigures(writeTargets);
536
+ if (unfiguredWrites) notRunChecks.push(unfiguredWrites);
471
537
  const tuned = tunedDetectors(thresholds);
472
538
  const actionable = allFindings.filter(isEligible);
473
539
  const fullModel = {
@@ -480,8 +546,6 @@ function buildJson(
480
546
  schemaVersion: EVIDENCE_SCHEMA_VERSION,
481
547
  summary: {
482
548
  app: {
483
- // `?? null`, not `?? undefined`: JSON.stringify drops undefined keys but keeps null, and
484
- // sparkVersion's null is a deliberate "unknown/absent" sentinel; undefined would drop these.
485
549
  id: app?.id ?? null,
486
550
  name: app?.name ?? null,
487
551
  sparkVersion: app?.sparkVersion ?? null,
@@ -509,9 +573,7 @@ function buildJson(
509
573
  // Order follows DETECTORS (stable) => byte-stable serialization.
510
574
  detectors: tunedDetectorCatalog(thresholds),
511
575
  findings: rows,
512
- writeTargets: extractWriteTargets(sql ?? new Map(), {
513
- skippedLines: appModel.skippedLines, unreadableSqlExecutions: appModel.unreadableSqlExecutions,
514
- }),
576
+ writeTargets,
515
577
  recommendations,
516
578
  cleanChecks,
517
579
  notRunChecks,
@@ -592,8 +654,9 @@ function renderRunShape(shape ) {
592
654
  function renderVerdict(verdict ) {
593
655
  const lines = ['## Verdict', '', verdict.title];
594
656
  if (verdict.summary.length > 0) lines.push('', verdict.summary.join(' '));
657
+ if (verdict.idle) lines.push('', verdict.idle.text);
595
658
  if (verdict.steps.length > 0) {
596
- lines.push('');
659
+ lines.push('', 'By wall clock (run time):', '');
597
660
  verdict.steps.forEach((step, i) => {
598
661
  lines.push(`${i + 1}. [${step.tag}] ${step.text}`);
599
662
  if (step.relatedTypes.length > 0) {
@@ -606,6 +669,13 @@ function renderVerdict(verdict ) {
606
669
  lines.push('', `${places} to look at in the Findings section below.`);
607
670
  }
608
671
  }
672
+ if (verdict.costSteps.length > 0) {
673
+ lines.push('', 'By core time (measured busy core time a fix removes):', '');
674
+ verdict.costSteps.forEach((step, i) => {
675
+ const where = step.stageId != null ? ` in Stage ${step.stageId}` : '';
676
+ lines.push(`${i + 1}. [${step.tag}] ${step.actionLabel}${where}: ${step.coreTime} of core time.`);
677
+ });
678
+ }
609
679
  lines.push('');
610
680
  return lines;
611
681
  }
@@ -713,6 +783,10 @@ function renderMarkdown(json , incomplete ) {
713
783
 
714
784
 
715
785
 
786
+
787
+
788
+
789
+
716
790
 
717
791
 
718
792
  // CLI/MCP-facing filter over FindingRow, delegating to the shared core predicate that also backs
@@ -721,21 +795,24 @@ function matchesFindingsFilter(row , filter )
721
795
  // A sql-scope finding carries its stages in evidence.stageIds, not a stageId column: it matches
722
796
  // the one stage it touches, as the dashboard's Stage details lists it.
723
797
  const stageIds = 'stageIds' in row.evidence ? row.evidence.stageIds : null;
798
+ if (filter.minBand && (IMPACT_BAND_ORDER[row.impactBand] ?? 9) > IMPACT_BAND_ORDER[filter.minBand]) return false;
724
799
  return matchesFindingFilterCriteria({ ...row, stageId: singleStageId({ stageId: row.stageId, stageIds }) }, filter);
725
800
  }
726
801
 
727
- /** Build a FindingsFilter from the three optional CLI/MCP filter dimensions, or undefined when
728
- * none were passed (the "is anything set" gate before calling buildEvidenceReport). */
802
+ /** Build a FindingsFilter from the optional CLI/MCP filter dimensions, or undefined when none were
803
+ * passed (the "is anything set" gate before calling buildEvidenceReport). */
729
804
  export function toFindingsFilter(
730
- impactBand , type , stageId ,
805
+ impactBand , type , stageId , minBand , maxFindings ,
731
806
  ) {
732
- return (impactBand || type || stageId !== undefined) ? { impactBand, type, stageId } : undefined;
807
+ return (impactBand || type || stageId !== undefined || minBand || maxFindings !== undefined)
808
+ ? { impactBand, type, stageId, minBand, maxFindings }
809
+ : undefined;
733
810
  }
734
811
 
735
812
  /**
736
813
  * Build a portable evidence report from an appModel.
737
814
  * @param opts redact=true pseudonymizes app ids / hosts; markdown=false skips the Markdown string;
738
- * findingsFilter narrows json.findings (and the Markdown Findings section) only, summary,
815
+ * findingsFilter (including its minBand and maxFindings limits) narrows json.findings (and the Markdown Findings section) only, summary,
739
816
  * recommendations, cleanChecks and notRunChecks stay computed from the full set, so a narrow filter never
740
817
  * hides that other checks passed or other fixes exist. thresholds runs the detectors with a user's
741
818
  * validated overrides (CLI/MCP only) and labels whatever they changed.
@@ -745,12 +822,18 @@ export function buildEvidenceReport(
745
822
  { redact = false, markdown: computeMarkdown = true, findingsFilter, thresholds }
746
823
 
747
824
  = {},
748
- ) {
825
+ ) {
749
826
  let json = redact ? buildRedactedJson(appModel, thresholds) : buildJson(appModel, thresholds);
750
827
  const incomplete = json.findings.some((row) => row.type === 'incompleteRun');
751
828
  // Filter after redact, not before: redaction only replaces string values on surviving rows,
752
829
  // never adds/removes rows, so the two orderings produce identical final content.
753
- if (findingsFilter) json = { ...json, findings: json.findings.filter((row) => matchesFindingsFilter(row, findingsFilter)) };
830
+ // `findingsMatched`: the rows that passed every filter but `maxFindings`, so a caller can say how many that limit left out.
831
+ let findingsMatched = json.findings.length;
832
+ if (findingsFilter) {
833
+ const kept = json.findings.filter((row) => matchesFindingsFilter(row, findingsFilter));
834
+ findingsMatched = kept.length;
835
+ json = { ...json, findings: findingsFilter.maxFindings !== undefined ? kept.slice(0, findingsFilter.maxFindings) : kept };
836
+ }
754
837
  const markdown = computeMarkdown ? renderMarkdown(json, incomplete) : '';
755
- return { markdown, json };
838
+ return { markdown, json, findingsMatched };
756
839
  }
@@ -0,0 +1,41 @@
1
+ // Per-executor JVM heap peaks from the two places a Spark event log can carry them: the executor
2
+ // metrics every TaskEnd reports (Spark 3.0+, always logged) and SparkListenerStageExecutorMetrics
3
+ // rows (only with spark.eventLog.logStageExecutorMetrics=true). Spark's own AppStatusListener
4
+ // folds the TaskEnd values into executor peak memory
5
+ // (https://github.com/apache/spark/blob/v4.0.0/core/src/main/scala/org/apache/spark/status/AppStatusListener.scala).
6
+
7
+ /** The id Spark gives the driver's own metrics row; the driver's heap is sized by spark.driver.memory. */
8
+ export const DRIVER_EXECUTOR_ID = 'driver';
9
+
10
+
11
+
12
+
13
+
14
+
15
+ // One analyze() context is asked once per stage detector call: fold it once.
16
+ const cache = new WeakMap ();
17
+
18
+ /** Executor id to its largest sampled JVMHeapMemory in bytes. Spark polls executor metrics at
19
+ * heartbeat (spark.executor.metrics.pollingInterval defaults to 0), so a peak is a lower bound.
20
+ * JVMHeapMemory is `MemoryMXBean.getHeapMemoryUsage().getUsed()`, which counts uncollected garbage.
21
+ * An executor whose every sample is zero (local mode reports zeros) is left out: no measurement. */
22
+ export function executorHeapPeaks(source ) {
23
+ const known = cache.get(source);
24
+ if (known) return known;
25
+ const peaks = new Map ();
26
+ const take = (executorId , heap ) => {
27
+ // The driver's heap is sized by spark.driver.memory, not the executor setting.
28
+ if (executorId === DRIVER_EXECUTOR_ID) return;
29
+ if (typeof heap === 'number' && heap > (peaks.get(executorId) ?? 0)) peaks.set(executorId, heap);
30
+ };
31
+ for (const [executorId, metrics] of Object.entries(source.runAggregates?.executorPeakMetrics ?? {})) {
32
+ take(executorId, metrics?.jvmHeapMemory);
33
+ }
34
+ for (const stage of source.stages.values()) {
35
+ const rows = stage.executorMetrics;
36
+ if (!(rows instanceof Map)) continue;
37
+ for (const [executorId, metrics] of rows) take(executorId, metrics?.jvmHeapMemory);
38
+ }
39
+ cache.set(source, peaks);
40
+ return peaks;
41
+ }
@@ -4,11 +4,11 @@
4
4
 
5
5
 
6
6
 
7
- /** Version 2 carries the interpretation layer (`interpretation`) and `provenance`. Version 3 types
8
- * findings per detector: a text figure (stageFailed, configAudit, incompleteRun) moved from `value`
9
- * to `valueText`, and Plan Advisor findings always carry `planNodeIds`. The export bundle renders
10
- * only the version it was built for and refuses any other, since an older payload lacks the
11
- * conclusions it would otherwise have to derive, or carries them in fields this bundle no longer reads. */
7
+ /** The export payload format. It carries the interpretation layer (`interpretation`) and `provenance`,
8
+ * and types findings per detector: a text figure (stageFailed, configAudit, incompleteRun) is in
9
+ * `valueText`, and Plan Advisor findings always carry `planNodeIds`. The export bundle renders only
10
+ * this version and refuses any other: a payload of another version lacks the conclusions the bundle
11
+ * would otherwise have to derive, or carries them in fields it does not read. */
12
12
  export const EXPORT_DATA_SCHEMA_VERSION = 3;
13
13
 
14
14
  /** @sparkforensics/core's own version (packages/core/package.json, which a test keeps in sync).