sparkforensics-cli 0.1.0 → 0.2.0
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.CndaAS6v.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.DNY8bVcl.js +1 -0
- package/export-template/docs/assets/chunks/VPLocalSearchBox.yJbZbsEo.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.Df2VAG9w.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.B-OsL91z.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_board-widgets.md.B-OsL91z.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.BOeH4d1J.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.BOeH4d1J.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.DYCDPgkh.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_impact-estimation.md.DYCDPgkh.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.m3S3UdMk.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_state-and-history.md.m3S3UdMk.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.DbqPf2OT.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.DbqPf2OT.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.B93qJ_tT.js +6 -0
- package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.B93qJ_tT.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.DXOMCXxn.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.sU3KGarf.js +1 -0
- package/export-template/docs/assets/user-guide_alternative-log-retrieval.md.sU3KGarf.lean.js +1 -0
- package/export-template/docs/assets/user-guide_getting-started.md.DtEM37MK.js +3 -0
- package/export-template/docs/assets/user-guide_getting-started.md.DtEM37MK.lean.js +1 -0
- package/export-template/docs/assets/user-guide_mcp-tools.md.C8MiIu7F.js +125 -0
- package/export-template/docs/assets/user-guide_mcp-tools.md.C8MiIu7F.lean.js +1 -0
- package/export-template/docs/assets/user-guide_run-comparison.md.S0TWWmLY.js +1 -0
- package/export-template/docs/assets/user-guide_run-comparison.md.S0TWWmLY.lean.js +1 -0
- package/export-template/docs/assets/user-guide_understanding-findings.md.D0R_Y-R2.js +1 -0
- package/export-template/docs/assets/user-guide_understanding-findings.md.D0R_Y-R2.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 +115 -0
- package/export-template/parser-worker-QqyEE4m9.js +64 -0
- package/package.json +16 -3
- 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 +256 -375
- 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 +6 -0
- package/vendor-core/docs-content/detection/cfg.md +15 -0
- package/vendor-core/docs-content/detection/chrn.md +7 -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 +7 -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 +7 -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 +72 -122
- 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 +18 -27
- 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 +65 -70
- 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
|
@@ -11,15 +11,11 @@ const BOTTLENECK_WIDGET = {
|
|
|
11
11
|
coldStart: 'executor-timeline', utilization: 'executor-timeline', speculationWaste: 'executor-timeline',
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
// Cross-widget stage recurrence: how many
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// `stageId` widened to also accept `undefined` (Task 22, src/detectors.ts):
|
|
20
|
-
// sql/config-scope findings never set `stageId` at all, so `Finding.stageId`
|
|
21
|
-
// is now `number | null | undefined`, and this `catalog` param is usually a
|
|
22
|
-
// live `Finding[]`. `stageId == null` below already treats both the same.
|
|
14
|
+
// Cross-widget stage recurrence: how many distinct board widgets (skew+straggler on one stage
|
|
15
|
+
// still count as one, task-skew) flag a stage. Computed once from the full catalog each widget
|
|
16
|
+
// already receives.
|
|
17
|
+
// `stageId` may be number|null|undefined: sql/config-scope findings never set it; `stageId == null`
|
|
18
|
+
// below treats both the same.
|
|
23
19
|
export function stageWidgetFrequency(catalog ) {
|
|
24
20
|
const perStage = new Map();
|
|
25
21
|
for (const b of catalog) {
|
|
@@ -34,12 +30,9 @@ export function stageWidgetFrequency(catalog
|
|
|
34
30
|
return freq;
|
|
35
31
|
}
|
|
36
32
|
|
|
37
|
-
// Canonical detector type
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// Alerts, which lists findings across every detector type.
|
|
41
|
-
// Exported (not just used internally by typeTag) so doc-sync checks can
|
|
42
|
-
// enumerate every tag value without hand-duplicating this list elsewhere.
|
|
33
|
+
// Canonical detector type -> ALL-CAPS board tag vocabulary. Single source of truth for every
|
|
34
|
+
// widget that renders a catalog entry's tag outside its own card (e.g. Bottleneck Alerts).
|
|
35
|
+
// Exported so doc-sync checks can enumerate every tag without hand-duplicating this list.
|
|
43
36
|
export const TYPE_TAG_MAP = {
|
|
44
37
|
skew: 'SKEW', shuffle: 'SHFL', spill: 'SPILL', gc: 'GC',
|
|
45
38
|
coldStart: 'COLD', utilization: 'UTIL', memoryUtilization: 'MEM',
|
|
@@ -58,23 +51,34 @@ export function typeTag(type ) {
|
|
|
58
51
|
return TYPE_TAG_MAP[type] ?? type.toUpperCase();
|
|
59
52
|
}
|
|
60
53
|
|
|
61
|
-
// Human-readable tooltips for the terse spill-classification badges
|
|
62
|
-
//
|
|
63
|
-
// without an external key. Kept domain-agnostic (Spark-internal terms only).
|
|
54
|
+
// Human-readable tooltips for the terse spill-classification badges (skew | vol | ?), surfaced via
|
|
55
|
+
// title=. Kept domain-agnostic (Spark-internal terms only).
|
|
64
56
|
export const SPILL_CLASS_TITLE = {
|
|
65
57
|
skew: 'Skew spill: a few heavy tasks spill while most do not; rebalance partitioning',
|
|
66
58
|
volume: 'Volume spill: most tasks spill because data exceeds memory; add partitions',
|
|
67
59
|
unclassified: 'Spill cause could not be classified',
|
|
68
60
|
};
|
|
69
61
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
62
|
+
// Short badge text, kept distinct from the long-form SPILL_CLASS_TITLE tooltip. Shared by
|
|
63
|
+
// Spill.tsx's per-row badge and StageTable.tsx's spill-column cell.
|
|
64
|
+
export const SPILL_CLASS_SHORT = {
|
|
65
|
+
skew: 'skew', volume: 'vol', unclassified: '?',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const DEFAULT_MAX_STAGE_IDS_SHOWN = 8;
|
|
69
|
+
|
|
70
|
+
// Caps a stage-id list's printed length: a sql-scope finding's stageIds can run into the hundreds,
|
|
71
|
+
// and joined raw that length balloons an auto-layout table's column width. Shared by FixTheseFirst
|
|
72
|
+
// and PlanFindings.
|
|
73
|
+
export function formatStageIdsLabel(stageIds , maxShown = DEFAULT_MAX_STAGE_IDS_SHOWN) {
|
|
74
|
+
if (stageIds.length <= maxShown) return stageIds.join(', ');
|
|
75
|
+
return `${stageIds.slice(0, maxShown).join(', ')}, +${stageIds.length - maxShown} more`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Worst (lowest IMPACT_BAND_ORDER) impact band across findings, undefined when empty. Shared by
|
|
79
|
+
* every widget that badges/colors a group of findings.
|
|
75
80
|
* @param {{ impactBand: 'critical' | 'warning' | 'info' }[]} findings
|
|
76
|
-
* @returns {'critical' | 'warning' | 'info' | undefined}
|
|
77
|
-
*/
|
|
81
|
+
* @returns {'critical' | 'warning' | 'info' | undefined} */
|
|
78
82
|
export function worstImpactBand(
|
|
79
83
|
findings ,
|
|
80
84
|
) {
|
|
@@ -89,12 +93,8 @@ export function escHtml(str ) {
|
|
|
89
93
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
90
94
|
}
|
|
91
95
|
|
|
92
|
-
// Reduces an embedded HDFS/S3/absolute-path fragment
|
|
93
|
-
//
|
|
94
|
-
// with no such fragment pass through unchanged; this is NOT a general
|
|
95
|
-
// truncator. Shared by cache-utilization.js (RDD names), detectors.js (plan
|
|
96
|
-
// node names embedded in recommendation text), and stage-detail.js (plan
|
|
97
|
-
// node detail labels).
|
|
96
|
+
// Reduces an embedded HDFS/S3/absolute-path fragment to its basename (e.g. "... hdfs://host/a/b/c"
|
|
97
|
+
// -> "c"). Strings with no such fragment pass through unchanged; NOT a general truncator.
|
|
98
98
|
export function pathBasename(str ) {
|
|
99
99
|
const s = String(str);
|
|
100
100
|
const m = s.match(/((?:hdfs?|s3[an]?):\/\/\S+|\/[^\s,)]+)/i);
|
|
@@ -103,10 +103,9 @@ export function pathBasename(str ) {
|
|
|
103
103
|
return segs[segs.length - 1] || s;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
// "collect at /u02
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
// includes a trailing ":<line>", since colons aren't excluded from its regex).
|
|
106
|
+
// "collect at /u02/.../utils.py:1869" -> "collect at utils.py:1869". Splits on the last " at "
|
|
107
|
+
// (Spark's callsite format), then reduces the location to its basename via pathBasename (whose
|
|
108
|
+
// match keeps a trailing ":<line>").
|
|
110
109
|
export function trimCallsite(callsite ) {
|
|
111
110
|
const idx = callsite.lastIndexOf(' at ');
|
|
112
111
|
if (idx === -1) return callsite;
|
|
@@ -115,9 +114,8 @@ export function trimCallsite(callsite ) {
|
|
|
115
114
|
return `${operation} at ${pathBasename(location)}`;
|
|
116
115
|
}
|
|
117
116
|
|
|
118
|
-
// recent-files ids are `${name}::${size}::${lastModified}`: show just the
|
|
119
|
-
//
|
|
120
|
-
// runs being compared and so wouldn't distinguish baseline from candidate.
|
|
117
|
+
// recent-files ids are `${name}::${size}::${lastModified}`: show just the name. Preferred over
|
|
118
|
+
// app.name, which is usually identical across the two compared runs.
|
|
121
119
|
export function runLabel(id ) {
|
|
122
120
|
return id.split('::')[0] || id;
|
|
123
121
|
}
|
|
@@ -136,6 +134,82 @@ export function nsToMs(ns ) {
|
|
|
136
134
|
return ns / 1e6;
|
|
137
135
|
}
|
|
138
136
|
|
|
137
|
+
// Finding.value is number|string only because a couple of detectors (configAudit, stageFailed)
|
|
138
|
+
// report a string; every other row formatter needs the numeric case and falls back to 0.
|
|
139
|
+
export function numericValue(f ) {
|
|
140
|
+
return typeof f.value === 'number' ? f.value : 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Low-level "number -> display string" step shared by every widget's per-finding label resolver:
|
|
144
|
+
// each keeps its own type/metric routing and calls this only for the final value-to-string step,
|
|
145
|
+
// so the same number renders identically ("42%", "2.3×") everywhere.
|
|
146
|
+
export function formatMetricValue(unit , value ) {
|
|
147
|
+
switch (unit) {
|
|
148
|
+
case 'pct': return `${value}%`;
|
|
149
|
+
// 'pct' expects an already-scaled 0-100 value (every detector's Finding.value convention); this
|
|
150
|
+
// variant is for the rare metric (slowHost's hostDurationShare) whose value is a raw 0-1 fraction.
|
|
151
|
+
case 'pctFraction': return `${Math.round(value * 100)}%`;
|
|
152
|
+
case 'ratio': return `${value}×`;
|
|
153
|
+
case 'ms': return formatDuration(value);
|
|
154
|
+
case 'count': return `${value}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Finding.metric -> display unit, for the compact plan-graph finding chip. Keyed by the exact
|
|
159
|
+
// `metric` string each per-stage/plan-advisor detector writes (see detectors.ts); a metric this map
|
|
160
|
+
// doesn't cover, or a non-numeric `value` (stageFailed's failure reason, configAudit's config text),
|
|
161
|
+
// yields no magnitude. 'bytes' handles the sub-KB tier formatBytes deliberately lacks; 'minutes'
|
|
162
|
+
// converts to ms so it shares formatDuration's phrasing.
|
|
163
|
+
const FINDING_METRIC_UNIT = {
|
|
164
|
+
shuffleReadBytes: 'bytes', memoryBytesSpilled: 'bytes', shuffleReadMax: 'bytes',
|
|
165
|
+
avgFileSizeBytes: 'bytes', smallerSideBytes: 'bytes', broadcastBytes: 'bytes',
|
|
166
|
+
speculationWasteMs: 'ms', retryWasteMs: 'ms', taskDurationP50: 'ms',
|
|
167
|
+
stageDurationMinutes: 'minutes',
|
|
168
|
+
'P95/median': 'ratio', 'max/median': 'ratio', pRatio: 'ratio', oiRatio: 'ratio',
|
|
169
|
+
taskStageSkew: 'ratio', hostMeanRatio: 'ratio', execMaxMedianRatio: 'ratio',
|
|
170
|
+
gcPct: 'pct', failureRate: 'pct', stragglerShare: 'pct',
|
|
171
|
+
hostDurationShare: 'pctFraction',
|
|
172
|
+
taskCount: 'count', speculativeTasks: 'count', subtreeOccurrences: 'count',
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// formatBytes floors at KB (its guarded contract, see partition-sizing.test.tsx); the plan-graph
|
|
176
|
+
// chip is the one place a sub-KB magnitude is meaningful (a smallFiles avgFileSizeBytes can be a
|
|
177
|
+
// few hundred bytes), so render that tier here rather than widening the shared formatter.
|
|
178
|
+
function formatChipBytes(bytes ) {
|
|
179
|
+
if (bytes > 0 && bytes < 1000) return `${Math.round(bytes)} B`;
|
|
180
|
+
return formatBytes(bytes);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Compact magnitude for a finding's plan-graph chip (e.g. "4.2 GB", "3.2×", "45%"), taken from the
|
|
184
|
+
* finding's own `value` rendered in its `metric`'s unit. Returns null when `value` is non-numeric
|
|
185
|
+
* (stageFailed/configAudit reuse it for text) or the metric isn't in FINDING_METRIC_UNIT. */
|
|
186
|
+
export function formatFindingMagnitude(finding ) {
|
|
187
|
+
const unit = finding.metric ? FINDING_METRIC_UNIT[finding.metric] : undefined;
|
|
188
|
+
if (!unit || typeof finding.value !== 'number') return null;
|
|
189
|
+
switch (unit) {
|
|
190
|
+
case 'bytes': return formatChipBytes(finding.value);
|
|
191
|
+
case 'minutes': return formatDuration(finding.value * 60000);
|
|
192
|
+
case 'ms': return formatMetricValue('ms', finding.value);
|
|
193
|
+
case 'ratio': return formatMetricValue('ratio', finding.value);
|
|
194
|
+
case 'pct': return formatMetricValue('pct', finding.value);
|
|
195
|
+
case 'pctFraction': return formatMetricValue('pctFraction', finding.value);
|
|
196
|
+
case 'count': return formatMetricValue('count', finding.value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The plan-graph finding chip's detail text: the finding's magnitude and, when a wall-clock
|
|
201
|
+
* recovery is claimed, "~<time>" (the optimistic `high` bound, matching formatImpactEstimateCompact),
|
|
202
|
+
* joined by " · " (e.g. "4.2 GB · ~38.0s"). null when neither part is available. */
|
|
203
|
+
export function formatFindingChipDetail(
|
|
204
|
+
finding ,
|
|
205
|
+
) {
|
|
206
|
+
const magnitude = formatFindingMagnitude(finding);
|
|
207
|
+
const wallClock = finding.impactEstimate?.wallClock;
|
|
208
|
+
const recoverable = wallClock ? `~${formatDuration(wallClock.high)}` : null;
|
|
209
|
+
const parts = [magnitude, recoverable].filter(Boolean);
|
|
210
|
+
return parts.length > 0 ? parts.join(' · ') : null;
|
|
211
|
+
}
|
|
212
|
+
|
|
139
213
|
export function formatDuration(ms ) {
|
|
140
214
|
if (!ms || ms <= 0) return '—';
|
|
141
215
|
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import { STRAGGLER_FLOOR_PCT_WARN, STRAGGLER_FLOOR_PCT_CRIT } from './detectors.js';
|
|
3
3
|
|
|
4
|
-
// Reused from
|
|
5
|
-
//
|
|
6
|
-
// already): applying the same, already-accepted noise floor globally rather
|
|
7
|
-
// than inventing a second, unrelated cutoff. Imported (not re-literaled) so
|
|
8
|
-
// the two can't drift out of sync.
|
|
4
|
+
// Reused from straggler's own thresholds (marked NOT SOURCED: unvalidated there): apply the same
|
|
5
|
+
// accepted noise floor globally rather than inventing a second cutoff. Imported so they can't drift.
|
|
9
6
|
const IMPACT_FLOOR_PCT_WARN = STRAGGLER_FLOOR_PCT_WARN;
|
|
10
7
|
const IMPACT_FLOOR_PCT_CRIT = STRAGGLER_FLOOR_PCT_CRIT;
|
|
11
8
|
|
|
@@ -16,31 +13,28 @@ function appDurationMs(app ) {
|
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
|
-
* Overwrites `.impactBand` for any finding with a quantified wall-clock
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* (`resourceOnly`/`informational` basis) or an unknown/zero app duration are
|
|
25
|
-
* left exactly as the detector set them: there is no comparable figure to
|
|
26
|
-
* grade them by, so the detector's own fixed classification stands as the
|
|
27
|
-
* final value. Mutates in place and returns the same array, matching
|
|
28
|
-
* `estimateImpact`'s own signature style.
|
|
16
|
+
* Overwrites `.impactBand` for any finding with a quantified wall-clock estimate, grading it purely
|
|
17
|
+
* by recoverable time as a fraction of the run's duration: a full replace (either direction), not a
|
|
18
|
+
* promote-only floor. Findings with no wallClock estimate (resourceOnly/informational), an
|
|
19
|
+
* unknown/zero app duration, or an explicit safety-signal exemption (partitionSizing's
|
|
20
|
+
* maxPartitionTooBig rule) are left as the detector set them. Mutates in place and returns the array.
|
|
29
21
|
*
|
|
30
|
-
* Grades
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* instead: severity grading (this function) wants the best-case number a fix
|
|
35
|
-
* could realistically claim, while the sort wants the guaranteed floor so an
|
|
36
|
-
* optimistic-but-contended finding never outranks a smaller but certain one.
|
|
37
|
-
* Same range, two different fields for two different questions — not an
|
|
38
|
-
* inconsistency to reconcile.
|
|
22
|
+
* Grades wallClock.high (matching triage-target/FixTheseFirst, which rank by the same optimistic
|
|
23
|
+
* figure), a deliberate split from view/impact-sort.ts's 'impact' sort, which ranks by .low so an
|
|
24
|
+
* optimistic-but-contended finding never outranks a smaller certain one. Same range, two fields for
|
|
25
|
+
* two questions.
|
|
39
26
|
*/
|
|
40
27
|
export function deriveImpactBand(findings , app ) {
|
|
41
28
|
const durationMs = appDurationMs(app);
|
|
42
29
|
if (durationMs == null) return findings;
|
|
43
30
|
for (const finding of findings) {
|
|
31
|
+
// maxPartitionTooBig is a hardcoded-critical OOM/crash-risk safety signal, not a
|
|
32
|
+
// time-recovery one, yet it carries a real wallClock estimate (unlike the other hardcoded-
|
|
33
|
+
// critical findings, which stay costOnly/informational and are exempted below by having no
|
|
34
|
+
// wallClock at all). Exempt it explicitly so a long-running job never demotes an active
|
|
35
|
+
// crash risk down to 'info' just because fixing it saves little wall-clock time relative to
|
|
36
|
+
// the run.
|
|
37
|
+
if (finding.type === 'partitionSizing' && finding.rule === 'maxPartitionTooBig') continue;
|
|
44
38
|
const recoverableMs = finding.impactEstimate?.wallClock?.high;
|
|
45
39
|
if (recoverableMs == null) continue;
|
|
46
40
|
const pct = recoverableMs / durationMs;
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
import { computeOccupancy, estimateSingleStage, estimateMultiStage, } from './occupancy.js';
|
|
3
3
|
import { detectorCatalog } from './detectors.js';
|
|
4
4
|
|
|
5
|
-
// Assumed shuffle-network throughput, ~1 Gbps. Starting assumption
|
|
6
|
-
// Task 10 real-log spot-check (docs/architecture.md's Impact estimation section).
|
|
5
|
+
// Assumed shuffle-network throughput, ~1 Gbps. Starting assumption, unvalidated.
|
|
7
6
|
const SHUFFLE_THROUGHPUT_BPS = 125_000_000;
|
|
8
7
|
// Assumed disk I/O throughput for spilled data, ~200 MB/s (conservative HDD/SSD blend).
|
|
9
8
|
const SPILL_IO_THROUGHPUT_BPS = 200_000_000;
|
|
@@ -22,11 +21,8 @@ const EXECUTOR_STARTUP_OVERHEAD_MS = 15000;
|
|
|
22
21
|
// Assumed re-read throughput, shared by cachingOpportunity and cacheUtilization.
|
|
23
22
|
const RE_READ_THROUGHPUT_BPS = 125_000_000;
|
|
24
23
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// stops being "normal", so it's also the floor for the waste this estimate reports.
|
|
28
|
-
// Read from the detector's own catalog entry (not a duplicated literal) so the two
|
|
29
|
-
// stay in sync automatically if the detector's threshold ever changes.
|
|
24
|
+
// stageSlowness flags a stage at `infoMin` minutes; that's the floor for the waste this estimate
|
|
25
|
+
// reports. Read from the detector's catalog entry so the two stay in sync automatically.
|
|
30
26
|
const STAGE_SLOWNESS_THRESHOLD_MINUTES = (() => {
|
|
31
27
|
const infoMin = detectorCatalog().find((d) => d.type === 'stageSlowness')?.thresholds?.infoMin;
|
|
32
28
|
if (typeof infoMin !== 'number') {
|
|
@@ -35,9 +31,8 @@ const STAGE_SLOWNESS_THRESHOLD_MINUTES = (() => {
|
|
|
35
31
|
return infoMin;
|
|
36
32
|
})();
|
|
37
33
|
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// 'resourceOnly'. Never a fake {low: 0, high: 0}: wallClock is null in both cases.
|
|
34
|
+
// No quantifiable magnitude -> 'informational'; a rawWaste figure with no stage window ->
|
|
35
|
+
// 'resourceOnly'. Never a fake {low:0, high:0}: wallClock is null in both cases.
|
|
41
36
|
function costOnly(estimateMethod , rawWaste ) {
|
|
42
37
|
return rawWaste
|
|
43
38
|
? { basis: 'resourceOnly', wallClock: null, estimateMethod, rawWaste }
|
|
@@ -67,9 +62,8 @@ function stageMappableWasteOrCostOnly(
|
|
|
67
62
|
if (!stageIds || stageIds.length === 0) {
|
|
68
63
|
return costOnly('modeled', rawWaste);
|
|
69
64
|
}
|
|
70
|
-
// One waste event spread over a span of stages, not N independent wastes:
|
|
71
|
-
//
|
|
72
|
-
// absorb the same amount claimed once per stage.
|
|
65
|
+
// One waste event spread over a span of stages, not N independent wastes: apportion evenly so
|
|
66
|
+
// estimateMultiStage's union cap doesn't absorb the same amount claimed once per stage.
|
|
73
67
|
const perStageWasteMs = wasteMs / stageIds.length;
|
|
74
68
|
const wasteMsByStage = new Map(stageIds.map((id) => [id, perStageWasteMs]));
|
|
75
69
|
const est = estimateMultiStage(stageIds, wasteMsByStage, stages , occupancy);
|
|
@@ -77,13 +71,8 @@ function stageMappableWasteOrCostOnly(
|
|
|
77
71
|
return { basis: est.basis, wallClock: est.wallClock, estimateMethod: 'modeled', rawWaste };
|
|
78
72
|
}
|
|
79
73
|
|
|
80
|
-
/**
|
|
81
|
-
*
|
|
82
|
-
* case here. A type with no case stays uncovered (no impactEstimate attached),
|
|
83
|
-
* every type this design covers must eventually get one, per the Global Constraints'
|
|
84
|
-
* "no silent omissions" rule; docs/architecture.md's Impact estimation table is the
|
|
85
|
-
* authoritative completeness check, not this switch by itself.
|
|
86
|
-
*/
|
|
74
|
+
/** Per-finding-type dispatch. A type with no case stays uncovered (no impactEstimate attached);
|
|
75
|
+
* docs/architecture.md's Impact estimation table is the authoritative completeness check, not this switch. */
|
|
87
76
|
function computeEstimateForFinding(
|
|
88
77
|
finding ,
|
|
89
78
|
stages ,
|
|
@@ -91,8 +80,7 @@ function computeEstimateForFinding(
|
|
|
91
80
|
) {
|
|
92
81
|
switch (finding.type) {
|
|
93
82
|
case 'retryWaste': {
|
|
94
|
-
// The waste figure lives on the Stage, not the Finding: the detector only
|
|
95
|
-
// re-publishes it as `metric`/`value` (src/detectors.ts's retryWaste entry).
|
|
83
|
+
// The waste figure lives on the Stage, not the Finding: the detector only re-publishes it as metric/value.
|
|
96
84
|
if (finding.stageId == null) return null;
|
|
97
85
|
const stage = stages.get(finding.stageId);
|
|
98
86
|
if (!stage) return null;
|
|
@@ -110,9 +98,8 @@ function computeEstimateForFinding(
|
|
|
110
98
|
// The detector reports the gap as `metric: 'startupGapSeconds', value: <seconds>`.
|
|
111
99
|
if (typeof finding.value !== 'number') return null;
|
|
112
100
|
const wasteMs = finding.value * 1000;
|
|
113
|
-
// Time before any task starts can never overlap any stage; a genuine,
|
|
114
|
-
//
|
|
115
|
-
// app-scoped, stageId: null): see Non-goals in the original design spec.
|
|
101
|
+
// Time before any task starts can never overlap any stage; a genuine unclipped point estimate,
|
|
102
|
+
// not tied to any stage's gate (coldStart is app-scoped, stageId: null).
|
|
116
103
|
return { basis: 'serial', wallClock: { low: wasteMs, high: wasteMs }, estimateMethod: 'measured' };
|
|
117
104
|
}
|
|
118
105
|
case 'gc': {
|
|
@@ -129,11 +116,8 @@ function computeEstimateForFinding(
|
|
|
129
116
|
return costOnly('modeled', rawWaste);
|
|
130
117
|
}
|
|
131
118
|
const avgConcurrency = executorRunTime / stageDurationMs;
|
|
132
|
-
// jvmGCTime is a cross-task core-time sum
|
|
133
|
-
//
|
|
134
|
-
// to an approximate wall-clock figure. This rides on measured inputs but is a
|
|
135
|
-
// modeled approximation, not an exact reconstruction: hence 'modeled', and see
|
|
136
|
-
// docs/architecture.md's spot-check note next to this formula.
|
|
119
|
+
// jvmGCTime is a cross-task core-time sum (same shape as executorRunTime); dividing by the
|
|
120
|
+
// stage's average concurrency converts it to an approximate wall-clock figure. Modeled, not exact.
|
|
137
121
|
const wasteMs = jvmGCTime / avgConcurrency;
|
|
138
122
|
return singleStageImpact(wasteMs, finding.stageId, stages, occupancy, 'modeled', rawWaste);
|
|
139
123
|
}
|
|
@@ -149,13 +133,9 @@ function computeEstimateForFinding(
|
|
|
149
133
|
}
|
|
150
134
|
case 'straggler':
|
|
151
135
|
case 'stageShape': {
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
// its own resourceOnly formula here, each returning early (not `break`,
|
|
156
|
-
// which would fall off the end of this function's switch and implicitly
|
|
157
|
-
// return `undefined` instead of `null` since the switch is the function's
|
|
158
|
-
// final statement).
|
|
136
|
+
// Shared case for two finding types. straggler (no `rule` field) falls through to the max-P50
|
|
137
|
+
// computation below; every stageShape rule returns early (not `break`, which would fall off
|
|
138
|
+
// the switch and return undefined instead of null since the switch is the function's last statement).
|
|
159
139
|
if (finding.type === 'stageShape') {
|
|
160
140
|
if (finding.rule === 'lowParallelism') {
|
|
161
141
|
const stage = stages.get(finding.stageId );
|
|
@@ -178,12 +158,9 @@ function computeEstimateForFinding(
|
|
|
178
158
|
if (!stage) return null;
|
|
179
159
|
const totalCores = (finding.totalCores ) ?? 0;
|
|
180
160
|
const taskCount = stage.taskCount ?? 0;
|
|
181
|
-
// Cores idle during the straggler's tail, at achieved concurrency (not full
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
// wall-clock estimate to zero on every firing, so this is resourceOnly, not a
|
|
185
|
-
// wall-clock claim (see the Overlap caveat section in
|
|
186
|
-
// docs-site/contributor-guide/architecture/impact-estimation.md).
|
|
161
|
+
// Cores idle during the straggler's tail, at achieved concurrency (not full cluster
|
|
162
|
+
// capacity, which is lowParallelism's territory): this rule's trigger forces the
|
|
163
|
+
// occupancy-clipped estimate to zero on every firing, so it's resourceOnly, not a wall-clock claim.
|
|
187
164
|
const idleCoreMs =
|
|
188
165
|
Math.max(0, Math.min(totalCores, taskCount) - 1) *
|
|
189
166
|
Math.max(0, (stage.taskDurationMax ?? 0) - (stage.taskDurationP50 ?? 0));
|
|
@@ -198,13 +175,10 @@ function computeEstimateForFinding(
|
|
|
198
175
|
return singleStageImpact(wasteMs, finding.stageId, stages, occupancy, 'measured', { value: wasteMs, unit: 'ms' });
|
|
199
176
|
}
|
|
200
177
|
case 'slowHost': {
|
|
201
|
-
// Three duration-based shapes, each carrying its absolute-ms figure under a
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
// - the multi-dimension branch, only for its taskTime dimension
|
|
206
|
-
// Every other shape (the byte-based multiDim dimensions) has no absolute
|
|
207
|
-
// figure today, so it stays informational.
|
|
178
|
+
// Three duration-based shapes, each carrying its absolute-ms figure under a different field
|
|
179
|
+
// (`value` is always a ratio/share, never ms): the per-host mean branch (discriminated by
|
|
180
|
+
// `metric`), the duration-share branch (`variant`), and the multiDim taskTime dimension. Every
|
|
181
|
+
// byte-based multiDim dimension has no absolute figure today, so it stays informational.
|
|
208
182
|
const absoluteMs =
|
|
209
183
|
finding.metric === 'hostMeanRatio' || finding.variant === 'durationShare'
|
|
210
184
|
? (finding.hostMeanMs )
|
|
@@ -223,13 +197,10 @@ function computeEstimateForFinding(
|
|
|
223
197
|
case 'duplicatePlanSubtree': {
|
|
224
198
|
const stageIds = finding.stageIds ;
|
|
225
199
|
if (!stageIds || stageIds.length === 0) return null;
|
|
226
|
-
// The detector reports
|
|
227
|
-
//
|
|
228
|
-
// redundant: computing the subtree once is real work, so the waste is
|
|
229
|
-
// (occurrences - 1) / occurrences of the contributing stages' time, not all of it.
|
|
200
|
+
// The detector reports subtreeOccurrences >= 2. Only repeats past the first are redundant:
|
|
201
|
+
// computing the subtree once is real work, so waste is (occurrences-1)/occurrences of the stages' time.
|
|
230
202
|
const occurrences = typeof finding.value === 'number' ? finding.value : 0;
|
|
231
|
-
// Defensive only:
|
|
232
|
-
// >= 2 on real data, so this is a malformed-`value` fallback, not a real formula run.
|
|
203
|
+
// Defensive only: minOccurrences guarantees occurrences >= 2 on real data; a malformed-value fallback.
|
|
233
204
|
if (occurrences < 2) return costOnly('none');
|
|
234
205
|
const redundantFraction = (occurrences - 1) / occurrences;
|
|
235
206
|
const wasteMsByStage = new Map ();
|
|
@@ -261,8 +232,7 @@ function computeEstimateForFinding(
|
|
|
261
232
|
if (!stage) return null;
|
|
262
233
|
const diskBytesSpilled = stage.diskBytesSpilled ?? 0;
|
|
263
234
|
const wasteMs = (diskBytesSpilled / SPILL_IO_THROUGHPUT_BPS) * 1000;
|
|
264
|
-
// Surfaces the number the formula
|
|
265
|
-
// is memoryBytesSpilled, but disk spill is what costs I/O time.
|
|
235
|
+
// Surfaces the number the formula uses: the displayed metric is memoryBytesSpilled, but disk spill costs the I/O time.
|
|
266
236
|
return singleStageImpact(wasteMs, finding.stageId, stages, occupancy, 'modeled', { value: diskBytesSpilled, unit: 'bytes' });
|
|
267
237
|
}
|
|
268
238
|
case 'stageSlowness': {
|
|
@@ -288,12 +258,10 @@ function computeEstimateForFinding(
|
|
|
288
258
|
const taskCount = stage.taskCount ?? 0;
|
|
289
259
|
if (targetTaskCount > taskCount && taskCount > 0) {
|
|
290
260
|
const stageDurationMs = Math.max(0, (stage.completedAt ?? 0) - (stage.submittedAt ?? 0));
|
|
291
|
-
// Too few shuffle partitions means each task processes more
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
// duration at target parallelism by scaling down proportionally to the partition
|
|
296
|
-
// shortfall, and claim the difference.
|
|
261
|
+
// Too few shuffle partitions means each task processes more than the ideal bytes,
|
|
262
|
+
// serializing work more partitions would run concurrently: the waste is that serialized
|
|
263
|
+
// work, not the scheduling cost of tasks you'd add (adding tasks incurs overhead, recovers
|
|
264
|
+
// nothing). Model the achievable duration at target parallelism by scaling down proportionally.
|
|
297
265
|
wasteMs = stageDurationMs * (1 - taskCount / targetTaskCount);
|
|
298
266
|
}
|
|
299
267
|
} else {
|
|
@@ -330,8 +298,7 @@ function computeEstimateForFinding(
|
|
|
330
298
|
return costOnly('measured', { value: finding.value, unit: 'mbSeconds' });
|
|
331
299
|
}
|
|
332
300
|
if (finding.variant === 'idleCores') {
|
|
333
|
-
// Idle core-time priced as memory held but unused: the same MB-seconds unit
|
|
334
|
-
// the wasteModel variant reports, so the two are comparable.
|
|
301
|
+
// Idle core-time priced as memory held but unused: the same MB-seconds unit as wasteModel, so comparable.
|
|
335
302
|
const idleRateFraction = finding.idleRateFraction ;
|
|
336
303
|
const allocatedMB = finding.allocatedMB ;
|
|
337
304
|
const peakExecutors = finding.peakExecutors ;
|
|
@@ -342,9 +309,8 @@ function computeEstimateForFinding(
|
|
|
342
309
|
}
|
|
343
310
|
return costOnly('modeled');
|
|
344
311
|
}
|
|
345
|
-
// Only the over-provisioned band is a waste; the near-capacity band is an OOM-risk
|
|
346
|
-
//
|
|
347
|
-
// at all: both stay informational.
|
|
312
|
+
// Only the over-provisioned band is a waste; the near-capacity band is an OOM-risk signal with
|
|
313
|
+
// no magnitude, and the dataUnavailable shape has no inputs: both stay informational.
|
|
348
314
|
if (finding.variant === 'memoryBand' && finding.rule === 'heapOverProvisioned') {
|
|
349
315
|
const allocatedBytes = finding.allocatedBytes ;
|
|
350
316
|
const heap = finding.heap ;
|
|
@@ -398,11 +364,9 @@ function computeEstimateForFinding(
|
|
|
398
364
|
const numPartitions = (finding.numPartitions ) ?? 0;
|
|
399
365
|
const numUncachedPartitions = Math.max(0, numPartitions - numCachedPartitions);
|
|
400
366
|
const cachedBytes = memorySize + diskSize;
|
|
401
|
-
// Extrapolate
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
// more on its own: those bytes are already cached, but on disk rather than memory, so
|
|
405
|
-
// re-reading them still costs I/O the way a genuinely-uncached partition would.
|
|
367
|
+
// Extrapolate never-cached partitions' size from the CACHED partitions' average (uncached/
|
|
368
|
+
// cached, not uncached/total: numCachedPartitions produced cachedBytes). diskSize is added
|
|
369
|
+
// once more: those bytes are cached but on disk, so re-reading them still costs I/O like an uncached partition.
|
|
406
370
|
const uncachedBytes = numCachedPartitions > 0 ? (cachedBytes / numCachedPartitions) * numUncachedPartitions : 0;
|
|
407
371
|
const uncachedOrSpilledBytes = uncachedBytes + diskSize;
|
|
408
372
|
const wasteMs = (uncachedOrSpilledBytes / RE_READ_THROUGHPUT_BPS) * 1000;
|
package/vendor-core/ingest.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
// Worker/CLI-shared message router: maps a worker (or Node-synchronous
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// `getTaskData` request, so it never receives a `taskData` message back.
|
|
6
|
-
// Handler dispatch is optional-chained (`handlers.onXxx?.(...)`), so a
|
|
7
|
-
// caller that adds a new message type here without wiring its handler
|
|
8
|
-
// fails silently (a no-op) rather than throwing.
|
|
1
|
+
// Worker/CLI-shared message router: maps a worker (or Node-synchronous) message to its handler.
|
|
2
|
+
// Exported so both contexts run one switch. `pendingTaskRequests` is optional: the CLI path never
|
|
3
|
+
// sends a `getTaskData` request, so never receives `taskData` back. Handler dispatch is
|
|
4
|
+
// optional-chained, so a caller that adds a message type without wiring its handler no-ops rather than throws.
|
|
9
5
|
|
|
10
6
|
|
|
11
7
|
|
|
@@ -77,11 +73,9 @@ export function createIngestClient()
|
|
|
77
73
|
|
|
78
74
|
let handlers = {};
|
|
79
75
|
|
|
80
|
-
// The `new Worker(new URL(...), ...)` expression must appear inline
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
// through an intermediate variable defeats that detection and leaves the
|
|
84
|
-
// worker's dependencies unbundled, 404ing at runtime in a production build.
|
|
76
|
+
// The `new Worker(new URL(...), ...)` expression must appear inline for Vite's worker plugin to
|
|
77
|
+
// statically detect and bundle it (with its own imports); routing the URL through a variable
|
|
78
|
+
// defeats detection and leaves the worker's deps unbundled, 404ing in a production build.
|
|
85
79
|
function makeWorker() {
|
|
86
80
|
worker = new Worker(new URL('./parser-worker.js', import.meta.url), { type: 'module' });
|
|
87
81
|
worker.onmessage = ({ data }) => routeMessage(data, handlers, pendingTaskRequests);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
// §8 Concurrent-job-group reliability guard
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// overlap, wall-clock-based estimates (scaling simulator, efficiency model)
|
|
5
|
-
// become unreliable. Jobs WITHIN a group overlapping (AQE/multi-stage) is
|
|
6
|
-
// normal and not the signal.
|
|
1
|
+
// §8 Concurrent-job-group reliability guard. Groups jobs by SQL
|
|
2
|
+
// execution id (jobs with none form singleton groups). If two DIFFERENT groups' intervals overlap,
|
|
3
|
+
// wall-clock-based estimates become unreliable. Jobs WITHIN a group overlapping (AQE) is normal.
|
|
7
4
|
export function checkConcurrentJobGroups(jobs ) {
|
|
8
5
|
const list = jobs ? [...jobs.values()] : [];
|
|
9
6
|
// Build one interval per group: [min submission, max completion].
|