sparkforensics-cli 0.1.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/bin/sparkforensics-analyze.mjs +113 -48
- package/export-template/docs/404.html +25 -0
- package/export-template/docs/assets/app.DQTZyGL1.js +1 -0
- package/export-template/docs/assets/aqe-loop.IwQSATHw.svg +1 -0
- package/export-template/docs/assets/aqe-loop.dark.DGbaxqJE.svg +1 -0
- package/export-template/docs/assets/broadcast-vs-shuffle.Db4WY1XK.svg +1 -0
- package/export-template/docs/assets/broadcast-vs-shuffle.dark.C7Bxs0mG.svg +1 -0
- package/export-template/docs/assets/cache-lifecycle.dark.B-hS7AgU.svg +1 -0
- package/export-template/docs/assets/cache-lifecycle.rEOVYQNU.svg +1 -0
- package/export-template/docs/assets/chunks/@localSearchIndexroot.DppnXnDE.js +1 -0
- package/export-template/docs/assets/chunks/VPLocalSearchBox.BkBIPFs6.js +9 -0
- package/export-template/docs/assets/chunks/duplicate-plan-subtree.dark.Cdp70QhV.js +1 -0
- package/export-template/docs/assets/chunks/framework.DSg0KOwT.js +20 -0
- package/export-template/docs/assets/chunks/retry-escalation-ladder.dark.DHipdJgZ.js +1 -0
- package/export-template/docs/assets/chunks/theme.DP0u1AUq.js +2 -0
- package/export-template/docs/assets/cold-start-timeline.DxC_Sc7w.svg +1 -0
- package/export-template/docs/assets/cold-start-timeline.dark.CZ17YcAG.svg +1 -0
- package/export-template/docs/assets/columnar-layout.PghGeOEA.svg +1 -0
- package/export-template/docs/assets/columnar-layout.dark.BVNlz0ff.svg +1 -0
- package/export-template/docs/assets/container-memory.DIO0AnIm.svg +1 -0
- package/export-template/docs/assets/container-memory.dark.CP-5zuCl.svg +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_board-widgets.md.CWpj01WU.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_board-widgets.md.CWpj01WU.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.CgzUsQ6W.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.CgzUsQ6W.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_drill-down.md.BtPdlM7r.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_drill-down.md.BtPdlM7r.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_impact-estimation.md.CooslVJt.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_impact-estimation.md.CooslVJt.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_overview.md.CehiRmGn.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_overview.md.CehiRmGn.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_state-and-history.md.C-xxn0q7.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_state-and-history.md.C-xxn0q7.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.R27gQrgY.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.R27gQrgY.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.IbnfNrV3.js +6 -0
- package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.IbnfNrV3.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_contributing.md.CvRsdr6J.js +1 -0
- package/export-template/docs/assets/contributor-guide_contributing.md.CvRsdr6J.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_development-setup.md.DvAN_9mK.js +12 -0
- package/export-template/docs/assets/contributor-guide_development-setup.md.DvAN_9mK.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_testing.md.6rIKqSyY.js +1 -0
- package/export-template/docs/assets/contributor-guide_testing.md.6rIKqSyY.lean.js +1 -0
- package/export-template/docs/assets/dag-stages.DSz_S937.svg +1 -0
- package/export-template/docs/assets/dag-stages.dark.F72UzxH4.svg +1 -0
- package/export-template/docs/assets/driver-executor.D5pQ7YN1.svg +1 -0
- package/export-template/docs/assets/driver-executor.dark.BmX9cPvh.svg +1 -0
- package/export-template/docs/assets/duplicate-plan-subtree.B4cvN6fj.svg +1 -0
- package/export-template/docs/assets/duplicate-plan-subtree.dark.Dw8wS0Ag.svg +1 -0
- package/export-template/docs/assets/index.md.CHJVslga.js +1 -0
- package/export-template/docs/assets/index.md.CHJVslga.lean.js +1 -0
- package/export-template/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/export-template/docs/assets/join-strategy.C_FvrCEo.svg +1 -0
- package/export-template/docs/assets/join-strategy.dark.ChMLnNII.svg +1 -0
- package/export-template/docs/assets/memory-borrowing.BqQRJg0u.svg +1 -0
- package/export-template/docs/assets/memory-borrowing.dark.Yhh20O9C.svg +1 -0
- package/export-template/docs/assets/memory-regions.XHvO7jHG.svg +1 -0
- package/export-template/docs/assets/memory-regions.dark.D4TP9_08.svg +1 -0
- package/export-template/docs/assets/repartition-vs-coalesce.BovLRrpj.svg +1 -0
- package/export-template/docs/assets/repartition-vs-coalesce.dark.BhAczKZQ.svg +1 -0
- package/export-template/docs/assets/retry-escalation-ladder.DyTKJJmZ.svg +1 -0
- package/export-template/docs/assets/retry-escalation-ladder.dark.BdsabtU3.svg +1 -0
- package/export-template/docs/assets/shuffle-map-reduce.KuOEZVmg.svg +1 -0
- package/export-template/docs/assets/shuffle-map-reduce.dark.BgQZnFSb.svg +1 -0
- package/export-template/docs/assets/spill-classification.BU2euYDO.svg +1 -0
- package/export-template/docs/assets/spill-classification.dark.D7i1M40d.svg +1 -0
- package/export-template/docs/assets/style.DSixAiZE.css +1 -0
- package/export-template/docs/assets/tuning-reference_anti-patterns.md.Df1YMIHu.js +1 -0
- package/export-template/docs/assets/tuning-reference_anti-patterns.md.Df1YMIHu.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_aqe.md.BIsCtLzm.js +1 -0
- package/export-template/docs/assets/tuning-reference_aqe.md.BIsCtLzm.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-broadcast-sizing.md.CEstB3Ia.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-broadcast-sizing.md.CEstB3Ia.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-cold-start.md.CEuy-72y.js +7 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-cold-start.md.CEuy-72y.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-duplicate-plan-subtree.md.CIohQDfn.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-duplicate-plan-subtree.md.CIohQDfn.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-failures.md.4z5BXGJ2.js +6 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-failures.md.4z5BXGJ2.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-gc.md.DSxzZRK7.js +6 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-gc.md.DSxzZRK7.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-job-failure-rate.md.BaJl__1W.js +8 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-job-failure-rate.md.BaJl__1W.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-memory-utilization.md.DbP-SJZc.js +7 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-memory-utilization.md.DbP-SJZc.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-retry-waste.md.D5JMjVOt.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-retry-waste.md.D5JMjVOt.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-shuffle.md.CM-nTmIH.js +12 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-shuffle.md.CM-nTmIH.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-skew.md.BdUwiDhn.js +14 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-skew.md.BdUwiDhn.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-slow-host.md.BlIo6UDW.js +7 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-slow-host.md.BlIo6UDW.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-small-files.md.B8kloyx8.js +5 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-small-files.md.B8kloyx8.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-spill.md.PNH7mITt.js +6 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-spill.md.PNH7mITt.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-straggler.md.DY36fHN5.js +7 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-straggler.md.DY36fHN5.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-tiny-tasks.md.QTV7O8kU.js +8 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-tiny-tasks.md.QTV7O8kU.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-utilization.md.DTiueZC3.js +5 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-utilization.md.DTiueZC3.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_caching.md.B7aQ8asB.js +1 -0
- package/export-template/docs/assets/tuning-reference_caching.md.B7aQ8asB.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_cluster-config.md.ZVmDGsQ3.js +1 -0
- package/export-template/docs/assets/tuning-reference_cluster-config.md.ZVmDGsQ3.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_config.md.UvveiWG3.js +1 -0
- package/export-template/docs/assets/tuning-reference_config.md.UvveiWG3.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_data-formats.md.bjCAWH3N.js +1 -0
- package/export-template/docs/assets/tuning-reference_data-formats.md.bjCAWH3N.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_index.md.BQ_NooMV.js +1 -0
- package/export-template/docs/assets/tuning-reference_index.md.BQ_NooMV.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_intro.md.CobD-lGB.js +1 -0
- package/export-template/docs/assets/tuning-reference_intro.md.CobD-lGB.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_joins.md.BtKs_CuW.js +1 -0
- package/export-template/docs/assets/tuning-reference_joins.md.BtKs_CuW.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_memory-model.md.DhT-n4y3.js +1 -0
- package/export-template/docs/assets/tuning-reference_memory-model.md.DhT-n4y3.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_metrics.md.mLOh7Apj.js +1 -0
- package/export-template/docs/assets/tuning-reference_metrics.md.mLOh7Apj.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_partitioning.md.q0zKF_8X.js +1 -0
- package/export-template/docs/assets/tuning-reference_partitioning.md.q0zKF_8X.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_pyspark.md.DDCfvN9t.js +6 -0
- package/export-template/docs/assets/tuning-reference_pyspark.md.DDCfvN9t.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_shuffle.md.BZZ7R4Ix.js +1 -0
- package/export-template/docs/assets/tuning-reference_shuffle.md.BZZ7R4Ix.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_spark-architecture.md.Dwzm5avO.js +1 -0
- package/export-template/docs/assets/tuning-reference_spark-architecture.md.Dwzm5avO.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_table-formats.md.D6wj-2dX.js +1 -0
- package/export-template/docs/assets/tuning-reference_table-formats.md.D6wj-2dX.lean.js +1 -0
- package/export-template/docs/assets/udf-execution-models.BUFDICuG.svg +1 -0
- package/export-template/docs/assets/udf-execution-models.dark.YTNS6GDq.svg +1 -0
- package/export-template/docs/assets/user-guide_alternative-log-retrieval.md.B4tPGIal.js +1 -0
- package/export-template/docs/assets/user-guide_alternative-log-retrieval.md.B4tPGIal.lean.js +1 -0
- package/export-template/docs/assets/user-guide_getting-started.md.BJvwLEIM.js +3 -0
- package/export-template/docs/assets/user-guide_getting-started.md.BJvwLEIM.lean.js +1 -0
- package/export-template/docs/assets/user-guide_mcp-tools.md.Vi3RoflJ.js +125 -0
- package/export-template/docs/assets/user-guide_mcp-tools.md.Vi3RoflJ.lean.js +1 -0
- package/export-template/docs/assets/user-guide_run-comparison.md.CQc1aoU8.js +1 -0
- package/export-template/docs/assets/user-guide_run-comparison.md.CQc1aoU8.lean.js +1 -0
- package/export-template/docs/assets/user-guide_understanding-findings.md.DL1UDhvR.js +1 -0
- package/export-template/docs/assets/user-guide_understanding-findings.md.DL1UDhvR.lean.js +1 -0
- package/export-template/docs/contributor-guide/architecture/board-widgets.html +25 -0
- package/export-template/docs/contributor-guide/architecture/detector-contract.html +25 -0
- package/export-template/docs/contributor-guide/architecture/drill-down.html +25 -0
- package/export-template/docs/contributor-guide/architecture/impact-estimation.html +25 -0
- package/export-template/docs/contributor-guide/architecture/index.html +25 -0
- package/export-template/docs/contributor-guide/architecture/overview.html +25 -0
- package/export-template/docs/contributor-guide/architecture/state-and-history.html +25 -0
- package/export-template/docs/contributor-guide/architecture/widget-rendering.html +25 -0
- package/export-template/docs/contributor-guide/architecture/worker-protocol.html +30 -0
- package/export-template/docs/contributor-guide/contributing.html +25 -0
- package/export-template/docs/contributor-guide/development-setup.html +36 -0
- package/export-template/docs/contributor-guide/testing.html +25 -0
- package/export-template/docs/favicon.svg +4 -0
- package/export-template/docs/hashmap.json +1 -0
- package/export-template/docs/index.html +25 -0
- package/export-template/docs/package.json +1 -0
- package/export-template/docs/tuning-reference/anti-patterns.html +25 -0
- package/export-template/docs/tuning-reference/aqe.html +25 -0
- package/export-template/docs/tuning-reference/bottleneck-broadcast-sizing.html +25 -0
- package/export-template/docs/tuning-reference/bottleneck-cold-start.html +31 -0
- package/export-template/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +25 -0
- package/export-template/docs/tuning-reference/bottleneck-failures.html +30 -0
- package/export-template/docs/tuning-reference/bottleneck-gc.html +30 -0
- package/export-template/docs/tuning-reference/bottleneck-job-failure-rate.html +32 -0
- package/export-template/docs/tuning-reference/bottleneck-memory-utilization.html +31 -0
- package/export-template/docs/tuning-reference/bottleneck-retry-waste.html +25 -0
- package/export-template/docs/tuning-reference/bottleneck-shuffle.html +36 -0
- package/export-template/docs/tuning-reference/bottleneck-skew.html +38 -0
- package/export-template/docs/tuning-reference/bottleneck-slow-host.html +31 -0
- package/export-template/docs/tuning-reference/bottleneck-small-files.html +29 -0
- package/export-template/docs/tuning-reference/bottleneck-spill.html +30 -0
- package/export-template/docs/tuning-reference/bottleneck-straggler.html +31 -0
- package/export-template/docs/tuning-reference/bottleneck-tiny-tasks.html +32 -0
- package/export-template/docs/tuning-reference/bottleneck-utilization.html +29 -0
- package/export-template/docs/tuning-reference/caching.html +25 -0
- package/export-template/docs/tuning-reference/cluster-config.html +25 -0
- package/export-template/docs/tuning-reference/config.html +25 -0
- package/export-template/docs/tuning-reference/data-formats.html +25 -0
- package/export-template/docs/tuning-reference/index.html +25 -0
- package/export-template/docs/tuning-reference/intro.html +25 -0
- package/export-template/docs/tuning-reference/joins.html +25 -0
- package/export-template/docs/tuning-reference/memory-model.html +25 -0
- package/export-template/docs/tuning-reference/metrics.html +25 -0
- package/export-template/docs/tuning-reference/partitioning.html +25 -0
- package/export-template/docs/tuning-reference/pyspark.html +30 -0
- package/export-template/docs/tuning-reference/shuffle.html +25 -0
- package/export-template/docs/tuning-reference/spark-architecture.html +25 -0
- package/export-template/docs/tuning-reference/table-formats.html +25 -0
- package/export-template/docs/user-guide/alternative-log-retrieval.html +25 -0
- package/export-template/docs/user-guide/getting-started.html +27 -0
- package/export-template/docs/user-guide/mcp-tools.html +149 -0
- package/export-template/docs/user-guide/run-comparison.html +25 -0
- package/export-template/docs/user-guide/understanding-findings.html +25 -0
- package/export-template/docs/vp-icons.css +0 -0
- package/export-template/favicon.svg +4 -0
- package/export-template/index.html +111 -0
- package/export-template/parser-worker-DyjiQvfP.js +112 -0
- package/export-template/sample-runs/sample-run.ndjson.gz +0 -0
- package/package.json +20 -6
- package/vendor-core/analyzer.js +74 -74
- package/vendor-core/cli/budgets.js +13 -27
- package/vendor-core/cli/collect-run.js +43 -19
- package/vendor-core/core-count.js +25 -27
- package/vendor-core/core-locality-ratio.js +4 -11
- package/vendor-core/core-time-series.js +6 -12
- package/vendor-core/core-usage-locality.js +3 -4
- package/vendor-core/detectors.js +395 -389
- package/vendor-core/docs-config.js +69 -21
- package/vendor-core/docs-content/chapters/01-intro.md +32 -0
- package/vendor-core/docs-content/chapters/02-spark-architecture.md +76 -0
- package/vendor-core/docs-content/chapters/03-memory-model.md +73 -0
- package/vendor-core/docs-content/chapters/04-partitioning.md +65 -0
- package/vendor-core/docs-content/chapters/05-joins.md +62 -0
- package/vendor-core/docs-content/chapters/06-shuffle.md +59 -0
- package/vendor-core/docs-content/chapters/07-data-formats.md +81 -0
- package/vendor-core/docs-content/chapters/07b-table-formats.md +56 -0
- package/vendor-core/docs-content/chapters/08-caching.md +58 -0
- package/vendor-core/docs-content/chapters/09-pyspark.md +78 -0
- package/vendor-core/docs-content/chapters/10-aqe.md +167 -0
- package/vendor-core/docs-content/chapters/11-cluster-config.md +170 -0
- package/vendor-core/docs-content/chapters/12-anti-patterns.md +171 -0
- package/vendor-core/docs-content/chapters/14-metrics.md +87 -0
- package/vendor-core/docs-content/chapters/15-config.md +93 -0
- package/vendor-core/docs-content/chapters/nav-index.json +370 -0
- package/vendor-core/docs-content/detection/cache.md +7 -0
- package/vendor-core/docs-content/detection/cfg.md +15 -0
- package/vendor-core/docs-content/detection/chrn.md +9 -0
- package/vendor-core/docs-content/detection/cold.md +4 -0
- package/vendor-core/docs-content/detection/cstor.md +4 -0
- package/vendor-core/docs-content/detection/fail.md +5 -0
- package/vendor-core/docs-content/detection/gc.md +4 -0
- package/vendor-core/docs-content/detection/host.md +5 -0
- package/vendor-core/docs-content/detection/incmp.md +6 -0
- package/vendor-core/docs-content/detection/jobs.md +4 -0
- package/vendor-core/docs-content/detection/local.md +6 -0
- package/vendor-core/docs-content/detection/mem.md +10 -0
- package/vendor-core/docs-content/detection/part.md +5 -0
- package/vendor-core/docs-content/detection/plan.md +14 -0
- package/vendor-core/docs-content/detection/retry.md +4 -0
- package/vendor-core/docs-content/detection/sfail.md +5 -0
- package/vendor-core/docs-content/detection/shape.md +5 -0
- package/vendor-core/docs-content/detection/shfl.md +4 -0
- package/vendor-core/docs-content/detection/skew.md +6 -0
- package/vendor-core/docs-content/detection/slow.md +6 -0
- package/vendor-core/docs-content/detection/spec.md +8 -0
- package/vendor-core/docs-content/detection/spill.md +7 -0
- package/vendor-core/docs-content/detection/strag.md +5 -0
- package/vendor-core/docs-content/detection/tiny.md +4 -0
- package/vendor-core/docs-content/detection/util.md +4 -0
- package/vendor-core/docs-content/diagrams/aqe-loop.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/aqe-loop.svg +1 -0
- package/vendor-core/docs-content/diagrams/broadcast-vs-shuffle.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/broadcast-vs-shuffle.svg +1 -0
- package/vendor-core/docs-content/diagrams/cache-lifecycle.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/cache-lifecycle.svg +1 -0
- package/vendor-core/docs-content/diagrams/cold-start-timeline.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/cold-start-timeline.svg +1 -0
- package/vendor-core/docs-content/diagrams/columnar-layout.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/columnar-layout.svg +1 -0
- package/vendor-core/docs-content/diagrams/container-memory.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/container-memory.svg +1 -0
- package/vendor-core/docs-content/diagrams/dag-stages.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/dag-stages.svg +1 -0
- package/vendor-core/docs-content/diagrams/driver-executor.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/driver-executor.svg +1 -0
- package/vendor-core/docs-content/diagrams/duplicate-plan-subtree.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/duplicate-plan-subtree.svg +1 -0
- package/vendor-core/docs-content/diagrams/join-strategy.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/join-strategy.svg +1 -0
- package/vendor-core/docs-content/diagrams/memory-borrowing.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/memory-borrowing.svg +1 -0
- package/vendor-core/docs-content/diagrams/memory-regions.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/memory-regions.svg +1 -0
- package/vendor-core/docs-content/diagrams/repartition-vs-coalesce.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/repartition-vs-coalesce.svg +1 -0
- package/vendor-core/docs-content/diagrams/retry-escalation-ladder.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/retry-escalation-ladder.svg +1 -0
- package/vendor-core/docs-content/diagrams/shuffle-map-reduce.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/shuffle-map-reduce.svg +1 -0
- package/vendor-core/docs-content/diagrams/spill-classification.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/spill-classification.svg +1 -0
- package/vendor-core/docs-content/diagrams/udf-execution-models.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/udf-execution-models.svg +1 -0
- package/vendor-core/docs-content/tuning/broadcast-sizing.md +78 -0
- package/vendor-core/docs-content/tuning/cold-start.md +81 -0
- package/vendor-core/docs-content/tuning/duplicate-plan-subtree.md +45 -0
- package/vendor-core/docs-content/tuning/failures.md +124 -0
- package/vendor-core/docs-content/tuning/gc.md +110 -0
- package/vendor-core/docs-content/tuning/job-failure-rate.md +101 -0
- package/vendor-core/docs-content/tuning/memory-utilization.md +58 -0
- package/vendor-core/docs-content/tuning/retry-waste.md +90 -0
- package/vendor-core/docs-content/tuning/shuffle.md +154 -0
- package/vendor-core/docs-content/tuning/skew.md +123 -0
- package/vendor-core/docs-content/tuning/slow-host.md +117 -0
- package/vendor-core/docs-content/tuning/small-files.md +99 -0
- package/vendor-core/docs-content/tuning/spill.md +114 -0
- package/vendor-core/docs-content/tuning/straggler.md +103 -0
- package/vendor-core/docs-content/tuning/tiny-tasks.md +94 -0
- package/vendor-core/docs-content/tuning/utilization.md +90 -0
- package/vendor-core/docs-site-config.js +10 -17
- package/vendor-core/efficiency-model.js +7 -13
- package/vendor-core/etl-phases.js +3 -5
- package/vendor-core/event-handlers.js +232 -134
- package/vendor-core/event-schemas.js +48 -114
- package/vendor-core/evidence-availability.js +5 -10
- package/vendor-core/evidence-report.js +73 -123
- package/vendor-core/export-data.js +48 -0
- package/vendor-core/finding-action-label.js +4 -10
- package/vendor-core/finding-filter-predicate.js +3 -7
- package/vendor-core/finding-generic-recommendation.js +112 -0
- package/vendor-core/finding-names.js +51 -0
- package/vendor-core/format-utils.js +112 -38
- package/vendor-core/impact-band.js +18 -24
- package/vendor-core/impact-estimator.js +38 -74
- package/vendor-core/ingest.js +7 -13
- package/vendor-core/job-groups.js +3 -6
- package/vendor-core/list-runs.js +278 -0
- package/vendor-core/load-vendored.js +6 -12
- package/vendor-core/log-header-peek.js +81 -0
- package/vendor-core/lz4-block.js +4 -6
- package/vendor-core/mcp-server-factory.js +38 -8
- package/vendor-core/mcp-tools.js +105 -76
- package/vendor-core/model-assembler.js +8 -16
- package/vendor-core/occupancy.js +5 -9
- package/vendor-core/parser-worker.js +20 -29
- package/vendor-core/plan-dot.js +2 -5
- package/vendor-core/plan-duration-attribution.js +78 -29
- package/vendor-core/plan-graph-model.js +126 -69
- package/vendor-core/plan-node-detail.js +31 -17
- package/vendor-core/plan-summary.js +19 -8
- package/vendor-core/recommendation-rollup.js +35 -39
- package/vendor-core/redact.js +72 -16
- package/vendor-core/rolling-log-reassembly.js +4 -6
- package/vendor-core/run-comparison.js +86 -72
- package/vendor-core/scaling-sim.js +5 -7
- package/vendor-core/session-snapshot.js +1 -1
- package/vendor-core/shs-fetch.js +4 -6
- package/vendor-core/shs-load.js +9 -13
- package/vendor-core/shs-request.js +1 -1
- package/vendor-core/stage-quantiles.js +14 -0
- package/vendor-core/types.js +78 -18
- package/vendor-core/wasted-core-hours.js +7 -12
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
// Portable, redacted evidence report
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// rendering. Raw task records are never included (privacy baseline); identifier
|
|
5
|
-
// redaction is opt-in via { redact: true }.
|
|
1
|
+
// Portable, redacted evidence report. Pure builder over an appModel: runs the detectors,
|
|
2
|
+
// then serializes a run summary + findings into a deterministic, byte-stable JSON + Markdown.
|
|
3
|
+
// Raw task records are never included (privacy baseline); redaction is opt-in via { redact: true }.
|
|
6
4
|
import { analyze, auditConfig } from './analyzer.js';
|
|
7
5
|
import { detectorCatalog } from './detectors.js';
|
|
8
6
|
import { typeTag, formatBytes, formatDuration, IMPACT_BAND_ORDER } from './format-utils.js';
|
|
7
|
+
import { FINDING_NAMES, titleCase } from './finding-names.js';
|
|
9
8
|
import { redactReport } from './redact.js';
|
|
10
9
|
import { coreFindingActionLabel } from './finding-action-label.js';
|
|
11
10
|
import { matchesFindingFilterCriteria } from './finding-filter-predicate.js';
|
|
@@ -17,42 +16,28 @@ import { getThresholdSummary } from './threshold-summary.js';
|
|
|
17
16
|
|
|
18
17
|
export const EVIDENCE_SCHEMA_VERSION = 3;
|
|
19
18
|
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// (
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// `SparkAppInfo | null`, and `SparkAppInfo.sparkVersion` documents `null` as
|
|
26
|
-
// a deliberate "unknown version" sentinel distinct from absence). So these
|
|
27
|
-
// can genuinely be `null` at runtime even though the plan pins them as
|
|
28
|
-
// plain/optional non-null types. Widened here to match reality rather than
|
|
29
|
-
// masking it with a cast, and kept as `?? null`, not `?? undefined`: since
|
|
30
|
-
// JSON.stringify drops `undefined` keys but keeps `null`, using `undefined`
|
|
31
|
-
// here would silently strip these fields from the serialized report.
|
|
19
|
+
// findingRow always sets id/metric/value/recommendation via `?? null` (never omits the key), and
|
|
20
|
+
// buildJson does the same for evidenceAvailability and summary.app.{id,name,sparkVersion}: these
|
|
21
|
+
// are genuinely nullable at runtime (AppModel.app is nullable, sparkVersion documents null as an
|
|
22
|
+
// "unknown version" sentinel). Kept as `?? null`, not `?? undefined`: JSON.stringify drops
|
|
23
|
+
// undefined keys but keeps null, so undefined would silently strip these from the report.
|
|
32
24
|
//
|
|
33
|
-
// `value` is
|
|
34
|
-
//
|
|
35
|
-
// human-readable text in `Finding.value` instead of a magnitude, and this
|
|
36
|
-
// row shape carries that value through unchanged.
|
|
25
|
+
// `value` is number|string|null: stageFailed and the configAudit entries put text in Finding.value
|
|
26
|
+
// instead of a magnitude, carried through unchanged.
|
|
37
27
|
|
|
38
|
-
|
|
28
|
+
|
|
39
29
|
|
|
40
30
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
46
34
|
|
|
47
35
|
|
|
48
36
|
|
|
49
37
|
|
|
50
38
|
|
|
51
|
-
// The `Fix these first` rollup row
|
|
52
|
-
//
|
|
53
|
-
// aggregation the dashboard's `FixTheseFirst.tsx` widget renders, so the
|
|
54
|
-
// CLI/MCP/download paths get the "what's the highest-leverage fix" ranking
|
|
55
|
-
// too, not just the flat impact-sorted `findings` list above.
|
|
39
|
+
// The `Fix these first` rollup row: one entry per buildRecommendationRollup
|
|
40
|
+
// group, so the CLI/MCP/download paths get the same impact-ranked aggregation the dashboard shows.
|
|
56
41
|
|
|
57
42
|
|
|
58
43
|
|
|
@@ -68,9 +53,8 @@ export const EVIDENCE_SCHEMA_VERSION = 3;
|
|
|
68
53
|
|
|
69
54
|
|
|
70
55
|
|
|
71
|
-
// One line per detector `type` that fired zero findings
|
|
72
|
-
//
|
|
73
|
-
// same way the dashboard's clean-checks table does.
|
|
56
|
+
// One line per detector `type` that fired zero findings, so a flat report can state "these were
|
|
57
|
+
// checked and came back clean" like the dashboard's clean-checks table.
|
|
74
58
|
|
|
75
59
|
|
|
76
60
|
|
|
@@ -94,19 +78,27 @@ export const EVIDENCE_SCHEMA_VERSION = 3;
|
|
|
94
78
|
// Fields surfaced as first-class report columns. Everything else on a finding
|
|
95
79
|
// becomes its `evidence` payload (sorted for stable key order).
|
|
96
80
|
const CORE_KEYS = new Set([
|
|
97
|
-
'id', 'type', 'impactBand', 'stageId', 'metric', 'value',
|
|
81
|
+
'id', 'type', 'name', 'impactBand', 'stageId', 'metric', 'value',
|
|
98
82
|
'recommendation', 'detectorVersion', 'confidence', 'validationRequired', 'docAnchor', 'impactEstimate',
|
|
99
83
|
'actionLabel',
|
|
100
84
|
]);
|
|
101
85
|
|
|
86
|
+
// Internal-only fields with no meaning to a human reading this report: never surfaced as a core
|
|
87
|
+
// column, and also excluded from the generic evidence dump (unlike stageIds, which IS actionable
|
|
88
|
+
// to a reader). `planNodeIds` is view-layer plan-graph node ids (Plan Advisor detectors, see
|
|
89
|
+
// plan-graph-model.ts): on a real log it can carry a hundred-plus ids, which would otherwise print
|
|
90
|
+
// as one unreadable `- planNodeIds: [...]` line and bloat the report for no reader benefit.
|
|
91
|
+
const NON_EVIDENCE_KEYS = new Set(['planNodeIds']);
|
|
92
|
+
|
|
102
93
|
function findingRow(f ) {
|
|
103
94
|
const evidence = {};
|
|
104
95
|
for (const k of Object.keys(f).sort()) {
|
|
105
|
-
if (!CORE_KEYS.has(k)) evidence[k] = (f )[k];
|
|
96
|
+
if (!CORE_KEYS.has(k) && !NON_EVIDENCE_KEYS.has(k)) evidence[k] = (f )[k];
|
|
106
97
|
}
|
|
107
98
|
const row = {
|
|
108
99
|
id: f.id ?? null,
|
|
109
100
|
type: f.type,
|
|
101
|
+
name: titleCase(FINDING_NAMES[f.type] ?? f.type),
|
|
110
102
|
tag: typeTag(f.type),
|
|
111
103
|
impactBand: f.impactBand,
|
|
112
104
|
stageId: f.stageId ?? null,
|
|
@@ -115,11 +107,9 @@ function findingRow(f ) {
|
|
|
115
107
|
recommendation: f.recommendation ?? null,
|
|
116
108
|
detectorVersion: f.detectorVersion ?? 1,
|
|
117
109
|
evidence,
|
|
118
|
-
// Deliberate simplification vs
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
// since coreFindingActionLabel's switch already covers every type the real
|
|
122
|
-
// detectors emit; only obscure/future unmatched sub-variants hit this fallback.
|
|
110
|
+
// Deliberate simplification vs the view layer's REGISTRY fallback: no widget registry here, and
|
|
111
|
+
// falling back to the finding's own `type` is fine since coreFindingActionLabel already covers
|
|
112
|
+
// every emitted type; only obscure/future sub-variants hit this fallback.
|
|
123
113
|
actionLabel: coreFindingActionLabel(f) ?? f.type,
|
|
124
114
|
};
|
|
125
115
|
// Threshold/confidence provenance, only when the detector emitted it.
|
|
@@ -130,8 +120,8 @@ function findingRow(f ) {
|
|
|
130
120
|
return row;
|
|
131
121
|
}
|
|
132
122
|
|
|
133
|
-
// Deterministic finding order: impact band, then type, then stage, then id, so a
|
|
134
|
-
//
|
|
123
|
+
// Deterministic finding order: impact band, then type, then stage, then id, so a fixed appModel
|
|
124
|
+
// always serializes byte-for-byte identically.
|
|
135
125
|
function sortFindings(rows ) {
|
|
136
126
|
return [...rows].sort((a, b) => {
|
|
137
127
|
const s = (IMPACT_BAND_ORDER[a.impactBand] ?? 9) - (IMPACT_BAND_ORDER[b.impactBand] ?? 9);
|
|
@@ -144,20 +134,13 @@ function sortFindings(rows ) {
|
|
|
144
134
|
});
|
|
145
135
|
}
|
|
146
136
|
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// FixTheseFirst.tsx's extra `REGISTRY[finding.type] != null` check: every
|
|
151
|
-
// real finding reaching this file already came out of `analyze()`/
|
|
152
|
-
// `auditConfig()`, so it's always a known type, and REGISTRY (a `.tsx` file)
|
|
153
|
-
// isn't importable from this core module anyway.
|
|
137
|
+
// isEligible/rankFindings are shared with FixTheseFirst.tsx via recommendation-rollup.ts; this
|
|
138
|
+
// file's isEligible call omits FixTheseFirst's REGISTRY check: every finding here already came out
|
|
139
|
+
// of analyze()/auditConfig() so it's a known type, and REGISTRY (.tsx) isn't importable here anyway.
|
|
154
140
|
|
|
155
|
-
// The impact-ranked "
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
// correct cross-group order (time groups by recoverable ms descending, then
|
|
159
|
-
// resource/count groups by worst impact band), so this only maps each group to
|
|
160
|
-
// its JSON row shape without re-sorting.
|
|
141
|
+
// The impact-ranked "highest-leverage fix" rollup, ported from FixTheseFirst.tsx so CLI/MCP/download
|
|
142
|
+
// get the same ranking. buildRecommendationRollup already returns groups in the correct cross-group
|
|
143
|
+
// order, so this only maps each group to its JSON row without re-sorting.
|
|
161
144
|
function buildRecommendations(
|
|
162
145
|
findings ,
|
|
163
146
|
stages ,
|
|
@@ -181,10 +164,8 @@ function buildRecommendations(
|
|
|
181
164
|
kind: 'time',
|
|
182
165
|
stageCount: group.stageCount,
|
|
183
166
|
recoverableMsHigh: group.recoverableMsHigh,
|
|
184
|
-
// A point estimate, not a range: matches FixTheseFirst.tsx's
|
|
185
|
-
//
|
|
186
|
-
// rather than the finding-level low-high spread this group already
|
|
187
|
-
// collapsed away via computeStageUnionMs.
|
|
167
|
+
// A point estimate, not a range: matches FixTheseFirst.tsx's trailingStat for time groups,
|
|
168
|
+
// which prints the same figure twice rather than the finding-level spread computeStageUnionMs collapsed.
|
|
188
169
|
impact: formatWallClockRange(group.recoverableMsHigh, group.recoverableMsHigh),
|
|
189
170
|
};
|
|
190
171
|
}
|
|
@@ -208,25 +189,16 @@ function buildRecommendations(
|
|
|
208
189
|
});
|
|
209
190
|
}
|
|
210
191
|
|
|
211
|
-
// Detector types that fired zero findings
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
// memoryUtilization/utilization/coreLocality (the "always-mounted reference"
|
|
216
|
-
// widgets; see isAlwaysMountedType in src/view/detector-registry.tsx)
|
|
217
|
-
// because those are already always shown as their own widget elsewhere on
|
|
218
|
-
// the board. A flat evidence report has no such separate always-visible
|
|
219
|
-
// surface for them, so this list intentionally includes them when they have
|
|
220
|
-
// zero findings, rather than mirroring the UI's display-consolidation
|
|
221
|
-
// exclusion.
|
|
192
|
+
// Detector types that fired zero findings, so a flat report can state "checked and clean". Differs
|
|
193
|
+
// from the dashboard's Alerts.tsx "Clean checks", which excludes coreLocality (the one remaining
|
|
194
|
+
// always-mounted reference widget, shown elsewhere); a flat report has no such separate surface,
|
|
195
|
+
// so this includes it too when it has zero findings.
|
|
222
196
|
function buildCleanChecks(findings ) {
|
|
223
197
|
const firedTypes = new Set(findings.map((f) => f.type));
|
|
224
198
|
const seen = new Set ();
|
|
225
199
|
const entries = [];
|
|
226
|
-
// detectorCatalog() can list the same
|
|
227
|
-
//
|
|
228
|
-
// type, keeping first occurrence, so a type with several sibling detector
|
|
229
|
-
// entries still contributes exactly one clean-check line.
|
|
200
|
+
// detectorCatalog() can list the same type more than once (configAudit has 4 entries); dedupe by
|
|
201
|
+
// type, keeping first, so a type with sibling entries contributes exactly one clean-check line.
|
|
230
202
|
for (const d of detectorCatalog() ) {
|
|
231
203
|
if (firedTypes.has(d.type) || seen.has(d.type)) continue;
|
|
232
204
|
seen.add(d.type);
|
|
@@ -235,15 +207,10 @@ function buildCleanChecks(findings ) {
|
|
|
235
207
|
return entries;
|
|
236
208
|
}
|
|
237
209
|
|
|
238
|
-
// Keyed by appModel object identity
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
// getFindingEvidence in particular calls buildEvidenceReport once per
|
|
243
|
-
// drill-down on an already-diagnosed run; without this, N findings looked up
|
|
244
|
-
// on one run meant N full detector re-runs. A WeakMap needs no explicit
|
|
245
|
-
// invalidation: once mcp-tools.ts evicts the appModel, this entry is
|
|
246
|
-
// unreachable and collectible too.
|
|
210
|
+
// Keyed by appModel object identity: mcp-tools.ts caches one fixed appModel per runId (never
|
|
211
|
+
// mutated), so re-running analyze()/auditConfig() reproduces the same catalog. getFindingEvidence
|
|
212
|
+
// calls buildEvidenceReport once per drill-down; without this, N lookups meant N detector re-runs.
|
|
213
|
+
// A WeakMap needs no invalidation: once mcp-tools.ts evicts the appModel, this entry is collectible.
|
|
247
214
|
const jsonCache = new WeakMap ();
|
|
248
215
|
|
|
249
216
|
function buildJson(appModel ) {
|
|
@@ -268,12 +235,8 @@ function buildJson(appModel ) {
|
|
|
268
235
|
schemaVersion: EVIDENCE_SCHEMA_VERSION,
|
|
269
236
|
summary: {
|
|
270
237
|
app: {
|
|
271
|
-
// `?? null`, not `?? undefined`:
|
|
272
|
-
//
|
|
273
|
-
// deliberate "unknown/absent" sentinel distinct from an omitted key.
|
|
274
|
-
// JSON.stringify drops `undefined` keys but keeps `null`, so using
|
|
275
|
-
// `?? undefined` here would have silently dropped these fields from
|
|
276
|
-
// the serialized report whenever `app` is null or its fields unset.
|
|
238
|
+
// `?? null`, not `?? undefined`: JSON.stringify drops undefined keys but keeps null, and
|
|
239
|
+
// sparkVersion's null is a deliberate "unknown/absent" sentinel; undefined would drop these.
|
|
277
240
|
id: app?.id ?? null,
|
|
278
241
|
name: app?.name ?? null,
|
|
279
242
|
sparkVersion: app?.sparkVersion ?? null,
|
|
@@ -285,9 +248,8 @@ function buildJson(appModel ) {
|
|
|
285
248
|
impactBandCounts,
|
|
286
249
|
},
|
|
287
250
|
evidenceAvailability: evidenceAvailability ?? null,
|
|
288
|
-
// Detector metadata
|
|
289
|
-
//
|
|
290
|
-
// Order follows DETECTORS, which is stable => byte-stable serialization.
|
|
251
|
+
// Detector metadata so the threshold set that produced each finding travels with the evidence.
|
|
252
|
+
// Order follows DETECTORS (stable) => byte-stable serialization.
|
|
291
253
|
detectors: detectorCatalog(),
|
|
292
254
|
findings: rows,
|
|
293
255
|
recommendations,
|
|
@@ -297,9 +259,8 @@ function buildJson(appModel ) {
|
|
|
297
259
|
return result;
|
|
298
260
|
}
|
|
299
261
|
|
|
300
|
-
// Human-readable rendering of an evidence value. Byte-magnitude keys are
|
|
301
|
-
//
|
|
302
|
-
// no payload is silently dropped from the Markdown.
|
|
262
|
+
// Human-readable rendering of an evidence value. Byte-magnitude keys are humanized; objects/arrays
|
|
263
|
+
// serialize compactly so no payload is silently dropped from the Markdown.
|
|
303
264
|
function renderEvidenceValue(key , value ) {
|
|
304
265
|
if (typeof value === 'number' && /bytes$/i.test(key)) return formatBytes(value);
|
|
305
266
|
if (value !== null && typeof value === 'object') return JSON.stringify(value);
|
|
@@ -308,7 +269,7 @@ function renderEvidenceValue(key , value ) {
|
|
|
308
269
|
|
|
309
270
|
function formatWallClockRange(low , high ) {
|
|
310
271
|
const fmtMs = (ms ) => (ms === 0 ? '0s' : formatDuration(ms));
|
|
311
|
-
return low === high ? `
|
|
272
|
+
return low === high ? `Estimated ${fmtMs(high)}` : `Estimated ${fmtMs(low)}-${fmtMs(high)}`;
|
|
312
273
|
}
|
|
313
274
|
|
|
314
275
|
function formatRawWaste(rawWaste ) {
|
|
@@ -359,7 +320,7 @@ function renderMarkdown(json ) {
|
|
|
359
320
|
lines.push('');
|
|
360
321
|
for (const r of findings) {
|
|
361
322
|
const where = r.stageId != null ? ` (stage ${r.stageId})` : '';
|
|
362
|
-
lines.push(`###
|
|
323
|
+
lines.push(`### ${r.name} · ${r.impactBand}${where}`);
|
|
363
324
|
lines.push(`- action: ${r.actionLabel}`);
|
|
364
325
|
if (r.metric != null) lines.push(`- ${r.metric}: ${r.value}`);
|
|
365
326
|
if (r.recommendation) lines.push(`- ${r.recommendation}`);
|
|
@@ -368,9 +329,8 @@ function renderMarkdown(json ) {
|
|
|
368
329
|
const impactText = r.impactEstimate ? renderImpactEstimate(r.impactEstimate) : null;
|
|
369
330
|
if (impactText) lines.push(`- impact: ${impactText}`);
|
|
370
331
|
lines.push(`- detector version: ${r.detectorVersion}`);
|
|
371
|
-
// Evidence payload (sorted for stable order) so two rows differing only by
|
|
372
|
-
//
|
|
373
|
-
// render distinctly and carry the full AC3 field set into the Markdown.
|
|
332
|
+
// Evidence payload (sorted for stable order) so two rows differing only by evidence (two
|
|
333
|
+
// smallFiles by direction, two partitionSizing by rule) render distinctly.
|
|
374
334
|
const evidence = Object.entries(r.evidence ?? {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
375
335
|
if (evidence.length) {
|
|
376
336
|
lines.push('- evidence:');
|
|
@@ -386,8 +346,7 @@ function renderMarkdown(json ) {
|
|
|
386
346
|
}
|
|
387
347
|
lines.push('');
|
|
388
348
|
}
|
|
389
|
-
// Detector catalog: the version + threshold set that produced each finding,
|
|
390
|
-
// so the Markdown (the format pasted into a ticket) carries provenance too.
|
|
349
|
+
// Detector catalog: the version + threshold set that produced each finding, so the Markdown carries provenance too.
|
|
391
350
|
if (Array.isArray(detectors) && detectors.length) {
|
|
392
351
|
lines.push('## Detectors');
|
|
393
352
|
lines.push('');
|
|
@@ -413,19 +372,14 @@ function renderMarkdown(json ) {
|
|
|
413
372
|
|
|
414
373
|
|
|
415
374
|
|
|
416
|
-
// CLI/MCP-facing filter over FindingRow
|
|
417
|
-
//
|
|
418
|
-
// backs the dashboard's src/view/finding-filter.ts.
|
|
375
|
+
// CLI/MCP-facing filter over FindingRow, delegating to the shared core predicate that also backs
|
|
376
|
+
// the dashboard's finding-filter.
|
|
419
377
|
function matchesFindingsFilter(row , filter ) {
|
|
420
378
|
return matchesFindingFilterCriteria(row, filter);
|
|
421
379
|
}
|
|
422
380
|
|
|
423
|
-
/**
|
|
424
|
-
*
|
|
425
|
-
* or `undefined` when none were passed (the "is anything actually set" gate
|
|
426
|
-
* both `bin/sparkforensics-analyze.mjs` and `diagnoseRun` need before calling
|
|
427
|
-
* `buildEvidenceReport`).
|
|
428
|
-
*/
|
|
381
|
+
/** Build a FindingsFilter from the three optional CLI/MCP filter dimensions, or undefined when
|
|
382
|
+
* none were passed (the "is anything set" gate before calling buildEvidenceReport). */
|
|
429
383
|
export function toFindingsFilter(
|
|
430
384
|
impactBand , type , stageId ,
|
|
431
385
|
) {
|
|
@@ -434,13 +388,10 @@ export function toFindingsFilter(
|
|
|
434
388
|
|
|
435
389
|
/**
|
|
436
390
|
* Build a portable evidence report from an appModel.
|
|
437
|
-
* @param
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* rendered Markdown's Findings section) only — `summary`, `recommendations`,
|
|
442
|
-
* and `cleanChecks` stay computed from the full, unfiltered set, so a
|
|
443
|
-
* narrow filter never hides that other checks passed or other fixes exist.
|
|
391
|
+
* @param opts redact=true pseudonymizes app ids / hosts; markdown=false skips the Markdown string;
|
|
392
|
+
* findingsFilter narrows json.findings (and the Markdown Findings section) only, summary,
|
|
393
|
+
* recommendations, and cleanChecks stay computed from the full set, so a narrow filter never
|
|
394
|
+
* hides that other checks passed or other fixes exist.
|
|
444
395
|
*/
|
|
445
396
|
export function buildEvidenceReport(
|
|
446
397
|
appModel ,
|
|
@@ -450,9 +401,8 @@ export function buildEvidenceReport(
|
|
|
450
401
|
) {
|
|
451
402
|
let json = buildJson(appModel);
|
|
452
403
|
if (redact) json = redactReport(json);
|
|
453
|
-
// Filter after redact, not before: redaction only replaces string values
|
|
454
|
-
//
|
|
455
|
-
// so the two orderings produce identical final content either way.
|
|
404
|
+
// Filter after redact, not before: redaction only replaces string values on surviving rows,
|
|
405
|
+
// never adds/removes rows, so the two orderings produce identical final content.
|
|
456
406
|
if (findingsFilter) json = { ...json, findings: json.findings.filter((row) => matchesFindingsFilter(row, findingsFilter)) };
|
|
457
407
|
const markdown = computeMarkdown ? renderMarkdown(json) : '';
|
|
458
408
|
return { markdown, json };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const EXPORT_DATA_SCHEMA_VERSION = 1;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/** Converts an in-memory AppModel (Map-based, as collectRun()/the browser
|
|
23
|
+
* worker produce it) plus the CLI's already-computed catalog/configFindings
|
|
24
|
+
* into a plain, JSON-serializable object for the HTML export's data.js.
|
|
25
|
+
* Maps become arrays; main-export.tsx rebuilds them client-side keyed the
|
|
26
|
+
* same way the store already expects (Stage.id, Job.id,
|
|
27
|
+
* SqlExecution.id). Deliberately excludes raw per-task TaskData, which is far
|
|
28
|
+
* too large to inline; task-detail widgets degrade gracefully in export mode. */
|
|
29
|
+
export function buildExportRunData(
|
|
30
|
+
appModel ,
|
|
31
|
+
catalog ,
|
|
32
|
+
configFindings ,
|
|
33
|
+
skippedLines ,
|
|
34
|
+
) {
|
|
35
|
+
return {
|
|
36
|
+
schemaVersion: EXPORT_DATA_SCHEMA_VERSION,
|
|
37
|
+
app: appModel.app,
|
|
38
|
+
stages: [...appModel.stages.values()],
|
|
39
|
+
jobs: [...appModel.jobs.values()],
|
|
40
|
+
sql: [...appModel.sql.values()],
|
|
41
|
+
executors: appModel.executors,
|
|
42
|
+
runAggregates: appModel.runAggregates,
|
|
43
|
+
evidenceAvailability: appModel.evidenceAvailability,
|
|
44
|
+
catalog,
|
|
45
|
+
configFindings,
|
|
46
|
+
skippedLines,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
/** A short, imperative action label for a finding's row
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* detectors.ts). Core-safe: no `src/view/**` import, so both the dashboard
|
|
8
|
-
* (`src/view/finding-action-label.ts`, which layers the REGISTRY fallback on
|
|
9
|
-
* top of this) and `src/evidence-report.ts` (CLI/MCP path, which cannot
|
|
10
|
-
* import React-only code) share the exact same switch logic instead of
|
|
11
|
-
* forking it. Returns `undefined` for any (type, variant) combination this
|
|
12
|
-
* switch doesn't cover: the caller decides what to fall back to. */
|
|
3
|
+
/** A short, imperative action label for a finding's row. Keyed off finding.type plus whichever
|
|
4
|
+
* discriminant field that detector uses (rule/direction/variant/property). Core-safe (no view
|
|
5
|
+
* import) so the dashboard's wrapper and evidence-report.ts share the same switch. Returns
|
|
6
|
+
* undefined for any combination this switch doesn't cover; the caller decides the fallback. */
|
|
13
7
|
export function coreFindingActionLabel(finding ) {
|
|
14
8
|
switch (finding.type) {
|
|
15
9
|
case 'skew':
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
// Core-owned finding-filter predicate, shared by the CLI/MCP evidence report
|
|
2
|
-
// (
|
|
3
|
-
//
|
|
4
|
-
// as src/finding-action-label.ts, whose view-layer counterpart wraps it
|
|
5
|
-
// instead of reimplementing its switch. Each dimension is unconstrained when
|
|
6
|
-
// empty/absent; a `stageId` criterion (single value or a set, for the
|
|
7
|
-
// dashboard's multi-select) only matches a row whose own stageId is present.
|
|
1
|
+
// Core-owned finding-filter predicate, shared by the CLI/MCP evidence report and the dashboard's
|
|
2
|
+
// filter bar (its view counterpart wraps this instead of reimplementing). Each dimension is
|
|
3
|
+
// unconstrained when empty; a stageId criterion only matches a row whose own stageId is present.
|
|
8
4
|
|
|
9
5
|
|
|
10
6
|
function isNonEmpty (m ) {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/** A generic, type-level recommendation sentence for a finding: the shape of the fix, with no
|
|
4
|
+
* instance data (numbers, stage ids, host names, file counts, config values). Mirrors
|
|
5
|
+
* coreFindingActionLabel's (type, discriminant) switch (same fields: rule/direction/variant/
|
|
6
|
+
* property, plus cacheUtilization's variant and memoryUtilization's dataUnavailable), but covers
|
|
7
|
+
* every finding type, not just the ones with a distinct action label.
|
|
8
|
+
*
|
|
9
|
+
* Used for a multi-finding group's muted description line (TypeGroupRow in FixTheseFirst.tsx),
|
|
10
|
+
* where the highest-impact member's own `recommendation` (real numbers, one stage) would
|
|
11
|
+
* misrepresent a summed-impact trailing stat covering every member. Returns undefined where a
|
|
12
|
+
* detector's real branch key isn't exposed as a Finding field (spill's skew/volume
|
|
13
|
+
* classification, tinyTask's shuffle-vs-no-shuffle fix, duplicatePlanSubtree's isExchangeRoot),
|
|
14
|
+
* so those combine into one sentence covering both cases, or for any (type, discriminant)
|
|
15
|
+
* combination this switch doesn't recognize; the caller shows no muted line rather than guess. */
|
|
16
|
+
export function coreFindingGenericRecommendation(finding ) {
|
|
17
|
+
switch (finding.type) {
|
|
18
|
+
case 'skew':
|
|
19
|
+
return 'For join-driven skew, enable AQE skew-join handling (spark.sql.adaptive.skewJoin.enabled); otherwise salt the key or repartition on a better key to reduce task skew.';
|
|
20
|
+
case 'stageShape':
|
|
21
|
+
switch (finding.rule) {
|
|
22
|
+
case 'lowParallelism': return 'Too few tasks run relative to the cores available, leaving cluster capacity idle: repartition to use more of it.';
|
|
23
|
+
case 'dataExplosion': return 'Output volume far exceeds input volume: check for an exploding join or a cross product.';
|
|
24
|
+
case 'taskStageSkew': return 'A single straggler task gates the whole stage\'s wall-clock duration.';
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
case 'shuffle':
|
|
28
|
+
return 'Consider increasing spark.sql.shuffle.partitions or adding a broadcast join to shrink the shuffle.';
|
|
29
|
+
case 'partitionSizing':
|
|
30
|
+
switch (finding.rule) {
|
|
31
|
+
case 'shufflePartitionSkew': return 'For join skew, enable AQE skew-join handling (spark.sql.adaptive.skewJoin.enabled); otherwise salt the key or repartition on a better key.';
|
|
32
|
+
case 'lowShuffleParallelism': return 'Raise spark.sql.shuffle.partitions so each partition is smaller.';
|
|
33
|
+
case 'maxPartitionTooBig': return 'Repartition to break up the oversized partition before this stage.';
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
case 'spill':
|
|
37
|
+
return 'If the spill is skew-driven, fix task skew first: adding memory will not help. Otherwise raise spark.sql.shuffle.partitions or increase executor memory.';
|
|
38
|
+
case 'gc':
|
|
39
|
+
return finding.direction === 'low'
|
|
40
|
+
? 'Memory may be over-provisioned here: consider reducing spark.executor.memory for cost savings.'
|
|
41
|
+
: 'Reduce object creation, use primitive types, avoid UDFs, or increase executor memory to cut GC time.';
|
|
42
|
+
case 'slowHost':
|
|
43
|
+
if (finding.variant === 'durationShare') return 'Check for data locality or partition assignment skewing work onto one node.';
|
|
44
|
+
if (finding.variant === 'multiDim') return 'Investigate uneven partition assignment or a degraded executor.';
|
|
45
|
+
return 'Check what this host was running: it may just hold data locality for its tasks or carry one heavy stage, rather than a hardware fault. Enable spark.speculation to relaunch a lagging task automatically.';
|
|
46
|
+
case 'stageSlowness':
|
|
47
|
+
return 'Often a partition-count problem: raise parallelism via spark.sql.shuffle.partitions or spark.default.parallelism, or check for a large per-task data volume driving heavy shuffle and spill.';
|
|
48
|
+
case 'stageFailed':
|
|
49
|
+
return 'Inspect the driver log for the failure reason and the job that triggered it.';
|
|
50
|
+
case 'failures':
|
|
51
|
+
return 'Investigate driver logs for executor instability or data-driven errors.';
|
|
52
|
+
case 'straggler':
|
|
53
|
+
return 'Rule out a GC pause or a slow shuffle fetch before assuming a hardware issue. If a skewed key is the real cause, that is a candidate for AQE\'s skew-join handling.';
|
|
54
|
+
case 'speculationWaste':
|
|
55
|
+
return 'If task durations are naturally variable rather than genuine stragglers, consider tuning spark.speculation.multiplier/quantile.';
|
|
56
|
+
case 'retryWaste':
|
|
57
|
+
return 'Investigate executor loss or fetch failures behind the retried attempts.';
|
|
58
|
+
case 'tinyTask':
|
|
59
|
+
return 'Scheduler overhead may dominate: lower spark.sql.shuffle.partitions, or coalesce down to fewer, larger tasks.';
|
|
60
|
+
case 'coldStart':
|
|
61
|
+
return 'Keep a warm pool of idle executors, or if using dynamic allocation, raise the minimum/initial executor count so it does not scale up from zero.';
|
|
62
|
+
case 'utilization':
|
|
63
|
+
return 'Consider reducing cluster size or enabling dynamic allocation.';
|
|
64
|
+
case 'memoryUtilization':
|
|
65
|
+
switch (finding.variant) {
|
|
66
|
+
case 'idleCores': return 'Reduce cluster size or enable dynamic allocation.';
|
|
67
|
+
case 'wasteModel': return 'Review spark.executor.memory and executor count.';
|
|
68
|
+
case 'memoryBand':
|
|
69
|
+
if (finding.dataUnavailable) break;
|
|
70
|
+
return finding.rule === 'heapNearCapacity'
|
|
71
|
+
? 'Memory may be too small: raise spark.executor.memory to avoid OOM/spill.'
|
|
72
|
+
: 'Memory may be over-provisioned: consider reducing spark.executor.memory for cost savings.';
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
case 'cacheUtilization':
|
|
76
|
+
switch (finding.variant) {
|
|
77
|
+
case 'partialCache': return 'Increase executor memory or reduce the cached dataset size so more of it stays cached.';
|
|
78
|
+
case 'diskSpillover': return 'Executor memory may be too small for this cached dataset: increase executor memory or reduce its size.';
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
case 'coreLocality':
|
|
82
|
+
return 'Check spark.locality.wait settings and executor/data colocation.';
|
|
83
|
+
case 'autoscalingChurn':
|
|
84
|
+
return 'This looks like wasteful re-provisioning rather than normal scale-down: consider raising spark.dynamicAllocation.executorIdleTimeout or widening the minExecutors/maxExecutors bounds to reduce flapping.';
|
|
85
|
+
case 'cachingOpportunity':
|
|
86
|
+
return finding.variant === 'composite'
|
|
87
|
+
? 'Cache or persist the repeated join/union result so it is computed once instead of recomputed per query.'
|
|
88
|
+
: 'Cache the shared DataFrame, or broadcast it if it is a small join lookup.';
|
|
89
|
+
case 'jobFailureRate':
|
|
90
|
+
return 'Inspect the driver log for the failed job(s) and the stage failures that triggered them.';
|
|
91
|
+
case 'configAudit':
|
|
92
|
+
switch (finding.property) {
|
|
93
|
+
case 'spark.shuffle.service.enabled': return 'Set spark.shuffle.service.enabled=true so shuffle data survives executor removal.';
|
|
94
|
+
case 'spark.dynamicAllocation.minExecutors': return 'Set the minimum executor bound at or below the maximum.';
|
|
95
|
+
case 'spark.dynamicAllocation.maxExecutors': return 'Set spark.dynamicAllocation.maxExecutors to cap cluster growth.';
|
|
96
|
+
case 'spark.serializer': return 'Consider spark.serializer=org.apache.spark.serializer.KryoSerializer for faster, smaller buffers.';
|
|
97
|
+
case 'spark.executor.memoryOverhead': return 'Raise executor memoryOverhead above Spark\'s default floor to avoid off-heap OOM-kills.';
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
case 'duplicatePlanSubtree':
|
|
101
|
+
return 'Check whether the repeated subtree could be computed once and reused, or cache/persist the shared computation.';
|
|
102
|
+
case 'smallFiles':
|
|
103
|
+
return finding.direction === 'write'
|
|
104
|
+
? 'Repartition or coalesce before writing to raise the average file size.'
|
|
105
|
+
: 'Compact the upstream output so fewer, larger files are produced.';
|
|
106
|
+
case 'underBroadcast':
|
|
107
|
+
return 'This could have been a broadcast join: consider a broadcast() hint or raising spark.sql.autoBroadcastJoinThreshold.';
|
|
108
|
+
case 'overBroadcast':
|
|
109
|
+
return 'Check for a misapplied broadcast hint or a misconfigured spark.sql.autoBroadcastJoinThreshold.';
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Canonical detector `type` -> human-readable label (single source of truth). detector-registry
|
|
2
|
+
// imports this (web uses it lowercase); evidence-report.ts Title Cases it for CLI/MCP names.
|
|
3
|
+
//
|
|
4
|
+
// broadcastSizing is the DETECTORS-level type but no real Finding carries it (the detector pushes
|
|
5
|
+
// underBroadcast/overBroadcast). Kept as a dead key so the DETECTORS-type completeness check finds it.
|
|
6
|
+
export const FINDING_NAMES = {
|
|
7
|
+
incompleteRun: 'incomplete run',
|
|
8
|
+
|
|
9
|
+
skew: 'task skew',
|
|
10
|
+
stageShape: 'stage shape',
|
|
11
|
+
tinyTask: 'tiny tasks',
|
|
12
|
+
|
|
13
|
+
shuffle: 'shuffle I/O',
|
|
14
|
+
partitionSizing: 'partition sizing',
|
|
15
|
+
|
|
16
|
+
spill: 'spill',
|
|
17
|
+
|
|
18
|
+
gc: 'GC pressure',
|
|
19
|
+
|
|
20
|
+
stageFailed: 'failed stage',
|
|
21
|
+
failures: 'failed tasks',
|
|
22
|
+
retryWaste: 'retry waste',
|
|
23
|
+
|
|
24
|
+
slowHost: 'slow executor host',
|
|
25
|
+
stageSlowness: 'slow stage',
|
|
26
|
+
straggler: 'straggling task',
|
|
27
|
+
speculationWaste: 'speculation waste',
|
|
28
|
+
coldStart: 'cold start',
|
|
29
|
+
|
|
30
|
+
memoryUtilization: 'memory utilization',
|
|
31
|
+
utilization: 'executor utilization',
|
|
32
|
+
coreLocality: 'core locality',
|
|
33
|
+
cachingOpportunity: 'caching opportunity',
|
|
34
|
+
cacheUtilization: 'cache utilization',
|
|
35
|
+
jobFailureRate: 'job failure rate',
|
|
36
|
+
autoscalingChurn: 'autoscaling churn',
|
|
37
|
+
|
|
38
|
+
configAudit: 'config audit',
|
|
39
|
+
|
|
40
|
+
duplicatePlanSubtree: 'duplicate plan subtree',
|
|
41
|
+
smallFiles: 'small files',
|
|
42
|
+
broadcastSizing: 'broadcast sizing',
|
|
43
|
+
underBroadcast: 'missed broadcast join',
|
|
44
|
+
overBroadcast: 'oversized broadcast join',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Capitalizes the first letter of each word, leaving other characters untouched so acronyms
|
|
48
|
+
// ('GC', 'I/O') survive.
|
|
49
|
+
export function titleCase(label ) {
|
|
50
|
+
return label.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
51
|
+
}
|