sparkforensics-server 0.6.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +16 -7
- package/package.json +1 -1
- package/public/assets/AutoscalingChurn-DenWKTv-.js +1 -0
- package/public/assets/CacheUtilization-DLz7NdjV.js +1 -0
- package/public/assets/CachingOpportunity-zpQIqhzm.js +1 -0
- package/public/assets/ChartTheme-DAiARbLP.js +8 -0
- package/public/assets/ColdStart-C-9HjxWl.js +1 -0
- package/public/assets/ConfigAudit-D0V9tcrJ.js +1 -0
- package/public/assets/CoreUsageArea-yeerrljd.js +1 -0
- package/public/assets/Dashboard-DkUPrHKV.js +1 -0
- package/public/assets/DisclosureContext-DK1_iz6J.js +1 -0
- package/public/assets/DuplicatePlanSubtree-xDNDbCd3.js +1 -0
- package/public/assets/DurationHistogram-B6jsE0Ov.js +1 -0
- package/public/assets/EmptyState-C1uL40VU.js +1 -0
- package/public/assets/ExecutorUtilization-DXDZKmSc.js +1 -0
- package/public/assets/GcPressure-MRf7I4IG.js +1 -0
- package/public/assets/ImpactEstimate-Dm3xWbzV.js +1 -0
- package/public/assets/IncompleteRun-CaLOhMtf.js +1 -0
- package/public/assets/JobFailures-CzmzlvhO.js +1 -0
- package/public/assets/MemoryUtilization-AWUMUjCH.js +1 -0
- package/public/assets/NestedLoopJoin-C9sNr6Ss.js +1 -0
- package/public/assets/OverBroadcast-BOmRE6ca.js +1 -0
- package/public/assets/PartitionHint-BL2EmJfE.js +1 -0
- package/public/assets/PartitionSizing-BzBySvUq.js +1 -0
- package/public/assets/PlanExplorer-DdiQRcJU.js +1 -0
- package/public/assets/PlanGraphRoute-CIH9gBuR.js +1 -0
- package/public/assets/PlanView-B1hxzFRs.js +2 -0
- package/public/assets/PythonUdf-DvgCCueD.js +1 -0
- package/public/assets/RetryWaste-uXXu_CyM.js +1 -0
- package/public/assets/RowPagination-C-LzWkI5.js +1 -0
- package/public/assets/RowStatusCluster-Dbtmte7l.js +1 -0
- package/public/assets/ShuffleIO-nALwzC1_.js +1 -0
- package/public/assets/Skew-CHMEKgvk.js +1 -0
- package/public/assets/SlowHost-yyvjpHk6.js +1 -0
- package/public/assets/SmallFiles-y6odJ97n.js +1 -0
- package/public/assets/SpeculationWaste-C-SD2LOt.js +1 -0
- package/public/assets/Spill-DHTMmOup.js +1 -0
- package/public/assets/StageFailed-CQ3DxC9R.js +1 -0
- package/public/assets/StageFindingGroup-BgYLyj5q.js +1 -0
- package/public/assets/StageHeader-CIETkjVZ.js +1 -0
- package/public/assets/StagePill--P7PDd3s.js +1 -0
- package/public/assets/StageShape-BIv8vx_o.js +1 -0
- package/public/assets/StageSlowness-MMqzERxN.js +1 -0
- package/public/assets/Straggler-BgrY7AyW.js +1 -0
- package/public/assets/TaskFailures-BHHyKYTs.js +1 -0
- package/public/assets/TinyTask-DDScNhCl.js +1 -0
- package/public/assets/UnderBroadcast-CGIC04TI.js +1 -0
- package/public/assets/WidgetLeadSummary-CXFZP00C.js +1 -0
- package/public/assets/button-DbpTz2yk.js +1 -0
- package/public/assets/charts-vendor-CtTxwTeu.js +59 -0
- package/public/assets/composite-C9Nu2EI9.js +1 -0
- package/public/assets/d3-vendor-DNlD2-cz.js +1 -0
- package/public/assets/detector-registry-Bp8hrLua.js +2 -0
- package/public/assets/downsample-WmNM1qc9.js +1 -0
- package/public/assets/finding-anchor-Dr40LkS9.js +1 -0
- package/public/assets/findings-of-type-BL2u3RGZ.js +1 -0
- package/public/assets/impact-sort-mC5ISgX8.js +1 -0
- package/public/assets/inbox-KPU1Gppm.js +1 -0
- package/public/assets/index-CaRVPvAT.css +2 -0
- package/public/assets/index-fDiwfuoO.js +14 -0
- package/public/assets/inertValue-L9VxV0VE.js +1 -0
- package/public/assets/layout.worker-CLcBHxVp.js +3 -0
- package/public/assets/{parser-worker-CA5RxqKc.js → parser-worker-CRIOPMOW.js} +8 -8
- package/public/assets/plan-graph-vendor-Ce6d4HU6.js +7 -0
- package/public/assets/react-vendor-C6KcUD36.js +9 -0
- package/public/assets/select-BHqOqun5.js +1 -0
- package/public/assets/toggle-DPeN6aNa.js +1 -0
- package/public/assets/toggle-group-Kl3uyHSr.js +1 -0
- package/public/assets/usePagedRows-BafD6bEN.js +1 -0
- package/public/assets/useRecentFiles-DzJZAoOT.js +3 -0
- package/public/assets/useRegisterFieldControl-pQv-ZDoU.js +1 -0
- package/public/assets/useScrollLock-BCxn_dMp.js +1 -0
- package/public/assets/useSortMode-DjrAN-z4.js +1 -0
- package/public/docs/404.html +3 -3
- package/public/docs/assets/{app.CnWL6bKV.js → app.1gvzd5Zb.js} +1 -1
- package/public/docs/assets/chunks/@localSearchIndexroot.DS2TQP5_.js +1 -0
- package/public/docs/assets/chunks/{VPLocalSearchBox.C0IBKeRQ.js → VPLocalSearchBox.CNxT3s0I.js} +1 -1
- package/public/docs/assets/chunks/{theme.BH-2HuRf.js → theme.C5lLqUv7.js} +2 -2
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.CqKM_jEG.lean.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.js → contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.WqY74T0T.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CHHgywoP.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_detector-contract.md.N4hhenrA.lean.js → contributor-guide_architecture_detector-contract.md.CHHgywoP.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.DIz3_oz9.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.lean.js → contributor-guide_architecture_detector-contract_threshold-tables.md.WH4tRHPL.lean.js} +1 -1
- package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.js} +1 -1
- package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js → contributor-guide_architecture_drill-down.md.yB-Hz2XT.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js → contributor-guide_architecture_drill-down_plan-graph.md.BQZE04sw.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.K71YB-Rr.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_impact-estimation.md.DfvbqEUu.lean.js → contributor-guide_architecture_impact-estimation.md.K71YB-Rr.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.lean.js → contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.DLCAffwn.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.JFrzDMwo.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_overview.md.nxkpOhgf.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js → contributor-guide_architecture_state-and-history_run-comparison-internals.md.U-9KMDFK.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.B7rjSF_h.lean.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.js} +1 -1
- package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.js} +1 -1
- package/public/docs/assets/{contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js → contributor-guide_architecture_worker-protocol.md.Br6OE7RW.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.Br6OE7RW.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.lean.js → contributor-guide_architecture_worker-protocol_cli-and-mcp.md.IVyaXrGe.lean.js} +1 -1
- package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.js} +1 -1
- package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.js} +2 -2
- package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js → contributor-guide_architecture_worker-protocol_evidence-availability.md.D3S5cyOW.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.lean.js → contributor-guide_architecture_worker-protocol_evidence-report.md.CtmCU57I.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_contributing.md.DBW4drtZ.js +1 -0
- package/public/docs/assets/contributor-guide_development-setup.md.B21ZTDhn.js +15 -0
- package/public/docs/assets/contributor-guide_development-setup.md.B21ZTDhn.lean.js +1 -0
- package/public/docs/assets/contributor-guide_testing.md.DpkozNoG.js +4 -0
- package/public/docs/assets/{contributor-guide_testing.md.By6XGR9a.lean.js → contributor-guide_testing.md.DpkozNoG.lean.js} +1 -1
- package/public/docs/assets/{index.md.CHJVslga.js → index.md.lMm7SUMz.js} +1 -1
- package/public/docs/assets/{index.md.CHJVslga.lean.js → index.md.lMm7SUMz.lean.js} +1 -1
- package/public/docs/assets/{style.CyK9Ss73.css → style.Cfxqlj0y.css} +1 -1
- package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.C0pcQoNc.js +29 -0
- package/public/docs/assets/{user-guide_getting-started_ci-and-automation.md.BEepbDDL.lean.js → user-guide_getting-started_ci-and-automation.md.C0pcQoNc.lean.js} +1 -1
- package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.js +1 -0
- package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.js} +2 -2
- package/public/docs/assets/{user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js → user-guide_getting-started_tuning-thresholds.md.BGVE-wrc.lean.js} +1 -1
- package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.js → user-guide_mcp-tools.md.49r3L2YE.js} +1 -1
- package/public/docs/assets/{user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js → user-guide_mcp-tools_compare-runs.md.BQ_kIptC.js} +11 -6
- package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.BQ_kIptC.lean.js +1 -0
- package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.azCZXkH8.js +44 -0
- package/public/docs/assets/{user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js → user-guide_mcp-tools_diagnose-run.md.azCZXkH8.lean.js} +1 -1
- package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.js} +2 -2
- package/public/docs/assets/{user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js → user-guide_mcp-tools_evaluate-budgets.md.dpQpRQ7z.lean.js} +1 -1
- package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.js} +2 -1
- package/public/docs/assets/{user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js → user-guide_mcp-tools_get-finding-evidence.md.CH6FkNLX.lean.js} +1 -1
- package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.js +1 -0
- package/public/docs/assets/user-guide_run-comparison.md.B_LOkdBf.lean.js +1 -0
- package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.js +1 -0
- package/public/docs/assets/user-guide_understanding-findings.md.C4riKCKl.lean.js +1 -0
- package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +6 -6
- package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +6 -6
- package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +6 -6
- package/public/docs/contributor-guide/architecture/board-widgets.html +6 -6
- package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +6 -6
- package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +6 -6
- package/public/docs/contributor-guide/architecture/detector-contract.html +6 -6
- package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +6 -6
- package/public/docs/contributor-guide/architecture/drill-down.html +6 -6
- package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +6 -6
- package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +6 -6
- package/public/docs/contributor-guide/architecture/impact-estimation.html +6 -6
- package/public/docs/contributor-guide/architecture/index.html +5 -5
- package/public/docs/contributor-guide/architecture/overview.html +6 -6
- package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +5 -5
- package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +6 -6
- package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
- package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +6 -6
- package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +5 -5
- package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +6 -6
- package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +5 -5
- package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +6 -6
- package/public/docs/contributor-guide/architecture/widget-rendering.html +6 -6
- package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +6 -6
- package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +6 -6
- package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +7 -7
- package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +6 -6
- package/public/docs/contributor-guide/architecture/worker-protocol.html +6 -6
- package/public/docs/contributor-guide/contributing.html +6 -6
- package/public/docs/contributor-guide/development-setup.html +8 -8
- package/public/docs/contributor-guide/testing.html +9 -6
- package/public/docs/hashmap.json +1 -1
- package/public/docs/index.html +6 -6
- package/public/docs/logo-dark.svg +1 -0
- package/public/docs/logo-light.svg +1 -0
- package/public/docs/tuning-reference/anti-patterns.html +5 -5
- package/public/docs/tuning-reference/aqe.html +5 -5
- package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +5 -5
- package/public/docs/tuning-reference/bottleneck-cold-start.html +5 -5
- package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +5 -5
- package/public/docs/tuning-reference/bottleneck-failures.html +5 -5
- package/public/docs/tuning-reference/bottleneck-gc.html +5 -5
- package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +5 -5
- package/public/docs/tuning-reference/bottleneck-memory-utilization.html +5 -5
- package/public/docs/tuning-reference/bottleneck-retry-waste.html +5 -5
- package/public/docs/tuning-reference/bottleneck-shuffle.html +5 -5
- package/public/docs/tuning-reference/bottleneck-skew.html +5 -5
- package/public/docs/tuning-reference/bottleneck-slow-host.html +5 -5
- package/public/docs/tuning-reference/bottleneck-small-files.html +5 -5
- package/public/docs/tuning-reference/bottleneck-spill.html +5 -5
- package/public/docs/tuning-reference/bottleneck-straggler.html +5 -5
- package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +5 -5
- package/public/docs/tuning-reference/bottleneck-utilization.html +5 -5
- package/public/docs/tuning-reference/caching.html +5 -5
- package/public/docs/tuning-reference/cluster-config.html +5 -5
- package/public/docs/tuning-reference/config.html +5 -5
- package/public/docs/tuning-reference/data-formats.html +5 -5
- package/public/docs/tuning-reference/index.html +5 -5
- package/public/docs/tuning-reference/intro.html +5 -5
- package/public/docs/tuning-reference/joins.html +5 -5
- package/public/docs/tuning-reference/memory-model.html +5 -5
- package/public/docs/tuning-reference/metrics.html +5 -5
- package/public/docs/tuning-reference/partitioning.html +5 -5
- package/public/docs/tuning-reference/pyspark.html +5 -5
- package/public/docs/tuning-reference/shuffle.html +5 -5
- package/public/docs/tuning-reference/spark-architecture.html +5 -5
- package/public/docs/tuning-reference/table-formats.html +5 -5
- package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +5 -5
- package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +5 -5
- package/public/docs/user-guide/alternative-log-retrieval.html +5 -5
- package/public/docs/user-guide/getting-started/ci-and-automation.html +16 -11
- package/public/docs/user-guide/getting-started/reading-the-dashboard.html +6 -6
- package/public/docs/user-guide/getting-started/regression-metric-keys.html +5 -5
- package/public/docs/user-guide/getting-started/tuning-thresholds.html +7 -7
- package/public/docs/user-guide/getting-started.html +5 -5
- package/public/docs/user-guide/mcp-tools/compare-runs.html +16 -11
- package/public/docs/user-guide/mcp-tools/diagnose-run.html +28 -7
- package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +6 -6
- package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +5 -5
- package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +7 -6
- package/public/docs/user-guide/mcp-tools/get-reference-doc.html +5 -5
- package/public/docs/user-guide/mcp-tools/get-run-summary.html +5 -5
- package/public/docs/user-guide/mcp-tools/list-runs.html +5 -5
- package/public/docs/user-guide/mcp-tools.html +6 -6
- package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +5 -5
- package/public/docs/user-guide/run-comparison.html +6 -6
- package/public/docs/user-guide/understanding-findings.html +6 -6
- package/public/export-template.html +45 -45
- package/public/index.html +8 -7
- package/public/sample-runs/sample-run.ndjson.gz +0 -0
- package/vendor-core/allocation.js +25 -23
- package/vendor-core/analyzer.js +85 -58
- package/vendor-core/api-merge-writes.js +2 -0
- package/vendor-core/aqe-skew.js +347 -0
- package/vendor-core/cli/budgets.js +6 -4
- package/vendor-core/cli/collect-run-pair.js +89 -0
- package/vendor-core/cli/collect-run-worker.js +16 -0
- package/vendor-core/cli/collect-run.js +1 -2
- package/vendor-core/cli/native-zstd.js +12 -16
- package/vendor-core/comparability.js +234 -0
- package/vendor-core/comparison-output.js +15 -7
- package/vendor-core/comparison-verdict.js +105 -25
- package/vendor-core/core-source-hash.txt +1 -1
- package/vendor-core/core-time-series.js +0 -1
- package/vendor-core/core-usage-locality.js +1 -1
- package/vendor-core/detectors.js +1007 -380
- package/vendor-core/docs-config.js +0 -4
- package/vendor-core/docs-content/detection/cache.md +2 -1
- package/vendor-core/docs-content/detection/cfg.md +19 -11
- package/vendor-core/docs-content/detection/cold.md +2 -1
- package/vendor-core/docs-content/detection/gc.md +7 -3
- package/vendor-core/docs-content/detection/host.md +11 -1
- package/vendor-core/docs-content/detection/mem.md +17 -6
- package/vendor-core/docs-content/detection/part.md +3 -1
- package/vendor-core/docs-content/detection/plan.md +55 -13
- package/vendor-core/docs-content/detection/shfl.md +11 -5
- package/vendor-core/docs-content/detection/skew.md +47 -1
- package/vendor-core/docs-content/detection/slow.md +4 -2
- package/vendor-core/docs-content/detection/spec.md +6 -1
- package/vendor-core/docs-content/detection/spill.md +6 -3
- package/vendor-core/docs-content/detection/strag.md +19 -6
- package/vendor-core/docs-content/detection/tiny.md +9 -3
- package/vendor-core/effective-conf.js +7 -10
- package/vendor-core/efficiency-model.js +2 -2
- package/vendor-core/etl-phases.js +1 -2
- package/vendor-core/event-handlers.js +175 -57
- package/vendor-core/event-schemas.js +21 -16
- package/vendor-core/evidence-availability.js +9 -2
- package/vendor-core/evidence-report.js +118 -35
- package/vendor-core/executor-peaks.js +41 -0
- package/vendor-core/export-data.js +5 -5
- package/vendor-core/finding-identity.js +78 -0
- package/vendor-core/finding-presentation.js +116 -33
- package/vendor-core/finding-types.js +88 -21
- package/vendor-core/format-utils.js +14 -15
- package/vendor-core/html-export.js +1 -1
- package/vendor-core/impact-band.js +2 -2
- package/vendor-core/impact-format.js +47 -19
- package/vendor-core/impact-model.js +26 -15
- package/vendor-core/job-groups.js +4 -4
- package/vendor-core/mcp-server-factory.js +48 -12
- package/vendor-core/mcp-tools.js +179 -24
- package/vendor-core/median.js +2 -2
- package/vendor-core/nested-loop-join.js +73 -0
- package/vendor-core/occupancy.js +1 -2
- package/vendor-core/parser-worker.js +1 -1
- package/vendor-core/plan-graph-model.js +14 -22
- package/vendor-core/plan-node-detail.js +2 -3
- package/vendor-core/plan-summary.js +24 -19
- package/vendor-core/plan-tree-walk.js +6 -8
- package/vendor-core/python-stage.js +12 -1
- package/vendor-core/recommendation-rollup.js +13 -12
- package/vendor-core/redact.js +52 -46
- package/vendor-core/run-aggregates.js +6 -8
- package/vendor-core/run-comparison.js +226 -66
- package/vendor-core/run-identity.js +12 -0
- package/vendor-core/run-interpretation.js +21 -0
- package/vendor-core/run-metrics.js +2 -2
- package/vendor-core/run-output.js +32 -10
- package/vendor-core/run-totals.js +12 -0
- package/vendor-core/run-verdict.js +76 -3
- package/vendor-core/scaling-sim.js +3 -4
- package/vendor-core/snappy-block.js +2 -2
- package/vendor-core/spark-conf.js +168 -0
- package/vendor-core/sql-stages.js +2 -2
- package/vendor-core/stage-alignment.js +2 -12
- package/vendor-core/stage-identity.js +58 -3
- package/vendor-core/stage-label.js +14 -0
- package/vendor-core/stage-plan-nodes.js +17 -3
- package/vendor-core/stage-quantiles.js +141 -9
- package/vendor-core/stage-structure.js +17 -3
- package/vendor-core/threshold-overrides.js +9 -3
- package/vendor-core/types.js +58 -21
- package/vendor-core/wasted-core-hours.js +0 -1
- package/vendor-core/write-targets.js +114 -14
- package/vendor-core/zstd-worker-client.js +1 -1
- package/vendor-core/zstd-worker.js +1 -1
- package/public/assets/AutoscalingChurn-CRA2Mb7F.js +0 -1
- package/public/assets/CacheUtilization-Bh9OY_V9.js +0 -1
- package/public/assets/CachingOpportunity-oCiQwmTJ.js +0 -1
- package/public/assets/ColdStart-BJrtjfmn.js +0 -1
- package/public/assets/ConfigAudit-DMvKxySP.js +0 -1
- package/public/assets/CoreUsageArea-REC2zsKw.js +0 -1
- package/public/assets/Dashboard-BHZ76JCu.js +0 -2
- package/public/assets/DisclosureContext-OO7X1HAx.js +0 -1
- package/public/assets/DuplicatePlanSubtree-BgG-L-cK.js +0 -1
- package/public/assets/ExecutorUtilization-D2KhSVbY.js +0 -1
- package/public/assets/GcPressure-D1BTE8qh.js +0 -1
- package/public/assets/IncompleteRun-DD40xUpy.js +0 -1
- package/public/assets/JobFailures-YwNzCF19.js +0 -1
- package/public/assets/MemoryUtilization-9b8ee_3_.js +0 -1
- package/public/assets/OverBroadcast-CfctS8a8.js +0 -1
- package/public/assets/PartitionHint-CUvbnnxy.js +0 -1
- package/public/assets/PartitionSizing-VG8kBgG5.js +0 -1
- package/public/assets/PlanExplorer-DmubTAOP.js +0 -1
- package/public/assets/PlanGraphRoute-DctPZjS0.js +0 -1
- package/public/assets/RetryWaste-CnpM8bB7.js +0 -1
- package/public/assets/ShuffleIO-D13vGmas.js +0 -1
- package/public/assets/Skew-DlagstYP.js +0 -1
- package/public/assets/SlowHost-cSkLRZ_e.js +0 -1
- package/public/assets/SmallFiles-B-kERMcq.js +0 -1
- package/public/assets/SpeculationWaste-DaysAzXO.js +0 -1
- package/public/assets/Spill-H44Gz4US.js +0 -1
- package/public/assets/StageFailed-DCfii9h_.js +0 -1
- package/public/assets/StageFindingGroup-BXJ0Mj5n.js +0 -1
- package/public/assets/StageHeader--oN2J-oD.js +0 -1
- package/public/assets/StageShape-IZ80CZiJ.js +0 -1
- package/public/assets/StageSlowness--BRIaBc1.js +0 -1
- package/public/assets/Straggler-Ctq0Df_o.js +0 -1
- package/public/assets/TaskFailures-Bks_gqhL.js +0 -1
- package/public/assets/TinyTask-BssfzGFB.js +0 -1
- package/public/assets/UnderBroadcast-DsScrxc-.js +0 -1
- package/public/assets/charts-vendor-0VMaLVLN.js +0 -59
- package/public/assets/finding-anchor-BYW1oz5R.js +0 -1
- package/public/assets/impact-sort-xzy0g-Vh.js +0 -1
- package/public/assets/index-DhpRGMZc.css +0 -2
- package/public/assets/index-DwqkFUFk.js +0 -14
- package/public/assets/plan-graph-vendor-BYl-AzN5.js +0 -7
- package/public/assets/react-vendor-DfpuPQZf.js +0 -9
- package/public/assets/select-C8edjYTq.js +0 -1
- package/public/assets/usePagedRows-CsuUFeYx.js +0 -1
- package/public/assets/useRecentFiles-BvkKo3JN.js +0 -11
- package/public/assets/useSortMode-joX71BZN.js +0 -1
- package/public/docs/assets/chunks/@localSearchIndexroot.tE5pz54z.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.wulOueeB.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.N4hhenrA.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.Dp3c40rx.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.DfvbqEUu.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.Cwm1jHmn.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.BX1XnFbq.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.CiQl1MLb.js +0 -1
- package/public/docs/assets/contributor-guide_contributing.md.CQMvGwFt.js +0 -1
- package/public/docs/assets/contributor-guide_development-setup.md.DQdzera7.js +0 -15
- package/public/docs/assets/contributor-guide_development-setup.md.DQdzera7.lean.js +0 -1
- package/public/docs/assets/contributor-guide_testing.md.By6XGR9a.js +0 -1
- package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.BEepbDDL.js +0 -24
- package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +0 -1
- package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js +0 -1
- package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +0 -23
- package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +0 -1
- package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +0 -1
- package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.js +0 -1
- package/public/docs/assets/user-guide_understanding-findings.md.3C7KCEZO.lean.js +0 -1
- /package/public/docs/assets/{contributor-guide_architecture_board-widgets_caching-and-memory.md.CQgn1t_M.lean.js → contributor-guide_architecture_board-widgets_caching-and-memory.md.BYDo3ClR.lean.js} +0 -0
- /package/public/docs/assets/{contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js → contributor-guide_architecture_board-widgets_plan-advisor.md.ajUAVdbc.lean.js} +0 -0
- /package/public/docs/assets/{contributor-guide_architecture_overview.md.C1sSvm9i.lean.js → contributor-guide_architecture_overview.md.nxkpOhgf.lean.js} +0 -0
- /package/public/docs/assets/{contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js → contributor-guide_architecture_widget-rendering.md.BrSz8Ezh.lean.js} +0 -0
- /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js → contributor-guide_architecture_widget-rendering_list-sort-mode.md.37hcZJ7q.lean.js} +0 -0
- /package/public/docs/assets/{contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js → contributor-guide_architecture_widget-rendering_row-expand-contract.md.mK_VmAWk.lean.js} +0 -0
- /package/public/docs/assets/{contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js → contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DVSXn-bb.lean.js} +0 -0
- /package/public/docs/assets/{contributor-guide_contributing.md.CQMvGwFt.lean.js → contributor-guide_contributing.md.DBW4drtZ.lean.js} +0 -0
- /package/public/docs/assets/{user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js → user-guide_getting-started_reading-the-dashboard.md.CGaxPX_1.lean.js} +0 -0
- /package/public/docs/assets/{user-guide_mcp-tools.md.C65SjWUL.lean.js → user-guide_mcp-tools.md.49r3L2YE.lean.js} +0 -0
package/vendor-core/mcp-tools.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { resolve as resolvePath, join, dirname } from 'node:path';
|
|
2
|
-
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, statSync, readFileSync, readdirSync, } from 'node:fs';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
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 {
|
|
9
|
+
import {
|
|
10
|
+
buildEvidenceReport, runFindings, toFindingsFilter,
|
|
11
|
+
|
|
12
|
+
} from './evidence-report.js';
|
|
10
13
|
import { computeWallClock } from './wall-clock.js';
|
|
11
14
|
import { renderComparisonMarkdown, } from './run-comparison.js';
|
|
12
15
|
import { buildComparisonOutput, } from './comparison-output.js';
|
|
13
16
|
import { compileNormalizePatterns } from './stage-alignment.js';
|
|
14
|
-
import {
|
|
17
|
+
import { compileConfIgnorePatterns } from './comparability.js';
|
|
18
|
+
import { runOutputBlocks, } from './run-output.js';
|
|
15
19
|
import { evaluateBudgets, } from './cli/budgets.js';
|
|
16
20
|
import { assertStageBudgetMetric, combineRegressionBudgets } from './cli/regression-budgets.js';
|
|
17
21
|
import { FINDING_NAMES, titleCase } from './finding-names.js';
|
|
@@ -20,7 +24,7 @@ import { DETECTORS, } from './detectors.js';
|
|
|
20
24
|
import { tunedDetectors } from './threshold-overrides.js';
|
|
21
25
|
import { tuningDocSlugForAnchor, pageForAnchor } from './docs-config.js';
|
|
22
26
|
import { typeTag } from './format-utils.js';
|
|
23
|
-
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
|
|
@@ -53,7 +57,7 @@ import { typeTag } from './format-utils.js';
|
|
|
53
57
|
|
|
54
58
|
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
|
|
57
61
|
|
|
58
62
|
|
|
59
63
|
|
|
@@ -72,18 +76,70 @@ const CACHE_TTL_MS = envInt('SPARKFORENSICS_MCP_CACHE_TTL_MS', 15 * 60 * 1000);
|
|
|
72
76
|
|
|
73
77
|
const byRunId = new Map (); // runId -> { appModel, cacheKey, lastAccess }
|
|
74
78
|
const byCacheKey = new Map (); // cacheKey -> runId
|
|
79
|
+
// Built comparisons, keyed by both runs and every option that changes the output. Each entry follows
|
|
80
|
+
// its runs' TTL and eviction (deleteRunEntry), and the map is capped on its own: a comparison holds
|
|
81
|
+
// a row per paired stage, and 8 runs allow 56 ordered pairs.
|
|
82
|
+
const COMPARISON_CACHE_CAP = 16;
|
|
83
|
+
|
|
84
|
+
const comparisonCache = new Map ();
|
|
85
|
+
// Threshold overrides are one fixed object per server process (or DEFAULT for none): identity keys them.
|
|
86
|
+
const thresholdsIds = new WeakMap ();
|
|
87
|
+
let nextThresholdsId = 1;
|
|
88
|
+
function thresholdsKey(thresholds ) {
|
|
89
|
+
if (!thresholds) return 0;
|
|
90
|
+
let id = thresholdsIds.get(thresholds);
|
|
91
|
+
if (id === undefined) {
|
|
92
|
+
id = nextThresholdsId++;
|
|
93
|
+
thresholdsIds.set(thresholds, id);
|
|
94
|
+
}
|
|
95
|
+
return id;
|
|
96
|
+
}
|
|
97
|
+
|
|
75
98
|
const pendingByCacheKey = new Map (); // cacheKey -> in-flight Promise<{runId, appModel}>
|
|
76
99
|
|
|
77
100
|
export function pathCacheKey(path ) {
|
|
78
101
|
const resolved = resolvePath(path);
|
|
79
102
|
if (!existsSync(resolved)) throw mcpError('invalid-event-log', `No such file: ${resolved}`);
|
|
103
|
+
const stat = statSync(resolved);
|
|
104
|
+
// A rolling event-log directory grows by appending to its live events_N file, which leaves the
|
|
105
|
+
// directory's own mtime and size alone: the key covers the files inside it.
|
|
106
|
+
if (stat.isDirectory()) return `path:${resolved}:${directoryStatKey(resolved)}`;
|
|
80
107
|
// Size narrows a same-millisecond mtime collision; ctime narrows the case where a copy tool
|
|
81
108
|
// (rsync --preserve-times, tar) restores an identical mtime+size for different content: ctime
|
|
82
109
|
// can't be set by the copying tool, so it still reflects when the file landed on disk.
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
return `path:${resolved}:${fileStatKey(stat)}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function fileStatKey({ mtimeMs, ctimeMs, size } ) {
|
|
114
|
+
return `${mtimeMs}:${ctimeMs}:${size}`;
|
|
85
115
|
}
|
|
86
116
|
|
|
117
|
+
// The file count, newest mtime and ctime, and total size of the files under a directory (the
|
|
118
|
+
// parts of a rolling log, which Spark appends to in place): any part growing, arriving or going
|
|
119
|
+
// changes it.
|
|
120
|
+
function directoryStatKey(dir ) {
|
|
121
|
+
let files = 0, mtimeMs = 0, ctimeMs = 0, size = 0;
|
|
122
|
+
const walk = (path , depth ) => {
|
|
123
|
+
for (const entry of readdirSync(path, { withFileTypes: true })) {
|
|
124
|
+
const child = join(path, entry.name);
|
|
125
|
+
if (entry.isDirectory()) {
|
|
126
|
+
if (depth < ROLLING_LOG_DEPTH) walk(child, depth + 1);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const stat = statSync(child);
|
|
130
|
+
files++;
|
|
131
|
+
mtimeMs = Math.max(mtimeMs, stat.mtimeMs);
|
|
132
|
+
ctimeMs = Math.max(ctimeMs, stat.ctimeMs);
|
|
133
|
+
size += stat.size;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
walk(dir, 0);
|
|
137
|
+
return `dir:${files}:${mtimeMs}:${ctimeMs}:${size}`;
|
|
138
|
+
}
|
|
139
|
+
// A rolling log's parts sit in the directory the path names, or one level down in its
|
|
140
|
+
// eventlog_v2_* subdirectory.
|
|
141
|
+
const ROLLING_LOG_DEPTH = 1;
|
|
142
|
+
|
|
87
143
|
function shsCacheKey({ shsBaseUrl, appId, attemptId } ) {
|
|
88
144
|
return `shs:${shsBaseUrl}:${appId}:${attemptId ?? ''}`;
|
|
89
145
|
}
|
|
@@ -99,6 +155,10 @@ function touch(runId ) {
|
|
|
99
155
|
|
|
100
156
|
function deleteRunEntry(id , entry ) {
|
|
101
157
|
byRunId.delete(id);
|
|
158
|
+
// A comparison outlives neither of its runs: its entry goes with the first one evicted.
|
|
159
|
+
for (const [key, cached] of comparisonCache) {
|
|
160
|
+
if (cached.runIdA === id || cached.runIdB === id) comparisonCache.delete(key);
|
|
161
|
+
}
|
|
102
162
|
if (byCacheKey.get(entry.cacheKey) === id) byCacheKey.delete(entry.cacheKey);
|
|
103
163
|
}
|
|
104
164
|
|
|
@@ -121,6 +181,11 @@ function evictOverflow() {
|
|
|
121
181
|
}
|
|
122
182
|
}
|
|
123
183
|
|
|
184
|
+
/** How many built comparisons the cache holds: the occupancy the cap and eviction rules bound. */
|
|
185
|
+
export function comparisonCacheSize() {
|
|
186
|
+
return comparisonCache.size;
|
|
187
|
+
}
|
|
188
|
+
|
|
124
189
|
export function getCachedAppModel(runId ) {
|
|
125
190
|
evictStale();
|
|
126
191
|
const entry = byRunId.get(runId);
|
|
@@ -166,6 +231,8 @@ export async function resolveOrCreateRun(
|
|
|
166
231
|
const appModel = 'path' in source
|
|
167
232
|
? await resolveFromPath(source.path)
|
|
168
233
|
: (await resolveFromShs(source.shsBaseUrl, source.appId, source.attemptId, { fetchImpl, maxArchiveBytes, idleTimeoutMs })).appModel;
|
|
234
|
+
// The attempt the caller asked the History Server for, when the log itself does not name one.
|
|
235
|
+
if (!('path' in source) && source.attemptId && appModel.app && !appModel.app.attemptId) appModel.app.attemptId = source.attemptId;
|
|
169
236
|
|
|
170
237
|
const newRunId = randomUUID();
|
|
171
238
|
byRunId.set(newRunId, { appModel, cacheKey, lastAccess: Date.now() });
|
|
@@ -180,30 +247,83 @@ export async function resolveOrCreateRun(
|
|
|
180
247
|
return resolution;
|
|
181
248
|
}
|
|
182
249
|
|
|
250
|
+
/** `compact`: an index, one short row per finding and no clean or not-run check lists. `full`: the
|
|
251
|
+
* report as the CLI writes it. */
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
/** A finding as `view: 'compact'` lists it: enough to rank it, name it and pair it across reruns.
|
|
255
|
+
* The rest of the row (recommendation, evidence, remediation, estimate) comes from
|
|
256
|
+
* `get_finding_evidence` by `id`. */
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
export function compactFindingRow(row ) {
|
|
266
|
+
return {
|
|
267
|
+
id: row.id, key: row.key, type: row.type, tag: row.tag, name: row.name,
|
|
268
|
+
impactBand: row.impactBand, stageId: row.stageId, actionLabel: row.actionLabel,
|
|
269
|
+
metric: row.metric ?? null, value: row.value ?? null,
|
|
270
|
+
...(row.valueText !== undefined ? { valueText: row.valueText } : {}),
|
|
271
|
+
...(row.impact !== undefined ? { impact: row.impact, impactMeaning: row.impactMeaning ?? null } : {}),
|
|
272
|
+
// Kept: a tuned finding's impact figure is uncalibrated, and a reader must see that on its row.
|
|
273
|
+
...(row.tunedThresholds !== undefined ? { tunedThresholds: row.tunedThresholds } : {}),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** A verdict step with its lead finding's row, which `findings` may not list under `maxFindings` or
|
|
278
|
+
* `minBand`. Null for a step whose lead has no id. */
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
183
295
|
// `thresholds` on every analyzing tool below: the server's --thresholds overrides, fixed for the
|
|
184
296
|
// process by createMcpServer(). A client can't set them per call; the dashboard never has them.
|
|
297
|
+
// `view` defaults to `full` here; the MCP tool defaults it to `compact`.
|
|
185
298
|
export function diagnoseRun(runId , opts
|
|
186
299
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
{
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
) {
|
|
194
303
|
const appModel = getCachedAppModel(runId);
|
|
195
|
-
const
|
|
196
|
-
const
|
|
304
|
+
const view = opts?.view ?? 'full';
|
|
305
|
+
const findingsFilter = toFindingsFilter(opts?.impactBand, opts?.type, opts?.stageId, opts?.minBand, opts?.maxFindings);
|
|
306
|
+
const { json, markdown, findingsMatched } = buildEvidenceReport(appModel, {
|
|
197
307
|
redact: opts?.redact, markdown: opts?.markdown, findingsFilter, thresholds: opts?.thresholds,
|
|
198
308
|
});
|
|
309
|
+
// The lead rows come from the unfiltered report (cached, so this costs no second analysis).
|
|
310
|
+
const allRows = findingsFilter
|
|
311
|
+
? buildEvidenceReport(appModel, { redact: opts?.redact, markdown: false, thresholds: opts?.thresholds }).json.findings
|
|
312
|
+
: json.findings;
|
|
313
|
+
const shape = (row ) => (view === 'compact' ? compactFindingRow(row) : row);
|
|
314
|
+
const steps = json.verdict.steps.map((step) => {
|
|
315
|
+
const lead = step.leadFindingId == null ? undefined : allRows.find((row) => row.id === step.leadFindingId && row.type === step.type);
|
|
316
|
+
return { ...step, leadFinding: lead ? shape(lead) : null };
|
|
317
|
+
});
|
|
199
318
|
const include = opts?.include ?? [];
|
|
200
319
|
const tuned = json.summary.tunedThresholds;
|
|
201
320
|
return {
|
|
202
|
-
runId, verdict: json.verdict, findings: json.findings,
|
|
203
|
-
|
|
321
|
+
runId, view, verdict: { ...json.verdict, steps }, findings: json.findings.map(shape), findingsOmitted: findingsMatched - json.findings.length,
|
|
322
|
+
recommendations: json.recommendations,
|
|
323
|
+
...(view === 'full' ? { cleanChecks: json.cleanChecks, notRunChecks: json.notRunChecks } : {}),
|
|
204
324
|
// The CLI report's own blocks, so a run reads the same through either surface.
|
|
205
325
|
writeTargets: json.writeTargets,
|
|
206
|
-
...runOutputBlocks(appModel, { redact: opts?.redact, thresholds: opts?.thresholds }),
|
|
326
|
+
...runOutputBlocks(appModel, { redact: opts?.redact, thresholds: opts?.thresholds, generator: opts?.generator }),
|
|
207
327
|
runComplete: appModel.app?.endTime != null,
|
|
208
328
|
// Top level too, so a client that never asks for `summary` still sees the run was tuned.
|
|
209
329
|
...(tuned ? { tunedThresholds: tuned } : {}),
|
|
@@ -214,6 +334,16 @@ export function diagnoseRun(runId , opts
|
|
|
214
334
|
};
|
|
215
335
|
}
|
|
216
336
|
|
|
337
|
+
/** The few fields `format: 'md'` returns as `structuredContent`: the Markdown carries the rest. */
|
|
338
|
+
export function diagnoseHeadline(result )
|
|
339
|
+
|
|
340
|
+
{
|
|
341
|
+
return {
|
|
342
|
+
runId: result.runId, runComplete: result.runComplete, title: result.verdict.title,
|
|
343
|
+
findingCount: result.findings.length, findingsOmitted: result.findingsOmitted, recommendationCount: result.recommendations.length,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
217
347
|
const DOCS_CONTENT_DIR = join(dirname(fileURLToPath(import.meta.url)), 'docs-content');
|
|
218
348
|
|
|
219
349
|
// Both vendored corpora lead with their own heading line; strips the leading '#'/'###' markers, an
|
|
@@ -351,6 +481,7 @@ async function resolveAndAnalyze(
|
|
|
351
481
|
|
|
352
482
|
export async function compareRuns(
|
|
353
483
|
a , b , opts
|
|
484
|
+
|
|
354
485
|
,
|
|
355
486
|
) {
|
|
356
487
|
// Before any log is read: a bad pattern refuses the call, as the CLI's usage error does.
|
|
@@ -359,6 +490,11 @@ export async function compareRuns(
|
|
|
359
490
|
} catch (e) {
|
|
360
491
|
throw mcpError('access-or-upstream-failure', `normalizePath: ${(e ).message}`);
|
|
361
492
|
}
|
|
493
|
+
try {
|
|
494
|
+
compileConfIgnorePatterns(opts?.ignoreConf);
|
|
495
|
+
} catch (e) {
|
|
496
|
+
throw mcpError('access-or-upstream-failure', `ignoreConf: ${(e ).message}`);
|
|
497
|
+
}
|
|
362
498
|
const [
|
|
363
499
|
{ runId: runIdA, appModel: appModelA, catalog: catalogA },
|
|
364
500
|
{ runId: runIdB, appModel: appModelB, catalog: catalogB },
|
|
@@ -368,11 +504,30 @@ export async function compareRuns(
|
|
|
368
504
|
// interactive stage-detail drill-down, which none of compare/matchStages/metricDeltas/findingsDelta
|
|
369
505
|
// read. Findings/metrics come from `catalog` and appModel.stages/sql, both fully populated. Produces
|
|
370
506
|
// identical output to the dashboard; the MCP tool just never exposes per-task drill-down.
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
507
|
+
const view = opts?.include?.includes('stagePairs') ? 'full' : 'summary';
|
|
508
|
+
const cacheKey = JSON.stringify([runIdA, runIdB, thresholdsKey(opts?.thresholds), !!opts?.redact, view, opts?.normalizePath ?? [], opts?.ignoreConf ?? [], opts?.noiseBand ?? null, opts?.generator ?? null]);
|
|
509
|
+
let built = comparisonCache.get(cacheKey)?.built;
|
|
510
|
+
if (built) {
|
|
511
|
+
// Re-insert so the oldest entry is the least recently used.
|
|
512
|
+
comparisonCache.delete(cacheKey);
|
|
513
|
+
comparisonCache.set(cacheKey, { runIdA, runIdB, built });
|
|
514
|
+
} else {
|
|
515
|
+
built = buildComparisonOutput(
|
|
516
|
+
{ label: runIdA, appModel: appModelA, catalog: catalogA },
|
|
517
|
+
{ label: runIdB, appModel: appModelB, catalog: catalogB },
|
|
518
|
+
{ redact: opts?.redact, normalizePath: opts?.normalizePath, ignoreConf: opts?.ignoreConf, noiseBand: opts?.noiseBand, view, generator: opts?.generator },
|
|
519
|
+
);
|
|
520
|
+
// Either run can be evicted while the other one is still being read or parsed (the awaits
|
|
521
|
+
// above): an entry for a run that is gone would outlive its eviction, so it is only kept
|
|
522
|
+
// while both are cached.
|
|
523
|
+
if (byRunId.has(runIdA) && byRunId.has(runIdB)) {
|
|
524
|
+
comparisonCache.set(cacheKey, { runIdA, runIdB, built });
|
|
525
|
+
while (comparisonCache.size > COMPARISON_CACHE_CAP) comparisonCache.delete(comparisonCache.keys().next().value );
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const { comparison } = built;
|
|
529
|
+
// A copy, so a caller that changes the result cannot change what the next caller is served.
|
|
530
|
+
const output = structuredClone(built.output);
|
|
376
531
|
|
|
377
532
|
return {
|
|
378
533
|
runIdA,
|
package/vendor-core/median.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Median of values already sorted ascending: the mean of the two middle values on an even count.
|
|
2
|
+
// 0 for an empty list.
|
|
3
3
|
export function medianOfSorted(sorted ) {
|
|
4
4
|
const n = sorted.length;
|
|
5
5
|
if (n === 0) return 0;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Reads a nested-loop join's operator, join type and condition from its plan line, and the row
|
|
2
|
+
// counts around it from the executors' "number of output rows" metric.
|
|
3
|
+
//
|
|
4
|
+
// Spark plans a join with no equi-join keys as a BroadcastNestedLoopJoin when one side can be
|
|
5
|
+
// broadcast, else a CartesianProduct for an inner-like join (JoinSelection in
|
|
6
|
+
// sql/core/src/main/scala/org/apache/spark/sql/execution/SparkStrategies.scala, v3.5.0). Both
|
|
7
|
+
// compare every left row with every right row.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const OUTPUT_ROWS = 'number of output rows';
|
|
21
|
+
|
|
22
|
+
// BroadcastNestedLoopJoin <BuildLeft|BuildRight>, <JoinType>[, <condition>]
|
|
23
|
+
const BNLJ_LINE = /^BroadcastNestedLoopJoin\s+Build(?:Left|Right),\s*(\w+)(?:,\s*([\s\S]+))?$/;
|
|
24
|
+
// CartesianProduct[ <condition>]: the operator only plans inner-like joins and prints no type.
|
|
25
|
+
const CARTESIAN_LINE = /^CartesianProduct(?:\s+([\s\S]+))?$/;
|
|
26
|
+
|
|
27
|
+
function cleanCondition(raw ) {
|
|
28
|
+
const text = (raw ?? '').replace(/#\d+L?/g, '').trim();
|
|
29
|
+
return text.length > 0 ? text : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The nested-loop operator, join type and condition behind a plan node, or null for any other node. */
|
|
33
|
+
export function parseNestedLoopJoin(node ) {
|
|
34
|
+
const detail = (node.detail ?? '').trim();
|
|
35
|
+
if (node.name === 'BroadcastNestedLoopJoin') {
|
|
36
|
+
const m = detail.match(BNLJ_LINE);
|
|
37
|
+
return { operator: 'BroadcastNestedLoopJoin', joinType: m?.[1] ?? null, condition: cleanCondition(m?.[2]) };
|
|
38
|
+
}
|
|
39
|
+
if (node.name === 'CartesianProduct') {
|
|
40
|
+
const m = detail.match(CARTESIAN_LINE);
|
|
41
|
+
return { operator: 'CartesianProduct', joinType: 'Inner', condition: cleanCondition(m?.[1]) };
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Operators that pass every row of their one child through, so the child's row count is theirs.
|
|
47
|
+
// A node outside this list that reports no row metric (Expand, Union, Limit...) can change the
|
|
48
|
+
// count, so the walk stops there rather than guess.
|
|
49
|
+
const ROW_PRESERVING = new Set([
|
|
50
|
+
'WholeStageCodegen', 'InputAdapter', 'Project', 'Sort', 'Exchange', 'BroadcastExchange',
|
|
51
|
+
'ShuffleQueryStage', 'BroadcastQueryStage', 'AQEShuffleRead', 'CustomShuffleReader',
|
|
52
|
+
'ColumnarToRow', 'AdaptiveSparkPlan',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
/** The rows a subtree produced: the nearest "number of output rows" at or below `node`, walking
|
|
56
|
+
* through row-preserving single-child operators. Null when the log reports none for it. */
|
|
57
|
+
export function outputRowsOf(node ) {
|
|
58
|
+
let current = node;
|
|
59
|
+
while (current) {
|
|
60
|
+
const metric = current.metrics?.find((m) => m.name === OUTPUT_ROWS);
|
|
61
|
+
if (metric) return metric.value;
|
|
62
|
+
const preserving = ROW_PRESERVING.has(current.name) || /^WholeStageCodegen/.test(current.name);
|
|
63
|
+
if (!preserving || current.children.length !== 1) return null;
|
|
64
|
+
current = current.children[0];
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The node's own "number of output rows" value, null when absent. */
|
|
70
|
+
export function ownOutputRows(node ) {
|
|
71
|
+
const metric = node.metrics?.find((m) => m.name === OUTPUT_ROWS);
|
|
72
|
+
return metric ? metric.value : null;
|
|
73
|
+
}
|
package/vendor-core/occupancy.js
CHANGED
|
@@ -251,7 +251,7 @@ export async function runParseFiles(
|
|
|
251
251
|
emitParseCompletion(state, emit, linesProcessed);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
//
|
|
254
|
+
// Message bus, active only when running as a Web Worker.
|
|
255
255
|
|
|
256
256
|
const isWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
|
|
257
257
|
|
|
@@ -1,11 +1,11 @@
|
|
|
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
3
|
// docs-site/contributor-guide/architecture/drill-down/plan-graph.md). The Exchange
|
|
4
|
-
// write/read split
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// write/read split happens upstream in resolvePlanTree (event-handlers.ts):
|
|
5
|
+
// this file walks the already-split tree and maps each PlanNode onto a
|
|
6
|
+
// PlanGraphNodeData. sourceNodeId lets a write half point back to its read
|
|
7
|
+
// half's id, so aggregate logic can group a pair without special-casing
|
|
8
|
+
// exchangeRole.
|
|
9
9
|
import { walkPlanTree } from './plan-tree-walk.js';
|
|
10
10
|
import {
|
|
11
11
|
classifyNode,
|
|
@@ -82,15 +82,9 @@ export function buildPlanGraphModel(
|
|
|
82
82
|
|
|
83
83
|
const durationMap =
|
|
84
84
|
sqlExec && sqlExecutionId != null ? buildDurationMap(planTree, appModel, sqlExec, sqlExecutionId, durationMode) : null;
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
// stable normalization total. PlanGraphCanvas needs this alongside
|
|
89
|
-
// `durationMap` (the mode-selected values actually shown) because summing
|
|
90
|
-
// `durationMap` itself would double/triple-count under 'inclusive' mode,
|
|
91
|
-
// where an ancestor's share already folds in every descendant's share.
|
|
92
|
-
// durationMode === 'exclusive' already computed the same map above; only
|
|
93
|
-
// redo the work when the requested mode actually differs.
|
|
85
|
+
// Always-exclusive map, independent of `durationMode`: the stable total a
|
|
86
|
+
// percentage denominator sums (see PlanGraphNodeData.exclusiveDurationShare).
|
|
87
|
+
// Reuses `durationMap` when the requested mode is already exclusive.
|
|
94
88
|
const exclusiveDurationMap =
|
|
95
89
|
durationMode === 'exclusive'
|
|
96
90
|
? durationMap
|
|
@@ -110,14 +104,12 @@ export function buildPlanGraphModel(
|
|
|
110
104
|
}
|
|
111
105
|
}
|
|
112
106
|
|
|
113
|
-
// resolvePlanTree (event-handlers.ts)
|
|
114
|
-
// execution, but
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
// execution means no findings attach, matching the sqlExec-falsy behavior
|
|
120
|
-
// used elsewhere in this function (durationMap, linkedStageIds).
|
|
107
|
+
// resolvePlanTree (event-handlers.ts) prefixes every id with its owning
|
|
108
|
+
// execution, but a finding is also scoped to the execution whose graph is
|
|
109
|
+
// being built, as defense in depth: a synthetic finding whose planNodeIds
|
|
110
|
+
// collide with this tree's ids (e.g. hand-built test fixtures) cannot badge
|
|
111
|
+
// onto the wrong tree. No linked SQL execution means no findings attach,
|
|
112
|
+
// like durationMap and linkedStageIds above.
|
|
121
113
|
const findingsByNodeId = new Map ();
|
|
122
114
|
if (sqlExecutionId != null) {
|
|
123
115
|
for (const finding of findings) {
|
|
@@ -164,9 +164,8 @@ export function buildDurationMap(
|
|
|
164
164
|
const s = appModel.stages.get(id);
|
|
165
165
|
if (s) stagesById.set(id, { submittedAt: s.submittedAt, completedAt: s.completedAt });
|
|
166
166
|
}
|
|
167
|
-
// attributeStageDurationToPlan reads
|
|
168
|
-
//
|
|
169
|
-
// data) one already on `sqlExec`.
|
|
167
|
+
// attributeStageDurationToPlan reads `.stageIds`; use the derived array, not sqlExec's own
|
|
168
|
+
// (see contributor-guide/architecture/detector-contract/plan-attribution.md).
|
|
170
169
|
return durationMode === 'inclusive'
|
|
171
170
|
? attributeStageDurationToPlanInclusive(planTree, stagesById, { ...sqlExec, stageIds })
|
|
172
171
|
: attributeStageDurationToPlan(planTree, stagesById, { ...sqlExec, stageIds });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Best-effort summary of a resolved plan tree (
|
|
1
|
+
// Best-effort summary of a resolved plan tree (`resolvePlanTree` in event-handlers.ts).
|
|
2
2
|
// Unparseable fragments are silently skipped, never surfaced as an error.
|
|
3
3
|
|
|
4
4
|
|
|
@@ -56,14 +56,14 @@ export function isDeltaLogRead(name , detail ) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// Stable relation-identity key for a scan node: "<format>:<relation>" (e.g.
|
|
59
|
-
// "delta:
|
|
59
|
+
// "delta:db.t", "parquet:db.a", "jdbc:db.b")
|
|
60
60
|
// or null when the node is internal Delta metadata / a non-scan / un-nameable.
|
|
61
61
|
// The single source of truth for scan identity, shared by `visitScan` (summary)
|
|
62
62
|
// and the `cachingOpportunity` detector so the regexes live in one place.
|
|
63
63
|
export function scanRelationId(name , detail ) {
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
64
|
+
// Named catalog table, untruncated, from the nodeName (the Location: path is truncated at
|
|
65
|
+
// ~100 chars and points at _delta_log for Delta tables). All catalog tables surface as
|
|
66
|
+
// `spark_catalog.<db>.<table>`.
|
|
67
67
|
let format = null, table = null;
|
|
68
68
|
const nameM = name.match(/^Scan\s+(parquet|orc|csv|json)\s+(spark_catalog\.\S+)$/i);
|
|
69
69
|
// Fast reject: every branch below that returns a key needs this name match, a FileScan
|
|
@@ -81,12 +81,12 @@ export function scanRelationId(name , detail ) {
|
|
|
81
81
|
return `${format}:${table.replace(/^spark_catalog\./, '')}`;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
//
|
|
84
|
+
// Internal Delta metadata (see isDeltaLogRead): no relation.
|
|
85
85
|
if (isDeltaLogRead(name, detail)) return null;
|
|
86
86
|
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
87
|
+
// Anonymous real file read (InMemoryFileIndex, no catalog name): best-effort Location basename.
|
|
88
|
+
// Preserves temp/snapshot self-reuse; a truncated Location can still fragment these, which is
|
|
89
|
+
// unrecoverable.
|
|
90
90
|
const fileM = detail.match(/FileScan\s+(parquet|orc|csv|json)\b/i);
|
|
91
91
|
if (fileM) {
|
|
92
92
|
const locM = detail.match(/Location:[^[]*\[([^\]]+)\]/);
|
|
@@ -94,15 +94,14 @@ export function scanRelationId(name , detail ) {
|
|
|
94
94
|
return `${fileM[1].toLowerCase()}:${pathBasename(locM[1].split(',')[0].trim())}`;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
//
|
|
98
|
-
//
|
|
97
|
+
// JDBC: the inner SQL's first real table, lowercased. null when the first post-FROM token is a
|
|
98
|
+
// subquery `(` or there is no single table (CTE/UNION).
|
|
99
99
|
if (/JDBCRelation/.test(detail)) {
|
|
100
100
|
const fromM = jdbcSql(detail).match(/\bFROM\s+(\(|[A-Za-z_][\w.]*)/i);
|
|
101
101
|
if (!fromM || fromM[1] === '(') return null;
|
|
102
102
|
return `jdbc:${fromM[1].toLowerCase()}`;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
// 5. Non-scan / unrecognized.
|
|
106
105
|
return null;
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -139,9 +138,17 @@ function visitScan(name , detail , result ) {
|
|
|
139
138
|
return true;
|
|
140
139
|
}
|
|
141
140
|
|
|
141
|
+
// <JoinOp> [leftKeys], [rightKeys], <JoinType>[, BuildSide]; the join type is Spark's JoinType.sql
|
|
142
|
+
// name (Inner, LeftOuter, RightOuter, FullOuter, LeftSemi, LeftAnti, Cross, ExistenceJoin(...)).
|
|
143
|
+
const JOIN_DETAIL = /^(SortMergeJoin|BroadcastHashJoin|ShuffledHashJoin|BroadcastNestedLoopJoin)\s+(\[[^\]]*\]),\s*(\[[^\]]*\]),\s*(\w+)/;
|
|
144
|
+
|
|
145
|
+
/** The join type a keyed join node's detail names, or null when the detail is not a join line. */
|
|
146
|
+
export function parseJoinType(detail ) {
|
|
147
|
+
return JOIN_DETAIL.exec(detail)?.[4] ?? null;
|
|
148
|
+
}
|
|
149
|
+
|
|
142
150
|
function visitJoin(_name , detail , result ) {
|
|
143
|
-
|
|
144
|
-
const m = detail.match(/^(SortMergeJoin|BroadcastHashJoin|ShuffledHashJoin|BroadcastNestedLoopJoin)\s+(\[[^\]]*\]),\s*(\[[^\]]*\]),\s*(\w+)/);
|
|
151
|
+
const m = detail.match(JOIN_DETAIL);
|
|
145
152
|
if (m) {
|
|
146
153
|
result.joins.push({ joinType: m[1], leftKeys: parseColList(m[2]), rightKeys: parseColList(m[3]) });
|
|
147
154
|
if (/^Cross$/i.test(m[4])) pushCrossJoinWarning(result, 'Cross join');
|
|
@@ -217,13 +224,12 @@ export function describePlanNode(
|
|
|
217
224
|
pushCrossJoinWarning(result, 'CartesianProduct');
|
|
218
225
|
} else if (visitScan(name, detail, result)) {
|
|
219
226
|
recognizedType = 'scan';
|
|
220
|
-
// handled
|
|
221
227
|
} else if (/Join/i.test(name) && visitJoin(name, detail, result)) {
|
|
222
228
|
recognizedType = 'join';
|
|
223
|
-
//
|
|
229
|
+
// A Cross join type may also have pushed a crossJoin warning.
|
|
224
230
|
} else if (/Aggregate/i.test(name) && visitAgg(name, detail, result)) {
|
|
225
231
|
recognizedType = 'agg';
|
|
226
|
-
//
|
|
232
|
+
// A partial-aggregation pass pushes nothing into result.aggs.
|
|
227
233
|
} else if (isExchangeNode(node)) {
|
|
228
234
|
recognizedType = 'exchange';
|
|
229
235
|
visitExchange(detail, result);
|
|
@@ -242,10 +248,9 @@ export function describePlanNode(
|
|
|
242
248
|
const fields = result.scans[0] ?? result.joins[0] ?? result.aggs[0] ?? result.exchanges[0] ?? null;
|
|
243
249
|
const warning = result.warnings[0] ?? null;
|
|
244
250
|
|
|
245
|
-
//
|
|
251
|
+
// An agg or filter with no content or warning is not described.
|
|
246
252
|
if (!recognizedType) return null;
|
|
247
253
|
if ((recognizedType === 'agg' || recognizedType === 'filter') && !kind && !warning) return null;
|
|
248
254
|
|
|
249
|
-
// For exchange or other types, or if there's content/warning, return the object
|
|
250
255
|
return { kind, warning, ...(fields ?? {}) };
|
|
251
256
|
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
// Canonical iterative walk over a resolved planTree (
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// they all share, so it must not import from detectors.js (or vice versa).
|
|
1
|
+
// Canonical iterative walk over a resolved planTree (resolvePlanTree in event-handlers.ts). Every
|
|
2
|
+
// plan-*.ts consumer and the sql-scope detectors in detectors.ts need the same traversal: this is
|
|
3
|
+
// the single implementation they all share, so it must not import from detectors.ts (or vice versa).
|
|
5
4
|
//
|
|
6
|
-
// Pre-order, left-to-right child order (children are reverse-pushed so the
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// per-node state (plan-dot.js's edges, plan-duration-attribution.js's segment
|
|
5
|
+
// Pre-order, left-to-right child order (children are reverse-pushed so the leftmost pops first):
|
|
6
|
+
// plan-summary.ts's finding order depends on this. `visit(node, parent)` gets the parent so callers
|
|
7
|
+
// needing per-edge or per-node state (plan-dot.ts's edges, plan-duration-attribution.ts's segment
|
|
10
8
|
// index) can track it themselves via a Map keyed by node.
|
|
11
9
|
export function walkPlanTree (
|
|
12
10
|
root ,
|