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
package/vendor-core/analyzer.js
CHANGED
|
@@ -9,9 +9,7 @@ import { IMPACT_BAND_ORDER } from './format-utils.js';
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
// FNV-1a 32-bit
|
|
13
|
-
// a deterministic finding `id` at the single choke point below so the same
|
|
14
|
-
// logical finding keeps the same id across runs (no timestamps, no randomness).
|
|
12
|
+
// FNV-1a 32-bit stable string hash: deterministic finding id across runs, no timestamps/randomness.
|
|
15
13
|
function fnv1a(str ) {
|
|
16
14
|
let h = 0x811c9dc5;
|
|
17
15
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -22,36 +20,19 @@ function fnv1a(str ) {
|
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
export function findingId(f ) {
|
|
25
|
-
// Location key
|
|
26
|
-
// execution, else the audited config property.
|
|
23
|
+
// Location key: stage, else SQL execution, else audited config property.
|
|
27
24
|
const locKey = f.stageId ?? f.executionId ?? f.property ?? '';
|
|
28
|
-
// Discriminators for detectors that
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
// duplicate-subtree groups can share rootName+subtreeSize, e.g. the same
|
|
40
|
-
// "BroadcastExchange over a Project/Filter/Scan" shape repeated once per
|
|
41
|
-
// dimension table; groupIndex (the group's deterministic position within
|
|
42
|
-
// one execution's plan) is the actual uniqueness guarantee, since the
|
|
43
|
-
// informational `sampleRelation` field can be null or coincide for two
|
|
44
|
-
// groups),
|
|
45
|
-
// broadcastSizing (per node/side, distinguished by value + largerSideBytes).
|
|
46
|
-
// The metric `value` folds in the per-node magnitude that broadcastSizing
|
|
47
|
-
// siblings carry no other field for; it is deterministic for a given log, so
|
|
48
|
-
// the id stays stable across re-parses (no timestamp/randomness).
|
|
49
|
-
// memoryUtilization's `rule` (heapNearCapacity/heapOverProvisioned) is excluded here: an
|
|
50
|
-
// executor falls in exactly one heap band, so `executorId` alone already guarantees
|
|
51
|
-
// uniqueness and folding `rule` in too would just add gratuitous id-rotation risk if the
|
|
52
|
-
// band logic ever changes. partitionSizing's `rule` (maxPartitionTooBig/
|
|
53
|
-
// shufflePartitionSkew/lowShuffleParallelism) IS load-bearing here: a stage can emit more
|
|
54
|
-
// than one of those rules at once, sharing the same stageId+metric.
|
|
25
|
+
// Discriminators for detectors that emit multiple findings on the same
|
|
26
|
+
// location+metric; without them these siblings hash to one id:
|
|
27
|
+
// slowHost (host), memoryUtilization (executorId+dimension), partitionSizing (rule);
|
|
28
|
+
// cacheUtilization (rddId+variant), cachingOpportunity (relation/format for leaf,
|
|
29
|
+
// operator+relation for composite, executionIds as last resort);
|
|
30
|
+
// smallFiles (direction/nodeName), duplicatePlanSubtree (groupIndex is the real
|
|
31
|
+
// uniqueness guarantee: rootName+subtreeSize can collide across groups),
|
|
32
|
+
// broadcastSizing (value+largerSideBytes per node/side).
|
|
33
|
+
// memoryUtilization excludes `rule`: one heap band per executor, so executorId is already
|
|
34
|
+
// unique and folding rule in risks id churn if band logic changes. partitionSizing keeps
|
|
35
|
+
// `rule`: a stage can emit several rules at once sharing stageId+metric.
|
|
55
36
|
const rule = f.type === 'memoryUtilization' ? undefined : f.rule;
|
|
56
37
|
const disc = [
|
|
57
38
|
f.host, f.executorId, rule, f.variant, f.dimension,
|
|
@@ -62,6 +43,35 @@ export function findingId(f ) {
|
|
|
62
43
|
return fnv1a(`${f.type}|${locKey}|${f.metric ?? ''}|${f.value ?? ''}|${disc}`);
|
|
63
44
|
}
|
|
64
45
|
|
|
46
|
+
// skew's max/median branch (stage.taskCount below minTasksForP95) and straggler are both driven
|
|
47
|
+
// by the identical (taskDurationMax - taskDurationP50) delta on the same stage: the same
|
|
48
|
+
// dominant outlier task reported by two detectors, each independently clipped (see "Overlap
|
|
49
|
+
// caveat: skew / straggler" in impact-estimation.md). skew's P95/median branch samples a
|
|
50
|
+
// different task and stays independent. Flags both sides via validationRequired (rather than
|
|
51
|
+
// suppressing either) so neither finding's own diagnostic value is lost; the flag rides the same
|
|
52
|
+
// confidence-caveat UI a reader already sees before trusting either finding's magnitude.
|
|
53
|
+
function overlapNote(otherType ) {
|
|
54
|
+
return `This overlaps with the ${otherType} finding on this stage: both are driven by the same dominant outlier task, so don't add their recoverable-time figures together.`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function flagSkewStragglerOverlap(findings ) {
|
|
58
|
+
const maxMedianSkewStages = new Set(
|
|
59
|
+
findings.filter((f) => f.type === 'skew' && f.metric === 'max/median' && f.stageId != null).map((f) => f.stageId),
|
|
60
|
+
);
|
|
61
|
+
if (maxMedianSkewStages.size === 0) return;
|
|
62
|
+
const stragglerStages = new Set(
|
|
63
|
+
findings.filter((f) => f.type === 'straggler' && f.stageId != null).map((f) => f.stageId),
|
|
64
|
+
);
|
|
65
|
+
const overlapStages = new Set([...maxMedianSkewStages].filter((id) => stragglerStages.has(id)));
|
|
66
|
+
if (overlapStages.size === 0) return;
|
|
67
|
+
for (const f of findings) {
|
|
68
|
+
if (f.stageId == null || !overlapStages.has(f.stageId)) continue;
|
|
69
|
+
const note = f.type === 'skew' ? overlapNote('straggler') : f.type === 'straggler' ? overlapNote('skew') : null;
|
|
70
|
+
if (!note) continue;
|
|
71
|
+
f.validationRequired = f.validationRequired ? `${f.validationRequired} ${note}` : note;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
function push(out , entry , result ) {
|
|
66
76
|
if (!result) return;
|
|
67
77
|
const detectorVersion = entry.version ?? 1;
|
|
@@ -70,28 +80,15 @@ function push(out , entry , result
|
|
|
70
80
|
if (entry.suppressWhen && entry.suppressWhen(f, out)) continue;
|
|
71
81
|
const stamped = { ...f, docAnchor: f.docAnchor ?? entry.docAnchor, detectorVersion };
|
|
72
82
|
const id = findingId(stamped);
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// occurrence: same id => same finding, keep only the first. Added after a
|
|
76
|
-
// real-log bug where duplicatePlanSubtree emitted two distinct findings
|
|
77
|
-
// sharing one id (see findingId's groupIndex discriminator above and
|
|
78
|
-
// tests/detectors-plan.test.js's duplicatePlanSubtree regression tests);
|
|
79
|
-
// this guard's correctness depends on findingId's discriminator list
|
|
80
|
-
// actually being unique per distinct finding, not on this line itself.
|
|
83
|
+
// Dedup guard: same id => same finding, keep first. Correctness depends on
|
|
84
|
+
// findingId's discriminators being unique per distinct finding, not on this line.
|
|
81
85
|
if (out.some((existing) => existing.id === id)) continue;
|
|
82
86
|
out.push({ ...stamped, id });
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
// `app`
|
|
87
|
-
//
|
|
88
|
-
// `buildJson`, useIngest.ts's `runDone`/`snapshotParsedRun`) pass
|
|
89
|
-
// `AppModel.app`, which is genuinely `SparkAppInfo | null` at the type
|
|
90
|
-
// level even though by the time `analyze()` runs in practice a full parse
|
|
91
|
-
// has completed and `app` is always populated (the same reasoning
|
|
92
|
-
// detectors.ts's `DetectorCtx.app` comment gives for treating its own,
|
|
93
|
-
// separate `app` field as non-nullable). Widened to match the real caller
|
|
94
|
-
// type rather than forcing a cast at every call site.
|
|
90
|
+
// `app` widened to `SparkAppInfo | null` to match real callers (AppModel.app is
|
|
91
|
+
// nullable at the type level); every detector below tolerates a null app.
|
|
95
92
|
export function analyze(
|
|
96
93
|
app ,
|
|
97
94
|
stages ,
|
|
@@ -101,29 +98,18 @@ export function analyze(
|
|
|
101
98
|
sql = new Map(),
|
|
102
99
|
runAggregates = null,
|
|
103
100
|
) {
|
|
104
|
-
// `app ?? {}`:
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
// concurrent-capacity bound, and computeTotalCores's cumulative sum over
|
|
110
|
-
// every addition overstates that under dynamic allocation/executor
|
|
111
|
-
// replacement (a churned-through executor's cores were never actually
|
|
112
|
-
// concurrent with its replacement's).
|
|
113
|
-
// `executorsAdded`/`executorsRemoved` casts: computePeakConcurrentCores only
|
|
114
|
-
// reads `executorId`/`timestamp`/`totalCores`, all present on
|
|
115
|
-
// ExecutorAddedEvent/ExecutorRemovedEvent, but `totalCores` isn't on
|
|
116
|
-
// ExecutorRemovedEvent, so the `ExecutorEvent` union as a whole is a
|
|
117
|
-
// structural mismatch against its parameter shapes.
|
|
101
|
+
// `app ?? {}`: detectors tolerate a null app (malformed logs), so this must too.
|
|
102
|
+
// computePeakConcurrentCores (not computeTotalCores): the occupancy ceiling needs a
|
|
103
|
+
// concurrent-capacity bound; a cumulative sum overstates it under dynamic allocation.
|
|
104
|
+
// Casts: the function reads only executorId/timestamp/totalCores, but totalCores isn't
|
|
105
|
+
// on ExecutorRemovedEvent, so the ExecutorEvent union mismatches its parameter shapes.
|
|
118
106
|
const totalCores = computePeakConcurrentCores(
|
|
119
107
|
app ?? {},
|
|
120
108
|
executorsAdded ,
|
|
121
109
|
executorsRemoved ,
|
|
122
110
|
);
|
|
123
|
-
// Computed once
|
|
124
|
-
//
|
|
125
|
-
// finding's savings (src/detectors.ts's `clippedWasteMs`), not a raw
|
|
126
|
-
// pre-clip delta the two passes would otherwise disagree on.
|
|
111
|
+
// Computed once so detectors gate impact band on the same occupancy-clipped waste
|
|
112
|
+
// estimateImpact displays as savings, not a raw pre-clip delta the two passes would disagree on.
|
|
127
113
|
const occupancy = computeOccupancy(stages , totalCores);
|
|
128
114
|
const ctx = {
|
|
129
115
|
app, stages, executorsAdded, executorsRemoved, jobs, sql, runAggregates, occupancy,
|
|
@@ -148,20 +134,34 @@ export function analyze(
|
|
|
148
134
|
}
|
|
149
135
|
estimateImpact(out, stages, totalCores);
|
|
150
136
|
deriveImpactBand(out, app);
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
// stable
|
|
137
|
+
flagSkewStragglerOverlap(out);
|
|
138
|
+
// Ascending IMPACT_BAND_ORDER (critical 0 -> info 2) puts the worst band first;
|
|
139
|
+
// stable sort keeps DETECTORS declaration order within a band.
|
|
154
140
|
out.sort((a, b) => IMPACT_BAND_ORDER[a.impactBand] - IMPACT_BAND_ORDER[b.impactBand]);
|
|
155
141
|
return out;
|
|
156
142
|
}
|
|
157
143
|
|
|
158
|
-
|
|
144
|
+
// Memoizes auditConfig by `app` identity (like evidence-report.ts's jsonCache) so config detectors
|
|
145
|
+
// don't re-run when both the export and the report path audit the same app. WeakMap can't key on
|
|
146
|
+
// `null`, so that case skips the cache. Returns a fresh copy each call so a caller's in-place
|
|
147
|
+
// mutation (e.g. `.sort()`) can't corrupt the cached array.
|
|
148
|
+
const auditConfigCache = new WeakMap ();
|
|
149
|
+
|
|
150
|
+
function computeAuditConfig(app ) {
|
|
159
151
|
const out = [];
|
|
160
152
|
for (const d of DETECTORS) if (d.scope === 'config') push(out, d, d.detect({ app }));
|
|
161
|
-
// configAudit's
|
|
162
|
-
//
|
|
163
|
-
// map is enough for parity with the same finding type produced via analyze().
|
|
153
|
+
// configAudit's impact case is unconditionally costOnly('none'): needs no stages/totalCores,
|
|
154
|
+
// an empty stages map gives parity with analyze().
|
|
164
155
|
estimateImpact(out, new Map());
|
|
165
156
|
deriveImpactBand(out, app);
|
|
166
157
|
return out.map((f) => ({ ...f, stageId: f.stageId ?? null }));
|
|
167
158
|
}
|
|
159
|
+
|
|
160
|
+
export function auditConfig(app ) {
|
|
161
|
+
if (app === null) return computeAuditConfig(app);
|
|
162
|
+
const cached = auditConfigCache.get(app);
|
|
163
|
+
if (cached) return cached.slice();
|
|
164
|
+
const result = computeAuditConfig(app);
|
|
165
|
+
auditConfigCache.set(app, result);
|
|
166
|
+
return result.slice();
|
|
167
|
+
}
|
|
@@ -26,10 +26,8 @@ function taskDataTrusted(appModel ) {
|
|
|
26
26
|
return entry?.state === 'present';
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
// types.ts). The 'spill' findings this is used for are always numeric; the
|
|
32
|
-
// typeof guard below narrows without changing behavior for real input.
|
|
29
|
+
// Finding.value is number|string (some detectors put text there); spill findings are
|
|
30
|
+
// always numeric, so the typeof guard narrows without changing behavior for real input.
|
|
33
31
|
function maxFindingValue(catalog , type ) {
|
|
34
32
|
const values = catalog
|
|
35
33
|
.filter((f) => f.type === type)
|
|
@@ -68,10 +66,8 @@ function checkSkew(appModel , maxSkewRatio ) {
|
|
|
68
66
|
if (stages.length === 0) {
|
|
69
67
|
return { name: 'max-skew', status: 'inconclusive', detail: 'No stage data observed in this event log.' };
|
|
70
68
|
}
|
|
71
|
-
// Recompute the true ratio per stage
|
|
72
|
-
//
|
|
73
|
-
// budget stricter than that floor could never be enforced by reading
|
|
74
|
-
// catalog findings alone.
|
|
69
|
+
// Recompute the true ratio per stage: the skew detector floors findings at
|
|
70
|
+
// thresholds.ratioWarn (3), so a stricter budget can't be enforced from catalog alone.
|
|
75
71
|
const ratios = stages
|
|
76
72
|
.map((stage) => computeSkewRatio(stage, SKEW_MIN_TASKS_FOR_P95))
|
|
77
73
|
.filter((r) => r !== null)
|
|
@@ -96,10 +92,8 @@ function checkFailedTaskRate(appModel , catalog , maxPct
|
|
|
96
92
|
if ((appModel.jobs?.size ?? 0) === 0) {
|
|
97
93
|
return { name: 'max-failed-task-rate', status: 'inconclusive', detail: 'No job data observed in this event log.' };
|
|
98
94
|
}
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
// failure rate is implicitly low too. Known v1 limitation: a budget
|
|
102
|
-
// stricter than that floor cannot be enforced.
|
|
95
|
+
// jobFailureRate never fired => rate below its 10% info floor, so task failure rate is
|
|
96
|
+
// implicitly low. v1 limitation: a budget stricter than that floor can't be enforced.
|
|
103
97
|
return { name: 'max-failed-task-rate', status: 'pass', detail: `No job-failure-rate finding: task failure rate is below the detector's reporting floor.` };
|
|
104
98
|
}
|
|
105
99
|
|
|
@@ -125,10 +119,8 @@ function checkRegression(comparison , maxRegressionPct
|
|
|
125
119
|
if (!row || row.direction === 'unavailable' || row.baseline == null || row.delta == null) {
|
|
126
120
|
return { name: 'max-regression', status: 'inconclusive', detail: `Metric "${regressionMetric}" is unavailable for this comparison.` };
|
|
127
121
|
}
|
|
128
|
-
// A neutral-direction metric (inputBytes
|
|
129
|
-
//
|
|
130
|
-
// workload volume, not performance: an increase isn't a regression, so a
|
|
131
|
-
// regression budget can't be meaningfully evaluated against it either way.
|
|
122
|
+
// A neutral-direction metric (inputBytes/outputBytes/taskCount/executorsAdded) measures
|
|
123
|
+
// workload volume, not performance: an increase isn't a regression, so no direction to check.
|
|
132
124
|
if (row.direction === 'neutral') {
|
|
133
125
|
return { name: 'max-regression', status: 'inconclusive', detail: `Metric "${regressionMetric}" measures workload volume, not performance: it has no regression direction to check.` };
|
|
134
126
|
}
|
|
@@ -156,8 +148,7 @@ function checkFailOnIntroduced(comparison , band )
|
|
|
156
148
|
: { name: 'fail-on-introduced', status: 'pass', detail: `No introduced findings match "${band}".` };
|
|
157
149
|
}
|
|
158
150
|
|
|
159
|
-
//
|
|
160
|
-
// "no comparison yet -> inconclusive" fallback instead of actually checking.
|
|
151
|
+
// Both comparison-dependent budgets share the "no comparison yet -> inconclusive" fallback.
|
|
161
152
|
function pushComparisonBudget(
|
|
162
153
|
results ,
|
|
163
154
|
comparison ,
|
|
@@ -176,18 +167,13 @@ export function evaluateBudgets({ appModel, catalog, budgets, comparison }
|
|
|
176
167
|
if (Number.isFinite(budgets.maxSkewRatio)) results.push(checkSkew(appModel, budgets.maxSkewRatio ));
|
|
177
168
|
if (Number.isFinite(budgets.maxFailedTaskRatePct)) results.push(checkFailedTaskRate(appModel, catalog, budgets.maxFailedTaskRatePct ));
|
|
178
169
|
if (Number.isFinite(budgets.minEfficiencyPct)) results.push(checkEfficiency(appModel, budgets.minEfficiencyPct ));
|
|
179
|
-
// Guarded here
|
|
180
|
-
//
|
|
181
|
-
// maxRegressionPct would otherwise skip the whole `if` below silently,
|
|
182
|
-
// reporting nothing at all instead of a visible inconclusive result.
|
|
170
|
+
// Guarded here so any evaluateBudgets caller benefits: regressionMetric without
|
|
171
|
+
// maxRegressionPct would otherwise skip the `if` silently, reporting nothing.
|
|
183
172
|
if (budgets.regressionMetric !== undefined && budgets.maxRegressionPct === undefined) {
|
|
184
173
|
results.push({ name: 'max-regression', status: 'inconclusive', detail: `regressionMetric "${budgets.regressionMetric}" was set without maxRegressionPct; the regression budget was not evaluated.` });
|
|
185
174
|
} else if (budgets.maxRegressionPct !== undefined) {
|
|
186
|
-
// `!== undefined`, not
|
|
187
|
-
//
|
|
188
|
-
// legitimate finite-typed-as-number input here (CLI's own flag validation
|
|
189
|
-
// already rejects non-finite --max-regression-pct input, so this only
|
|
190
|
-
// widens what a direct evaluateBudgets caller, e.g. a test, can express).
|
|
175
|
+
// `!== undefined`, not Number.isFinite: a zero-baseline regression's pct is Infinity,
|
|
176
|
+
// so an "unlimited" budget is a legitimate input (the CLI already rejects non-finite flags).
|
|
191
177
|
pushComparisonBudget(results, comparison, 'max-regression',
|
|
192
178
|
(c) => checkRegression(c, budgets.maxRegressionPct , budgets.regressionMetric ?? 'wallClock'));
|
|
193
179
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
1
|
+
import { readFileSync, readdirSync, statSync, openSync, readSync, closeSync } from 'node:fs';
|
|
2
2
|
import { join, basename } from 'node:path';
|
|
3
3
|
import { createState, runParse, runParseFiles, reassembleRollingEntries } from '../parser-worker.js';
|
|
4
4
|
import { createModelCallbacks } from '../model-assembler.js';
|
|
@@ -23,8 +23,7 @@ export function emptyAppModel() {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
//
|
|
27
|
-
// runParse/runParseFiles need (name, size, slice().arrayBuffer(), arrayBuffer()).
|
|
26
|
+
// File-like shape runParse/runParseFiles need: name, size, slice().arrayBuffer(), arrayBuffer().
|
|
28
27
|
export function nodeFileFromPath(path ) {
|
|
29
28
|
const bytes = readFileSync(path);
|
|
30
29
|
const u8 = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
@@ -39,27 +38,53 @@ export function nodeFileFromPath(path ) {
|
|
|
39
38
|
};
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
41
|
+
// Lazy-loading variant for peekLogHeader: only reads the requested byte range from disk,
|
|
42
|
+
// avoiding eager full-file slurps for large logs during directory scans.
|
|
43
|
+
// Used by list_runs local-mode scanning; does NOT have arrayBuffer() since peekLogHeader
|
|
44
|
+
// only needs slice() for bounded reads of the file header.
|
|
45
|
+
export function nodeLazyPeekableFromPath(path )
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
const size = statSync(path).size;
|
|
51
|
+
return {
|
|
52
|
+
name: basename(path),
|
|
53
|
+
size,
|
|
54
|
+
slice(start , end ) {
|
|
55
|
+
return {
|
|
56
|
+
async arrayBuffer() {
|
|
57
|
+
const len = Math.max(0, Math.min(end, size) - start);
|
|
58
|
+
if (len === 0) return new ArrayBuffer(0);
|
|
59
|
+
const buf = Buffer.alloc(len);
|
|
60
|
+
const fd = openSync(path, 'r');
|
|
61
|
+
try {
|
|
62
|
+
readSync(fd, buf, 0, len, start);
|
|
63
|
+
} finally {
|
|
64
|
+
closeSync(fd);
|
|
65
|
+
}
|
|
66
|
+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// routeMessage is the single source of truth for worker-message -> handler wiring, shared by
|
|
74
|
+
// the browser Worker and this Node CLI path. No pendingTaskRequests: the CLI never sends
|
|
75
|
+
// 'getTaskData', so never receives 'taskData' back.
|
|
47
76
|
export function dispatch(msg , handlers ) {
|
|
48
77
|
routeMessage(msg , handlers);
|
|
49
78
|
}
|
|
50
79
|
|
|
51
|
-
function isRollingLogDirectory(dirPath ) {
|
|
80
|
+
export function isRollingLogDirectory(dirPath ) {
|
|
52
81
|
const names = readdirSync(dirPath);
|
|
53
82
|
return names.some((n ) => /^events_\d+_/.test(n));
|
|
54
83
|
}
|
|
55
84
|
|
|
56
|
-
// Shared ingest scaffold for both consumers
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// fresh AppModel wired to the shared handlers, then hands `run` a
|
|
60
|
-
// (state, emit, reject) triple so each caller only supplies its own decode
|
|
61
|
-
// step (and its own error type via `onDecodeError` — a plain Error here, an
|
|
62
|
-
// mcpError over in shs-load.ts).
|
|
85
|
+
// Shared ingest scaffold for both routeMessage consumers (this file's collectRun over a
|
|
86
|
+
// local file/dir, shs-load.ts's collectShsAppModel over fetched bytes): each caller supplies
|
|
87
|
+
// its own decode step and error type via onDecodeError.
|
|
63
88
|
export function collectViaDispatch(
|
|
64
89
|
run ,
|
|
65
90
|
onDecodeError ,
|
|
@@ -96,9 +121,8 @@ export async function collectRun(inputPath )
|
|
|
96
121
|
return;
|
|
97
122
|
}
|
|
98
123
|
const files = ordered.map((name) => nodeFileFromPath(join(inputPath, name)));
|
|
99
|
-
// .catch(reject), not void:
|
|
100
|
-
//
|
|
101
|
-
// pending forever and surface only as an unhandled rejection.
|
|
124
|
+
// .catch(reject), not void: a throw past the parser's guards would otherwise leave
|
|
125
|
+
// this Promise pending forever, surfacing only as an unhandled rejection.
|
|
102
126
|
runParseFiles(files, state, { emit }).catch(reject);
|
|
103
127
|
} else {
|
|
104
128
|
runParse(nodeFileFromPath(inputPath), state, { emit }).catch(reject);
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
// Total cores
|
|
2
|
-
// peakExecutors × configured cores. Used by efficiency model, scaling simulator,
|
|
3
|
-
// wasted core-hours calculation, and utilization/memory-utilization detectors.
|
|
1
|
+
// Total cores: sum executor totalCores, else peakExecutors x configured cores.
|
|
4
2
|
export function computeTotalCores(app , executorsAdded ) {
|
|
5
3
|
let total = executorsAdded.reduce((s, e) => s + (e.totalCores ?? 0), 0);
|
|
6
4
|
if (total <= 0) {
|
|
@@ -10,19 +8,11 @@ export function computeTotalCores(app
|
|
|
10
8
|
return total;
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
// Peak concurrently-alive core count:
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// allocation/executor replacement, since a churned-through executor's cores
|
|
19
|
-
// are never actually concurrent with its replacement's; this is the
|
|
20
|
-
// concurrency-aware sibling used to bound the impact estimator's ceiling
|
|
21
|
-
// (src/occupancy.ts's computeCeiling) against that inflation.
|
|
22
|
-
// Tie-break same-timestamp events by delta ascending, so a removal is applied
|
|
23
|
-
// before a same-instant replacement's addition: without this, a seamless swap
|
|
24
|
-
// (old executor gone exactly when its replacement joins) would momentarily
|
|
25
|
-
// double-count both as concurrent.
|
|
11
|
+
// Peak concurrently-alive core count: sweep add/remove events by timestamp, track running max,
|
|
12
|
+
// rather than summing every addition. A cumulative sum overstates capacity under dynamic
|
|
13
|
+
// allocation (a churned-through executor's cores were never concurrent with its replacement's).
|
|
14
|
+
// Tie-break same-timestamp events by delta ascending so a removal applies before a same-instant
|
|
15
|
+
// addition; else a seamless swap would momentarily double-count both as concurrent.
|
|
26
16
|
function sweepPeak(events ) {
|
|
27
17
|
const sorted = [...events].sort((a, b) => a.time - b.time || a.delta - b.delta);
|
|
28
18
|
let running = 0;
|
|
@@ -41,28 +31,36 @@ export function computePeakConcurrentCores(
|
|
|
41
31
|
) {
|
|
42
32
|
const coresByExecutor = new Map ();
|
|
43
33
|
const coreEvents = [];
|
|
44
|
-
const countEvents = [];
|
|
45
34
|
for (const e of executorsAdded) {
|
|
46
35
|
const cores = e.totalCores ?? 0;
|
|
47
36
|
coresByExecutor.set(e.executorId, cores);
|
|
48
37
|
coreEvents.push({ time: e.timestamp, delta: cores });
|
|
49
|
-
countEvents.push({ time: e.timestamp, delta: 1 });
|
|
50
38
|
}
|
|
51
39
|
for (const e of executorsRemoved) {
|
|
52
40
|
const cores = coresByExecutor.get(e.executorId) ?? 0;
|
|
53
41
|
coreEvents.push({ time: e.timestamp, delta: -cores });
|
|
54
|
-
countEvents.push({ time: e.timestamp, delta: -1 });
|
|
55
42
|
}
|
|
56
43
|
const peak = sweepPeak(coreEvents);
|
|
57
44
|
if (peak > 0) return peak;
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
// (a churned-through executor and its replacement both counted, even
|
|
63
|
-
// though they were never alive at once). Sweep peak *executor count*
|
|
64
|
-
// instead: still concurrency-aware, just cores-blind.
|
|
65
|
-
const peakExecutorCount = sweepPeak(countEvents);
|
|
45
|
+
// totalCores missing/zero for every add, so the cores sweep is blind. Falling back to
|
|
46
|
+
// executorsAdded.length * cores would reintroduce the cumulative overcount this avoids;
|
|
47
|
+
// sweep peak executor count instead: concurrency-aware, cores-blind.
|
|
48
|
+
const peakExecutorCount = computePeakConcurrentExecutorCount(executorsAdded, executorsRemoved);
|
|
66
49
|
const cores = app.resources?.executor?.cores ?? null;
|
|
67
50
|
return cores != null ? peakExecutorCount * cores : 0;
|
|
68
51
|
}
|
|
52
|
+
|
|
53
|
+
// Peak concurrently-alive executor COUNT (not cores): the denominator memoryUtilization's
|
|
54
|
+
// per-executor waste math (allocatedMB x executor count) needs. Same sweep as
|
|
55
|
+
// computePeakConcurrentCores, over add/remove events rather than cores, so a churned-through
|
|
56
|
+
// executor's slot is never double-counted against its replacement's.
|
|
57
|
+
export function computePeakConcurrentExecutorCount(
|
|
58
|
+
executorsAdded ,
|
|
59
|
+
executorsRemoved ,
|
|
60
|
+
) {
|
|
61
|
+
const countEvents = [
|
|
62
|
+
...executorsAdded.map((e) => ({ time: e.timestamp, delta: 1 })),
|
|
63
|
+
...executorsRemoved.map((e) => ({ time: e.timestamp, delta: -1 })),
|
|
64
|
+
];
|
|
65
|
+
return sweepPeak(countEvents);
|
|
66
|
+
}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
// Whole-run core-usage-locality ratio: non-local task share across every
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// stage-breakdown list.
|
|
6
|
-
//
|
|
7
|
-
// Locality classification (Spark, best to worst): PROCESS_LOCAL > NODE_LOCAL
|
|
8
|
-
// > NO_PREF > RACK_LOCAL > ANY. NO_PREF is not a locality failure; it's what
|
|
9
|
-
// shuffle-read stages report because there's no location-preference concept
|
|
10
|
-
// for a shuffle fetch, so it stays in the denominator (diluting the ratio for
|
|
11
|
-
// shuffle-heavy stages, which is correct) but never in the numerator.
|
|
1
|
+
// Whole-run core-usage-locality ratio: non-local task share across every stage's localityStats.
|
|
2
|
+
// Locality tiers (Spark, best to worst): PROCESS_LOCAL > NODE_LOCAL > NO_PREF > RACK_LOCAL > ANY.
|
|
3
|
+
// NO_PREF is not a locality failure (shuffle-read stages report it, no location preference for a
|
|
4
|
+
// shuffle fetch), so it stays in the denominator but never the numerator.
|
|
12
5
|
const NON_LOCAL_TIERS = new Set(['RACK_LOCAL', 'ANY']);
|
|
13
6
|
const TOP_N = 5;
|
|
14
7
|
const MIN_TASKS_PER_STAGE = 10;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
// Sweep-line "busy cores over time"
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// `hypotheticalCores` clamps the concurrent busy-core count to N (the
|
|
7
|
-
// deterministic "utilization at N cores" curve). It does NOT re-schedule tasks;
|
|
8
|
-
// makespan re-estimation is the scaling simulator's job, layered on this signal.
|
|
1
|
+
// Sweep-line "busy cores over time". Pure so it runs in parser-worker (over retained task
|
|
2
|
+
// timestamps) or on the main thread (scaling simulator). One core per task: Spark's default.
|
|
3
|
+
// `hypotheticalCores` clamps the concurrent busy-core count to N; it does NOT re-schedule
|
|
4
|
+
// tasks (makespan re-estimation is the scaling simulator's job, layered on this signal).
|
|
9
5
|
|
|
10
6
|
|
|
11
7
|
|
|
@@ -19,18 +15,16 @@
|
|
|
19
15
|
|
|
20
16
|
|
|
21
17
|
function buildStepFunction(intervals , hypotheticalCores ) {
|
|
22
|
-
// Emit +1 at launch, -1 at finish. Skip empty/negative intervals.
|
|
23
18
|
const events = [];
|
|
24
19
|
for (const { launch, finish } of intervals) {
|
|
25
20
|
if (!(finish > launch)) continue;
|
|
26
21
|
events.push({ t: launch, delta: 1 });
|
|
27
22
|
events.push({ t: finish, delta: -1 });
|
|
28
23
|
}
|
|
29
|
-
//
|
|
30
|
-
//
|
|
24
|
+
// At equal time process -1 before +1 so [launch, finish) is half-open (a task finishing
|
|
25
|
+
// exactly as another launches does not overlap).
|
|
31
26
|
events.sort((a, b) => (a.t - b.t) || (a.delta - b.delta));
|
|
32
27
|
|
|
33
|
-
// Walk consecutive timestamps, emitting the busy level held on each interval.
|
|
34
28
|
const segments = [];
|
|
35
29
|
let busy = 0;
|
|
36
30
|
for (let i = 0; i < events.length; i++) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// Stage-granular approximation of core-usage-by-locality over time.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// split by localityStats proportions. Not exact; approximate by construction.
|
|
1
|
+
// Stage-granular approximation of core-usage-by-locality over time. Per-task locality is not
|
|
2
|
+
// retained, so this distributes each stage's executorRunTime across its wall-clock window,
|
|
3
|
+
// split by localityStats proportions. Approximate by construction.
|
|
5
4
|
export const LOCALITY_TIERS = ['PROCESS_LOCAL', 'NODE_LOCAL', 'RACK_LOCAL', 'NO_PREF', 'ANY'];
|
|
6
5
|
|
|
7
6
|
|