sparkforensics-server 0.5.0 → 0.5.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.
- package/package.json +3 -3
- package/public/assets/{AutoscalingChurn-9Zo6IMGS.js → AutoscalingChurn-8t3U3isq.js} +1 -1
- package/public/assets/{CacheUtilization-MBDfMsKC.js → CacheUtilization-CJs9tTum.js} +1 -1
- package/public/assets/CachingOpportunity-D1NcHs0V.js +1 -0
- package/public/assets/{ColdStart-BtS12b6_.js → ColdStart-CdRJ9gD8.js} +1 -1
- package/public/assets/{ConfigAudit-DNPV6EQg.js → ConfigAudit-B5Gbe5js.js} +1 -1
- package/public/assets/CoreUsageArea-a--NTRGY.js +1 -0
- package/public/assets/{Dashboard-CC_BXrNy.js → Dashboard-C1hNeX49.js} +2 -2
- package/public/assets/DisclosureContext-ZpE6CkHx.js +1 -0
- package/public/assets/{DuplicatePlanSubtree-vN3zqRsi.js → DuplicatePlanSubtree-dobXdz7E.js} +1 -1
- package/public/assets/{ExecutorUtilization-CZObSLIj.js → ExecutorUtilization-D9grwi7t.js} +1 -1
- package/public/assets/{GcPressure-ByJlLOua.js → GcPressure-8-G2ri10.js} +1 -1
- package/public/assets/{IncompleteRun-MzqV4W8y.js → IncompleteRun-B91lky7a.js} +1 -1
- package/public/assets/{JobFailures-LbMvAwAb.js → JobFailures-BsWvBMyJ.js} +1 -1
- package/public/assets/{MemoryUtilization-DH4jYvJK.js → MemoryUtilization-BM5schZI.js} +1 -1
- package/public/assets/{OverBroadcast-euk6HKg4.js → OverBroadcast-DdN8JIJH.js} +1 -1
- package/public/assets/{PartitionHint-DThaMMRT.js → PartitionHint-CUvbnnxy.js} +1 -1
- package/public/assets/{PartitionSizing-yIN-iZzx.js → PartitionSizing-CY5HDYtp.js} +1 -1
- package/public/assets/{PlanExplorer-Dd9WPK4S.js → PlanExplorer-DgHCDBD_.js} +1 -1
- package/public/assets/PlanGraphRoute-DH3KJb8j.js +1 -0
- package/public/assets/{RetryWaste-CXH4D-xH.js → RetryWaste-DOnE9vGF.js} +1 -1
- package/public/assets/{ShuffleIO-CW7U68ph.js → ShuffleIO-MMnDESkF.js} +1 -1
- package/public/assets/{Skew-PL79TppX.js → Skew-BmZZJ5ms.js} +1 -1
- package/public/assets/{SlowHost-GBO73hCi.js → SlowHost-CsJzfEqo.js} +1 -1
- package/public/assets/{SmallFiles-B5cUjEav.js → SmallFiles-BoK3R1Ex.js} +1 -1
- package/public/assets/{SpeculationWaste-mxi3jgb0.js → SpeculationWaste-CnVxB5AJ.js} +1 -1
- package/public/assets/Spill-BhNicyBy.js +1 -0
- package/public/assets/{StageFailed-YvxC11ii.js → StageFailed-mAhHU4l_.js} +1 -1
- package/public/assets/StageFindingGroup-BEtPWcT0.js +1 -0
- package/public/assets/{StageHeader-BMNmUy-s.js → StageHeader-DJkYkrsU.js} +1 -1
- package/public/assets/{StageShape-DO3mQ6NS.js → StageShape-BDxRknLp.js} +1 -1
- package/public/assets/{StageSlowness-SwRxfAEx.js → StageSlowness-BPRw-otN.js} +1 -1
- package/public/assets/{Straggler-DHLTiBY-.js → Straggler-CZCbnOWP.js} +1 -1
- package/public/assets/{TaskFailures-Bj0QxiOc.js → TaskFailures-C57YcLFQ.js} +1 -1
- package/public/assets/{TinyTask-COUQQ5bQ.js → TinyTask-Bi84qU7n.js} +1 -1
- package/public/assets/{UnderBroadcast-RAUbQ6vw.js → UnderBroadcast-DdQV7htE.js} +1 -1
- package/public/assets/{finding-anchor-Dl8mIHiU.js → finding-anchor-Cl6_5hfV.js} +1 -1
- package/public/assets/{impact-sort-Eihua9dY.js → impact-sort-CssZ6MT2.js} +1 -1
- package/public/assets/index-2h_TgUAb.css +2 -0
- package/public/assets/index-B-03mG7K.js +14 -0
- package/public/assets/parser-worker-CA5RxqKc.js +122 -0
- package/public/assets/{plan-graph-vendor-B0sEms9Z.js → plan-graph-vendor-BYl-AzN5.js} +1 -1
- package/public/assets/{react-vendor-BAKt3vny.js → react-vendor-DEdfrRTJ.js} +1 -1
- package/public/assets/{select-DkjFHlXR.js → select-CQjNhprm.js} +1 -1
- package/public/assets/{usePagedRows-Bo-aSztx.js → usePagedRows-Dyl6T8Ok.js} +1 -1
- package/public/assets/useRecentFiles-XZEnLlal.js +11 -0
- package/public/assets/{useSortMode-lDedD5a3.js → useSortMode-Dhvit8dt.js} +1 -1
- package/public/docs/404.html +2 -2
- package/public/docs/assets/{app.yjczoibf.js → app.eYTPqTeq.js} +1 -1
- package/public/docs/assets/chunks/@localSearchIndexroot.u_e5bQz4.js +1 -0
- package/public/docs/assets/chunks/{VPLocalSearchBox.DOBbPfix.js → VPLocalSearchBox.DsZrQKQR.js} +1 -1
- package/public/docs/assets/chunks/{theme.DFcAl8Ws.js → theme.DsTNE2pE.js} +2 -2
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_board-widgets.md.D0Kkk_r5.lean.js → contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_index.md.B9yZieYL.lean.js → contributor-guide_architecture_index.md.tymxf1Fh.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DyCBdjuM.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_state-and-history.md.w4lwybdh.lean.js → contributor-guide_architecture_state-and-history.md.DyCBdjuM.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js +6 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js +1 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.js +4 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.lean.js +1 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.js +4 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.lean.js +1 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.js +1 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.js +1 -0
- package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.js +23 -0
- package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +1 -0
- package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.js +1 -0
- package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js +4 -0
- package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.js +16 -0
- package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js +56 -0
- package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +23 -0
- package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js +26 -0
- package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.js +14 -0
- package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js +20 -0
- package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.js +5 -0
- package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js +15 -0
- package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.js +12 -0
- package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.lean.js +1 -0
- package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +1 -0
- package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +1 -0
- package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.js +1 -0
- package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.lean.js +1 -0
- package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.js +1 -0
- package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.lean.js +1 -0
- package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +30 -0
- package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +30 -0
- package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +30 -0
- package/public/docs/contributor-guide/architecture/board-widgets.html +5 -5
- package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +30 -0
- package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +30 -0
- package/public/docs/contributor-guide/architecture/detector-contract.html +5 -5
- package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
- package/public/docs/contributor-guide/architecture/drill-down.html +5 -5
- package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +30 -0
- package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +30 -0
- package/public/docs/contributor-guide/architecture/impact-estimation.html +5 -5
- package/public/docs/contributor-guide/architecture/index.html +5 -5
- package/public/docs/contributor-guide/architecture/overview.html +5 -5
- package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +30 -0
- package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +30 -0
- package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
- package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +30 -0
- package/public/docs/contributor-guide/architecture/widget-rendering.html +5 -5
- package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +30 -0
- package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +30 -0
- package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +35 -0
- package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +30 -0
- package/public/docs/contributor-guide/architecture/worker-protocol.html +5 -10
- package/public/docs/contributor-guide/contributing.html +4 -4
- package/public/docs/contributor-guide/development-setup.html +4 -4
- package/public/docs/contributor-guide/testing.html +4 -4
- package/public/docs/hashmap.json +1 -1
- package/public/docs/index.html +3 -3
- package/public/docs/tuning-reference/anti-patterns.html +3 -3
- package/public/docs/tuning-reference/aqe.html +3 -3
- package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +3 -3
- package/public/docs/tuning-reference/bottleneck-cold-start.html +3 -3
- package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +3 -3
- package/public/docs/tuning-reference/bottleneck-failures.html +3 -3
- package/public/docs/tuning-reference/bottleneck-gc.html +3 -3
- package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +3 -3
- package/public/docs/tuning-reference/bottleneck-memory-utilization.html +3 -3
- package/public/docs/tuning-reference/bottleneck-retry-waste.html +3 -3
- package/public/docs/tuning-reference/bottleneck-shuffle.html +3 -3
- package/public/docs/tuning-reference/bottleneck-skew.html +3 -3
- package/public/docs/tuning-reference/bottleneck-slow-host.html +3 -3
- package/public/docs/tuning-reference/bottleneck-small-files.html +3 -3
- package/public/docs/tuning-reference/bottleneck-spill.html +3 -3
- package/public/docs/tuning-reference/bottleneck-straggler.html +3 -3
- package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +3 -3
- package/public/docs/tuning-reference/bottleneck-utilization.html +3 -3
- package/public/docs/tuning-reference/caching.html +3 -3
- package/public/docs/tuning-reference/cluster-config.html +3 -3
- package/public/docs/tuning-reference/config.html +3 -3
- package/public/docs/tuning-reference/data-formats.html +3 -3
- package/public/docs/tuning-reference/index.html +3 -3
- package/public/docs/tuning-reference/intro.html +3 -3
- package/public/docs/tuning-reference/joins.html +3 -3
- package/public/docs/tuning-reference/memory-model.html +3 -3
- package/public/docs/tuning-reference/metrics.html +3 -3
- package/public/docs/tuning-reference/partitioning.html +3 -3
- package/public/docs/tuning-reference/pyspark.html +3 -3
- package/public/docs/tuning-reference/shuffle.html +3 -3
- package/public/docs/tuning-reference/spark-architecture.html +3 -3
- package/public/docs/tuning-reference/table-formats.html +3 -3
- package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +33 -0
- package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +30 -0
- package/public/docs/user-guide/alternative-log-retrieval.html +6 -9
- package/public/docs/user-guide/getting-started/ci-and-automation.html +52 -0
- package/public/docs/user-guide/getting-started/reading-the-dashboard.html +30 -0
- package/public/docs/user-guide/getting-started/regression-metric-keys.html +30 -0
- package/public/docs/user-guide/getting-started/tuning-thresholds.html +33 -0
- package/public/docs/user-guide/getting-started.html +5 -28
- package/public/docs/user-guide/mcp-tools/compare-runs.html +85 -0
- package/public/docs/user-guide/mcp-tools/diagnose-run.html +52 -0
- package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +55 -0
- package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +43 -0
- package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +49 -0
- package/public/docs/user-guide/mcp-tools/get-reference-doc.html +34 -0
- package/public/docs/user-guide/mcp-tools/get-run-summary.html +44 -0
- package/public/docs/user-guide/mcp-tools/list-runs.html +41 -0
- package/public/docs/user-guide/mcp-tools.html +8 -140
- package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
- package/public/docs/user-guide/run-comparison.html +5 -5
- package/public/docs/user-guide/understanding-findings.html +5 -5
- package/public/export-template.html +45 -45
- package/public/index.html +5 -5
- package/vendor-core/analyzer.js +1 -1
- package/vendor-core/api-merge-writes.js +80 -0
- package/vendor-core/cli/budgets.js +62 -2
- package/vendor-core/cli/regression-budgets.js +35 -0
- package/vendor-core/comparison-output.js +50 -0
- package/vendor-core/core-source-hash.txt +1 -1
- package/vendor-core/delta-commands.js +13 -0
- package/vendor-core/delta-log-paths.js +42 -0
- package/vendor-core/delta-targets.js +127 -0
- package/vendor-core/detectors.js +219 -56
- package/vendor-core/docs-content/detection/chrn.md +3 -1
- package/vendor-core/docs-content/detection/cold.md +2 -1
- package/vendor-core/docs-content/detection/plan.md +9 -2
- package/vendor-core/docs-content/detection/shfl.md +8 -2
- package/vendor-core/docs-content/detection/skew.md +11 -5
- package/vendor-core/docs-content/detection/slow.md +6 -2
- package/vendor-core/docs-content/detection/spill.md +4 -1
- package/vendor-core/docs-content/detection/strag.md +5 -3
- package/vendor-core/docs-content/detection/tiny.md +3 -2
- package/vendor-core/event-handlers.js +95 -9
- package/vendor-core/event-schemas.js +3 -1
- package/vendor-core/evidence-report.js +24 -15
- package/vendor-core/finding-presentation.js +39 -13
- package/vendor-core/finding-types.js +50 -6
- package/vendor-core/mcp-server-factory.js +17 -6
- package/vendor-core/mcp-tools.js +53 -35
- package/vendor-core/median.js +8 -0
- package/vendor-core/plan-graph-model.js +1 -1
- package/vendor-core/plan-summary.js +13 -10
- package/vendor-core/run-comparison.js +81 -120
- package/vendor-core/run-output.js +26 -0
- package/vendor-core/sequence-alignment.js +83 -0
- package/vendor-core/stage-alignment.js +586 -0
- package/vendor-core/stage-identity.js +112 -0
- package/vendor-core/stage-quantiles.js +3 -1
- package/vendor-core/stage-structure.js +83 -0
- package/vendor-core/types.js +5 -0
- package/vendor-core/write-target-args.js +48 -0
- package/vendor-core/write-targets.js +35 -52
- package/public/assets/CachingOpportunity-Cfw3wAFa.js +0 -1
- package/public/assets/CoreUsageArea-BObEeenE.js +0 -1
- package/public/assets/DisclosureContext-Ds0hcZFn.js +0 -1
- package/public/assets/PlanGraphRoute-NV5mCXRm.js +0 -1
- package/public/assets/Spill-CH1bNpxL.js +0 -1
- package/public/assets/StageFindingGroup-B1A0nf-e.js +0 -1
- package/public/assets/index-BcqTLKFp.css +0 -2
- package/public/assets/index-DaC8A2FS.js +0 -13
- package/public/assets/parser-worker-CfDvPkwF.js +0 -122
- package/public/assets/useRecentFiles-Bb4-ukVz.js +0 -11
- package/public/docs/assets/chunks/@localSearchIndexroot.r9imX8Aq.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.D0Kkk_r5.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_index.md.B9yZieYL.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.DBGo-bwn.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_state-and-history.md.w4lwybdh.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.js +0 -6
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.lean.js +0 -1
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.js +0 -7
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.lean.js +0 -1
- package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.js +0 -24
- package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.lean.js +0 -1
- package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.js +0 -148
- package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.lean.js +0 -1
- package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.js +0 -1
- package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.lean.js +0 -1
- package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.js +0 -1
- package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.lean.js +0 -1
- /package/public/docs/assets/{contributor-guide_architecture_overview.md.DBGo-bwn.lean.js → contributor-guide_architecture_overview.md.C1sSvm9i.lean.js} +0 -0
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
} from './event-schemas.js';
|
|
21
|
+
import { DELTA_COMMANDS } from './delta-commands.js';
|
|
21
22
|
import { assertNever } from './assert-never.js';
|
|
22
23
|
import { finalizeStage } from './stage-quantiles.js';
|
|
23
24
|
import { MAX_FAILURE_DETAILS_PER_STAGE, extractTaskFailureDetail, taskFailureKey, } from './task-failure.js';
|
|
@@ -198,6 +199,9 @@ const MAX_TASK_SAMPLES = 20;
|
|
|
198
199
|
|
|
199
200
|
|
|
200
201
|
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
201
205
|
|
|
202
206
|
|
|
203
207
|
|
|
@@ -279,6 +283,10 @@ export function buildChunkDecoder() {
|
|
|
279
283
|
// line again after every slice scanned about 10 GB on a 100 MB line.
|
|
280
284
|
let pendingIsSqlEvent = false;
|
|
281
285
|
let keyTail = '';
|
|
286
|
+
// Where the pending line's plan-description value starts, once its key is found but too little
|
|
287
|
+
// of the value has arrived to tell whether it is a small Delta command description (see
|
|
288
|
+
// descriptionDisposition); -1 otherwise.
|
|
289
|
+
let undecidedValueStart = -1;
|
|
282
290
|
let skippingPlanDescription = false;
|
|
283
291
|
// Length of the backslash run the skipped bytes ended with, which escapes a quote at the start
|
|
284
292
|
// of the next chunk when odd.
|
|
@@ -312,11 +320,24 @@ export function buildChunkDecoder() {
|
|
|
312
320
|
keyTail = '';
|
|
313
321
|
appended = pending; // nothing of it was searched while it was shorter than the prefix
|
|
314
322
|
}
|
|
315
|
-
|
|
316
|
-
if (
|
|
323
|
+
let valueStart = undecidedValueStart;
|
|
324
|
+
if (valueStart === -1) {
|
|
325
|
+
const keyAt = findPlanKey(appended);
|
|
326
|
+
if (keyAt === -1) return; // the key may still arrive in a later chunk
|
|
327
|
+
valueStart = keyAt + PLAN_DESCRIPTION_KEY.length;
|
|
328
|
+
}
|
|
329
|
+
if (closingQuoteIndex(pending, valueStart) !== -1) { // complete: stripPlanDescription handles it
|
|
330
|
+
undecidedValueStart = -1;
|
|
331
|
+
pendingSettled = true;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// A small kept description is not skipped: the line completes in a later chunk and
|
|
335
|
+
// stripPlanDescription keeps its arguments line.
|
|
336
|
+
const disposition = descriptionDisposition(pending, valueStart);
|
|
337
|
+
if (disposition === 'wait') { undecidedValueStart = valueStart; return; }
|
|
338
|
+
undecidedValueStart = -1;
|
|
317
339
|
pendingSettled = true;
|
|
318
|
-
|
|
319
|
-
if (closingQuoteIndex(pending, valueStart) !== -1) return; // complete: stripPlanDescription empties it
|
|
340
|
+
if (disposition === 'keep') return;
|
|
320
341
|
// Only a chunk whose last byte is a backslash carries a run over; any other last byte (such
|
|
321
342
|
// as part of a split multibyte char) ends it.
|
|
322
343
|
let run = 0;
|
|
@@ -378,6 +399,7 @@ export function buildChunkDecoder() {
|
|
|
378
399
|
pending = pendingHead = appended = start < text.length ? text.substring(start) : '';
|
|
379
400
|
pendingSettled = false;
|
|
380
401
|
pendingIsSqlEvent = false;
|
|
402
|
+
undecidedValueStart = -1;
|
|
381
403
|
}
|
|
382
404
|
if (!pendingSettled && pending !== '') settlePending(buffer[buffer.length - 1], appended);
|
|
383
405
|
}
|
|
@@ -1109,6 +1131,10 @@ export function startSqlExecution(event
|
|
|
1109
1131
|
sparkPlanInfo,
|
|
1110
1132
|
hadAdaptiveUpdate: false,
|
|
1111
1133
|
};
|
|
1134
|
+
if (event.rootExecutionId !== undefined) exec.rootExecutionId = event.rootExecutionId;
|
|
1135
|
+
if (event.physicalPlanDescription && KEPT_ARGUMENTS.test(event.physicalPlanDescription)) {
|
|
1136
|
+
exec.commandArguments = event.physicalPlanDescription;
|
|
1137
|
+
}
|
|
1112
1138
|
state.sqlExecutions.set(exec.id, exec);
|
|
1113
1139
|
// A restarted execution carries a new plan: its next end must resolve it again.
|
|
1114
1140
|
state.resolvedPlanExecutions.delete(exec.id);
|
|
@@ -1295,13 +1321,32 @@ const KNOWN_EVENT_TYPES = new Set(
|
|
|
1295
1321
|
);
|
|
1296
1322
|
|
|
1297
1323
|
// SQLExecutionStart and SQLAdaptiveExecutionUpdate carry `physicalPlanDescription`, Spark's text
|
|
1298
|
-
// rendering of the plan.
|
|
1299
|
-
//
|
|
1324
|
+
// rendering of the plan. The plan tree comes from sparkPlanInfo, yet on a real 3.5 GB log the
|
|
1325
|
+
// description was 72% of the AQE-update bytes, which were themselves 73% of the log. Cutting its
|
|
1300
1326
|
// string value out before JSON.parse halves the parse cost of those lines.
|
|
1327
|
+
//
|
|
1328
|
+
// One exception: a Delta write command's node prints only its bare name in sparkPlanInfo, and its
|
|
1329
|
+
// target table appears only on its `Arguments:` line of the description. When the description is
|
|
1330
|
+
// that of one of KEPT_ARGUMENTS_COMMANDS (checked on its first bytes, so no other line pays for it),
|
|
1331
|
+
// the value is cut down to that one line, never kept whole. A value that spans decoder chunks is
|
|
1332
|
+
// still dropped unread (buildChunkDecoder), so the write target then falls back to its child paths.
|
|
1301
1333
|
const SQL_UI_EVENT_PREFIX = '{"Event":"org.apache.spark.sql.execution.ui.SparkListenerSQL';
|
|
1302
1334
|
const PLAN_DESCRIPTION_KEY = '"physicalPlanDescription":"';
|
|
1303
1335
|
|
|
1304
|
-
const QUOTE = 0x22, BACKSLASH = 0x5c, NEWLINE = 0x0a;
|
|
1336
|
+
const QUOTE = 0x22, BACKSLASH = 0x5c, NEWLINE = 0x0a, LOWER_N = 0x6e;
|
|
1337
|
+
|
|
1338
|
+
// Delta commands whose root node carries no target in sparkPlanInfo. The description of a
|
|
1339
|
+
// command execution starts `== Physical Plan ==\nExecute <Command> (1)` (JSON-escaped newline).
|
|
1340
|
+
const KEPT_ARGUMENTS_COMMANDS = new Set(DELTA_COMMANDS);
|
|
1341
|
+
// Those whose description is just the command (no child plan printed under it), so it is small
|
|
1342
|
+
// enough to decode whole when it spans decoder chunks. SaveIntoDataSourceCommand prints the whole
|
|
1343
|
+
// query under it, which can be as large as any other plan description.
|
|
1344
|
+
const SMALL_DESCRIPTION_COMMANDS = new Set(
|
|
1345
|
+
[...KEPT_ARGUMENTS_COMMANDS].filter((command) => command !== 'SaveIntoDataSourceCommand'),
|
|
1346
|
+
);
|
|
1347
|
+
const PLAN_HEADER = '== Physical Plan ==\\nExecute ';
|
|
1348
|
+
// The kept form once JSON-parsed: `<Command>\nArguments: <line>`.
|
|
1349
|
+
const KEPT_ARGUMENTS = /^[A-Za-z]+\nArguments: /;
|
|
1305
1350
|
|
|
1306
1351
|
// Index of the closing quote of the JSON string whose content starts at `valueStart`: the first
|
|
1307
1352
|
// quote preceded by an even number of backslashes. -1 when the string is unterminated.
|
|
@@ -1326,7 +1371,48 @@ function closingQuoteAt(buf , limit , carried )
|
|
|
1326
1371
|
return -1;
|
|
1327
1372
|
}
|
|
1328
1373
|
|
|
1329
|
-
//
|
|
1374
|
+
// The command named by a description that starts at text[valueStart] with PLAN_HEADER, when text
|
|
1375
|
+
// holds the whole name; null otherwise.
|
|
1376
|
+
function describedCommand(text , valueStart ) {
|
|
1377
|
+
if (!text.startsWith(PLAN_HEADER, valueStart)) return null;
|
|
1378
|
+
const nameStart = valueStart + PLAN_HEADER.length;
|
|
1379
|
+
let nameEnd = nameStart;
|
|
1380
|
+
while (nameEnd < text.length && /[A-Za-z]/.test(text[nameEnd])) nameEnd++;
|
|
1381
|
+
return nameEnd < text.length ? text.slice(nameStart, nameEnd) : null;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// What to do with an open description value whose first bytes are text[valueStart...]: 'keep' it
|
|
1385
|
+
// (a small Delta command description), 'skip' it, or 'wait' when too little has arrived to tell.
|
|
1386
|
+
function descriptionDisposition(text , valueStart ) {
|
|
1387
|
+
const seen = text.slice(valueStart, valueStart + PLAN_HEADER.length);
|
|
1388
|
+
if (!PLAN_HEADER.startsWith(seen)) return 'skip';
|
|
1389
|
+
if (seen.length < PLAN_HEADER.length) return 'wait';
|
|
1390
|
+
const command = describedCommand(text, valueStart);
|
|
1391
|
+
if (command === null) return 'wait'; // the command name is still arriving
|
|
1392
|
+
return SMALL_DESCRIPTION_COMMANDS.has(command) ? 'keep' : 'skip';
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// The `<Command>\nArguments: <line>` part (still JSON-escaped) of the root command of a plan
|
|
1396
|
+
// description whose value spans line[valueStart, quote), or null when it is not one of
|
|
1397
|
+
// KEPT_ARGUMENTS_COMMANDS. SaveIntoDataSourceCommand is kept only for the Delta source: its other
|
|
1398
|
+
// providers (JDBC) print option maps that this tool has no use for.
|
|
1399
|
+
function keptCommandArguments(line , valueStart , quote ) {
|
|
1400
|
+
const command = describedCommand(line, valueStart);
|
|
1401
|
+
if (command === null || !KEPT_ARGUMENTS_COMMANDS.has(command)) return null;
|
|
1402
|
+
const key = `${command}\\nArguments: `;
|
|
1403
|
+
const at = line.indexOf(key, valueStart + PLAN_HEADER.length + command.length);
|
|
1404
|
+
if (at === -1 || at >= quote) return null;
|
|
1405
|
+
// The line ends at the first escaped newline: a backslash starts a two-char escape.
|
|
1406
|
+
let end = at + key.length;
|
|
1407
|
+
while (end < quote && !(line.charCodeAt(end) === BACKSLASH && line.charCodeAt(end + 1) === LOWER_N)) {
|
|
1408
|
+
end += line.charCodeAt(end) === BACKSLASH ? 2 : 1;
|
|
1409
|
+
}
|
|
1410
|
+
const kept = line.slice(at, Math.min(end, quote));
|
|
1411
|
+
return command === 'SaveIntoDataSourceCommand' && !kept.includes('DeltaDataSource') ? null : kept;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// Returns `line` with the physicalPlanDescription string value emptied (or cut to the kept
|
|
1415
|
+
// arguments line, see keptCommandArguments), or `line` unchanged when
|
|
1330
1416
|
// the key isn't found in Spark's compact form. The key pattern can't match inside another JSON
|
|
1331
1417
|
// string: there its quotes would be backslash-escaped. buildChunkDecoder already empties a value
|
|
1332
1418
|
// that spans chunks, so here the value is empty or within one chunk.
|
|
@@ -1339,7 +1425,7 @@ export function stripPlanDescription(line ) {
|
|
|
1339
1425
|
const quote = closingQuoteIndex(line, valueStart);
|
|
1340
1426
|
// Unterminated string (a truncated line): leave it for JSON.parse to reject.
|
|
1341
1427
|
if (quote === -1) return line;
|
|
1342
|
-
return line.slice(0, valueStart) + line.slice(quote);
|
|
1428
|
+
return line.slice(0, valueStart) + (keptCommandArguments(line, valueStart, quote) ?? '') + line.slice(quote);
|
|
1343
1429
|
}
|
|
1344
1430
|
|
|
1345
1431
|
const TASK_END_PREFIX = '{"Event":"SparkListenerTaskEnd",';
|
|
@@ -321,13 +321,15 @@ export const TaskEndEventSchema = z.object({
|
|
|
321
321
|
export const SqlExecutionStartEventSchema = z.object({
|
|
322
322
|
Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLExecutionStart'),
|
|
323
323
|
executionId: z.number(),
|
|
324
|
+
rootExecutionId: z.number().optional(),
|
|
324
325
|
description: z.string().optional(),
|
|
326
|
+
physicalPlanDescription: z.string().optional(),
|
|
325
327
|
time: z.number(),
|
|
326
328
|
sparkPlanInfo: SparkPlanInfoFieldSchema,
|
|
327
329
|
});
|
|
328
330
|
|
|
329
331
|
// applyAdaptiveExecutionUpdate: AQE re-plans mid-query and re-emits sparkPlanInfo for the same
|
|
330
|
-
// executionId; last write wins.
|
|
332
|
+
// executionId; last write wins. This schema does not declare physicalPlanDescription: nothing reads
|
|
331
333
|
// it, so zod strips it (dispatchLine already empties it before JSON.parse, see stripPlanDescription).
|
|
332
334
|
export const SqlAdaptiveExecutionUpdateEventSchema = z.object({
|
|
333
335
|
Event: z.literal('org.apache.spark.sql.execution.ui.SparkListenerSQLAdaptiveExecutionUpdate'),
|
|
@@ -180,29 +180,29 @@ export const EVIDENCE_SCHEMA_VERSION = 5;
|
|
|
180
180
|
// module (stageShape's totalCores, utilization's unrounded fraction) is left off both, so it never
|
|
181
181
|
// reaches the report; adding, renaming or dropping a key here changes the report contract.
|
|
182
182
|
const EVIDENCE_KEYS = {
|
|
183
|
-
skew: [],
|
|
183
|
+
skew: ['origin'],
|
|
184
184
|
stageShape: ['rule'],
|
|
185
|
-
shuffle: [],
|
|
186
|
-
partitionSizing: ['rule'],
|
|
187
|
-
spill: ['spillMagnitude'],
|
|
185
|
+
shuffle: ['partitions'],
|
|
186
|
+
partitionSizing: ['rule', 'origin', 'partitions'],
|
|
187
|
+
spill: ['spillMagnitude', 'reads'],
|
|
188
188
|
gc: ['direction'],
|
|
189
189
|
slowHost: ['variant', 'host', 'hostTaskShare', 'hostMeanMs', 'dimension', 'executorId', 'execMaxValue'],
|
|
190
|
-
stageSlowness: [],
|
|
190
|
+
stageSlowness: ['reads'],
|
|
191
191
|
stageFailed: ['variant', 'numTasks', 'memoryBytesSpilled', 'failedTaskDetails'],
|
|
192
192
|
failures: ['failedTasks', 'dominantReason', 'dominantError', 'failureGroups', 'otherFailedTasks'],
|
|
193
|
-
straggler: ['unit', 'speculativeTasks', 'stragglerCount'],
|
|
193
|
+
straggler: ['unit', 'speculativeTasks', 'stragglerCount', 'origin'],
|
|
194
194
|
speculationWaste: [],
|
|
195
195
|
retryWaste: ['numTasks', 'memoryBytesSpilled', 'retriedTaskDetails'],
|
|
196
|
-
tinyTask: [],
|
|
196
|
+
tinyTask: ['reads'],
|
|
197
197
|
incompleteRun: [],
|
|
198
|
-
coldStart: [],
|
|
198
|
+
coldStart: ['dynamicAllocation'],
|
|
199
199
|
utilization: ['cpuUtilizationPct'],
|
|
200
200
|
memoryUtilization: ['variant', 'rule', 'executorId', 'heap', 'dataUnavailable'],
|
|
201
201
|
cacheUtilization: [
|
|
202
202
|
'variant', 'rddId', 'rddName', 'memorySize', 'diskSize', 'numCachedPartitions', 'numPartitions', 'dataUnavailable',
|
|
203
203
|
],
|
|
204
204
|
coreLocality: ['nonLocalTaskCount'],
|
|
205
|
-
autoscalingChurn: ['shortLivedExecutorCount'],
|
|
205
|
+
autoscalingChurn: ['shortLivedExecutorCount', 'dynamicAllocation'],
|
|
206
206
|
cachingOpportunity: ['variant', 'relation', 'format', 'relations', 'operator', 'executionIds', 'totalReadBytes'],
|
|
207
207
|
jobFailureRate: ['failedJobs', 'totalJobs', 'failedTasks', 'totalTasks', 'avgJobDurationMs', 'taskFailureRate'],
|
|
208
208
|
configAudit: ['property'],
|
|
@@ -211,8 +211,8 @@ const EVIDENCE_KEYS = {
|
|
|
211
211
|
'groupIndex',
|
|
212
212
|
],
|
|
213
213
|
smallFiles: ['executionId', 'stageIds', 'fileCount', 'direction', 'nodeName'],
|
|
214
|
-
underBroadcast: ['executionId', 'stageIds', 'largerSideBytes'],
|
|
215
|
-
overBroadcast: ['executionId', 'stageIds'],
|
|
214
|
+
underBroadcast: ['executionId', 'stageIds', 'largerSideBytes', 'broadcastThreshold'],
|
|
215
|
+
overBroadcast: ['executionId', 'stageIds', 'broadcastThreshold'],
|
|
216
216
|
} ;
|
|
217
217
|
|
|
218
218
|
// The other direction: an evidence field EVIDENCE_KEYS doesn't list fails here.
|
|
@@ -405,13 +405,13 @@ function verdictJson(model ) {
|
|
|
405
405
|
// A WeakMap needs no invalidation: once mcp-tools.ts evicts the appModel, this entry is collectible.
|
|
406
406
|
// Each cache is split first by the overrides object the report ran under (one fixed, frozen object
|
|
407
407
|
// per CLI invocation or MCP server process; DEFAULT_THRESHOLDS for the specification's).
|
|
408
|
-
|
|
408
|
+
|
|
409
409
|
const DEFAULT_THRESHOLDS = {};
|
|
410
410
|
const jsonCache = new WeakMap();
|
|
411
411
|
// The redacted report, keyed by the unredacted appModel it was built from.
|
|
412
412
|
const redactedJsonCache = new WeakMap();
|
|
413
413
|
|
|
414
|
-
function cacheFor(cache
|
|
414
|
+
function cacheFor (cache , thresholds ) {
|
|
415
415
|
const key = thresholds ?? DEFAULT_THRESHOLDS;
|
|
416
416
|
let byModel = cache.get(key);
|
|
417
417
|
if (!byModel) {
|
|
@@ -421,14 +421,23 @@ function cacheFor(cache , thresholds
|
|
|
421
421
|
return byModel;
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
-
|
|
424
|
+
const findingsCache = new WeakMap();
|
|
425
|
+
|
|
426
|
+
/** The run's detector catalog and config audit, computed once per run and overrides so the report
|
|
427
|
+
* and the blocks built beside it (run-output.ts) share one analysis. */
|
|
428
|
+
export function runFindings(appModel , thresholds ) {
|
|
429
|
+
const cache = cacheFor(findingsCache, thresholds);
|
|
430
|
+
const cached = cache.get(appModel);
|
|
431
|
+
if (cached) return cached;
|
|
425
432
|
const { app, stages, executors, sql, jobs, runAggregates } = appModel;
|
|
426
433
|
const catalog = analyze(
|
|
427
434
|
app, stages, executors?.added ?? [], executors?.removed ?? [],
|
|
428
435
|
jobs ?? new Map(), sql ?? new Map(),
|
|
429
436
|
runAggregates ?? null, { thresholds },
|
|
430
437
|
);
|
|
431
|
-
|
|
438
|
+
const result = { catalog, config: auditConfig(app) };
|
|
439
|
+
cache.set(appModel, result);
|
|
440
|
+
return result;
|
|
432
441
|
}
|
|
433
442
|
|
|
434
443
|
// Redacts the model and findings before the report derives any text from them, the same order
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// detectors.ts is type-only, so it is erased at build time. A detector's scope, order and emits
|
|
9
9
|
// list stay on its DETECTORS entry; renderers get them through detectorInfoByType().
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
|
|
@@ -43,7 +43,9 @@ const SKEW_JOIN_ALREADY_ON = 'AQE skew-join handling is already on, so salt the
|
|
|
43
43
|
const SKEW_JOIN_AQE_OFF = 'AQE is off, so enable it (spark.sql.adaptive.enabled) for skew-join handling to apply; otherwise salt the key or repartition on a better key.';
|
|
44
44
|
|
|
45
45
|
// The skew-join generic line, worded per the row's remediation: AQE logged off, switch already on, or neither.
|
|
46
|
-
function skewJoinGeneric(f
|
|
46
|
+
function skewJoinGeneric(f , unset ) {
|
|
47
|
+
if (f.origin === 'inputScan') return 'Uneven input files: compact small files or split large ones (lower spark.sql.files.maxPartitionBytes).';
|
|
48
|
+
if (f.origin === 'other') return 'Work is uneven across tasks: salt the key or repartition on a better key.';
|
|
47
49
|
if (f.remediation?.some((r) => r.key === 'spark.sql.adaptive.enabled')) return SKEW_JOIN_AQE_OFF;
|
|
48
50
|
return switchAlreadyOn(f, SKEW_JOIN_KEY) ? SKEW_JOIN_ALREADY_ON : unset;
|
|
49
51
|
}
|
|
@@ -120,8 +122,9 @@ export const FINDING_PRESENTATION
|
|
|
120
122
|
tag: 'TINY',
|
|
121
123
|
thresholdSummary: (t) => `${t.minTasks}+ tasks with a median of ${t.maxP50}ms or less and a P95 of ${t.maxP95}ms or less`,
|
|
122
124
|
actionLabel: () => 'Coalesce small tasks',
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
genericRecommendation: (f) => (f.reads != null && f.reads !== 'shuffle'
|
|
126
|
+
? 'Scheduler overhead may dominate: coalesce down to fewer, larger tasks.'
|
|
127
|
+
: 'Scheduler overhead may dominate: lower spark.sql.shuffle.partitions, or coalesce down to fewer, larger tasks.'),
|
|
125
128
|
},
|
|
126
129
|
|
|
127
130
|
shuffle: {
|
|
@@ -129,7 +132,14 @@ export const FINDING_PRESENTATION
|
|
|
129
132
|
tag: 'SHFL',
|
|
130
133
|
thresholdSummary: (t) => `stage shuffle read above ${t.minBytes / 1048576} MiB`,
|
|
131
134
|
actionLabel: () => 'Reduce shuffle size',
|
|
132
|
-
genericRecommendation: () =>
|
|
135
|
+
genericRecommendation: (f) => {
|
|
136
|
+
switch (f.partitions) {
|
|
137
|
+
case 'sufficient': return 'The tasks are already a good size, so more partitions will not help: use a broadcast join for the smaller side.';
|
|
138
|
+
case 'aqeCoalesced': return 'AQE coalesced the shuffle: lower spark.sql.adaptive.advisoryPartitionSizeInBytes, or use a broadcast join for the smaller side.';
|
|
139
|
+
case 'ownPartitioning': return "spark.sql.shuffle.partitions is already high enough, so raise this stage's own partition count (its repartition(n) or RDD parallelism), or use a broadcast join for the smaller side.";
|
|
140
|
+
default: return 'Raise spark.sql.shuffle.partitions, or use a broadcast join for the smaller side.';
|
|
141
|
+
}
|
|
142
|
+
},
|
|
133
143
|
},
|
|
134
144
|
partitionSizing: {
|
|
135
145
|
name: 'partition sizing',
|
|
@@ -146,7 +156,9 @@ export const FINDING_PRESENTATION
|
|
|
146
156
|
genericRecommendation(f) {
|
|
147
157
|
switch (f.rule) {
|
|
148
158
|
case 'shufflePartitionSkew': return skewJoinGeneric(f, 'For join skew, enable AQE skew-join handling (spark.sql.adaptive.skewJoin.enabled); otherwise salt the key or repartition on a better key.');
|
|
149
|
-
case 'lowShuffleParallelism':
|
|
159
|
+
case 'lowShuffleParallelism':
|
|
160
|
+
if (f.partitions === 'aqeCoalesced') return 'AQE coalesced the shuffle into few tasks: lower spark.sql.adaptive.advisoryPartitionSizeInBytes so each partition is smaller.';
|
|
161
|
+
return switchAlreadyOn(f, 'spark.sql.shuffle.partitions')
|
|
150
162
|
? "spark.sql.shuffle.partitions is already high enough, so raise this stage's own partition count (its repartition(n) or RDD parallelism) so each partition is smaller."
|
|
151
163
|
: 'Raise spark.sql.shuffle.partitions so each partition is smaller.';
|
|
152
164
|
case 'maxPartitionTooBig': return 'Repartition to break up the oversized partition before this stage.';
|
|
@@ -161,7 +173,9 @@ export const FINDING_PRESENTATION
|
|
|
161
173
|
thresholdSummary: (t) => `single-task disk spill above ${t.singleTaskDiskGiB} GiB`,
|
|
162
174
|
actionLabel: () => 'Reduce spill',
|
|
163
175
|
// The skew/volume classification isn't a Finding field, so one sentence covers both.
|
|
164
|
-
genericRecommendation: () =>
|
|
176
|
+
genericRecommendation: (f) => (f.reads != null && f.reads !== 'shuffle'
|
|
177
|
+
? 'If the spill is skew-driven, fix task skew first: adding memory will not help. Otherwise increase executor memory or process less data per task.'
|
|
178
|
+
: 'If the spill is skew-driven, fix task skew first: adding memory will not help. Otherwise raise spark.sql.shuffle.partitions or increase executor memory.'),
|
|
165
179
|
},
|
|
166
180
|
|
|
167
181
|
gc: {
|
|
@@ -219,14 +233,18 @@ export const FINDING_PRESENTATION
|
|
|
219
233
|
tag: 'SLOW',
|
|
220
234
|
thresholdSummary: () => 'a stage running far longer than its peers, not attributable to a single slow host',
|
|
221
235
|
actionLabel: () => 'Profile slow stage',
|
|
222
|
-
genericRecommendation: () =>
|
|
236
|
+
genericRecommendation: (f) => f.reads === 'input'
|
|
237
|
+
? 'Often too few or too uneven input partitions: check input file sizes and lower spark.sql.files.maxPartitionBytes, or look for a large per-task data volume driving heavy spill.'
|
|
238
|
+
: f.reads === 'other'
|
|
239
|
+
? 'Check what the stage computes and for a large per-task data volume driving heavy spill.'
|
|
240
|
+
: 'Often a partition-count problem: raise parallelism via spark.sql.shuffle.partitions or spark.default.parallelism, or check for a large per-task data volume driving heavy shuffle and spill.',
|
|
223
241
|
},
|
|
224
242
|
straggler: {
|
|
225
243
|
name: 'straggling task',
|
|
226
244
|
tag: 'STRAG',
|
|
227
245
|
thresholdSummary: () => 'one or more tasks finishing far after the rest of their stage',
|
|
228
246
|
actionLabel: () => 'Fix stragglers',
|
|
229
|
-
genericRecommendation: () =>
|
|
247
|
+
genericRecommendation: (f) => `Rule out a GC pause or a slow shuffle fetch before assuming a hardware issue. If uneven data is the cause: ${skewJoinGeneric(f, 'for join-driven skew, enable AQE skew-join handling (spark.sql.adaptive.skewJoin.enabled); otherwise salt the key or repartition on a better key.')}`,
|
|
230
248
|
},
|
|
231
249
|
speculationWaste: {
|
|
232
250
|
name: 'speculation waste',
|
|
@@ -240,7 +258,9 @@ export const FINDING_PRESENTATION
|
|
|
240
258
|
tag: 'COLD',
|
|
241
259
|
thresholdSummary: (t) => `the first stage waiting over ${t.gapSeconds}s for an executor`,
|
|
242
260
|
actionLabel: () => 'Pre-warm cluster',
|
|
243
|
-
genericRecommendation: () =>
|
|
261
|
+
genericRecommendation: (f) => (f.dynamicAllocation === 'off'
|
|
262
|
+
? 'Keep a warm pool of idle executors: dynamic allocation is off, so no executor-count property applies.'
|
|
263
|
+
: 'Keep a warm pool of idle executors, or if using dynamic allocation, raise the minimum/initial executor count so it does not scale up from zero.'),
|
|
244
264
|
},
|
|
245
265
|
|
|
246
266
|
memoryUtilization: {
|
|
@@ -322,7 +342,9 @@ export const FINDING_PRESENTATION
|
|
|
322
342
|
tag: 'CHRN',
|
|
323
343
|
thresholdSummary: (t) => `over ${shareLabel(t.warningPct)} of executors living under ${t.shortLivedMs / 60000} minutes`,
|
|
324
344
|
actionLabel: () => 'Reduce autoscaling churn',
|
|
325
|
-
genericRecommendation: () =>
|
|
345
|
+
genericRecommendation: (f) => (f.dynamicAllocation === 'off'
|
|
346
|
+
? 'Dynamic allocation is off, so the churn comes from executors lost or preempted: check the cluster manager\'s preemption and executor-loss logs.'
|
|
347
|
+
: 'This looks like wasteful re-provisioning rather than normal scale-down: consider raising spark.dynamicAllocation.executorIdleTimeout or widening the minExecutors/maxExecutors bounds to reduce flapping.'),
|
|
326
348
|
},
|
|
327
349
|
|
|
328
350
|
configAudit: CONFIG_AUDIT_PRESENTATION,
|
|
@@ -349,14 +371,18 @@ export const FINDING_PRESENTATION
|
|
|
349
371
|
tag: 'PLAN',
|
|
350
372
|
thresholdSummary: () => 'a join below the configured size floor that skipped broadcast',
|
|
351
373
|
actionLabel: () => 'Use broadcast join',
|
|
352
|
-
genericRecommendation: () =>
|
|
374
|
+
genericRecommendation: (f) => (f.broadcastThreshold === 'notLimiting'
|
|
375
|
+
? 'The threshold already admits the smaller side, so it is not what stopped the broadcast: consider a broadcast() hint or collecting table statistics.'
|
|
376
|
+
: 'This could have been a broadcast join: consider a broadcast() hint or raising spark.sql.autoBroadcastJoinThreshold.'),
|
|
353
377
|
},
|
|
354
378
|
overBroadcast: {
|
|
355
379
|
name: 'oversized broadcast join',
|
|
356
380
|
tag: 'PLAN',
|
|
357
381
|
thresholdSummary: (t) => `a broadcast over ${t.overBroadcastBytes / 1073741824} GiB`,
|
|
358
382
|
actionLabel: () => 'Fix oversized broadcast',
|
|
359
|
-
genericRecommendation: (f) => (
|
|
383
|
+
genericRecommendation: (f) => (f.broadcastThreshold === 'notLimiting'
|
|
384
|
+
? 'The configured threshold is below this broadcast, so remove the broadcast() hint that forced it.'
|
|
385
|
+
: switchAlreadyOn(f, 'spark.sql.autoBroadcastJoinThreshold')
|
|
360
386
|
? 'Automatic broadcast is already disabled, so remove the broadcast() hint that forced it.'
|
|
361
387
|
: 'Check for a misapplied broadcast hint or a misconfigured spark.sql.autoBroadcastJoinThreshold.'),
|
|
362
388
|
},
|
|
@@ -79,7 +79,22 @@
|
|
|
79
79
|
|
|
80
80
|
// ── Per-stage detectors ─────────────────────────────────────────────────────
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
/** What a skewed stage reads, which decides the fix: 'shuffleJoin' (a shuffle feeding a join, where
|
|
83
|
+
* AQE skew-join handling applies), 'inputScan' (uneven input files) or 'other'. */
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
/** What a stage reads, by the dominant side: a shuffle, input files, or neither. */
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
/** Why a shuffle finding's partition-count advice is or is not given: 'raise' (the property limits
|
|
90
|
+
* the stage), 'sufficient' (tasks are already a good size), 'aqeCoalesced' (AQE merged the
|
|
91
|
+
* partitions, so the advisory size is the lever) or 'ownPartitioning' (the property is already high
|
|
92
|
+
* enough, so the stage's own repartition(n) or RDD parallelism is). */
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
83
98
|
|
|
84
99
|
|
|
85
100
|
|
|
@@ -90,16 +105,24 @@
|
|
|
90
105
|
|
|
91
106
|
|
|
92
107
|
|
|
93
|
-
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
94
111
|
|
|
95
112
|
|
|
96
113
|
|
|
97
114
|
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
98
119
|
|
|
99
120
|
|
|
100
121
|
|
|
101
122
|
|
|
102
123
|
|
|
124
|
+
|
|
125
|
+
|
|
103
126
|
|
|
104
127
|
|
|
105
128
|
|
|
@@ -124,7 +147,10 @@
|
|
|
124
147
|
|
|
125
148
|
|
|
126
149
|
|
|
127
|
-
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
128
154
|
|
|
129
155
|
|
|
130
156
|
|
|
@@ -151,6 +177,8 @@
|
|
|
151
177
|
|
|
152
178
|
|
|
153
179
|
|
|
180
|
+
|
|
181
|
+
|
|
154
182
|
|
|
155
183
|
|
|
156
184
|
|
|
@@ -167,7 +195,10 @@
|
|
|
167
195
|
|
|
168
196
|
|
|
169
197
|
|
|
170
|
-
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
171
202
|
|
|
172
203
|
|
|
173
204
|
// ── App-level detectors ─────────────────────────────────────────────────────
|
|
@@ -176,7 +207,10 @@
|
|
|
176
207
|
// valueText is always 'missing': the ApplicationEnd event the log lacks.
|
|
177
208
|
|
|
178
209
|
|
|
179
|
-
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
180
214
|
|
|
181
215
|
|
|
182
216
|
|
|
@@ -231,6 +265,8 @@
|
|
|
231
265
|
|
|
232
266
|
|
|
233
267
|
|
|
268
|
+
|
|
269
|
+
|
|
234
270
|
|
|
235
271
|
|
|
236
272
|
|
|
@@ -296,13 +332,21 @@
|
|
|
296
332
|
|
|
297
333
|
|
|
298
334
|
|
|
335
|
+
|
|
336
|
+
/** How the effective spark.sql.autoBroadcastJoinThreshold relates to the finding: 'limits' (the
|
|
337
|
+
* property decided it, or is unknown), 'notLimiting' (already admits, or already below, the
|
|
338
|
+
* broadcast, so something else decided it) or 'disabled' (-1). */
|
|
339
|
+
|
|
299
340
|
|
|
300
341
|
|
|
301
342
|
|
|
343
|
+
|
|
302
344
|
|
|
303
345
|
|
|
304
346
|
|
|
305
|
-
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
306
350
|
|
|
307
351
|
|
|
308
352
|
// ── Unions ──────────────────────────────────────────────────────────────────
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from './mcp-tools.js';
|
|
7
7
|
import { listRuns } from './list-runs.js';
|
|
8
8
|
|
|
9
|
+
import { MAX_NORMALIZE_PATTERN_LENGTH, MAX_NORMALIZE_PATTERNS } from './stage-alignment.js';
|
|
9
10
|
|
|
10
11
|
const sourceSchema = z.union([
|
|
11
12
|
z.object({ path: z.string() }),
|
|
@@ -71,7 +72,7 @@ export function createMcpServer({ thresholds }
|
|
|
71
72
|
}, (params) => toolResult(listRuns(params)));
|
|
72
73
|
|
|
73
74
|
server.registerTool('diagnose_run', {
|
|
74
|
-
description: 'Diagnose a Spark run: the dashboard verdict (title, summary, and the top places to look, ranked by potential savings), thresholded findings with remediation text, an impact-ranked fix recommendation rollup, clean-check status,
|
|
75
|
+
description: 'Diagnose a Spark run: the dashboard verdict (title, summary, and the top places to look, ranked by potential savings), thresholded findings with remediation text, an impact-ranked fix recommendation rollup, clean-check status, the checks the log lacked the data to run, and the writeTargets, metrics and effectiveConf blocks of the CLI report. When the server was started with --thresholds, findings and clean checks from a tuned detector carry tunedThresholds, and their impact estimates are uncalibrated.',
|
|
75
76
|
inputSchema: {
|
|
76
77
|
...runRefSchema, redact: z.boolean().optional(),
|
|
77
78
|
include: z.array(z.enum(['summary', 'evidenceAvailability', 'detectors'])).optional(),
|
|
@@ -91,15 +92,19 @@ export function createMcpServer({ thresholds }
|
|
|
91
92
|
));
|
|
92
93
|
|
|
93
94
|
server.registerTool('compare_runs', {
|
|
94
|
-
description: 'Compare two runs: categorized findings delta and
|
|
95
|
+
description: 'Compare two runs: the verdict, metric deltas (metrics), categorized findings delta (findings) and the stage pairs (stagePairs) with the share of executor run time they cover (runtimeCoverage). confidence is ok, low or insufficient. metricDeltas and findingsDelta repeat metrics and findings under their earlier names and will be removed.',
|
|
95
96
|
inputSchema: {
|
|
96
97
|
runIdA: z.string().optional(), sourceA: sourceSchema.optional(),
|
|
97
98
|
...secondRunRefSchema,
|
|
98
99
|
redact: z.boolean().optional(),
|
|
100
|
+
include: z.array(z.enum(['stagePairs'])).optional()
|
|
101
|
+
.describe('Extra blocks to return. stagePairs: one row per paired stage with its deltas, which can be large.'),
|
|
102
|
+
normalizePath: z.array(z.string().max(MAX_NORMALIZE_PATTERN_LENGTH)).max(MAX_NORMALIZE_PATTERNS).optional()
|
|
103
|
+
.describe('Regular expressions whose every match in a plan node\'s text is replaced with a fixed token before stages are paired, so run-specific text (an output directory that differs per run) does not keep the same stage from pairing. A generic pattern, not only for paths. Affects stage pairing, runtimeCoverage and confidence, never findings. A pattern that backtracks catastrophically can stall the server: keep them simple and anchored.'),
|
|
99
104
|
...formatSchema,
|
|
100
105
|
},
|
|
101
|
-
}, ({ runIdA, sourceA, runIdB, sourceB, redact, format }) => toolResultWithMarkdown(
|
|
102
|
-
compareRuns({ runId: runIdA, source: sourceA }, { runId: runIdB, source: sourceB }, { redact, markdown: format === 'md', thresholds }),
|
|
106
|
+
}, ({ runIdA, sourceA, runIdB, sourceB, redact, include, normalizePath, format }) => toolResultWithMarkdown(
|
|
107
|
+
compareRuns({ runId: runIdA, source: sourceA }, { runId: runIdB, source: sourceB }, { redact, markdown: format === 'md', thresholds, normalizePath, include }),
|
|
103
108
|
));
|
|
104
109
|
|
|
105
110
|
server.registerTool('evaluate_budgets', {
|
|
@@ -112,14 +117,20 @@ export function createMcpServer({ thresholds }
|
|
|
112
117
|
sourceB: sourceSchema.optional().describe('Optional candidate run, compared against source/runId as the regression baseline (maxRegressionPct/failOnIntroduced). When given, the absolute budgets (maxRuntimeMs etc.) are evaluated on this run.'),
|
|
113
118
|
runIdB: secondRunRefSchema.runIdB.describe('Same as `sourceB`, referencing an already-resolved run by id.'),
|
|
114
119
|
maxRegressionPct: z.number().optional(), regressionMetric: z.string().optional(), failOnIntroduced: z.string().optional(),
|
|
120
|
+
regressionBudgets: z.array(z.object({ metric: z.string(), maxPct: z.number().nonnegative() })).optional()
|
|
121
|
+
.describe('Further regression budgets, one per metric (the CLI\'s repeated --regression-budget). A metric can be budgeted once across this list and maxRegressionPct/regressionMetric.'),
|
|
122
|
+
stageRegressionBudgets: z.array(z.object({ metric: z.string(), maxPct: z.number().nonnegative() })).optional()
|
|
123
|
+
.describe('Per paired stage (needs sourceB/runIdB): fail when any eligible pair\'s metric (executorRunTime, executorCpuTime, memoryBytesSpilled, diskBytesSpilled, shuffleReadBytes, shuffleWriteBytes) regressed by more than maxPct. One entry per metric. Inconclusive when no stage pairs.'),
|
|
124
|
+
stageQualities: z.array(z.enum(['exact', 'structural', 'aligned'])).optional()
|
|
125
|
+
.describe('Pair qualities the stage budgets read. Default exact and structural: an aligned pair may compare different work.'),
|
|
115
126
|
},
|
|
116
127
|
}, ({
|
|
117
128
|
source, runId, runIdB, sourceB,
|
|
118
129
|
maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct,
|
|
119
|
-
maxRegressionPct, regressionMetric, failOnIntroduced,
|
|
130
|
+
maxRegressionPct, regressionMetric, failOnIntroduced, regressionBudgets, stageRegressionBudgets, stageQualities,
|
|
120
131
|
}) => toolResult(evaluateBudgetsForRun(
|
|
121
132
|
{ source, runId },
|
|
122
|
-
{ maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct, maxRegressionPct, regressionMetric, failOnIntroduced },
|
|
133
|
+
{ maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct, maxRegressionPct, regressionMetric, failOnIntroduced, regressionBudgets, stageRegressionBudgets, stageQualities },
|
|
123
134
|
(runIdB || sourceB) ? { runId: runIdB, source: sourceB } : undefined,
|
|
124
135
|
{ thresholds },
|
|
125
136
|
)));
|