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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// The waste models every detector entry's estimate() builds its ImpactEstimate from: the assumed
|
|
2
2
|
// throughputs, the per-stage measurements behind them and the occupancy clip wrappers. Each
|
|
3
3
|
// finding type's own composition of these lives on its DETECTORS entry, next to its detect().
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import { isPythonStage } from './python-stage.js';
|
|
5
6
|
import { nsToMs } from './format-utils.js';
|
|
6
7
|
import {
|
|
7
8
|
estimateSingleStage, estimateMultiStage,
|
|
@@ -16,6 +17,8 @@ import {
|
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
|
|
20
|
+
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
// Assumed shuffle-network throughput per executor link, ~1 Gbps. Starting assumption, unvalidated.
|
|
@@ -104,13 +107,14 @@ const IDLE_CPU_SHARE_MAX = 0.01;
|
|
|
104
107
|
|
|
105
108
|
// True when the stage's tasks spent under IDLE_CPU_SHARE_MAX of their run time on CPU. False when
|
|
106
109
|
// the share can't be trusted: no CPU time recorded (older Spark logs omit the metric), or Python
|
|
107
|
-
// code run through
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
+
// code run through a Python worker (isPythonStage: a PythonRDD stage or a Python UDF operator in
|
|
111
|
+
// its SQL plan), whose worker-process CPU executorCpuTime (the JVM task thread's) never counts
|
|
112
|
+
// (such stages read 0.1% on the same logs while computing).
|
|
113
|
+
export function tasksMostlyIdle(stage , sql = new Map()) {
|
|
110
114
|
const runMs = stage.executorRunTime ?? 0;
|
|
111
115
|
const cpuMs = nsToMs(stage.executorCpuTime ?? 0);
|
|
112
116
|
if (runMs <= 0 || cpuMs <= 0) return false;
|
|
113
|
-
if (
|
|
117
|
+
if (isPythonStage(stage, sql)) return false;
|
|
114
118
|
return cpuMs / runMs < IDLE_CPU_SHARE_MAX;
|
|
115
119
|
}
|
|
116
120
|
|
|
@@ -173,3 +177,21 @@ export function stageMappableWasteOrCostOnly(
|
|
|
173
177
|
// null: every stage excluded from the sweep
|
|
174
178
|
return multiStageImpact(stageIds, wasteMsByStage, ctx, 'modeled', rawWaste) ?? costOnly('modeled', rawWaste);
|
|
175
179
|
}
|
|
180
|
+
|
|
181
|
+
// Finding types whose raw figure is busy core time read straight from the log: gc's jvmGCTime
|
|
182
|
+
// (coreMs) and the discarded speculative or retried attempts' run time ('ms' cross-task sums).
|
|
183
|
+
// Any other core figure is idle capacity, or modeled on an assumed constant (coreLocality's
|
|
184
|
+
// per-task fetch penalty, autoscalingChurn's executor-hours, jobFailureRate's job-hours).
|
|
185
|
+
const MEASURED_CORE_TIME_FIGURE = new Set(['gc', 'retryWaste', 'speculationWaste']);
|
|
186
|
+
|
|
187
|
+
/** The busy core time a finding's fix removes, in core-milliseconds, or null when the detector
|
|
188
|
+
* measures none. Only a measured figure counts: one its estimate() already set (skew and
|
|
189
|
+
* straggler's removed task time), or the raw figure of a MEASURED_CORE_TIME_FIGURE type. A
|
|
190
|
+
* wall-clock claim, an idle capacity figure and a modeled figure are never converted. It never
|
|
191
|
+
* reads executorCpuTime, which leaves out Python worker CPU. */
|
|
192
|
+
export function coreTimeFor(finding , estimate ) {
|
|
193
|
+
if (estimate.coreTimeMs !== undefined) return estimate.coreTimeMs;
|
|
194
|
+
const raw = estimate.rawWaste;
|
|
195
|
+
if (!raw || !MEASURED_CORE_TIME_FIGURE.has(finding.type)) return null;
|
|
196
|
+
return { low: raw.value, high: raw.value };
|
|
197
|
+
}
|
package/vendor-core/ingest.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
|
|
@@ -39,9 +40,10 @@ export function routeMessage(
|
|
|
39
40
|
case 'runAggregates': handlers.onRunAggregates?.(data.data); break;
|
|
40
41
|
case 'stageExecutorMetrics': handlers.onStageExecutorMetrics?.(data.data); break;
|
|
41
42
|
case 'stageSpeculationWaste': handlers.onStageSpeculationWaste?.(data.data); break;
|
|
43
|
+
case 'stageLateAttemptWork': handlers.onStageLateAttemptWork?.(data.data); break;
|
|
42
44
|
case 'done': {
|
|
43
|
-
const { skippedLines } = data;
|
|
44
|
-
handlers.onDone?.({ skippedLines });
|
|
45
|
+
const { skippedLines, unreadableSqlExecutions } = data;
|
|
46
|
+
handlers.onDone?.(unreadableSqlExecutions ? { skippedLines, unreadableSqlExecutions } : { skippedLines });
|
|
45
47
|
break;
|
|
46
48
|
}
|
|
47
49
|
case 'error': handlers.onError?.(data); break;
|
package/vendor-core/list-runs.js
CHANGED
|
@@ -5,6 +5,7 @@ import { reassembleRollingEntries } from './parser-worker.js';
|
|
|
5
5
|
import { peekLogHeader } from './log-header-peek.js';
|
|
6
6
|
import { mcpError } from './mcp-error.js';
|
|
7
7
|
import { normalizeBaseUrl } from './shs-request.js';
|
|
8
|
+
import { isConnectionFailure } from './proxy.js';
|
|
8
9
|
import { DEFAULT_IDLE_TIMEOUT_MS, DEFAULT_MAX_ARCHIVE_BYTES } from './shs-load.js';
|
|
9
10
|
|
|
10
11
|
|
|
@@ -205,10 +206,12 @@ export async function listRunsShs(
|
|
|
205
206
|
let res ;
|
|
206
207
|
try {
|
|
207
208
|
// An unresponsive SHS would otherwise hang the tool call forever. A timed-out signal rejects
|
|
208
|
-
// the fetch with
|
|
209
|
+
// the fetch with a TimeoutError, which this same catch turns into upstream-unreachable, the
|
|
210
|
+
// code a refused connection gets too.
|
|
209
211
|
res = await fetchImpl(url.toString(), { signal: AbortSignal.timeout(DEFAULT_IDLE_TIMEOUT_MS) });
|
|
210
212
|
} catch (e) {
|
|
211
|
-
|
|
213
|
+
const code = isConnectionFailure(e) ? 'upstream-unreachable' : 'access-or-upstream-failure';
|
|
214
|
+
throw mcpError(code, `Could not reach ${normalized}: ${e instanceof Error ? e.message : String(e)}`);
|
|
212
215
|
}
|
|
213
216
|
if (!res.ok) {
|
|
214
217
|
throw mcpError('access-or-upstream-failure', `SHS applications list request failed with status ${res.status}.`);
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from './mcp-tools.js';
|
|
7
7
|
import { listRuns } from './list-runs.js';
|
|
8
8
|
|
|
9
|
+
import { MAX_NORMALIZE_PATTERN_LENGTH, MAX_NORMALIZE_PATTERNS } from './stage-alignment.js';
|
|
9
10
|
|
|
10
11
|
const sourceSchema = z.union([
|
|
11
12
|
z.object({ path: z.string() }),
|
|
@@ -71,7 +72,7 @@ export function createMcpServer({ thresholds }
|
|
|
71
72
|
}, (params) => toolResult(listRuns(params)));
|
|
72
73
|
|
|
73
74
|
server.registerTool('diagnose_run', {
|
|
74
|
-
description: 'Diagnose a Spark run: the dashboard verdict (title, summary, and the top places to look, ranked by potential savings), thresholded findings with remediation text, an impact-ranked fix recommendation rollup, clean-check status,
|
|
75
|
+
description: 'Diagnose a Spark run: the dashboard verdict (title, summary, and the top places to look, ranked by potential savings), thresholded findings with remediation text, an impact-ranked fix recommendation rollup, clean-check status, the checks the log lacked the data to run, and the writeTargets, metrics and effectiveConf blocks of the CLI report. When the server was started with --thresholds, findings and clean checks from a tuned detector carry tunedThresholds, and their impact estimates are uncalibrated.',
|
|
75
76
|
inputSchema: {
|
|
76
77
|
...runRefSchema, redact: z.boolean().optional(),
|
|
77
78
|
include: z.array(z.enum(['summary', 'evidenceAvailability', 'detectors'])).optional(),
|
|
@@ -91,15 +92,19 @@ export function createMcpServer({ thresholds }
|
|
|
91
92
|
));
|
|
92
93
|
|
|
93
94
|
server.registerTool('compare_runs', {
|
|
94
|
-
description: 'Compare two runs: categorized findings delta and
|
|
95
|
+
description: 'Compare two runs: the verdict, metric deltas (metrics), categorized findings delta (findings) and the stage pairs (stagePairs) with the share of executor run time they cover (runtimeCoverage). confidence is ok, low or insufficient. metricDeltas and findingsDelta repeat metrics and findings under their earlier names and will be removed.',
|
|
95
96
|
inputSchema: {
|
|
96
97
|
runIdA: z.string().optional(), sourceA: sourceSchema.optional(),
|
|
97
98
|
...secondRunRefSchema,
|
|
98
99
|
redact: z.boolean().optional(),
|
|
100
|
+
include: z.array(z.enum(['stagePairs'])).optional()
|
|
101
|
+
.describe('Extra blocks to return. stagePairs: one row per paired stage with its deltas, which can be large.'),
|
|
102
|
+
normalizePath: z.array(z.string().max(MAX_NORMALIZE_PATTERN_LENGTH)).max(MAX_NORMALIZE_PATTERNS).optional()
|
|
103
|
+
.describe('Regular expressions whose every match in a plan node\'s text is replaced with a fixed token before stages are paired, so run-specific text (an output directory that differs per run) does not keep the same stage from pairing. A generic pattern, not only for paths. Affects stage pairing, runtimeCoverage and confidence, never findings. A pattern that backtracks catastrophically can stall the server: keep them simple and anchored.'),
|
|
99
104
|
...formatSchema,
|
|
100
105
|
},
|
|
101
|
-
}, ({ runIdA, sourceA, runIdB, sourceB, redact, format }) => toolResultWithMarkdown(
|
|
102
|
-
compareRuns({ runId: runIdA, source: sourceA }, { runId: runIdB, source: sourceB }, { redact, markdown: format === 'md', thresholds }),
|
|
106
|
+
}, ({ runIdA, sourceA, runIdB, sourceB, redact, include, normalizePath, format }) => toolResultWithMarkdown(
|
|
107
|
+
compareRuns({ runId: runIdA, source: sourceA }, { runId: runIdB, source: sourceB }, { redact, markdown: format === 'md', thresholds, normalizePath, include }),
|
|
103
108
|
));
|
|
104
109
|
|
|
105
110
|
server.registerTool('evaluate_budgets', {
|
|
@@ -112,14 +117,16 @@ export function createMcpServer({ thresholds }
|
|
|
112
117
|
sourceB: sourceSchema.optional().describe('Optional candidate run, compared against source/runId as the regression baseline (maxRegressionPct/failOnIntroduced). When given, the absolute budgets (maxRuntimeMs etc.) are evaluated on this run.'),
|
|
113
118
|
runIdB: secondRunRefSchema.runIdB.describe('Same as `sourceB`, referencing an already-resolved run by id.'),
|
|
114
119
|
maxRegressionPct: z.number().optional(), regressionMetric: z.string().optional(), failOnIntroduced: z.string().optional(),
|
|
120
|
+
regressionBudgets: z.array(z.object({ metric: z.string(), maxPct: z.number().nonnegative() })).optional()
|
|
121
|
+
.describe('Further regression budgets, one per metric (the CLI\'s repeated --regression-budget). A metric can be budgeted once across this list and maxRegressionPct/regressionMetric.'),
|
|
115
122
|
},
|
|
116
123
|
}, ({
|
|
117
124
|
source, runId, runIdB, sourceB,
|
|
118
125
|
maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct,
|
|
119
|
-
maxRegressionPct, regressionMetric, failOnIntroduced,
|
|
126
|
+
maxRegressionPct, regressionMetric, failOnIntroduced, regressionBudgets,
|
|
120
127
|
}) => toolResult(evaluateBudgetsForRun(
|
|
121
128
|
{ source, runId },
|
|
122
|
-
{ maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct, maxRegressionPct, regressionMetric, failOnIntroduced },
|
|
129
|
+
{ maxRuntimeMs, maxSpillGb, maxSkewRatio, maxFailedTaskRatePct, minEfficiencyPct, maxRegressionPct, regressionMetric, failOnIntroduced, regressionBudgets },
|
|
123
130
|
(runIdB || sourceB) ? { runId: runIdB, source: sourceB } : undefined,
|
|
124
131
|
{ thresholds },
|
|
125
132
|
)));
|
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 { 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
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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,16 @@ 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
|
+
}
|
|
411
419
|
const [first, second] = await Promise.all([
|
|
412
420
|
resolveAndAnalyze(primary, opts?.thresholds),
|
|
413
421
|
secondary ? resolveAndAnalyze(secondary, opts?.thresholds) : Promise.resolve(undefined),
|
|
@@ -418,11 +426,12 @@ export async function evaluateBudgetsForRun(
|
|
|
418
426
|
// apply to the candidate.
|
|
419
427
|
const candidate = second ?? first;
|
|
420
428
|
const baseline = second ? first : undefined;
|
|
429
|
+
// Not redacted: no budget reads the stage text redaction rewrites (the parity suite checks it).
|
|
421
430
|
const comparison = baseline
|
|
422
|
-
?
|
|
431
|
+
? buildComparisonOutput(
|
|
423
432
|
{ label: baseline.runId, appModel: baseline.appModel, catalog: baseline.catalog },
|
|
424
433
|
{ label: candidate.runId, appModel: candidate.appModel, catalog: candidate.catalog },
|
|
425
|
-
)
|
|
434
|
+
).comparison
|
|
426
435
|
: undefined;
|
|
427
436
|
|
|
428
437
|
const { results, violated, inconclusive } = evaluateBudgets({
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
@@ -9,6 +10,15 @@
|
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
// The done message's counts of what the parse could not read. On the model, not only passed to onDone,
|
|
14
|
+
// so every consumer of the model (dashboard export, CLI, MCP) reports them the same way.
|
|
15
|
+
function recordParseGaps(appModel , data ) {
|
|
16
|
+
const done = data ;
|
|
17
|
+
appModel.skippedLines = done?.skippedLines ?? 0;
|
|
18
|
+
if (done?.unreadableSqlExecutions) appModel.unreadableSqlExecutions = done.unreadableSqlExecutions;
|
|
19
|
+
else delete appModel.unreadableSqlExecutions;
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
// Worker-message -> appModel assembly. Shared by the file-load and SHS-URL-load paths. Pure model
|
|
13
23
|
// mutation: analysis/render/persist stay in the caller via the onDone/onProgress/onError hooks.
|
|
14
24
|
export function createModelCallbacks(
|
|
@@ -75,6 +85,18 @@ export function createModelCallbacks(
|
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
},
|
|
78
|
-
onDone
|
|
88
|
+
onDone(data ) {
|
|
89
|
+
recordParseGaps(appModel, data);
|
|
90
|
+
onDone?.(data);
|
|
91
|
+
},
|
|
92
|
+
// Patch the late work of failed attempts, whose TaskEnds arrive after StageCompleted.
|
|
93
|
+
// `data` is Map<stageId, StageAttemptTotals>.
|
|
94
|
+
onStageLateAttemptWork(data ) {
|
|
95
|
+
for (const [stageId, work] of data ) {
|
|
96
|
+
const stage = appModel.stages.get(stageId);
|
|
97
|
+
if (stage) stage.lateAttemptWork = work;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
onError,
|
|
79
101
|
};
|
|
80
102
|
}
|
|
@@ -45,7 +45,7 @@ const NOT_AN_EVENT_LOG = 'Not a Spark event log: no application-start event foun
|
|
|
45
45
|
// Minimal shape streamFile/runParse/runParseFiles read off `file` (name, size,
|
|
46
46
|
// slice(start,end).arrayBuffer()): narrower than the full DOM `File`. A real
|
|
47
47
|
// `File` (the browser Worker path) satisfies it structurally, but so does the
|
|
48
|
-
// plain object src/cli/collect-run.ts's nodeFileFromPath builds for Node, which
|
|
48
|
+
// plain object packages/core/src/cli/collect-run.ts's nodeFileFromPath builds for Node, which
|
|
49
49
|
// has no DOM `File` constructor.
|
|
50
50
|
|
|
51
51
|
|
|
@@ -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
|
package/vendor-core/proxy.js
CHANGED
|
@@ -10,7 +10,9 @@ function sendSafeError(res, status, code) {
|
|
|
10
10
|
res.end(body);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// Also read by list-runs.ts, so a History Server listing that can't be reached reports the
|
|
14
|
+
// same upstream-unreachable code as a run fetch.
|
|
15
|
+
export function isConnectionFailure(error) {
|
|
14
16
|
if (error?.name === 'AbortError' || error?.name === 'TimeoutError') return true;
|
|
15
17
|
const code = error?.code ?? error?.cause?.code;
|
|
16
18
|
if (['ECONNREFUSED', 'ECONNRESET', 'EHOSTUNREACH', 'ENETUNREACH', 'ENOTFOUND', 'ETIMEDOUT'].includes(code)) return true;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { planNodesOfStage } from './stage-plan-nodes.js';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// Plan operators that hand rows to a Python worker process: the row-at-a-time and Arrow Python UDF
|
|
5
|
+
// evaluators, the pandas/Arrow grouped and map operators, and the PythonRDD scan of an RDD
|
|
6
|
+
// pipeline. A suffixed name is a variant of the same operator (BatchEvalPythonUDTF,
|
|
7
|
+
// FlatMapGroupsInPandasWithState). Spark 4.1 renamed AggregateInPandas and WindowInPandas to
|
|
8
|
+
// ArrowAggregatePython and ArrowWindowPython. Spark prefixes a whole-stage-codegen child's name
|
|
9
|
+
// with "*(n) " in some plan strings.
|
|
10
|
+
const PYTHON_PLAN_NODE = /^(?:\*\(\d+\)\s*)?(?:PythonRDD|BatchEvalPython|ArrowEvalPython|ArrowAggregatePython|ArrowWindowPython|\w+InPandas|\w+InArrow)\w*\b/;
|
|
11
|
+
|
|
12
|
+
// An RDD lambda or map function has no SQL plan to match, and a stage whose plan could not be
|
|
13
|
+
// matched is left with nothing but its name and call site.
|
|
14
|
+
const PYTHON_STAGE_NAME = /PythonRDD/;
|
|
15
|
+
const PYTHON_STAGE_DETAILS = /org\.apache\.spark\.api\.python\./;
|
|
16
|
+
|
|
17
|
+
/** True when the stage ran Python code in a worker process: the union of a Python operator among
|
|
18
|
+
* the plan nodes attributed to it (catches Python UDFs inside SQL) and the stage's own name or
|
|
19
|
+
* call site naming PythonRDD / org.apache.spark.api.python (catches RDD lambdas, which have no
|
|
20
|
+
* plan, and stages that cannot be matched to one). The executor CPU time of such a stage misses
|
|
21
|
+
* the worker process's CPU. */
|
|
22
|
+
export function isPythonStage(stage , sql ) {
|
|
23
|
+
if (PYTHON_STAGE_NAME.test(stage.name ?? '') || PYTHON_STAGE_DETAILS.test(stage.details ?? '')) return true;
|
|
24
|
+
return planNodesOfStage(stage, sql).some((node) => PYTHON_PLAN_NODE.test(node.name ?? ''));
|
|
25
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Explicit .ts extensions: plain Node's ESM resolver (the runtime CLI/MCP path
|
|
2
2
|
// runs under) requires the exact specifier, unlike a bundler.
|
|
3
3
|
import { mergeIntervals } from './intervals.js';
|
|
4
|
-
import { formatWallClockRange, worstImpactBand, IMPACT_BAND_ORDER } from './format-utils.js';
|
|
4
|
+
import { formatRawWaste, formatWallClockRange, readsAsZero, worstImpactBand, IMPACT_BAND_ORDER } from './format-utils.js';
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
@@ -124,11 +124,15 @@ export function buildRecommendationRollup(
|
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
/** A group's
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
127
|
+
/** A resource group's summed waste as the board and CLI print it; null when it reads as zero. */
|
|
128
|
+
export function resourceGroupTotal(group ) {
|
|
129
|
+
const text = formatRawWaste({ value: group.total, unit: group.unit });
|
|
130
|
+
return readsAsZero(text) ? null : text;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** A group's trailing figure on the Findings board, and its tooltip. Every kind leads with the
|
|
134
|
+
* "×N" finding count (the "worth expanding" signal). The row stays terse ("×2 · 476ms
|
|
135
|
+
* recoverable") to fit a dense right-aligned column; the title spells the shorthand out. */
|
|
132
136
|
export function rollupGroupStat(group ) {
|
|
133
137
|
if (group.kind === 'time') {
|
|
134
138
|
const recoverable = formatWallClockRange(group.recoverableMsHigh, group.recoverableMsHigh);
|
|
@@ -138,13 +142,16 @@ export function rollupGroupStat(group )
|
|
|
138
142
|
};
|
|
139
143
|
}
|
|
140
144
|
if (group.kind === 'resource') {
|
|
145
|
+
const total = resourceGroupTotal(group);
|
|
141
146
|
return {
|
|
142
|
-
stat: `×${group.findingCount} ·
|
|
143
|
-
statTitle: `${group.findingCount} findings of this type
|
|
147
|
+
stat: total ? `×${group.findingCount} · ${total}` : `×${group.findingCount}`,
|
|
148
|
+
statTitle: `${group.findingCount} findings of this type${total ? `; ${total} in total, a resource cost, not run time` : ''}`,
|
|
144
149
|
};
|
|
145
150
|
}
|
|
151
|
+
// The band heading above the row already names a one-band group's band.
|
|
152
|
+
const bands = Object.entries(group.byImpactBand);
|
|
146
153
|
return {
|
|
147
|
-
stat:
|
|
154
|
+
stat: bands.length === 1 ? `×${group.findingCount}` : `×${group.findingCount} · ${bands.map(([impactBand, count]) => `${count} ${impactBand}`).join(', ')}`,
|
|
148
155
|
statTitle: `${group.findingCount} findings of this type, by impact`,
|
|
149
156
|
};
|
|
150
157
|
}
|
package/vendor-core/redact.js
CHANGED
|
@@ -9,13 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import { decodeCollections, encodeCollections, } from './export-data.js';
|
|
11
11
|
|
|
12
|
-
import { redactTaskFailureGroup, } from './task-failure.js';
|
|
12
|
+
import { REDACTED_TEXT, redactTaskFailureGroup, } from './task-failure.js';
|
|
13
13
|
|
|
14
14
|
// Host / IP identifier patterns. Used to enumerate host names that surface only
|
|
15
|
-
// inside free text: recommendation strings,
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// pattern, keeping the scan idempotent.
|
|
15
|
+
// inside free text: recommendation strings, SQL relation/node names, never as a
|
|
16
|
+
// structured `host` field, so redaction reaches those residuals too. Pseudonyms
|
|
17
|
+
// (`host-1`) match neither pattern, keeping the scan idempotent.
|
|
19
18
|
const HOST_PATTERNS = [
|
|
20
19
|
// EC2-style ip-10-1-2-3 with an optional dotted domain (ip-10-1-2-3.ec2.internal).
|
|
21
20
|
// Each domain label must start with an alphanumeric, so a trailing sentence
|
|
@@ -95,6 +94,31 @@ function redactFailureGroups (node ) {
|
|
|
95
94
|
return node;
|
|
96
95
|
}
|
|
97
96
|
|
|
97
|
+
// A stage's failure reason is Spark's free-text message and can carry file paths and data values
|
|
98
|
+
// too, so it is replaced outright, like a failure group's message: a `stageFailed` finding's
|
|
99
|
+
// `valueText`, the `stageFailureReason` of a stage record, a job's `exception` (the run outcome's
|
|
100
|
+
// fallback reason), and the report's `failureReason` copy. Walking by key name, like
|
|
101
|
+
// collectHostFields, needs no path list. Returns a fresh tree.
|
|
102
|
+
const REASON_KEYS = new Set(['stageFailureReason', 'exception', 'failureReason']);
|
|
103
|
+
function redactStageFailureReasons (node ) {
|
|
104
|
+
if (Array.isArray(node)) return node.map((n) => redactStageFailureReasons(n)) ;
|
|
105
|
+
if (node && typeof node === 'object') {
|
|
106
|
+
const isStageFailed = (node ).type === 'stageFailed';
|
|
107
|
+
const out = {};
|
|
108
|
+
for (const [k, v] of Object.entries(node)) {
|
|
109
|
+
const isReason = REASON_KEYS.has(k) || (isStageFailed && k === 'valueText');
|
|
110
|
+
out[k] = isReason && typeof v === 'string' ? REDACTED_TEXT : redactStageFailureReasons(v);
|
|
111
|
+
}
|
|
112
|
+
return out ;
|
|
113
|
+
}
|
|
114
|
+
return node;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Both passes for free text no host/app-id pattern recognizes.
|
|
118
|
+
function redactFailureText (node ) {
|
|
119
|
+
return redactStageFailureReasons(redactFailureGroups(node));
|
|
120
|
+
}
|
|
121
|
+
|
|
98
122
|
// Spark config keys ending in `host`/`hostname` (e.g. spark.driver.host,
|
|
99
123
|
// spark.yarn.am.hostname) carry plain FQDN host names that neither
|
|
100
124
|
// HOST_PATTERNS matches (no IP/EC2 shape) nor collectHostFields's by-key-name
|
|
@@ -122,7 +146,7 @@ function collectConfigHostValues(config , hos
|
|
|
122
146
|
|
|
123
147
|
|
|
124
148
|
|
|
125
|
-
|
|
149
|
+
|
|
126
150
|
|
|
127
151
|
|
|
128
152
|
|
|
@@ -188,10 +212,19 @@ function applyReplacements (node , ids
|
|
|
188
212
|
return deepReplace(node, merged) ;
|
|
189
213
|
}
|
|
190
214
|
|
|
215
|
+
// The app name identifies a job as much as its id, so a redacted name becomes the id's
|
|
216
|
+
// pseudonym, as list_runs does. Only the structured field is replaced, never substrings: a
|
|
217
|
+
// short name ("t", "etl") would otherwise corrupt every string it happens to occur in.
|
|
218
|
+
function withRedactedName (app ) {
|
|
219
|
+
return app.name == null ? app : { ...app, name: app.id ?? null };
|
|
220
|
+
}
|
|
221
|
+
|
|
191
222
|
export function redactReport (input ) {
|
|
192
|
-
const report =
|
|
223
|
+
const report = redactFailureText(input);
|
|
193
224
|
const { appIds, hosts } = collectIds(report);
|
|
194
|
-
|
|
225
|
+
const out = applyReplacements(report, { appIds, hosts });
|
|
226
|
+
const app = out.summary?.app;
|
|
227
|
+
return app ? { ...out, summary: { ...out.summary, app: withRedactedName(app) } } : out;
|
|
195
228
|
}
|
|
196
229
|
|
|
197
230
|
// Run-comparison counterpart: no single app-id *field* to pseudonymize
|
|
@@ -218,7 +251,7 @@ export function redactComparison (comparison ) {
|
|
|
218
251
|
|
|
219
252
|
|
|
220
253
|
function redactRunTree (input ) {
|
|
221
|
-
const data =
|
|
254
|
+
const data = redactFailureText(input);
|
|
222
255
|
const appIds = new Set ();
|
|
223
256
|
const hosts = new Set ();
|
|
224
257
|
const appId = data.app?.id;
|
|
@@ -229,7 +262,12 @@ function redactRunTree (input ) {
|
|
|
229
262
|
collectHostFields(data.configFindings, hosts);
|
|
230
263
|
collectConfigHostValues(data.app?.config, hosts);
|
|
231
264
|
scanTokens(data, [{ patterns: HOST_PATTERNS, out: hosts }, { patterns: APP_ID_PATTERNS, out: appIds }]);
|
|
232
|
-
|
|
265
|
+
const out = applyReplacements(data, { appIds, hosts });
|
|
266
|
+
if (!out.app) return out;
|
|
267
|
+
const app = withRedactedName(out.app);
|
|
268
|
+
// spark.app.name repeats the name in the config the HTML export ships.
|
|
269
|
+
if (app.config?.['spark.app.name'] == null) return { ...out, app };
|
|
270
|
+
return { ...out, app: { ...app, config: { ...app.config, 'spark.app.name': app.id ?? '' } } };
|
|
233
271
|
}
|
|
234
272
|
|
|
235
273
|
/** A run's model and findings with every identifier pseudonymized. Redact this before anything derives
|
|
@@ -264,6 +302,9 @@ export function redactRunModel(
|
|
|
264
302
|
executors: redacted.executors,
|
|
265
303
|
runAggregates: redacted.runAggregates ,
|
|
266
304
|
evidenceAvailability: redacted.evidenceAvailability ,
|
|
305
|
+
// Counts and execution ids, nothing to pseudonymize.
|
|
306
|
+
skippedLines: appModel.skippedLines,
|
|
307
|
+
unreadableSqlExecutions: appModel.unreadableSqlExecutions,
|
|
267
308
|
} ,
|
|
268
309
|
catalog: redacted.catalog,
|
|
269
310
|
configFindings: redacted.configFindings,
|