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
package/vendor-core/mcp-tools.js
CHANGED
|
@@ -6,13 +6,14 @@ import { collectRun } from './cli/collect-run.js';
|
|
|
6
6
|
import { deriveEvidenceAvailability } from './evidence-availability.js';
|
|
7
7
|
import { resolveFromShs, DEFAULT_MAX_ARCHIVE_BYTES, DEFAULT_IDLE_TIMEOUT_MS } from './shs-load.js';
|
|
8
8
|
import { mcpError } from './mcp-error.js';
|
|
9
|
-
import { buildEvidenceReport, toFindingsFilter, } from './evidence-report.js';
|
|
10
|
-
import { redactComparison } from './redact.js';
|
|
9
|
+
import { buildEvidenceReport, runFindings, toFindingsFilter, } from './evidence-report.js';
|
|
11
10
|
import { computeWallClock } from './wall-clock.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
11
|
+
import { renderComparisonMarkdown, } from './run-comparison.js';
|
|
12
|
+
import { buildComparisonOutput, } from './comparison-output.js';
|
|
13
|
+
import { compileNormalizePatterns } from './stage-alignment.js';
|
|
14
|
+
import { runOutputBlocks } from './run-output.js';
|
|
15
15
|
import { evaluateBudgets, } from './cli/budgets.js';
|
|
16
|
+
import { assertStageBudgetMetric, combineRegressionBudgets } from './cli/regression-budgets.js';
|
|
16
17
|
import { FINDING_NAMES, titleCase } from './finding-names.js';
|
|
17
18
|
import { docAnchorForType } from './detector-docs.js';
|
|
18
19
|
import { DETECTORS, } from './detectors.js';
|
|
@@ -46,19 +47,15 @@ import { typeTag } from './format-utils.js';
|
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
|
|
49
|
-
// compareRuns returns
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
50
|
+
// compareRuns returns the shared comparison projection (comparison-output.ts, the CLI's `comparison`
|
|
51
|
+
// object), not the full raw shape (no baselineLabel/stageSkew/baseStages/candStages/jobOutcomes).
|
|
52
|
+
// `verdict` is the dashboard comparison page's headline (baseline = runIdA, candidate = runIdB).
|
|
53
|
+
|
|
53
54
|
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
|
|
62
59
|
|
|
63
60
|
|
|
64
61
|
function envInt(name , fallback ) {
|
|
@@ -192,6 +189,7 @@ export function diagnoseRun(runId , opts
|
|
|
192
189
|
|
|
193
190
|
|
|
194
191
|
|
|
192
|
+
|
|
195
193
|
{
|
|
196
194
|
const appModel = getCachedAppModel(runId);
|
|
197
195
|
const findingsFilter = toFindingsFilter(opts?.impactBand, opts?.type, opts?.stageId);
|
|
@@ -203,6 +201,9 @@ export function diagnoseRun(runId , opts
|
|
|
203
201
|
return {
|
|
204
202
|
runId, verdict: json.verdict, findings: json.findings, recommendations: json.recommendations, cleanChecks: json.cleanChecks,
|
|
205
203
|
notRunChecks: json.notRunChecks,
|
|
204
|
+
// The CLI report's own blocks, so a run reads the same through either surface.
|
|
205
|
+
writeTargets: json.writeTargets,
|
|
206
|
+
...runOutputBlocks(appModel, { redact: opts?.redact, thresholds: opts?.thresholds }),
|
|
206
207
|
runComplete: appModel.app?.endTime != null,
|
|
207
208
|
// Top level too, so a client that never asks for `summary` still sees the run was tuned.
|
|
208
209
|
...(tuned ? { tunedThresholds: tuned } : {}),
|
|
@@ -345,16 +346,19 @@ async function resolveAndAnalyze(
|
|
|
345
346
|
ref , thresholds ,
|
|
346
347
|
) {
|
|
347
348
|
const { runId, appModel } = await resolveOrCreateRun(ref);
|
|
348
|
-
|
|
349
|
-
appModel.app, appModel.stages, appModel.executors.added, appModel.executors.removed,
|
|
350
|
-
appModel.jobs, appModel.sql, appModel.runAggregates, { thresholds },
|
|
351
|
-
);
|
|
352
|
-
return { runId, appModel, catalog };
|
|
349
|
+
return { runId, appModel, catalog: runFindings(appModel, thresholds).catalog };
|
|
353
350
|
}
|
|
354
351
|
|
|
355
352
|
export async function compareRuns(
|
|
356
|
-
a , b , opts
|
|
353
|
+
a , b , opts
|
|
354
|
+
,
|
|
357
355
|
) {
|
|
356
|
+
// Before any log is read: a bad pattern refuses the call, as the CLI's usage error does.
|
|
357
|
+
try {
|
|
358
|
+
compileNormalizePatterns(opts?.normalizePath);
|
|
359
|
+
} catch (e) {
|
|
360
|
+
throw mcpError('access-or-upstream-failure', `normalizePath: ${(e ).message}`);
|
|
361
|
+
}
|
|
358
362
|
const [
|
|
359
363
|
{ runId: runIdA, appModel: appModelA, catalog: catalogA },
|
|
360
364
|
{ runId: runIdB, appModel: appModelB, catalog: catalogB },
|
|
@@ -364,26 +368,20 @@ export async function compareRuns(
|
|
|
364
368
|
// interactive stage-detail drill-down, which none of compare/matchStages/metricDeltas/findingsDelta
|
|
365
369
|
// read. Findings/metrics come from `catalog` and appModel.stages/sql, both fully populated. Produces
|
|
366
370
|
// identical output to the dashboard; the MCP tool just never exposes per-task drill-down.
|
|
367
|
-
const
|
|
371
|
+
const { comparison, output } = buildComparisonOutput(
|
|
368
372
|
{ label: runIdA, appModel: appModelA, catalog: catalogA },
|
|
369
373
|
{ label: runIdB, appModel: appModelB, catalog: catalogB },
|
|
374
|
+
{ redact: opts?.redact, normalizePath: opts?.normalizePath, view: opts?.include?.includes('stagePairs') ? 'full' : 'summary' },
|
|
370
375
|
);
|
|
371
|
-
// Stage names throughout `built` carry raw Spark stage text, which can embed a host/IP token as
|
|
372
|
-
// free text, the same residual redactReport() already scrubs from the evidence report.
|
|
373
|
-
const result = opts?.redact ? redactComparison(built) : built;
|
|
374
|
-
const verdict = comparisonVerdict(result);
|
|
375
376
|
|
|
376
377
|
return {
|
|
377
378
|
runIdA,
|
|
378
379
|
runIdB,
|
|
379
|
-
|
|
380
|
-
findingsDelta:
|
|
381
|
-
metricDeltas:
|
|
382
|
-
confidence: result.confidence,
|
|
383
|
-
reason: result.reason,
|
|
384
|
-
matchedCoverage: result.matchedCoverage,
|
|
380
|
+
...output,
|
|
381
|
+
findingsDelta: output.findings,
|
|
382
|
+
metricDeltas: output.metrics,
|
|
385
383
|
...tunedField(opts?.thresholds),
|
|
386
|
-
...(opts?.markdown ? { markdown: renderComparisonMarkdown(
|
|
384
|
+
...(opts?.markdown ? { markdown: renderComparisonMarkdown(comparison, output.verdict, tunedDetectors(opts?.thresholds)) } : {}),
|
|
387
385
|
};
|
|
388
386
|
}
|
|
389
387
|
|
|
@@ -408,6 +406,25 @@ export async function evaluateBudgetsForRun(
|
|
|
408
406
|
if (budgets.regressionMetric !== undefined && budgets.maxRegressionPct === undefined) {
|
|
409
407
|
throw mcpError('access-or-upstream-failure', 'regressionMetric requires maxRegressionPct.');
|
|
410
408
|
}
|
|
409
|
+
// The CLI's rule: a metric is budgeted once across the single pair and the list.
|
|
410
|
+
try {
|
|
411
|
+
const pair = budgets.maxRegressionPct === undefined ? [] : [{ metric: budgets.regressionMetric ?? 'wallClock', maxPct: budgets.maxRegressionPct }];
|
|
412
|
+
combineRegressionBudgets([
|
|
413
|
+
...pair.map((budget) => ({ origin: 'maxRegressionPct/regressionMetric', budget })),
|
|
414
|
+
...(budgets.regressionBudgets ?? []).map((budget) => ({ origin: 'regressionBudgets', budget })),
|
|
415
|
+
]);
|
|
416
|
+
} catch (e) {
|
|
417
|
+
throw mcpError('access-or-upstream-failure', (e ).message);
|
|
418
|
+
}
|
|
419
|
+
if (budgets.stageQualities !== undefined && budgets.stageRegressionBudgets === undefined) {
|
|
420
|
+
throw mcpError('access-or-upstream-failure', 'stageQualities requires stageRegressionBudgets.');
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
for (const { metric } of budgets.stageRegressionBudgets ?? []) assertStageBudgetMetric(metric, 'stageRegressionBudgets');
|
|
424
|
+
combineRegressionBudgets((budgets.stageRegressionBudgets ?? []).map((budget) => ({ origin: 'stageRegressionBudgets', budget })));
|
|
425
|
+
} catch (e) {
|
|
426
|
+
throw mcpError('access-or-upstream-failure', (e ).message);
|
|
427
|
+
}
|
|
411
428
|
const [first, second] = await Promise.all([
|
|
412
429
|
resolveAndAnalyze(primary, opts?.thresholds),
|
|
413
430
|
secondary ? resolveAndAnalyze(secondary, opts?.thresholds) : Promise.resolve(undefined),
|
|
@@ -418,11 +435,12 @@ export async function evaluateBudgetsForRun(
|
|
|
418
435
|
// apply to the candidate.
|
|
419
436
|
const candidate = second ?? first;
|
|
420
437
|
const baseline = second ? first : undefined;
|
|
438
|
+
// Not redacted: no budget reads the stage text redaction rewrites (the parity suite checks it).
|
|
421
439
|
const comparison = baseline
|
|
422
|
-
?
|
|
440
|
+
? buildComparisonOutput(
|
|
423
441
|
{ label: baseline.runId, appModel: baseline.appModel, catalog: baseline.catalog },
|
|
424
442
|
{ label: candidate.runId, appModel: candidate.appModel, catalog: candidate.catalog },
|
|
425
|
-
)
|
|
443
|
+
).comparison
|
|
426
444
|
: undefined;
|
|
427
445
|
|
|
428
446
|
const { results, violated, inconclusive } = evaluateBudgets({
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The textbook median of values already sorted ascending: the middle value for an odd count, the
|
|
2
|
+
// mean of the two middle values for an even one. 0 for an empty list.
|
|
3
|
+
export function medianOfSorted(sorted ) {
|
|
4
|
+
const n = sorted.length;
|
|
5
|
+
if (n === 0) return 0;
|
|
6
|
+
const mid = n >> 1;
|
|
7
|
+
return n % 2 === 1 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
8
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Flattens a resolved planTree into a { nodes, edges } graph shape for the
|
|
2
2
|
// React Flow + Dagre plan graph view ("Plan graph view" in
|
|
3
|
-
// docs-site/contributor-guide/architecture/drill-down.md). The Exchange
|
|
3
|
+
// docs-site/contributor-guide/architecture/drill-down/plan-graph.md). The Exchange
|
|
4
4
|
// write/read split is done upstream now, in resolvePlanTree
|
|
5
5
|
// (event-handlers.ts): this file just walks the already-split tree and maps
|
|
6
6
|
// each real PlanNode onto a PlanGraphNodeData. sourceNodeId still lets a
|
|
@@ -44,6 +44,17 @@ function pushLongFilterWarning(result , len ) {
|
|
|
44
44
|
result.warnings.push({ type: 'longFilterCondition', detail: `Filter condition is ${len} characters: consider simplifying or pushing the filter down closer to the scan.` });
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// True when a node reads internal Delta metadata: the _delta_log state scan or an anonymous
|
|
48
|
+
// commit/checkpoint read. `Location:` is often truncated to `Del...`, so it also keys off
|
|
49
|
+
// DeltaLogFileIndex or the Delta-log action column signature. (Column-signature heuristic: a
|
|
50
|
+
// future Spark renaming those action columns would leak a noise row; acceptable.) Shared by
|
|
51
|
+
// `scanRelationId` and the comparison's Delta bookkeeping flag (stage-alignment.ts).
|
|
52
|
+
export function isDeltaLogRead(name , detail ) {
|
|
53
|
+
return /_delta_log/.test(name) || /_delta_log/.test(detail) ||
|
|
54
|
+
/DeltaLogFileIndex/i.test(name + detail) ||
|
|
55
|
+
/checkpointMetadata#|sidecar#|commitInfo#/.test(name + detail);
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
// Stable relation-identity key for a scan node: "<format>:<relation>" (e.g.
|
|
48
59
|
// "delta:mx.store_map", "parquet:warehouse.sales", "jdbc:dw.dim_product")
|
|
49
60
|
// or null when the node is internal Delta metadata / a non-scan / un-nameable.
|
|
@@ -70,16 +81,8 @@ export function scanRelationId(name , detail ) {
|
|
|
70
81
|
return `${format}:${table.replace(/^spark_catalog\./, '')}`;
|
|
71
82
|
}
|
|
72
83
|
|
|
73
|
-
// 2. Internal Delta metadata (
|
|
74
|
-
|
|
75
|
-
// so also key off DeltaLogFileIndex or the Delta-log action column signature.
|
|
76
|
-
// (Column-signature heuristic: a future Spark renaming those action columns
|
|
77
|
-
// would leak a noise row; acceptable.)
|
|
78
|
-
if (/_delta_log/.test(name) || /_delta_log/.test(detail) ||
|
|
79
|
-
/DeltaLogFileIndex/i.test(name + detail) ||
|
|
80
|
-
/checkpointMetadata#|sidecar#|commitInfo#/.test(name + detail)) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
84
|
+
// 2. Internal Delta metadata (see isDeltaLogRead) → null.
|
|
85
|
+
if (isDeltaLogRead(name, detail)) return null;
|
|
83
86
|
|
|
84
87
|
// 3. Anonymous real file read (InMemoryFileIndex, no catalog name) →
|
|
85
88
|
// best-effort Location basename. Preserves temp/snapshot self-reuse; a
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { computeWallClock } from './wall-clock.js';
|
|
2
2
|
import { normalizeDetail } from './detectors.js';
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeStageName, stageIdentityWith, identityIndexWith, pairEqualCounts } from './stage-identity.js';
|
|
4
4
|
import { computeAllocation } from './allocation.js';
|
|
5
5
|
import { totalExecutorCpuMs, withEarlierAttempts } from './run-totals.js';
|
|
6
|
-
import { planNodesOfStage } from './stage-plan-nodes.js';
|
|
7
6
|
import { captureSnapshot } from './session-snapshot.js';
|
|
7
|
+
import { alignStages, compileNormalizePatterns, COMPARISON_SCHEMA_VERSION, } from './stage-alignment.js';
|
|
8
8
|
import { tunedRunNote } from './threshold-overrides.js';
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
import { isIncompleteRun } from './check-coverage.js';
|
|
@@ -21,13 +21,27 @@ import { summarizeRunOutcome } from './run-outcome.js';
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
28
42
|
|
|
29
43
|
|
|
30
|
-
|
|
44
|
+
|
|
31
45
|
|
|
32
46
|
|
|
33
47
|
|
|
@@ -41,68 +55,10 @@ function jobOutcome(snapshot ) {
|
|
|
41
55
|
return { failedJobs, totalJobs, incomplete: isIncompleteRun(snapshot.catalog) };
|
|
42
56
|
}
|
|
43
57
|
|
|
44
|
-
|
|
45
|
-
// the same logical stage across two runs normalizes to one identity.
|
|
46
|
-
export function normalizeStageName(name ) {
|
|
47
|
-
return String(name)
|
|
48
|
-
.toLowerCase()
|
|
49
|
-
.replace(/\b[0-9a-f]{8,}\b/g, '#') // hex ids/uuids first (they contain digits)
|
|
50
|
-
.replace(/\d+/g, '#')
|
|
51
|
-
.replace(/\s+/g, ' ')
|
|
52
|
-
.trim();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Bottom-up, order-independent structural identity of a resolved plan tree:
|
|
56
|
-
// each node folds its normalized name/detail with its children's digests
|
|
57
|
-
// (children sorted, so AQE picking a different broadcast side still matches),
|
|
58
|
-
// so two plans collide only when their whole shape and every node's detail
|
|
59
|
-
// agree. `normalizeDetail` strips the run-to-run noise (expr ids, `plan_id=`,
|
|
60
|
-
// codegen numbers, AQE build-side choice, commutative-operand order). Each
|
|
61
|
-
// node folds to a fixed-length cyrb53 digest (JSON.stringify-encoded, so
|
|
62
|
-
// detail text containing `<`/`>`/`{`/`,` can't collide two different plans)
|
|
63
|
-
// instead of embedding full child identity strings, which would re-escape
|
|
64
|
-
// every level below and blow identity size up to ~2^depth on the 20-60+
|
|
65
|
-
// operator-deep plans real Spark produces.
|
|
66
|
-
function planTreeIdentity(root ) {
|
|
67
|
-
if (!root) return null;
|
|
68
|
-
function visit(node ) {
|
|
69
|
-
const childDigests = (node.children ?? []).map(visit).sort();
|
|
70
|
-
return cyrb53(JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? ''), childDigests]));
|
|
71
|
-
}
|
|
72
|
-
return visit(root);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Plan identity for the stage's SQL execution, scoped to only the plan nodes
|
|
76
|
-
// this stage actually ran (`node.stageIds`), not the whole tree: two stages
|
|
77
|
-
// sharing one SQL execution (e.g. a self-join's two Exchange stages) otherwise
|
|
78
|
-
// collapse onto one identity regardless of which part of the plan each ran.
|
|
79
|
-
// Falls back to the coarser whole-tree identity when the stage has no
|
|
80
|
-
// attributed nodes (hand-built snapshots without `stageIds`, or unmatched
|
|
81
|
-
// accumulables).
|
|
82
|
-
function sqlNodeIdentity(stage , snapshot ) {
|
|
83
|
-
const execId = stage.sqlExecutionId;
|
|
84
|
-
if (execId == null) return '';
|
|
85
|
-
const root = snapshot.sql.get(execId)?.planTree ?? null;
|
|
86
|
-
if (!root) return '';
|
|
87
|
-
const fingerprints = planNodesOfStage(stage, snapshot.sql)
|
|
88
|
-
.map((node) => JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? '')]));
|
|
89
|
-
if (fingerprints.length === 0) return planTreeIdentity(root) ?? '';
|
|
90
|
-
return cyrb53(JSON.stringify(fingerprints.sort()));
|
|
91
|
-
}
|
|
58
|
+
export { normalizeStageName };
|
|
92
59
|
|
|
93
60
|
export function stageIdentity(stage , snapshot ) {
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function identityIndex(snapshot ) {
|
|
98
|
-
const byIdentity = new Map ();
|
|
99
|
-
for (const [id, stage] of snapshot.stages) {
|
|
100
|
-
const key = stageIdentity(stage, snapshot);
|
|
101
|
-
const ids = byIdentity.get(key);
|
|
102
|
-
if (ids) ids.push(id);
|
|
103
|
-
else byIdentity.set(key, [id]);
|
|
104
|
-
}
|
|
105
|
-
return byIdentity;
|
|
61
|
+
return stageIdentityWith(stage, snapshot, normalizeDetail);
|
|
106
62
|
}
|
|
107
63
|
|
|
108
64
|
export function matchStages(baseSnap , candSnap )
|
|
@@ -111,10 +67,8 @@ export function matchStages(baseSnap , candSnap
|
|
|
111
67
|
|
|
112
68
|
|
|
113
69
|
{
|
|
114
|
-
const baseIdx =
|
|
115
|
-
const candIdx =
|
|
116
|
-
const pairs = [];
|
|
117
|
-
const matchedIdentities = new Set ();
|
|
70
|
+
const baseIdx = identityIndexWith(baseSnap, (stage) => stageIdentity(stage, baseSnap));
|
|
71
|
+
const candIdx = identityIndexWith(candSnap, (stage) => stageIdentity(stage, candSnap));
|
|
118
72
|
const collisionIdentities = new Set ();
|
|
119
73
|
// A collision is a single-run property: more than one stage in the SAME run
|
|
120
74
|
// shares an identity. Recorded per-run regardless of whether the other run
|
|
@@ -122,26 +76,7 @@ export function matchStages(baseSnap , candSnap
|
|
|
122
76
|
// don't, so this set means "was ambiguous", not "stayed unpaired".
|
|
123
77
|
for (const idx of [baseIdx, candIdx])
|
|
124
78
|
for (const [identity, ids] of idx) if (ids.length > 1) collisionIdentities.add(identity);
|
|
125
|
-
|
|
126
|
-
// *count*, so pair its stages off positionally by sorted id rather than
|
|
127
|
-
// dropping them. This is exact when the identity actually distinguishes
|
|
128
|
-
// stages (e.g. self-comparing a run: every stage matches itself). It's a
|
|
129
|
-
// best-effort guess when the identity is coarse (no SQL/attribution) and
|
|
130
|
-
// the two sides are genuinely different runs -- two unrelated same-named
|
|
131
|
-
// stages could get cross-paired. Accepted tradeoff: dropping them instead
|
|
132
|
-
// would also sacrifice the exact-self-comparison case, which matters more.
|
|
133
|
-
for (const identity of [...baseIdx.keys()].sort()) {
|
|
134
|
-
const candIds = candIdx.get(identity);
|
|
135
|
-
if (!candIds) continue;
|
|
136
|
-
const baseIds = baseIdx.get(identity) ;
|
|
137
|
-
if (baseIds.length !== candIds.length) continue;
|
|
138
|
-
const sortedBaseIds = [...baseIds].sort((a, b) => a - b);
|
|
139
|
-
const sortedCandIds = [...candIds].sort((a, b) => a - b);
|
|
140
|
-
for (let i = 0; i < sortedBaseIds.length; i++) {
|
|
141
|
-
pairs.push({ identity, baseId: sortedBaseIds[i], candId: sortedCandIds[i] });
|
|
142
|
-
}
|
|
143
|
-
matchedIdentities.add(identity);
|
|
144
|
-
}
|
|
79
|
+
const { pairs, matchedIdentities } = pairEqualCounts(baseIdx, candIdx);
|
|
145
80
|
const total = baseSnap.stages.size + candSnap.stages.size;
|
|
146
81
|
// Both runs stage-less: nothing to compare, not "nothing matched".
|
|
147
82
|
const coverage = total === 0 ? 1 : (2 * pairs.length) / total;
|
|
@@ -348,19 +283,19 @@ function namesConflict(a , b )
|
|
|
348
283
|
}
|
|
349
284
|
|
|
350
285
|
// A pair's skew ratio is null when the stage's duration wasn't measurable (see
|
|
351
|
-
// `stageSkewRatio`).
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}).sort((x, y) => x.
|
|
286
|
+
// `stageSkewRatio`). Rows come from the aligner's `stagePairs`, so the table and the coverage
|
|
287
|
+
// banner agree. A pair covers one stage per side here; `pairId` is the unique row key.
|
|
288
|
+
function stageSkewDeltas(baseSnap , candSnap , stagePairs ) {
|
|
289
|
+
return stagePairs.map((p) => {
|
|
290
|
+
const baseId = p.baseStageIds[0], candId = p.candStageIds[0];
|
|
291
|
+
const baseStage = baseSnap.stages.get(baseId) ;
|
|
292
|
+
const b = stageSkewRatio(baseStage);
|
|
293
|
+
const c = stageSkewRatio(candSnap.stages.get(candId) );
|
|
294
|
+
return {
|
|
295
|
+
pairId: p.pairId, name: normalizeStageName(baseStage.name ?? ''), baseId, candId,
|
|
296
|
+
baseline: b, candidate: c, delta: b != null && c != null ? c - b : null,
|
|
297
|
+
};
|
|
298
|
+
}).sort((x, y) => x.name < y.name ? -1 : x.name > y.name ? 1 : x.baseId - y.baseId);
|
|
364
299
|
}
|
|
365
300
|
|
|
366
301
|
// Compact, view-friendly per-stage record for the manual stage-pinning panel.
|
|
@@ -402,6 +337,14 @@ function stageList(snap )
|
|
|
402
337
|
// caller's reference, so one shared empty Map is safe here.
|
|
403
338
|
const EMPTY_TASK_DATA = new Map ();
|
|
404
339
|
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
405
348
|
// Wraps the analyze()-to-compareRuns() snapshot-building sequence shared by
|
|
406
349
|
// the CLI's --baseline path and mcp-tools.ts's compareRuns tool: both need
|
|
407
350
|
// captureSnapshot (with an empty taskDataCache, the interactive drill-down
|
|
@@ -410,50 +353,66 @@ const EMPTY_TASK_DATA = new Map ();
|
|
|
410
353
|
export function buildComparison(
|
|
411
354
|
baseline ,
|
|
412
355
|
candidate ,
|
|
356
|
+
options ,
|
|
413
357
|
) {
|
|
414
358
|
return compareRuns(
|
|
415
359
|
{ label: baseline.label, snapshot: captureSnapshot(baseline.appModel, baseline.catalog, EMPTY_TASK_DATA) },
|
|
416
360
|
{ label: candidate.label, snapshot: captureSnapshot(candidate.appModel, candidate.catalog, EMPTY_TASK_DATA) },
|
|
361
|
+
options,
|
|
417
362
|
);
|
|
418
363
|
}
|
|
419
364
|
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
|
|
424
|
-
// comparison is dominated by guesswork, not genuine pairing. 0.5 is thus the
|
|
425
|
-
// natural midpoint for "more matched than not," not an arbitrary tuning knob.
|
|
426
|
-
const LOW_COVERAGE_THRESHOLD = 0.5;
|
|
365
|
+
// `ok` needs this share of both runs' executor run time to sit in paired stages. Run time, not
|
|
366
|
+
// stage count: the stages a run spends its time in decide whether a per-stage delta compares the
|
|
367
|
+
// same work, and a count lets many small matched stages hide an unmatched heavy one.
|
|
368
|
+
const RUNTIME_COVERAGE_THRESHOLD = 0.9;
|
|
427
369
|
|
|
428
370
|
export function compareRuns(
|
|
429
371
|
baseline ,
|
|
430
372
|
candidate ,
|
|
373
|
+
options = {},
|
|
431
374
|
) {
|
|
432
375
|
const baseSnap = baseline.snapshot, candSnap = candidate.snapshot;
|
|
376
|
+
// The old exact matcher stays the source of `matchedCoverage`; pairs and the gate come from the aligner.
|
|
433
377
|
const match = matchStages(baseSnap, candSnap);
|
|
378
|
+
const alignment = alignStages(baseSnap, candSnap, { normalizePath: compileNormalizePatterns(options.normalizePath) });
|
|
379
|
+
const runtimeCoverage = alignment.runtimeCoverage;
|
|
434
380
|
// Name equality is a weak confidence signal, not a hard gate: renaming a job
|
|
435
381
|
// is the normal way to label an A/B experiment, so a mismatch must not block
|
|
436
382
|
// the (matching-free, name-independent) deltas. Surface it as `low` instead.
|
|
437
383
|
const namesDiffer = namesConflict(baseSnap.app, candSnap.app);
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
|
|
384
|
+
// Runtime coverage is the other half of the signal: identical names on two runs whose heavy
|
|
385
|
+
// stages do not pair are just as misleading as differing names on two runs that pair well, so
|
|
386
|
+
// either condition alone drops confidence to `low`. No run time at all is `insufficient`.
|
|
387
|
+
const insufficient = runtimeCoverage === null;
|
|
388
|
+
const lowCoverage = runtimeCoverage !== null && runtimeCoverage < RUNTIME_COVERAGE_THRESHOLD;
|
|
389
|
+
const confidence = insufficient ? 'insufficient' : namesDiffer || lowCoverage ? 'low' : 'ok';
|
|
390
|
+
// Rounded down, so a share just under the gate never reads as 90%.
|
|
391
|
+
const share = runtimeCoverage === null ? '' : `${Math.floor(runtimeCoverage * 100)}%`;
|
|
392
|
+
const reason = insufficient
|
|
393
|
+
? `${namesDiffer ? 'Run names differ and no' : 'No'} executor run time was recorded in either run, so there is no work to compare.`
|
|
394
|
+
: namesDiffer && lowCoverage
|
|
395
|
+
? `Run names differ and only ${share} of executor run time is in matched stages, so deltas may compare different work.`
|
|
444
396
|
: namesDiffer
|
|
445
397
|
? 'Run names differ, so deltas may compare different work.'
|
|
446
398
|
: lowCoverage
|
|
447
|
-
? `Only ${
|
|
399
|
+
? `Only ${share} of executor run time is in matched stages, so per-stage rows mostly compare unrelated work.`
|
|
448
400
|
: null;
|
|
449
401
|
return {
|
|
450
402
|
baselineLabel: baseline.label, candidateLabel: candidate.label,
|
|
451
|
-
confidence
|
|
403
|
+
confidence,
|
|
452
404
|
reason,
|
|
453
405
|
matchedCoverage: match.coverage,
|
|
406
|
+
comparisonSchemaVersion: COMPARISON_SCHEMA_VERSION,
|
|
407
|
+
runtimeCoverage,
|
|
408
|
+
stagePairs: alignment.pairs,
|
|
409
|
+
unmatched: alignment.unmatched,
|
|
410
|
+
replanned: alignment.replanned,
|
|
411
|
+
bookkeepingStageIds: alignment.bookkeepingStageIds,
|
|
412
|
+
executionAlignment: alignment.executionAlignment,
|
|
454
413
|
metrics: metricDeltas(baseSnap, candSnap),
|
|
455
414
|
findings: findingsDelta(baseSnap, candSnap),
|
|
456
|
-
stageSkew: stageSkewDeltas(baseSnap, candSnap,
|
|
415
|
+
stageSkew: stageSkewDeltas(baseSnap, candSnap, alignment.pairs),
|
|
457
416
|
baseStages: stageList(baseSnap),
|
|
458
417
|
candStages: stageList(candSnap),
|
|
459
418
|
jobOutcomes: { baseline: jobOutcome(baseSnap), candidate: jobOutcome(candSnap) },
|
|
@@ -491,11 +450,13 @@ export function renderComparisonMarkdown(
|
|
|
491
450
|
if (verdict.sentences.length > 0) lines.push('', verdict.sentences.join(' '));
|
|
492
451
|
lines.push('');
|
|
493
452
|
}
|
|
494
|
-
if (comparison.confidence
|
|
495
|
-
lines.push(`- confidence:
|
|
453
|
+
if (comparison.confidence !== 'ok') {
|
|
454
|
+
lines.push(`- confidence: ${comparison.confidence}, ${comparison.reason}`);
|
|
496
455
|
lines.push('');
|
|
497
456
|
}
|
|
498
457
|
lines.push(`- matched stage coverage: ${(comparison.matchedCoverage * 100).toFixed(1)}%`);
|
|
458
|
+
lines.push(`- runtime coverage: ${comparison.runtimeCoverage === null ? 'n/a' : `${(comparison.runtimeCoverage * 100).toFixed(1)}%`}`);
|
|
459
|
+
lines.push(`- stage pairs: ${comparison.stagePairs.length}, unmatched: ${comparison.unmatched.baseStageIds.length} baseline / ${comparison.unmatched.candStageIds.length} candidate, Delta bookkeeping: ${comparison.bookkeepingStageIds.baseStageIds.length} baseline / ${comparison.bookkeepingStageIds.candStageIds.length} candidate`);
|
|
499
460
|
lines.push('');
|
|
500
461
|
lines.push('### Metric deltas');
|
|
501
462
|
lines.push('');
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { runFindings } from './evidence-report.js';
|
|
2
|
+
import { redactRunModel } from './redact.js';
|
|
3
|
+
import { computeRunMetrics, } from './run-metrics.js';
|
|
4
|
+
import { buildEffectiveConf, } from './effective-conf.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/** The CLI report's `metrics` and `effectiveConf` blocks, shared with MCP diagnose_run. Under
|
|
9
|
+
* `redact` they come from the run redacted with the report's own inputs, so host and app
|
|
10
|
+
* pseudonyms match the report's. `confKeys`/`confRedactRegex` narrow and mask `effectiveConf`. */
|
|
11
|
+
export function runOutputBlocks(
|
|
12
|
+
appModel ,
|
|
13
|
+
{ redact, thresholds, confKeys, confRedactRegex }
|
|
14
|
+
|
|
15
|
+
= {},
|
|
16
|
+
) {
|
|
17
|
+
let blocksModel = appModel;
|
|
18
|
+
if (redact) {
|
|
19
|
+
const { catalog, config } = runFindings(appModel, thresholds);
|
|
20
|
+
blocksModel = redactRunModel(appModel, catalog, config).appModel;
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
metrics: computeRunMetrics(blocksModel, thresholds),
|
|
24
|
+
effectiveConf: buildEffectiveConf(blocksModel.app, { keys: confKeys, userPattern: confRedactRegex }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Order-preserving sequence alignment: the weighted longest-common-subsequence dynamic program the
|
|
2
|
+
// stage aligner runs over each run's SQL executions. Pure and deterministic.
|
|
3
|
+
//
|
|
4
|
+
// Cost: the full table has n*m cells and one similarity call per cell. Above `FULL_ALIGNMENT_MAX_CELLS`
|
|
5
|
+
// the table is restricted to a band around the diagonal, which costs about n*(2W+1) cells with
|
|
6
|
+
// W = `BAND_BASE_HALF_WIDTH` + |n-m|/2, and the result reports that the bounded form ran.
|
|
7
|
+
|
|
8
|
+
/** Largest n*m the alignment runs in full (1000 x 1000 executions). */
|
|
9
|
+
export const FULL_ALIGNMENT_MAX_CELLS = 1_000_000;
|
|
10
|
+
/** Band half-width added to half of |n-m| once the table is bounded: the band is centered on the line
|
|
11
|
+
* from corner to corner, and a run of insertions or deletions in one place drifts the best path from
|
|
12
|
+
* that line by at most |n-m|/2. */
|
|
13
|
+
export const BAND_BASE_HALF_WIDTH = 100;
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/** Aligns two sequences of lengths `n` and `m`, maximizing the summed `score` of matched pairs. `score`
|
|
23
|
+
* returns a non-negative integer; a pair scoring below `minScore` is never matched. Of equally good
|
|
24
|
+
* alignments the one that matches whenever matching is optimal wins, and where it must skip, it skips the
|
|
25
|
+
* element with the higher id (`idA`, `idB`), so the lower ids stay and swapping the two sequences mirrors
|
|
26
|
+
* the pairs. */
|
|
27
|
+
export function alignSequences(
|
|
28
|
+
n , m ,
|
|
29
|
+
score ,
|
|
30
|
+
minScore ,
|
|
31
|
+
idA , idB ,
|
|
32
|
+
) {
|
|
33
|
+
if (n === 0 || m === 0) return { pairs: [], bounded: false };
|
|
34
|
+
const bounded = n * m > FULL_ALIGNMENT_MAX_CELLS;
|
|
35
|
+
const half = BAND_BASE_HALF_WIDTH + Math.ceil(Math.abs(n - m) / 2);
|
|
36
|
+
// Row i may match columns lo[i]..hi[i]; both bounds only grow with i.
|
|
37
|
+
const lo = new Int32Array(n), hi = new Int32Array(n);
|
|
38
|
+
for (let i = 0; i < n; i++) {
|
|
39
|
+
const center = Math.floor((i * m) / n);
|
|
40
|
+
lo[i] = bounded ? Math.max(0, center - half) : 0;
|
|
41
|
+
hi[i] = bounded ? Math.min(m - 1, center + half) : m - 1;
|
|
42
|
+
}
|
|
43
|
+
// best[i][j - lo[i]]: the best total from the suffixes starting at (i, j), for j inside row i's band.
|
|
44
|
+
const best = new Array(n);
|
|
45
|
+
// Cells outside the band are not stored: left of it the row's first cell stands in (those columns can
|
|
46
|
+
// only match in later rows, whose bands start no earlier), right of it the row cannot match, so the
|
|
47
|
+
// next row answers.
|
|
48
|
+
const get = (i , j ) => {
|
|
49
|
+
for (;;) {
|
|
50
|
+
if (i >= n || j >= m) return 0;
|
|
51
|
+
if (j < lo[i]) j = lo[i];
|
|
52
|
+
else if (j > hi[i]) i++;
|
|
53
|
+
else return best[i][j - lo[i]];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
// The caller memoizes `score`, so the walk below re-asking for visited cells is cheap.
|
|
57
|
+
const cell = score;
|
|
58
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
59
|
+
const row = new Int32Array(hi[i] - lo[i] + 1);
|
|
60
|
+
best[i] = row;
|
|
61
|
+
for (let j = hi[i]; j >= lo[i]; j--) {
|
|
62
|
+
let v = Math.max(get(i + 1, j), j + 1 <= hi[i] ? row[j + 1 - lo[i]] : get(i + 1, j + 1));
|
|
63
|
+
const s = cell(i, j);
|
|
64
|
+
if (s >= minScore) v = Math.max(v, s + get(i + 1, j + 1));
|
|
65
|
+
row[j - lo[i]] = v;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const pairs = [];
|
|
70
|
+
let i = 0, j = 0;
|
|
71
|
+
while (i < n && j < m) {
|
|
72
|
+
if (j < lo[i]) { j = lo[i]; continue; }
|
|
73
|
+
if (j > hi[i]) { i++; continue; }
|
|
74
|
+
const v = best[i][j - lo[i]];
|
|
75
|
+
const s = cell(i, j);
|
|
76
|
+
if (s >= minScore && s + get(i + 1, j + 1) === v) { pairs.push([i, j]); i++; j++; continue; }
|
|
77
|
+
const skipA = get(i + 1, j) === v, skipB = get(i, j + 1) === v;
|
|
78
|
+
if (skipA && skipB) { if (idA(i) >= idB(j)) i++; else j++; }
|
|
79
|
+
else if (skipA) i++;
|
|
80
|
+
else j++;
|
|
81
|
+
}
|
|
82
|
+
return { pairs, bounded };
|
|
83
|
+
}
|