sparkforensics-server 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +3 -0
- package/package.json +3 -3
- package/public/assets/AutoscalingChurn-5hDGIXi4.js +1 -0
- package/public/assets/CacheUtilization-CT2SCnJJ.js +1 -0
- package/public/assets/CachingOpportunity-D_O_WkbH.js +1 -0
- package/public/assets/ColdStart-B38OiAOp.js +1 -0
- package/public/assets/ConfigAudit-BoplryCH.js +1 -0
- package/public/assets/CoreUsageArea-csgwNxkl.js +1 -0
- package/public/assets/Dashboard-BgKodJl2.js +2 -0
- package/public/assets/DisclosureContext-B0siMVdg.js +1 -0
- package/public/assets/DuplicatePlanSubtree-BqS2A2j4.js +1 -0
- package/public/assets/ExecutorUtilization-Dp52QGWo.js +1 -0
- package/public/assets/GcPressure-CT1yzR_s.js +1 -0
- package/public/assets/IncompleteRun-C7Qkjtjn.js +1 -0
- package/public/assets/JobFailures-WHl3ELfh.js +1 -0
- package/public/assets/MemoryUtilization-D8IUDHJI.js +1 -0
- package/public/assets/OverBroadcast-6oSSAQ_R.js +1 -0
- package/public/assets/PartitionHint-CUvbnnxy.js +1 -0
- package/public/assets/PartitionSizing-BwdFL-mw.js +1 -0
- package/public/assets/{PlanExplorer-DdmPkKO1.js → PlanExplorer-DcxmiQWL.js} +1 -1
- package/public/assets/PlanGraphRoute-pqnHTt4i.js +1 -0
- package/public/assets/RetryWaste-BTbeHkJc.js +1 -0
- package/public/assets/ShuffleIO-CRhrYNE0.js +1 -0
- package/public/assets/Skew-DSNXRG9V.js +1 -0
- package/public/assets/SlowHost-B7Lwu2EU.js +1 -0
- package/public/assets/SmallFiles-C082c2rd.js +1 -0
- package/public/assets/SpeculationWaste-BS2vpadr.js +1 -0
- package/public/assets/Spill-B4DVKHXx.js +1 -0
- package/public/assets/StageFailed-D3ac9Sou.js +1 -0
- package/public/assets/StageFindingGroup-D9naK109.js +1 -0
- package/public/assets/{StageHeader-BSV8ALhT.js → StageHeader-pI1nTwVf.js} +1 -1
- package/public/assets/StageShape-eO5le8iG.js +1 -0
- package/public/assets/StageSlowness-BaNyfNvL.js +1 -0
- package/public/assets/Straggler-D5Fnok_y.js +1 -0
- package/public/assets/TaskFailures-BWnC-K_G.js +1 -0
- package/public/assets/{TinyTask-CIPRxWfT.js → TinyTask-8_5lpq_2.js} +1 -1
- package/public/assets/UnderBroadcast-DJ0XSwth.js +1 -0
- package/public/assets/{finding-anchor-DF1cSHO1.js → finding-anchor-Cys8Z6Yn.js} +1 -1
- package/public/assets/{impact-sort-7z0ZcBZf.js → impact-sort-BTQndbof.js} +1 -1
- package/public/assets/index-DCeWge12.js +13 -0
- package/public/assets/parser-worker-DmeCnLWU.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-Cy5pmbrh.js → select-DIYN6D3y.js} +1 -1
- package/public/assets/{usePagedRows-BjPOpUU8.js → usePagedRows-BxvdFnPk.js} +1 -1
- package/public/assets/useRecentFiles-C3Ov47U7.js +11 -0
- package/public/assets/{useSortMode-CU0vzThu.js → useSortMode-B8SRfAoG.js} +1 -1
- package/public/docs/404.html +4 -3
- package/public/docs/assets/{app.C6w47a9W.js → app.D2GqAkhy.js} +1 -1
- package/public/docs/assets/chunks/@localSearchIndexroot.D4OdkQjy.js +1 -0
- package/public/docs/assets/chunks/{VPLocalSearchBox.BcTNvT55.js → VPLocalSearchBox.BS4cSmtW.js} +1 -1
- package/public/docs/assets/chunks/{theme.D-mE6j02.js → theme.BuxVpb0H.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.YQcj5EI4.lean.js +1 -0
- 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.fkPBXsU2.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.fkPBXsU2.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.tymxf1Fh.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.lean.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.DOTVHjTV.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.uuAKpY0Y.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.uuAKpY0Y.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.Dk08VS7n.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.Dk08VS7n.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/contributor-guide_contributing.md.CQMvGwFt.js +1 -0
- package/public/docs/assets/{contributor-guide_contributing.md.B2E9ni79.lean.js → contributor-guide_contributing.md.CQMvGwFt.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_development-setup.md.D9bu4j_c.js +15 -0
- package/public/docs/assets/contributor-guide_development-setup.md.D9bu4j_c.lean.js +1 -0
- package/public/docs/assets/contributor-guide_testing.md.By6XGR9a.js +1 -0
- package/public/docs/assets/{contributor-guide_testing.md.DTwhpoN2.lean.js → contributor-guide_testing.md.By6XGR9a.lean.js} +1 -1
- package/public/docs/assets/{style.DSixAiZE.css → style.tmDhD3c8.css} +1 -1
- 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.DpnfKz3x.js +22 -0
- package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.DpnfKz3x.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.CPowlb2q.js +55 -0
- package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.CPowlb2q.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.Dq5Azk8G.js +26 -0
- package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.Dq5Azk8G.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.B6aNtDYF.js +1 -0
- package/public/docs/assets/user-guide_run-comparison.md.B6aNtDYF.lean.js +1 -0
- package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.js +1 -0
- package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.XBzQ1oEU.lean.js +1 -0
- package/public/docs/assets/user-guide_understanding-findings.md.Dqt2H_C1.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 +7 -6
- 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 +7 -6
- package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
- package/public/docs/contributor-guide/architecture/drill-down.html +7 -6
- 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 +7 -6
- package/public/docs/contributor-guide/architecture/index.html +7 -6
- package/public/docs/contributor-guide/architecture/overview.html +7 -6
- 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 +7 -6
- 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 +7 -6
- 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 +7 -11
- package/public/docs/contributor-guide/contributing.html +7 -6
- package/public/docs/contributor-guide/development-setup.html +11 -10
- package/public/docs/contributor-guide/testing.html +7 -6
- package/public/docs/hashmap.json +1 -1
- package/public/docs/index.html +5 -4
- package/public/docs/tuning-reference/anti-patterns.html +5 -4
- package/public/docs/tuning-reference/aqe.html +5 -4
- package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +5 -4
- package/public/docs/tuning-reference/bottleneck-cold-start.html +5 -4
- package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +5 -4
- package/public/docs/tuning-reference/bottleneck-failures.html +5 -4
- package/public/docs/tuning-reference/bottleneck-gc.html +5 -4
- package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +5 -4
- package/public/docs/tuning-reference/bottleneck-memory-utilization.html +5 -4
- package/public/docs/tuning-reference/bottleneck-retry-waste.html +5 -4
- package/public/docs/tuning-reference/bottleneck-shuffle.html +5 -4
- package/public/docs/tuning-reference/bottleneck-skew.html +5 -4
- package/public/docs/tuning-reference/bottleneck-slow-host.html +5 -4
- package/public/docs/tuning-reference/bottleneck-small-files.html +5 -4
- package/public/docs/tuning-reference/bottleneck-spill.html +5 -4
- package/public/docs/tuning-reference/bottleneck-straggler.html +5 -4
- package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +5 -4
- package/public/docs/tuning-reference/bottleneck-utilization.html +5 -4
- package/public/docs/tuning-reference/caching.html +5 -4
- package/public/docs/tuning-reference/cluster-config.html +5 -4
- package/public/docs/tuning-reference/config.html +5 -4
- package/public/docs/tuning-reference/data-formats.html +5 -4
- package/public/docs/tuning-reference/index.html +5 -4
- package/public/docs/tuning-reference/intro.html +5 -4
- package/public/docs/tuning-reference/joins.html +5 -4
- package/public/docs/tuning-reference/memory-model.html +5 -4
- package/public/docs/tuning-reference/metrics.html +5 -4
- package/public/docs/tuning-reference/partitioning.html +5 -4
- package/public/docs/tuning-reference/pyspark.html +5 -4
- package/public/docs/tuning-reference/shuffle.html +5 -4
- package/public/docs/tuning-reference/spark-architecture.html +5 -4
- package/public/docs/tuning-reference/table-formats.html +5 -4
- 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 +11 -7
- package/public/docs/user-guide/getting-started/ci-and-automation.html +51 -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 +7 -11
- package/public/docs/user-guide/mcp-tools/compare-runs.html +84 -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 +12 -126
- package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
- package/public/docs/user-guide/run-comparison.html +7 -6
- package/public/docs/user-guide/understanding-findings.html +7 -6
- package/public/export-template.html +44 -44
- package/public/index.html +4 -4
- package/vendor-core/allocation.js +106 -0
- package/vendor-core/analyzer.js +13 -13
- package/vendor-core/api-merge-writes.js +80 -0
- package/vendor-core/cli/budgets.js +23 -9
- package/vendor-core/cli/collect-run.js +11 -4
- package/vendor-core/cli/regression-budgets.js +83 -0
- package/vendor-core/comparison-output.js +49 -0
- package/vendor-core/comparison-verdict.js +22 -22
- 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 +202 -68
- package/vendor-core/docs-content/detection/cache.md +3 -2
- package/vendor-core/docs-content/detection/cfg.md +9 -8
- package/vendor-core/docs-content/detection/chrn.md +1 -2
- package/vendor-core/docs-content/detection/cold.md +4 -2
- package/vendor-core/docs-content/detection/fail.md +3 -2
- package/vendor-core/docs-content/detection/gc.md +3 -2
- package/vendor-core/docs-content/detection/host.md +2 -1
- package/vendor-core/docs-content/detection/local.md +1 -1
- package/vendor-core/docs-content/detection/mem.md +5 -2
- package/vendor-core/docs-content/detection/plan.md +2 -1
- package/vendor-core/docs-content/detection/sfail.md +2 -1
- package/vendor-core/docs-content/detection/shape.md +5 -4
- package/vendor-core/docs-content/detection/skew.md +3 -1
- package/vendor-core/docs-content/detection/slow.md +2 -2
- package/vendor-core/docs-content/detection/spec.md +2 -3
- package/vendor-core/docs-content/detection/spill.md +1 -1
- package/vendor-core/docs-site-config.js +1 -1
- package/vendor-core/effective-conf.js +107 -0
- package/vendor-core/efficiency-model.js +8 -6
- package/vendor-core/event-handlers.js +255 -56
- package/vendor-core/event-schemas.js +5 -1
- package/vendor-core/evidence-report.js +31 -14
- package/vendor-core/finding-generic-recommendation.js +20 -1
- package/vendor-core/finding-names.js +7 -0
- package/vendor-core/finding-presentation.js +61 -26
- package/vendor-core/finding-tag-help.js +1 -1
- package/vendor-core/finding-types.js +12 -0
- package/vendor-core/format-utils.js +4 -3
- package/vendor-core/impact-estimator.js +20 -2
- package/vendor-core/impact-format.js +14 -13
- package/vendor-core/impact-model.js +27 -5
- package/vendor-core/ingest.js +4 -2
- package/vendor-core/list-runs.js +5 -2
- package/vendor-core/mcp-server-factory.js +13 -6
- package/vendor-core/mcp-tools.js +43 -34
- package/vendor-core/model-assembler.js +23 -1
- package/vendor-core/parser-worker.js +1 -1
- package/vendor-core/plan-graph-model.js +1 -1
- package/vendor-core/plan-summary.js +13 -10
- package/vendor-core/proxy.js +3 -1
- package/vendor-core/python-stage.js +25 -0
- package/vendor-core/recommendation-rollup.js +16 -9
- package/vendor-core/redact.js +51 -10
- package/vendor-core/remediation.js +20 -0
- package/vendor-core/run-comparison.js +118 -140
- package/vendor-core/run-interpretation.js +2 -1
- package/vendor-core/run-metrics.js +198 -0
- package/vendor-core/run-output.js +26 -0
- package/vendor-core/run-totals.js +24 -0
- package/vendor-core/run-verdict.js +3 -4
- package/vendor-core/scorecard-estimates.js +1 -0
- package/vendor-core/session-snapshot.js +7 -0
- package/vendor-core/shs-schemas.js +2 -2
- package/vendor-core/spark-memory.js +17 -0
- package/vendor-core/stage-alignment.js +262 -0
- package/vendor-core/stage-identity.js +112 -0
- package/vendor-core/stage-plan-nodes.js +18 -0
- package/vendor-core/stage-quantiles.js +4 -0
- package/vendor-core/types.js +54 -1
- package/vendor-core/wasted-core-hours.js +10 -7
- package/vendor-core/write-target-args.js +48 -0
- package/vendor-core/write-targets.js +295 -0
- package/public/assets/AutoscalingChurn-CUSIvvab.js +0 -1
- package/public/assets/CacheUtilization-Ce0K6_X2.js +0 -1
- package/public/assets/CachingOpportunity-Cn-LmRA4.js +0 -1
- package/public/assets/ColdStart-BoiYzEyV.js +0 -1
- package/public/assets/ConfigAudit-BoJc6PAw.js +0 -1
- package/public/assets/CoreUsageArea-BFfXKu7K.js +0 -1
- package/public/assets/Dashboard-Bo_1jGqH.js +0 -2
- package/public/assets/DisclosureContext-LYzHroHO.js +0 -1
- package/public/assets/DuplicatePlanSubtree-BaAuGAoo.js +0 -1
- package/public/assets/ExecutorUtilization-OE6VMFkG.js +0 -1
- package/public/assets/GcPressure-AoL_ZNrV.js +0 -1
- package/public/assets/IncompleteRun-Bv6IzgsQ.js +0 -1
- package/public/assets/JobFailures-DF3U2z1f.js +0 -1
- package/public/assets/MemoryUtilization-B65OVZlq.js +0 -1
- package/public/assets/OverBroadcast-DXbJREDl.js +0 -1
- package/public/assets/PartitionSizing-DZLnjy0d.js +0 -1
- package/public/assets/PlanGraphRoute-C-BGmC6j.js +0 -1
- package/public/assets/RetryWaste-BbEPsdMw.js +0 -1
- package/public/assets/ShuffleIO-wZB94LS8.js +0 -1
- package/public/assets/Skew-SpQE7zeJ.js +0 -1
- package/public/assets/SlowHost-DwDBCTvr.js +0 -1
- package/public/assets/SmallFiles-yjY_6sT1.js +0 -1
- package/public/assets/SpeculationWaste-73PDmj0c.js +0 -1
- package/public/assets/Spill-DEw0Kd1i.js +0 -1
- package/public/assets/StageFailed-BEfXdEJc.js +0 -1
- package/public/assets/StageFindingGroup-Bzm2TY03.js +0 -1
- package/public/assets/StageShape-CzR4jHOt.js +0 -1
- package/public/assets/StageSlowness-vMPsnAaa.js +0 -1
- package/public/assets/Straggler-Cf__OGl5.js +0 -1
- package/public/assets/TaskFailures-BZ8kLZiO.js +0 -1
- package/public/assets/UnderBroadcast-CBm8ZPoS.js +0 -1
- package/public/assets/index-CEM9bz_x.js +0 -13
- package/public/assets/parser-worker-Q5KCPdKT.js +0 -122
- package/public/assets/useRecentFiles-BrBl-AP1.js +0 -11
- package/public/docs/assets/chunks/@localSearchIndexroot.DbZy5EtM.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.VTcYTH4d.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.VTcYTH4d.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CsqZHz_T.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CsqZHz_T.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CFNCE8JB.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CFNCE8JB.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.HT9IJGIl.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.HT9IJGIl.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.CTEqVeV4.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.CTEqVeV4.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DOTVHjTV.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BmpARZve.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BmpARZve.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.21pTYhFF.js +0 -6
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.21pTYhFF.lean.js +0 -1
- package/public/docs/assets/contributor-guide_contributing.md.B2E9ni79.js +0 -1
- package/public/docs/assets/contributor-guide_development-setup.md.CjgZJwjK.js +0 -15
- package/public/docs/assets/contributor-guide_development-setup.md.CjgZJwjK.lean.js +0 -1
- package/public/docs/assets/contributor-guide_testing.md.DTwhpoN2.js +0 -1
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.ZHPSbBkF.js +0 -1
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.ZHPSbBkF.lean.js +0 -1
- package/public/docs/assets/user-guide_getting-started.md.CEa2ZX-l.js +0 -6
- package/public/docs/assets/user-guide_getting-started.md.CEa2ZX-l.lean.js +0 -1
- package/public/docs/assets/user-guide_mcp-tools.md.dQwIvdTF.js +0 -131
- package/public/docs/assets/user-guide_mcp-tools.md.dQwIvdTF.lean.js +0 -1
- package/public/docs/assets/user-guide_run-comparison.md.ChLI3MK0.js +0 -1
- package/public/docs/assets/user-guide_run-comparison.md.ChLI3MK0.lean.js +0 -1
- package/public/docs/assets/user-guide_understanding-findings.md.D1ccs60C.js +0 -1
- /package/public/docs/assets/{user-guide_understanding-findings.md.D1ccs60C.lean.js → user-guide_understanding-findings.md.Dqt2H_C1.lean.js} +0 -0
|
@@ -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,24 @@
|
|
|
1
|
+
// The run-level sums every surface reads: the CLI metrics block and the run comparison (so the
|
|
2
|
+
// dashboard's comparison view and the MCP compare_runs tool) share these, so one name never means
|
|
3
|
+
// two figures.
|
|
4
|
+
import { nsToMs } from './format-utils.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/** Each stage followed by the work its own figures leave out, shaped like a stage: the attempts a
|
|
8
|
+
* resubmit replaced, and task attempts the stage record dropped (a failed attempt's late tasks,
|
|
9
|
+
* failed retries, losing speculative copies). Summing the result counts every attempt's work,
|
|
10
|
+
* failed ones too. A stage's own record, which the detectors and per-stage views read, is left as
|
|
11
|
+
* it is. */
|
|
12
|
+
export function withEarlierAttempts(stages ) {
|
|
13
|
+
return stages.flatMap((s) => [s, ...[s.earlierAttempts, s.lateAttemptWork]
|
|
14
|
+
.filter((work) => work != null)
|
|
15
|
+
.map(({ durationMs, ...totals }) => ({ id: s.id, ...totals, submittedAt: 0, completedAt: durationMs ?? undefined }))]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Summed executor CPU time in ms. Spark records it in nanoseconds and the parser reads an absent
|
|
19
|
+
* metric as 0, so stages that all read 0 never recorded it (older Spark): null, not 0. */
|
|
20
|
+
export function totalExecutorCpuMs(stages ) {
|
|
21
|
+
let ns = 0;
|
|
22
|
+
for (const s of stages) if (typeof s.executorCpuTime === 'number' && Number.isFinite(s.executorCpuTime) && s.executorCpuTime > 0) ns += s.executorCpuTime;
|
|
23
|
+
return ns > 0 ? nsToMs(ns) : null;
|
|
24
|
+
}
|
|
@@ -236,13 +236,12 @@ export function verdictSummary(eligible , steps , facts
|
|
|
236
236
|
} else {
|
|
237
237
|
sentences.push(`${plural(eligible.length, 'finding')} in ${plural(steps.length, 'place')}.`);
|
|
238
238
|
const wallClock = steps[0].lead.impactEstimate?.wallClock;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
} else if (wallClock && facts.runMs != null) {
|
|
239
|
+
// An idle-capacity lead needs no sentence here: the title gives the idle share and step 1 the fix.
|
|
240
|
+
if (!isIdleCapacityStep(steps[0]) && wallClock && facts.runMs != null) {
|
|
242
241
|
sentences.push(`The first fix could save up to ${formatDuration(wallClock.high)} of this ${formatDuration(facts.runMs)} run.`);
|
|
243
242
|
}
|
|
244
243
|
if (steps.some((step) => step.related.length > 0)) {
|
|
245
|
-
sentences.push('Findings
|
|
244
|
+
sentences.push('Findings on one stage are grouped, and their savings overlap.');
|
|
246
245
|
}
|
|
247
246
|
}
|
|
248
247
|
if (facts.incomplete) {
|
|
@@ -24,6 +24,8 @@ import { isSupportedEvidenceAvailability } from './evidence-availability.js';
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
|
|
28
|
+
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
|
|
@@ -44,6 +46,8 @@ export function captureSnapshot(
|
|
|
44
46
|
jobs: new Map(appModel.jobs),
|
|
45
47
|
runAggregates: appModel.runAggregates,
|
|
46
48
|
evidenceAvailability: appModel.evidenceAvailability,
|
|
49
|
+
skippedLines: appModel.skippedLines,
|
|
50
|
+
unreadableSqlExecutions: appModel.unreadableSqlExecutions && [...appModel.unreadableSqlExecutions],
|
|
47
51
|
catalog: [...catalog],
|
|
48
52
|
taskData: new Map(taskDataCache),
|
|
49
53
|
};
|
|
@@ -71,6 +75,9 @@ export function applySnapshot(
|
|
|
71
75
|
appModel.evidenceAvailability = isSupportedEvidenceAvailability(snapshot.evidenceAvailability)
|
|
72
76
|
? snapshot.evidenceAvailability
|
|
73
77
|
: null;
|
|
78
|
+
appModel.skippedLines = snapshot.skippedLines;
|
|
79
|
+
if (snapshot.unreadableSqlExecutions) appModel.unreadableSqlExecutions = [...snapshot.unreadableSqlExecutions];
|
|
80
|
+
else delete appModel.unreadableSqlExecutions;
|
|
74
81
|
|
|
75
82
|
taskDataCache.clear();
|
|
76
83
|
for (const [k, v] of snapshot.taskData) taskDataCache.set(k, v);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
3
|
// Proxy-level error envelope: the JSON body the local server's SHS proxy
|
|
4
|
-
// (./
|
|
5
|
-
// `{ code: '
|
|
4
|
+
// (./proxy.js) sends back on a non-OK upstream response, e.g.
|
|
5
|
+
// `{ code: 'upstream-unreachable' }`. This is NOT a SparkListener* event shape, so
|
|
6
6
|
// it lives here rather than in event-schemas.ts. `.passthrough()` since the
|
|
7
7
|
// proxy may attach extra debugging fields the consumer doesn't care about;
|
|
8
8
|
// only `code` is read.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Parse a Spark memory-size string to MiB. Spark's JVM-memory configs use bytesConf(ByteUnit.MiB),
|
|
2
|
+
// so a bare number means MiB. A k/m/g/t suffix sets the unit (trailing "b" redundant); a lone "b"
|
|
3
|
+
// ("10b") means bytes.
|
|
4
|
+
export function parseSparkMemoryMB(value ) {
|
|
5
|
+
if (value == null) return null;
|
|
6
|
+
const m = String(value).trim().toLowerCase().match(/^([\d.]+)\s*([kmgt]?)(b?)$/);
|
|
7
|
+
if (!m) return null;
|
|
8
|
+
const n = parseFloat(m[1]);
|
|
9
|
+
if (!Number.isFinite(n)) return null;
|
|
10
|
+
switch (m[2]) {
|
|
11
|
+
case 'k': return Math.round(n / 1024);
|
|
12
|
+
case 'g': return Math.round(n * 1024);
|
|
13
|
+
case 't': return Math.round(n * 1024 * 1024);
|
|
14
|
+
case 'm': return Math.round(n);
|
|
15
|
+
default: return m[3] === 'b' ? Math.round(n / (1024 * 1024)) : Math.round(n);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// The stage aligner: two snapshots and options in, typed stage pairs out. Pure (no I/O, no
|
|
2
|
+
// dependency on the CLI, MCP or UI) and deterministic; swapping baseline and candidate mirrors the
|
|
3
|
+
// pairs. `compareRuns` calls it, and every surface that compares runs reads its result
|
|
4
|
+
// (`stagePairs`) instead of comparing stage keys.
|
|
5
|
+
//
|
|
6
|
+
// Pairing keys on `comparisonIdentity`: the `stageIdentity` recipe run with the comparison
|
|
7
|
+
// normalizer, so the volatile tokens that make two runs of one job differ (staging directories,
|
|
8
|
+
// dates, IN-lists, Delta log file counts) and the caller's `--normalize-path`
|
|
9
|
+
// patterns do not split a stage in two. `stageIdentity` itself stays the frozen exact key.
|
|
10
|
+
|
|
11
|
+
import { normalizeDetail } from './detectors.js';
|
|
12
|
+
import { isDeltaLogRead, scanRelationId } from './plan-summary.js';
|
|
13
|
+
import { withEarlierAttempts, totalExecutorCpuMs } from './run-totals.js';
|
|
14
|
+
import { planNodesOfStage } from './stage-plan-nodes.js';
|
|
15
|
+
import { identityIndexWith, pairEqualCounts, stageIdentityWith, } from './stage-identity.js';
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/** Version of the comparison block (`stagePairs`, `unmatched`, `replanned`, `bookkeepingStageIds`,
|
|
20
|
+
* `runtimeCoverage`). Additive changes keep it; a consumer refuses a version it does not know. */
|
|
21
|
+
export const COMPARISON_SCHEMA_VERSION = 1;
|
|
22
|
+
|
|
23
|
+
// ---- Comparison normalizer ------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
// Targeted patches for the volatile tokens that differ between two runs of one job. Each applies
|
|
26
|
+
// on top of `normalizeDetail`, in this order. No blanket numeric or path stripping: those fuse
|
|
27
|
+
// distinct stages of one run.
|
|
28
|
+
export const COMPARISON_PATCHES = [
|
|
29
|
+
// Delta log and file-index file counts track how many files or commits exist, not the query:
|
|
30
|
+
// DeltaLogFileIndex(4 paths) vs DeltaLogFileIndex(8 paths).
|
|
31
|
+
{ name: 'fileCounts', apply: (s) => s.replace(/FileIndex\(\d+ paths\)/g, 'FileIndex(n paths)') },
|
|
32
|
+
// Date and timestamp literals: the run's processing window.
|
|
33
|
+
{ name: 'dates', apply: (s) => s.replace(/\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?)?/g, 'DATE') },
|
|
34
|
+
// IN-lists grow with the window. Spark prints small lists as `IN (a, b)` and large ones
|
|
35
|
+
// (InSet) as `INSET a, b` with no opening parenthesis.
|
|
36
|
+
{
|
|
37
|
+
name: 'inLists',
|
|
38
|
+
apply: (s) => s.replace(/\bIN \([^()]*\)/g, 'IN (...)').replace(/\bINSET [^()]*?(?=\))/g, 'INSET ...'),
|
|
39
|
+
},
|
|
40
|
+
// Random per-run staging directory segments (`/ds/aB3xYz/`): a 6-8 character alphanumeric path
|
|
41
|
+
// segment that mixes cases, or letters with digits. An ALL-CAPS or all-lowercase name survives,
|
|
42
|
+
// but a real directory that looks like that (`/output01/`, `/Region3/`) is rewritten too.
|
|
43
|
+
{
|
|
44
|
+
name: 'stagingDirs',
|
|
45
|
+
apply: (s) => s.replace(/\/([A-Za-z0-9]{6,8})(?=\/)/g, (m, seg ) =>
|
|
46
|
+
(/[A-Z]/.test(seg) && /[a-z]/.test(seg)) || (/[A-Za-z]/.test(seg) && /\d/.test(seg)) ? '/TMP' : m),
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
/** What every match of a caller-supplied pattern is replaced with. */
|
|
51
|
+
export const NORMALIZE_PATH_TOKEN = 'NORMALIZED';
|
|
52
|
+
/** Longest caller pattern accepted. A pattern from an agent can backtrack catastrophically, so
|
|
53
|
+
* the length is capped; the cap does not make a bad pattern safe. */
|
|
54
|
+
export const MAX_NORMALIZE_PATTERN_LENGTH = 200;
|
|
55
|
+
export const MAX_NORMALIZE_PATTERNS = 16;
|
|
56
|
+
|
|
57
|
+
/** Compiles caller-supplied normalize patterns (global, every match replaced). Throws an Error
|
|
58
|
+
* naming the offending pattern when one is too long, not a valid regular expression, or matches
|
|
59
|
+
* the empty string (which would insert the token between every character). */
|
|
60
|
+
export function compileNormalizePatterns(patterns ) {
|
|
61
|
+
if (!patterns || patterns.length === 0) return [];
|
|
62
|
+
if (patterns.length > MAX_NORMALIZE_PATTERNS) {
|
|
63
|
+
throw new Error(`At most ${MAX_NORMALIZE_PATTERNS} normalize patterns are accepted, got ${patterns.length}.`);
|
|
64
|
+
}
|
|
65
|
+
return patterns.map((source) => {
|
|
66
|
+
if (typeof source !== 'string' || source.length === 0) throw new Error('A normalize pattern must be a non-empty string.');
|
|
67
|
+
if (source.length > MAX_NORMALIZE_PATTERN_LENGTH) {
|
|
68
|
+
throw new Error(`Normalize pattern "${source.slice(0, 40)}..." is ${source.length} characters; the limit is ${MAX_NORMALIZE_PATTERN_LENGTH}.`);
|
|
69
|
+
}
|
|
70
|
+
let re ;
|
|
71
|
+
try {
|
|
72
|
+
re = new RegExp(source, 'g');
|
|
73
|
+
} catch (e) {
|
|
74
|
+
throw new Error(`Normalize pattern "${source}" is not a valid regular expression: ${(e ).message}`);
|
|
75
|
+
}
|
|
76
|
+
if (re.test('')) throw new Error(`Normalize pattern "${source}" matches the empty string.`);
|
|
77
|
+
re.lastIndex = 0;
|
|
78
|
+
return re;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The detail normalizer the comparison keys use: caller patterns first, on the text as Spark
|
|
83
|
+
* printed it, then `normalizeDetail`, then the targeted patches. */
|
|
84
|
+
export function comparisonDetailNormalizer(callerPatterns = []) {
|
|
85
|
+
return (detail) => {
|
|
86
|
+
let s = detail;
|
|
87
|
+
for (const re of callerPatterns) s = s.replace(re, NORMALIZE_PATH_TOKEN);
|
|
88
|
+
s = normalizeDetail(s);
|
|
89
|
+
for (const patch of COMPARISON_PATCHES) s = patch.apply(s);
|
|
90
|
+
return s;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---- Identity and bookkeeping ---------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/** `stageIdentity`'s recipe (normalized stage name plus the attributed nodes' fingerprint) with the
|
|
97
|
+
* comparison normalizer. Internal to the aligner: consumers key on a pair's `pairId`. */
|
|
98
|
+
export function comparisonIdentity(
|
|
99
|
+
stage , snapshot , normalizer , nodes ,
|
|
100
|
+
) {
|
|
101
|
+
return stageIdentityWith(stage, snapshot, normalizer, nodes);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isScanNode(node ) {
|
|
105
|
+
return /^(Scan|BatchScan)\b/.test(node.name ?? '') || /FileScan|JDBCRelation/.test(node.detail ?? '');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** True for a stage whose scans only read Delta log files or checkpoints: it has at least one scan
|
|
109
|
+
* and every scan is internal Delta metadata. Such a stage is table bookkeeping, not query work.
|
|
110
|
+
* A stage with no attributed plan nodes has no scans to test, so it is never flagged. */
|
|
111
|
+
export function isDeltaBookkeepingStage(
|
|
112
|
+
stage , sql , nodes = planNodesOfStage(stage, sql),
|
|
113
|
+
) {
|
|
114
|
+
let scans = 0;
|
|
115
|
+
for (const node of nodes) {
|
|
116
|
+
if (!isScanNode(node)) continue;
|
|
117
|
+
const name = node.name ?? '', detail = node.detail ?? '';
|
|
118
|
+
if (scanRelationId(name, detail) !== null || !isDeltaLogRead(name, detail)) return false;
|
|
119
|
+
scans++;
|
|
120
|
+
}
|
|
121
|
+
return scans > 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ---- Alignment ------------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
export const PAIR_DELTA_METRICS = [
|
|
127
|
+
'executorRunTime', 'executorCpuTime', 'memoryBytesSpilled', 'diskBytesSpilled',
|
|
128
|
+
'inputBytes', 'outputBytes', 'shuffleReadBytes', 'shuffleWriteBytes',
|
|
129
|
+
] ;
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
// Sum of a field over the stage's own record and the work its figures leave out (earlier
|
|
164
|
+
// attempts, a failed attempt's late tasks), the same all-attempt sums the run totals use.
|
|
165
|
+
// null when no row carried a finite value, so an unrecorded field is not a false 0.
|
|
166
|
+
function attemptSum(rows , field ) {
|
|
167
|
+
let sum = 0, present = false;
|
|
168
|
+
for (const row of rows) {
|
|
169
|
+
const v = row[field];
|
|
170
|
+
if (typeof v === 'number' && Number.isFinite(v)) { sum += v; present = true; }
|
|
171
|
+
}
|
|
172
|
+
return present ? sum : null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function stageFigures(stage ) {
|
|
176
|
+
const rows = withEarlierAttempts([stage]);
|
|
177
|
+
return {
|
|
178
|
+
executorRunTime: attemptSum(rows, 'executorRunTime'),
|
|
179
|
+
executorCpuTime: totalExecutorCpuMs(rows),
|
|
180
|
+
memoryBytesSpilled: attemptSum(rows, 'memoryBytesSpilled'),
|
|
181
|
+
diskBytesSpilled: attemptSum(rows, 'diskBytesSpilled'),
|
|
182
|
+
inputBytes: attemptSum(rows, 'inputBytes'),
|
|
183
|
+
outputBytes: attemptSum(rows, 'outputBytes'),
|
|
184
|
+
shuffleReadBytes: attemptSum(rows, 'shuffleReadBytes'),
|
|
185
|
+
shuffleWriteBytes: attemptSum(rows, 'shuffleWriteBytes'),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const ascending = (a , b ) => a - b;
|
|
190
|
+
|
|
191
|
+
/** Pairs the stages of two runs. At this stage the pairing is `matchStages`' algorithm over
|
|
192
|
+
* `comparisonIdentity`: stages sharing an identity pair off by sorted id when each run has the same
|
|
193
|
+
* count of them, and every pair is `exact`. */
|
|
194
|
+
export function alignStages(baseSnap , candSnap , options = {}) {
|
|
195
|
+
const normalizer = comparisonDetailNormalizer(options.normalizePath);
|
|
196
|
+
// One walk of the plan per stage serves the bookkeeping test and the identity.
|
|
197
|
+
const index = (snap ) => {
|
|
198
|
+
const nodesOf = new Map ();
|
|
199
|
+
const bookkeeping = new Set ();
|
|
200
|
+
for (const [id, stage] of snap.stages) {
|
|
201
|
+
const nodes = planNodesOfStage(stage, snap.sql);
|
|
202
|
+
nodesOf.set(id, nodes);
|
|
203
|
+
if (isDeltaBookkeepingStage(stage, snap.sql, nodes)) bookkeeping.add(id);
|
|
204
|
+
}
|
|
205
|
+
const identities = identityIndexWith(
|
|
206
|
+
snap, (stage, id) => comparisonIdentity(stage, snap, normalizer, nodesOf.get(id)), (id) => !bookkeeping.has(id),
|
|
207
|
+
);
|
|
208
|
+
return { identities, bookkeeping };
|
|
209
|
+
};
|
|
210
|
+
const base = index(baseSnap), cand = index(candSnap);
|
|
211
|
+
const baseBook = base.bookkeeping, candBook = cand.bookkeeping;
|
|
212
|
+
const { pairs: idPairs } = pairEqualCounts(base.identities, cand.identities);
|
|
213
|
+
|
|
214
|
+
const baseFigures = new Map ();
|
|
215
|
+
const candFigures = new Map ();
|
|
216
|
+
const figures = (cache , snap , id ) => {
|
|
217
|
+
let f = cache.get(id);
|
|
218
|
+
if (!f) { f = stageFigures(snap.stages.get(id) ); cache.set(id, f); }
|
|
219
|
+
return f;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const pairs = idPairs
|
|
223
|
+
.map(({ baseId, candId }) => {
|
|
224
|
+
const b = figures(baseFigures, baseSnap, baseId), c = figures(candFigures, candSnap, candId);
|
|
225
|
+
const deltas = {} ;
|
|
226
|
+
for (const metric of PAIR_DELTA_METRICS) {
|
|
227
|
+
const [bv, cv] = [b[metric], c[metric]];
|
|
228
|
+
deltas[metric] = { baseline: bv, candidate: cv, delta: bv != null && cv != null ? cv - bv : null };
|
|
229
|
+
}
|
|
230
|
+
return { pairId: `b${baseId}-c${candId}`, baseStageIds: [baseId], candStageIds: [candId], quality: 'exact', score: 1, deltas };
|
|
231
|
+
})
|
|
232
|
+
.sort((x, y) => x.baseStageIds[0] - y.baseStageIds[0] || x.candStageIds[0] - y.candStageIds[0]);
|
|
233
|
+
|
|
234
|
+
const pairedBase = new Set(pairs.flatMap((p) => p.baseStageIds));
|
|
235
|
+
const pairedCand = new Set(pairs.flatMap((p) => p.candStageIds));
|
|
236
|
+
const unmatchedOf = (snap , book , paired ) =>
|
|
237
|
+
[...snap.stages.keys()].filter((id) => !book.has(id) && !paired.has(id)).sort(ascending);
|
|
238
|
+
|
|
239
|
+
// Run time counts every attempt, the same figure the stage-pair deltas and the run totals use.
|
|
240
|
+
const runTime = (cache , snap , id ) => figures(cache, snap, id).executorRunTime ?? 0;
|
|
241
|
+
let total = 0, matched = 0;
|
|
242
|
+
for (const id of baseSnap.stages.keys()) {
|
|
243
|
+
if (baseBook.has(id)) continue;
|
|
244
|
+
const t = runTime(baseFigures, baseSnap, id);
|
|
245
|
+
total += t;
|
|
246
|
+
if (pairedBase.has(id)) matched += t;
|
|
247
|
+
}
|
|
248
|
+
for (const id of candSnap.stages.keys()) {
|
|
249
|
+
if (candBook.has(id)) continue;
|
|
250
|
+
const t = runTime(candFigures, candSnap, id);
|
|
251
|
+
total += t;
|
|
252
|
+
if (pairedCand.has(id)) matched += t;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
pairs,
|
|
257
|
+
unmatched: { baseStageIds: unmatchedOf(baseSnap, baseBook, pairedBase), candStageIds: unmatchedOf(candSnap, candBook, pairedCand) },
|
|
258
|
+
replanned: [],
|
|
259
|
+
bookkeepingStageIds: { baseStageIds: [...baseBook].sort(ascending), candStageIds: [...candBook].sort(ascending) },
|
|
260
|
+
runtimeCoverage: total > 0 ? matched / total : null,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { cyrb53 } from './string-hash.js';
|
|
2
|
+
import { planNodesOfStage } from './stage-plan-nodes.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
// The stage-identity recipe, parameterized by the detail normalizer. `stageIdentity` (the frozen
|
|
7
|
+
// exact key, run-comparison.ts) runs it with `normalizeDetail`; the aligner's `comparisonIdentity`
|
|
8
|
+
// runs it with the comparison normalizer. One recipe, so the two keys can differ only in how a
|
|
9
|
+
// detail string is normalized.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
// Replace run-varying tokens (digit runs, long hex ids) with a stable marker so
|
|
14
|
+
// the same logical stage across two runs normalizes to one identity.
|
|
15
|
+
export function normalizeStageName(name ) {
|
|
16
|
+
return String(name)
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.replace(/\b[0-9a-f]{8,}\b/g, '#') // hex ids/uuids first (they contain digits)
|
|
19
|
+
.replace(/\d+/g, '#')
|
|
20
|
+
.replace(/\s+/g, ' ')
|
|
21
|
+
.trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Bottom-up, order-independent structural identity of a resolved plan tree:
|
|
25
|
+
// each node folds its normalized name/detail with its children's digests
|
|
26
|
+
// (children sorted, so AQE picking a different broadcast side still matches),
|
|
27
|
+
// so two plans collide only when their whole shape and every node's detail
|
|
28
|
+
// agree. `normalizeDetail` strips the run-to-run noise (expr ids, `plan_id=`,
|
|
29
|
+
// codegen numbers, AQE build-side choice, commutative-operand order). Each
|
|
30
|
+
// node folds to a fixed-length cyrb53 digest (JSON.stringify-encoded, so
|
|
31
|
+
// detail text containing `<`/`>`/`{`/`,` can't collide two different plans)
|
|
32
|
+
// instead of embedding full child identity strings, which would re-escape
|
|
33
|
+
// every level below and blow identity size up to ~2^depth on the 20-60+
|
|
34
|
+
// operator-deep plans real Spark produces.
|
|
35
|
+
function planTreeIdentity(root , normalizeDetail ) {
|
|
36
|
+
if (!root) return null;
|
|
37
|
+
function visit(node ) {
|
|
38
|
+
const childDigests = (node.children ?? []).map(visit).sort();
|
|
39
|
+
return cyrb53(JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? ''), childDigests]));
|
|
40
|
+
}
|
|
41
|
+
return visit(root);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Plan identity for the stage's SQL execution, scoped to only the plan nodes
|
|
45
|
+
// this stage actually ran (`node.stageIds`), not the whole tree: two stages
|
|
46
|
+
// sharing one SQL execution (e.g. a self-join's two Exchange stages) otherwise
|
|
47
|
+
// collapse onto one identity regardless of which part of the plan each ran.
|
|
48
|
+
// Falls back to the coarser whole-tree identity when the stage has no
|
|
49
|
+
// attributed nodes (hand-built snapshots without `stageIds`, or unmatched
|
|
50
|
+
// accumulables).
|
|
51
|
+
function sqlNodeIdentity(
|
|
52
|
+
stage , snapshot , normalizeDetail , nodes ,
|
|
53
|
+
) {
|
|
54
|
+
const execId = stage.sqlExecutionId;
|
|
55
|
+
if (execId == null) return '';
|
|
56
|
+
const root = snapshot.sql.get(execId)?.planTree ?? null;
|
|
57
|
+
if (!root) return '';
|
|
58
|
+
const fingerprints = (nodes ?? planNodesOfStage(stage, snapshot.sql))
|
|
59
|
+
.map((node) => JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? '')]));
|
|
60
|
+
if (fingerprints.length === 0) return planTreeIdentity(root, normalizeDetail) ?? '';
|
|
61
|
+
return cyrb53(JSON.stringify(fingerprints.sort()));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `nodes`: the stage's attributed plan nodes when the caller already has them (`planNodesOfStage`),
|
|
65
|
+
* so one walk of the plan serves several readers. */
|
|
66
|
+
export function stageIdentityWith(
|
|
67
|
+
stage , snapshot , normalizeDetail , nodes ,
|
|
68
|
+
) {
|
|
69
|
+
return normalizeStageName(stage.name ?? '') + '§' + sqlNodeIdentity(stage, snapshot, normalizeDetail, nodes);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function identityIndexWith(
|
|
73
|
+
snapshot , identityOf , only = () => true,
|
|
74
|
+
) {
|
|
75
|
+
const byIdentity = new Map ();
|
|
76
|
+
for (const [id, stage] of snapshot.stages) {
|
|
77
|
+
if (!only(id)) continue;
|
|
78
|
+
const key = identityOf(stage, id);
|
|
79
|
+
const ids = byIdentity.get(key);
|
|
80
|
+
if (ids) ids.push(id);
|
|
81
|
+
else byIdentity.set(key, [id]);
|
|
82
|
+
}
|
|
83
|
+
return byIdentity;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Pairs the stages of two identity indexes. An identity colliding equally on both sides has no
|
|
87
|
+
* genuine ambiguity about *count*, so its stages pair off positionally by sorted id rather than
|
|
88
|
+
* being dropped. This is exact when the identity actually distinguishes stages (e.g.
|
|
89
|
+
* self-comparing a run: every stage matches itself). It's a best-effort guess when the identity is
|
|
90
|
+
* coarse (no SQL/attribution) and the two sides are genuinely different runs -- two unrelated
|
|
91
|
+
* same-named stages could get cross-paired. Accepted tradeoff: dropping them instead would also
|
|
92
|
+
* sacrifice the exact-self-comparison case, which matters more. An identity with different counts
|
|
93
|
+
* per side pairs nothing. Deterministic and symmetric: identities visit in sorted order. */
|
|
94
|
+
export function pairEqualCounts(
|
|
95
|
+
baseIdx , candIdx ,
|
|
96
|
+
) {
|
|
97
|
+
const pairs = [];
|
|
98
|
+
const matchedIdentities = new Set ();
|
|
99
|
+
for (const identity of [...baseIdx.keys()].sort()) {
|
|
100
|
+
const candIds = candIdx.get(identity);
|
|
101
|
+
if (!candIds) continue;
|
|
102
|
+
const baseIds = baseIdx.get(identity) ;
|
|
103
|
+
if (baseIds.length !== candIds.length) continue;
|
|
104
|
+
const sortedBaseIds = [...baseIds].sort((a, b) => a - b);
|
|
105
|
+
const sortedCandIds = [...candIds].sort((a, b) => a - b);
|
|
106
|
+
for (let i = 0; i < sortedBaseIds.length; i++) {
|
|
107
|
+
pairs.push({ identity, baseId: sortedBaseIds[i], candId: sortedCandIds[i] });
|
|
108
|
+
}
|
|
109
|
+
matchedIdentities.add(identity);
|
|
110
|
+
}
|
|
111
|
+
return { pairs, matchedIdentities };
|
|
112
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/** The plan nodes a stage actually ran: its SQL execution's resolved plan tree, filtered to the
|
|
4
|
+
* nodes attributed to it (`node.stageIds`). Empty when the stage has no SQL execution, the
|
|
5
|
+
* execution has no resolved plan, or no node is attributed to it. The one stage-to-plan mapping:
|
|
6
|
+
* stageIdentity's fingerprint and the Python-stage check both read it. */
|
|
7
|
+
export function planNodesOfStage(stage , sql ) {
|
|
8
|
+
const execId = stage.sqlExecutionId;
|
|
9
|
+
if (execId == null) return [];
|
|
10
|
+
const root = sql.get(execId)?.planTree ?? null;
|
|
11
|
+
if (!root) return [];
|
|
12
|
+
const nodes = [];
|
|
13
|
+
(function collect(node ) {
|
|
14
|
+
if (node.stageIds?.includes(stage.id)) nodes.push(node);
|
|
15
|
+
for (const child of node.children ?? []) collect(child);
|
|
16
|
+
})(root);
|
|
17
|
+
return nodes;
|
|
18
|
+
}
|
|
@@ -56,6 +56,7 @@ const TASK_FIELD_PROPS = TASK_FIELD_DESCRIPTORS.map((d) => d.prop);
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
export function finalizeStage(
|
|
@@ -124,9 +125,11 @@ export function finalizeStage(
|
|
|
124
125
|
acc.executorCpuTime += t.executorCpuTime;
|
|
125
126
|
acc.inputBytes += t.inputBytes;
|
|
126
127
|
acc.outputBytes += t.outputBytes;
|
|
128
|
+
if (t.outputRecords != null) acc.outputRecords = (acc.outputRecords ?? 0) + t.outputRecords;
|
|
127
129
|
for (let i = 0; i < TASK_FIELD_PROPS.length; i++) buf.push(t[TASK_FIELD_PROPS[i]]);
|
|
128
130
|
}
|
|
129
131
|
stage.taskCount = taskCount;
|
|
132
|
+
stage.peakExecutionMemoryMax = peakExecutionMemoryMax;
|
|
130
133
|
stage.failedTasks = failedTasks;
|
|
131
134
|
stage.speculativeTasks = speculativeTasks;
|
|
132
135
|
stage.taskAttempts = null; // no longer needed after finalize, freeing memory
|
|
@@ -201,6 +204,7 @@ export function finalizeStage(
|
|
|
201
204
|
delete data.failureDetails; // internal-only intern table, summarized by failureGroups
|
|
202
205
|
delete data.speculativeWinners; // internal-only late-TaskEnd pairing state, kept worker-side
|
|
203
206
|
delete data.lateSpeculationWaste;
|
|
207
|
+
delete data.stageAttemptId;
|
|
204
208
|
|
|
205
209
|
return { type: 'stage', data };
|
|
206
210
|
}
|
package/vendor-core/types.js
CHANGED
|
@@ -57,6 +57,24 @@
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
60
78
|
|
|
61
79
|
|
|
62
80
|
|
|
@@ -70,6 +88,20 @@
|
|
|
70
88
|
|
|
71
89
|
|
|
72
90
|
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
73
105
|
|
|
74
106
|
|
|
75
107
|
|
|
@@ -124,6 +156,11 @@
|
|
|
124
156
|
|
|
125
157
|
|
|
126
158
|
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
127
164
|
|
|
128
165
|
|
|
129
166
|
|
|
@@ -263,6 +300,10 @@
|
|
|
263
300
|
|
|
264
301
|
|
|
265
302
|
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
266
307
|
|
|
267
308
|
|
|
268
309
|
|
|
@@ -272,6 +313,9 @@
|
|
|
272
313
|
|
|
273
314
|
|
|
274
315
|
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
275
319
|
|
|
276
320
|
|
|
277
321
|
|
|
@@ -288,11 +332,20 @@
|
|
|
288
332
|
|
|
289
333
|
|
|
290
334
|
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
291
344
|
|
|
292
345
|
|
|
293
346
|
// `Finding` is a union discriminated on `type`, one member per emitted finding type: see
|
|
294
347
|
// finding-types.ts for each detector's shape and which of its fields are public evidence.
|
|
295
|
-
|
|
348
|
+
|
|
296
349
|
|
|
297
350
|
|
|
298
351
|
|