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
|
@@ -24,12 +24,9 @@ import { computeRunAggregates } from './run-aggregates.js';
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
// the public AppModel types in types.ts (e.g. Stage), which describe the
|
|
31
|
-
// *posted* message shape after finalizeStage/appMessage reshape things.
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
27
|
+
// Internal parser-state shapes: the real runtime objects the handlers build and mutate, not the
|
|
28
|
+
// public AppModel types in types.ts (which describe the posted message shape after
|
|
29
|
+
// finalizeStage/appMessage reshape things).
|
|
33
30
|
|
|
34
31
|
|
|
35
32
|
|
|
@@ -50,9 +47,8 @@ import { computeRunAggregates } from './run-aggregates.js';
|
|
|
50
47
|
|
|
51
48
|
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
|
|
51
|
+
|
|
56
52
|
|
|
57
53
|
|
|
58
54
|
|
|
@@ -70,12 +66,31 @@ import { computeRunAggregates } from './run-aggregates.js';
|
|
|
70
66
|
|
|
71
67
|
|
|
72
68
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// Cap for both StageRecord.retryTaskSamples (populated here, live during ingest) and
|
|
81
|
+
// stage.failedTaskSamples (populated in stage-quantiles.ts's finalizeStage): a starting judgment
|
|
82
|
+
// call, not empirically validated against a pathological case. Kept as a separate constant in each
|
|
83
|
+
// file rather than a shared import, to avoid a circular value import between the two modules
|
|
84
|
+
// (event-handlers.ts already imports finalizeStage FROM stage-quantiles.ts).
|
|
85
|
+
const MAX_TASK_SAMPLES = 20;
|
|
86
|
+
|
|
87
|
+
// One accumulated task-attempt record, keyed by `<stageAttemptId>:<index>` (or a unique Symbol
|
|
88
|
+
// when the raw event has no Index).
|
|
76
89
|
|
|
77
90
|
|
|
78
91
|
|
|
92
|
+
|
|
93
|
+
|
|
79
94
|
|
|
80
95
|
|
|
81
96
|
|
|
@@ -121,6 +136,7 @@ import { computeRunAggregates } from './run-aggregates.js';
|
|
|
121
136
|
|
|
122
137
|
|
|
123
138
|
|
|
139
|
+
|
|
124
140
|
|
|
125
141
|
|
|
126
142
|
|
|
@@ -136,8 +152,7 @@ import { computeRunAggregates } from './run-aggregates.js';
|
|
|
136
152
|
|
|
137
153
|
|
|
138
154
|
|
|
139
|
-
|
|
140
|
-
|
|
155
|
+
|
|
141
156
|
|
|
142
157
|
|
|
143
158
|
|
|
@@ -160,19 +175,85 @@ import { computeRunAggregates } from './run-aggregates.js';
|
|
|
160
175
|
|
|
161
176
|
|
|
162
177
|
|
|
178
|
+
|
|
179
|
+
// Splits decompressed byte chunks into NDJSON lines. Each chunk is decoded whole in one
|
|
180
|
+
// TextDecoder.decode call, newlines located by scanning raw bytes (indexOf(0x0A)), and each line
|
|
181
|
+
// is a substring of the `pending + text` concatenation.
|
|
182
|
+
//
|
|
183
|
+
// Why (profiled on a 138MB / 3.5GB-decompressed log, decode phase, each line JSON.parsed to force
|
|
184
|
+
// materialization): per-line decode (227k calls) ~57s vs whole-chunk decode (~36k calls) ~7s;
|
|
185
|
+
// scanning the decoded UTF-16 string for newlines costs 130-450s while scanning raw bytes ~7s;
|
|
186
|
+
// the line MUST be a substring of the freshly concatenated string (V8 flattens once per chunk),
|
|
187
|
+
// substrings of raw decode() output cost ~4x more. Combined ~14s, byte-identical output.
|
|
188
|
+
//
|
|
189
|
+
// 0x0A can't appear inside a UTF-8 multibyte sequence, so byte-scanning for it is UTF-8 safe. A
|
|
190
|
+
// newline's byte offset equals its char offset only when the chunk is 1:1 byte<->char, detected by
|
|
191
|
+
// text.length === buffer.length (the all-ASCII fast path); otherwise a byte walk recovers char
|
|
192
|
+
// offsets. `{ stream: true }` reassembles a character split across the chunk boundary.
|
|
193
|
+
const NEWLINE = 0x0a;
|
|
163
194
|
|
|
164
195
|
export function buildChunkDecoder() {
|
|
165
196
|
const decoder = new TextDecoder('utf-8');
|
|
197
|
+
// Decoded partial line after the last newline, carried to the next chunk. A character split
|
|
198
|
+
// across the boundary is reassembled by the streaming decoder, so this is already-decoded.
|
|
166
199
|
let pending = '';
|
|
200
|
+
|
|
167
201
|
return {
|
|
168
202
|
decode(buffer ) {
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
203
|
+
const lines = [];
|
|
204
|
+
const text = decoder.decode(buffer, { stream: true });
|
|
205
|
+
const full = pending === '' ? text : pending + text;
|
|
206
|
+
const base = pending.length;
|
|
207
|
+
const len = buffer.length;
|
|
208
|
+
// Char offset in `full` of the current line's start. Zero-length lines
|
|
209
|
+
// (`cut === start`) are dropped to match a `.filter(l => l.length)`.
|
|
210
|
+
let start = 0;
|
|
211
|
+
|
|
212
|
+
if (text.length === len) {
|
|
213
|
+
// 1:1 byte<->char: a newline's byte offset is its char offset (+ base).
|
|
214
|
+
let nl = buffer.indexOf(NEWLINE, 0);
|
|
215
|
+
while (nl !== -1) {
|
|
216
|
+
const cut = base + nl;
|
|
217
|
+
if (cut > start) lines.push(full.substring(start, cut));
|
|
218
|
+
start = cut + 1;
|
|
219
|
+
nl = buffer.indexOf(NEWLINE, nl + 1);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
// Some byte does not map 1:1. Walk the bytes counting UTF-16 units to
|
|
223
|
+
// turn each newline's byte offset into a char offset in `full`.
|
|
224
|
+
let unit = base;
|
|
225
|
+
let bpos = 0;
|
|
226
|
+
// A char whose lead byte was in the previous chunk arrives here as
|
|
227
|
+
// leading continuation bytes; the streaming decoder emits it as text[0].
|
|
228
|
+
// Count it once (a surrogate pair is two units), then skip its bytes.
|
|
229
|
+
if (len > 0 && (buffer[0] & 0xc0) === 0x80) {
|
|
230
|
+
const c0 = text.charCodeAt(0);
|
|
231
|
+
unit += c0 >= 0xd800 && c0 <= 0xdbff ? 2 : 1;
|
|
232
|
+
while (bpos < len && (buffer[bpos] & 0xc0) === 0x80) bpos++;
|
|
233
|
+
}
|
|
234
|
+
let nl = buffer.indexOf(NEWLINE, bpos);
|
|
235
|
+
while (nl !== -1) {
|
|
236
|
+
for (let i = bpos; i < nl; i++) {
|
|
237
|
+
const b = buffer[i];
|
|
238
|
+
if (b < 0x80) unit++; // ASCII
|
|
239
|
+
else if (b >= 0xf0) unit += 2; // 4-byte lead -> surrogate pair
|
|
240
|
+
else if (b >= 0xc0) unit++; // 2/3-byte lead -> one unit
|
|
241
|
+
// continuation byte (0x80..0xBF) -> zero units
|
|
242
|
+
}
|
|
243
|
+
if (unit > start) lines.push(full.substring(start, unit));
|
|
244
|
+
unit++; // the '\n' itself
|
|
245
|
+
start = unit;
|
|
246
|
+
bpos = nl + 1;
|
|
247
|
+
nl = buffer.indexOf(NEWLINE, bpos);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
pending = start < full.length ? full.substring(start) : '';
|
|
252
|
+
return lines;
|
|
173
253
|
},
|
|
174
254
|
flush() {
|
|
175
|
-
|
|
255
|
+
// Flush any incomplete trailing character the streaming decoder still holds.
|
|
256
|
+
const last = (pending + decoder.decode()).trim();
|
|
176
257
|
pending = '';
|
|
177
258
|
return last ? [last] : [];
|
|
178
259
|
},
|
|
@@ -209,13 +290,9 @@ export function createState() {
|
|
|
209
290
|
};
|
|
210
291
|
}
|
|
211
292
|
|
|
212
|
-
// Normalize the `Spark Properties` payload of SparkListenerEnvironmentUpdate.
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
// (EnvironmentUpdateEventSchema) tolerates raw values that are a bare JSON
|
|
216
|
-
// number or boolean instead of a string, so those are coerced to their
|
|
217
|
-
// string form here, keeping this function's `Record<string, string>`
|
|
218
|
-
// contract intact for every downstream reader of `app.config`.
|
|
293
|
+
// Normalize the `Spark Properties` payload of SparkListenerEnvironmentUpdate. Modern Spark emits
|
|
294
|
+
// an object of key->value; older logs use [key, value] pairs. Both collapse to a string->string
|
|
295
|
+
// map; the schema tolerates bare number/boolean values, coerced to string to keep the contract.
|
|
219
296
|
|
|
220
297
|
export function normalizeSparkProperties(
|
|
221
298
|
props
|
|
@@ -234,10 +311,9 @@ export function normalizeSparkProperties(
|
|
|
234
311
|
return map;
|
|
235
312
|
}
|
|
236
313
|
|
|
237
|
-
// Parse a Spark memory-size string to MiB. Spark's JVM-memory configs
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
// trailing "b", as in "gb", is redundant). A lone trailing "b" ("10b") means bytes.
|
|
314
|
+
// Parse a Spark memory-size string to MiB. Spark's JVM-memory configs use bytesConf(ByteUnit.MiB),
|
|
315
|
+
// so a bare number means MiB. A k/m/g/t suffix sets the unit (trailing "b" redundant); a lone "b"
|
|
316
|
+
// ("10b") means bytes.
|
|
241
317
|
export function parseSparkMemoryMB(value ) {
|
|
242
318
|
if (value == null) return null;
|
|
243
319
|
const m = String(value).trim().toLowerCase().match(/^([\d.]+)\s*([kmgt]?)(b?)$/);
|
|
@@ -253,9 +329,8 @@ export function parseSparkMemoryMB(value ) {
|
|
|
253
329
|
}
|
|
254
330
|
}
|
|
255
331
|
|
|
256
|
-
// Derive an allocated-resource summary
|
|
257
|
-
//
|
|
258
|
-
// Absent keys degrade to null rather than guessed defaults.
|
|
332
|
+
// Derive an allocated-resource summary from the Spark config map. Absent keys degrade to null,
|
|
333
|
+
// not guessed defaults.
|
|
259
334
|
export function extractResources(config ) {
|
|
260
335
|
const cfg = config ?? {};
|
|
261
336
|
const int = (k ) => {
|
|
@@ -287,11 +362,8 @@ export function extractResources(config
|
|
|
287
362
|
};
|
|
288
363
|
}
|
|
289
364
|
|
|
290
|
-
// Convert the live rddInfo Map (
|
|
291
|
-
//
|
|
292
|
-
// with `stageIds` as a sorted array, used at every 'app' message post site
|
|
293
|
-
// so the main thread never receives a live Set it can't structured-clone
|
|
294
|
-
// meaningfully or that could keep mutating after being posted.
|
|
365
|
+
// Convert the live rddInfo Map (with a mutable stageIds Set) into a postable copy with stageIds
|
|
366
|
+
// as a sorted array, so the main thread never receives a live Set that could keep mutating after post.
|
|
295
367
|
function snapshotRddInfo(rddInfo ) {
|
|
296
368
|
const out = new Map ();
|
|
297
369
|
for (const [id, r] of rddInfo) {
|
|
@@ -305,16 +377,10 @@ function snapshotEvidenceInputs(state ) {
|
|
|
305
377
|
}
|
|
306
378
|
|
|
307
379
|
/**
|
|
308
|
-
* Snapshots the
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
* `emitParseCompletion` is authoritative; mid-parse snapshots are partial and
|
|
313
|
-
* must not be used for final evidence-availability conclusions.
|
|
314
|
-
*
|
|
315
|
-
* Only ever called once `state.app` has been assigned (from startApplication,
|
|
316
|
-
* or from updateEnvironment/the ApplicationEnd case which both guard on
|
|
317
|
-
* `state.app` truthiness before calling this), hence the non-null assertion.
|
|
380
|
+
* Snapshots the evidence counters and posts an `app` message. Called from ApplicationStart,
|
|
381
|
+
* EnvironmentUpdate, and ApplicationEnd, so every streaming `app` message carries a snapshot at
|
|
382
|
+
* that moment. Only emitParseCompletion's terminal snapshot is authoritative; mid-parse ones are
|
|
383
|
+
* partial. Only called once state.app is assigned (all callers guard on it), hence the non-null assertion.
|
|
318
384
|
*/
|
|
319
385
|
function appMessage(state ) {
|
|
320
386
|
const evidenceInputs = snapshotEvidenceInputs(state);
|
|
@@ -325,14 +391,19 @@ function appMessage(state )
|
|
|
325
391
|
};
|
|
326
392
|
}
|
|
327
393
|
|
|
394
|
+
function taskRecordToSample(t ) {
|
|
395
|
+
return {
|
|
396
|
+
taskId: t.taskId, attemptNumber: t.attemptNumber, host: t.host, executorId: t.executorId,
|
|
397
|
+
reason: t.reason, peakExecMem: t.peakExecMem, memSpilled: t.memSpilled, shuffleWrite: t.shuffleWrite,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
328
401
|
export function accumulateTask(event , state ) {
|
|
329
402
|
const stageId = event['Stage ID'];
|
|
330
403
|
const stage = state.stages.get(stageId);
|
|
331
404
|
if (!stage) return null;
|
|
332
|
-
// Late TaskEnd for a stage whose StageCompleted already
|
|
333
|
-
//
|
|
334
|
-
// SparkListenerStageExecutorMetrics above; the task's stats are already
|
|
335
|
-
// baked into the finalized stage and don't need to be re-added.
|
|
405
|
+
// Late TaskEnd for a stage whose StageCompleted already freed taskAttempts (finalizeStage): its
|
|
406
|
+
// stats are already baked into the finalized stage, don't re-add.
|
|
336
407
|
if (stage.taskAttempts === null) return null;
|
|
337
408
|
|
|
338
409
|
state.evidenceInputs.taskRecords++;
|
|
@@ -343,10 +414,8 @@ export function accumulateTask(event , state
|
|
|
343
414
|
state.taskAccumStages.get(acc.ID) .add(stageId);
|
|
344
415
|
}
|
|
345
416
|
|
|
346
|
-
// 'Task Info'
|
|
347
|
-
//
|
|
348
|
-
// lets the {} fallback (an absent 'Task Info') type-check while every
|
|
349
|
-
// read below still defaults via `??`/`||`.
|
|
417
|
+
// 'Task Info' and its Failed/Killed/Speculative fields are optional in the schema; Partial<>
|
|
418
|
+
// lets the {} fallback type-check while reads below default via ??/||.
|
|
350
419
|
|
|
351
420
|
const info = event['Task Info'] ?? {};
|
|
352
421
|
const m = event['Task Metrics'] ?? {};
|
|
@@ -360,6 +429,8 @@ export function accumulateTask(event , state
|
|
|
360
429
|
|
|
361
430
|
const record = {
|
|
362
431
|
duration, failed,
|
|
432
|
+
taskId: info['Task ID'] ?? null,
|
|
433
|
+
attemptNumber: info['Attempt Number'] ?? 0,
|
|
363
434
|
launchTime: info['Launch Time'] ?? 0,
|
|
364
435
|
finishTime: info['Finish Time'] ?? 0,
|
|
365
436
|
reason: event['Task End Reason']?.['Reason'] ?? null,
|
|
@@ -380,9 +451,8 @@ export function accumulateTask(event , state
|
|
|
380
451
|
outputBytes: out['Bytes Written'] ?? 0,
|
|
381
452
|
};
|
|
382
453
|
|
|
383
|
-
// Dedupe only when Index is present (always true for real
|
|
384
|
-
//
|
|
385
|
-
// fixture behavior for tests that omit Index.
|
|
454
|
+
// Dedupe only when Index is present (always true for real logs). Without it every event is a
|
|
455
|
+
// distinct task, preserving behavior for fixtures that omit Index.
|
|
386
456
|
const index = info['Index'];
|
|
387
457
|
const key = index != null ? `${event['Stage Attempt ID'] ?? 0}:${index}` : Symbol('no-index');
|
|
388
458
|
const existing = stage.taskAttempts.get(key);
|
|
@@ -390,21 +460,18 @@ export function accumulateTask(event , state
|
|
|
390
460
|
if (!existing) {
|
|
391
461
|
stage.taskAttempts.set(key, record);
|
|
392
462
|
} else if (existing.failed && !record.failed) {
|
|
393
|
-
// A retry succeeded where the earlier attempt failed: the earlier
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
// Cause classification: Spark only marks the speculative COPY's own
|
|
397
|
-
// TaskInfo.Speculative as true, never the original it raced against,
|
|
398
|
-
// so checking either side of the comparison (not just the discarded
|
|
399
|
-
// record) is required to catch both "original loses to its speculative
|
|
400
|
-
// twin" and "speculative twin loses to the original" symmetrically,
|
|
401
|
-
// regardless of arrival order.
|
|
463
|
+
// A retry succeeded where the earlier attempt failed: the earlier attempt's time was wasted.
|
|
464
|
+
// Spark marks only the speculative COPY's Speculative flag, never the original it raced, so
|
|
465
|
+
// check both sides to catch "original loses to twin" and "twin loses to original" either way.
|
|
402
466
|
if (existing.speculative || record.speculative) {
|
|
403
467
|
stage.speculationWasteMs += existing.duration;
|
|
404
468
|
stage.speculationWastedAttempts++;
|
|
405
469
|
} else {
|
|
406
470
|
stage.retryWasteMs += existing.duration;
|
|
407
471
|
stage.wastedAttempts++;
|
|
472
|
+
if (stage.retryTaskSamples.length < MAX_TASK_SAMPLES) {
|
|
473
|
+
stage.retryTaskSamples.push(taskRecordToSample(existing));
|
|
474
|
+
}
|
|
408
475
|
}
|
|
409
476
|
stage.taskAttempts.set(key, record);
|
|
410
477
|
} else {
|
|
@@ -416,6 +483,9 @@ export function accumulateTask(event , state
|
|
|
416
483
|
} else {
|
|
417
484
|
stage.retryWasteMs += record.duration;
|
|
418
485
|
stage.wastedAttempts++;
|
|
486
|
+
if (stage.retryTaskSamples.length < MAX_TASK_SAMPLES) {
|
|
487
|
+
stage.retryTaskSamples.push(taskRecordToSample(record));
|
|
488
|
+
}
|
|
419
489
|
}
|
|
420
490
|
}
|
|
421
491
|
|
|
@@ -427,11 +497,24 @@ export function resolvePlanTree(
|
|
|
427
497
|
accumMap ,
|
|
428
498
|
taskAccumStages ,
|
|
429
499
|
executionStageIds ,
|
|
500
|
+
executionId ,
|
|
430
501
|
) {
|
|
431
502
|
const seen = new WeakSet ();
|
|
432
|
-
const nodeMap = new WeakMap ();
|
|
433
|
-
|
|
434
|
-
|
|
503
|
+
const nodeMap = new WeakMap ();
|
|
504
|
+
let nextId = 0;
|
|
505
|
+
// Prefixed with the owning SQL execution so PlanNode.id is globally unique,
|
|
506
|
+
// not just unique within this one call: two executions' trees can each
|
|
507
|
+
// produce a node named "n1", and a consumer keying off id alone (e.g. a
|
|
508
|
+
// findings-by-nodeId index) must be able to tell them apart without also
|
|
509
|
+
// threading execution scope through separately. No executionId (e.g. the
|
|
510
|
+
// legacy unit tests below) falls back to the old unprefixed "n0", "n1", ...
|
|
511
|
+
// shape.
|
|
512
|
+
const idPrefix = executionId != null ? `e${executionId}:` : '';
|
|
513
|
+
|
|
514
|
+
function computeMetricsAndStageIds(info )
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
{
|
|
435
518
|
const metrics = (info.metrics ?? []).reduce ((acc, m) => {
|
|
436
519
|
if (m.accumulatorId !== undefined && accumMap.has(m.accumulatorId)) {
|
|
437
520
|
acc.push({ name: m.name, value: accumMap.get(m.accumulatorId) , metricType: m.metricType });
|
|
@@ -439,27 +522,63 @@ export function resolvePlanTree(
|
|
|
439
522
|
return acc;
|
|
440
523
|
}, []);
|
|
441
524
|
|
|
442
|
-
// Union the stages where this node's accumulator IDs were actually
|
|
443
|
-
// observed running (Task 2's data), then clip to this SQL execution's own
|
|
444
|
-
// stage universe (Task 3's data): an accumulator ID can occasionally
|
|
445
|
-
// belong to a different execution's stages (e.g. a reused subquery),
|
|
446
|
-
// and without the clip that would misattribute another execution's work.
|
|
447
525
|
const stageIdSet = new Set ();
|
|
448
526
|
for (const m of info.metrics ?? []) {
|
|
449
527
|
if (m.accumulatorId === undefined) continue;
|
|
450
528
|
const stages = taskAccumStages.get(m.accumulatorId);
|
|
451
529
|
if (!stages) continue;
|
|
530
|
+
// Clip to executionStageIds (this execution's own stage set) rather than trusting
|
|
531
|
+
// taskAccumStages wholesale: an accumulator id can be shared with another execution's
|
|
532
|
+
// stages (e.g. a reused subquery re-executes the same accumulator-tagged operator under
|
|
533
|
+
// a different SQL execution id), and without this clip a plan node here would pick up
|
|
534
|
+
// stage ids that actually belong to that other execution's work.
|
|
452
535
|
for (const sid of stages) {
|
|
453
536
|
if (executionStageIds && executionStageIds.has(sid)) stageIdSet.add(sid);
|
|
454
537
|
}
|
|
455
538
|
}
|
|
539
|
+
return stageIdSet.size > 0 ? { metrics, stageIds: [...stageIdSet].sort((a, b) => a - b) } : { metrics };
|
|
540
|
+
}
|
|
456
541
|
|
|
457
|
-
|
|
458
|
-
|
|
542
|
+
function makeNode(info , children ) {
|
|
543
|
+
const { metrics, stageIds } = computeMetricsAndStageIds(info);
|
|
544
|
+
const node = { id: `${idPrefix}n${nextId++}`, name: info.nodeName, detail: info.simpleString ?? '', metrics, children };
|
|
545
|
+
if (stageIds) node.stageIds = stageIds;
|
|
459
546
|
return node;
|
|
460
547
|
}
|
|
461
548
|
|
|
462
|
-
//
|
|
549
|
+
// Only a node whose raw name is exactly Exchange/BroadcastExchange gets
|
|
550
|
+
// split; ReusedExchange and any other exchange-family variant stay a
|
|
551
|
+
// single node (confirmed against real event logs: ReusedExchange carries
|
|
552
|
+
// no metrics of its own, it references an already-materialized Exchange
|
|
553
|
+
// elsewhere). Runs on the raw name because no PlanNode/exchangeRole
|
|
554
|
+
// exists yet at this point in construction.
|
|
555
|
+
function makeExchangeSplit(info , children ) {
|
|
556
|
+
const { metrics, stageIds } = computeMetricsAndStageIds(info);
|
|
557
|
+
// write = producer side: keeps the real metrics (e.g. "data size", the
|
|
558
|
+
// shuffle write metrics) and the original children.
|
|
559
|
+
const write = {
|
|
560
|
+
id: `${idPrefix}n${nextId++}`,
|
|
561
|
+
name: info.nodeName,
|
|
562
|
+
detail: '',
|
|
563
|
+
metrics,
|
|
564
|
+
children,
|
|
565
|
+
exchangeRole: 'write',
|
|
566
|
+
};
|
|
567
|
+
if (stageIds) write.stageIds = stageIds;
|
|
568
|
+
// read = consumer side: keeps the original detail (plan-summary.ts's
|
|
569
|
+
// partitioning-key regex needs it), no metrics of its own.
|
|
570
|
+
const read = {
|
|
571
|
+
id: `${idPrefix}n${nextId++}`,
|
|
572
|
+
name: info.nodeName,
|
|
573
|
+
detail: info.simpleString ?? '',
|
|
574
|
+
metrics: [],
|
|
575
|
+
children: [write],
|
|
576
|
+
exchangeRole: 'read',
|
|
577
|
+
};
|
|
578
|
+
if (stageIds) read.stageIds = stageIds;
|
|
579
|
+
return read;
|
|
580
|
+
}
|
|
581
|
+
|
|
463
582
|
const stack = [rootInfo];
|
|
464
583
|
const order = [];
|
|
465
584
|
|
|
@@ -473,24 +592,24 @@ export function resolvePlanTree(
|
|
|
473
592
|
}
|
|
474
593
|
}
|
|
475
594
|
|
|
476
|
-
// Build nodes bottom-up (reverse discovery = children before parents)
|
|
477
595
|
for (let i = order.length - 1; i >= 0; i--) {
|
|
478
596
|
const info = order[i];
|
|
479
|
-
const
|
|
480
|
-
nodeMap.set(info, node);
|
|
597
|
+
const children = [];
|
|
481
598
|
for (const child of (info.children ?? [])) {
|
|
482
599
|
const childNode = nodeMap.get(child);
|
|
483
|
-
if (childNode)
|
|
600
|
+
if (childNode) children.push(childNode);
|
|
484
601
|
}
|
|
602
|
+
const node = info.nodeName === 'Exchange' || info.nodeName === 'BroadcastExchange'
|
|
603
|
+
? makeExchangeSplit(info, children)
|
|
604
|
+
: makeNode(info, children);
|
|
605
|
+
nodeMap.set(info, node);
|
|
485
606
|
}
|
|
486
607
|
|
|
487
608
|
return nodeMap.get(rootInfo) ;
|
|
488
609
|
}
|
|
489
610
|
|
|
490
|
-
// Maps SparkListenerStageExecutorMetrics' "Executor Metrics" field names
|
|
491
|
-
//
|
|
492
|
-
// Only listed fields are captured: anything else is ignored (forward-compat
|
|
493
|
-
// with future Spark ExecutorMetricType additions).
|
|
611
|
+
// Maps SparkListenerStageExecutorMetrics' "Executor Metrics" field names to our camelCase names.
|
|
612
|
+
// Only listed fields are captured (forward-compat with future Spark ExecutorMetricType additions).
|
|
494
613
|
const EXECUTOR_METRIC_FIELD_MAP = {
|
|
495
614
|
JVMHeapMemory: 'jvmHeapMemory', JVMOffHeapMemory: 'jvmOffHeapMemory',
|
|
496
615
|
OnHeapExecutionMemory: 'onHeapExecutionMemory', OffHeapExecutionMemory: 'offHeapExecutionMemory',
|
|
@@ -524,9 +643,8 @@ export function updateEnvironment(event
|
|
|
524
643
|
state.evidenceInputs.environmentUpdates++;
|
|
525
644
|
const config = normalizeSparkProperties(event['Spark Properties']);
|
|
526
645
|
const resources = extractResources(config);
|
|
527
|
-
// EnvironmentUpdate normally precedes ApplicationStart: stash the config
|
|
528
|
-
//
|
|
529
|
-
// update), apply it live and re-post the app so the main thread refreshes.
|
|
646
|
+
// EnvironmentUpdate normally precedes ApplicationStart: stash the config so ApplicationStart can
|
|
647
|
+
// attach it. If it arrives after (a mid-run update), apply live and re-post the app.
|
|
530
648
|
if (state.app) {
|
|
531
649
|
state.app.config = config;
|
|
532
650
|
state.app.resources = resources;
|
|
@@ -597,6 +715,7 @@ export function submitStage(event , st
|
|
|
597
715
|
failureReasons: new Map(),
|
|
598
716
|
stageFailureReason: null,
|
|
599
717
|
taskAttempts: new Map(),
|
|
718
|
+
retryTaskSamples: [],
|
|
600
719
|
retryWasteMs: 0,
|
|
601
720
|
wastedAttempts: 0,
|
|
602
721
|
speculationWasteMs: 0,
|
|
@@ -631,11 +750,9 @@ export function mergeStageRddInfo(
|
|
|
631
750
|
replication: sl['Replication'] ?? 1,
|
|
632
751
|
},
|
|
633
752
|
numPartitions: rdd['Number of Partitions'] ?? 0,
|
|
634
|
-
// Merge forward, don't overwrite: an RDD
|
|
635
|
-
//
|
|
636
|
-
//
|
|
637
|
-
// resubmission arrives before the cache numbers are re-observed,
|
|
638
|
-
// keep the last real value instead of regressing to 0/em-dash.
|
|
753
|
+
// Merge forward, don't overwrite: an RDD cached for the first time in THIS stage legitimately
|
|
754
|
+
// reports 0 (snapshot reflects BlockManager state at submission). Keep the last real value on
|
|
755
|
+
// a resubmission instead of regressing to 0.
|
|
639
756
|
numCachedPartitions: rdd['Number of Cached Partitions'] || prev?.numCachedPartitions || 0,
|
|
640
757
|
memorySize: rdd['Memory Size'] || prev?.memorySize || 0,
|
|
641
758
|
diskSize: rdd['Disk Size'] || prev?.diskSize || 0,
|
|
@@ -644,13 +761,9 @@ export function mergeStageRddInfo(
|
|
|
644
761
|
}
|
|
645
762
|
}
|
|
646
763
|
|
|
647
|
-
// Spark
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
// time they arrive. We keep accumulating into the worker-side stage here,
|
|
651
|
-
// then re-post all populated maps once via `stageExecutorMetrics` just
|
|
652
|
-
// before `done` (see collectStageExecutorMetrics) so the main-thread
|
|
653
|
-
// stages are patched before analyze() runs.
|
|
764
|
+
// Spark logs these AFTER SparkListenerStageCompleted, so finalizeStage has already posted the
|
|
765
|
+
// stage with an empty executorMetrics Map. Keep accumulating worker-side, then re-post all maps
|
|
766
|
+
// once via `stageExecutorMetrics` just before `done` so main-thread stages are patched before analyze().
|
|
654
767
|
export function recordStageExecutorMetrics(event , state ) {
|
|
655
768
|
const stage = state.stages.get(event['Stage ID']);
|
|
656
769
|
if (!stage) return null;
|
|
@@ -697,7 +810,9 @@ export function endSqlExecution(
|
|
|
697
810
|
}
|
|
698
811
|
|
|
699
812
|
const accumMap = state.accumState.get(event.executionId) ?? new Map ();
|
|
700
|
-
const planTree = resolvePlanTree(
|
|
813
|
+
const planTree = resolvePlanTree(
|
|
814
|
+
planInfo, accumMap, state.taskAccumStages, state.sqlExecStages.get(event.executionId), event.executionId,
|
|
815
|
+
);
|
|
701
816
|
state.accumState.delete(event.executionId);
|
|
702
817
|
|
|
703
818
|
state.evidenceInputs.resolvedSqlPlans++;
|
|
@@ -723,13 +838,9 @@ export function applyAdaptiveExecutionUpdate(
|
|
|
723
838
|
if (event.sparkPlanInfo != null) exec.sparkPlanInfo = event.sparkPlanInfo;
|
|
724
839
|
if (event.physicalPlanDescription != null) exec.physicalPlanDescription = event.physicalPlanDescription;
|
|
725
840
|
exec.hadAdaptiveUpdate = true;
|
|
726
|
-
// Re-emit a 'sql' message
|
|
727
|
-
//
|
|
728
|
-
//
|
|
729
|
-
// Node-path object aliasing (no postMessage clone in that path), never in
|
|
730
|
-
// the shipping worker. Shallow copy so the posted object isn't the same
|
|
731
|
-
// mutable reference the worker keeps mutating (mirrors endSqlExecution's
|
|
732
|
-
// `{ ...exec }` pattern above).
|
|
841
|
+
// Re-emit a 'sql' message so the browser's structured-cloned appModel.sql copy sees the flip:
|
|
842
|
+
// otherwise hadAdaptiveUpdate only reads true via collectRun's Node-path object aliasing, never
|
|
843
|
+
// in the shipping worker. Shallow copy so the posted object isn't the mutable reference the worker keeps mutating.
|
|
733
844
|
return { type: 'sql', data: { ...exec } };
|
|
734
845
|
}
|
|
735
846
|
|
|
@@ -793,12 +904,8 @@ export function processEvent(event , state ) {
|
|
|
793
904
|
if (!stage) return null;
|
|
794
905
|
stage.completedAt = info['Completion Time'] ?? 0;
|
|
795
906
|
stage.stageFailureReason = info['Failure Reason'] ?? null;
|
|
796
|
-
// finalizeStage
|
|
797
|
-
//
|
|
798
|
-
// comment); StageRecord's real shape (more precise than that loose
|
|
799
|
-
// type, e.g. taskAttempts values carry string/boolean fields too, not
|
|
800
|
-
// just numbers) is bridged across that boundary with an explicit cast
|
|
801
|
-
// rather than re-litigating finalizeStage's own established signature.
|
|
907
|
+
// finalizeStage keeps its `stage` parameter typed as a loose Record (see that module); bridge
|
|
908
|
+
// StageRecord's more precise shape across that boundary with an explicit cast.
|
|
802
909
|
return finalizeStage(
|
|
803
910
|
id,
|
|
804
911
|
stage ,
|
|
@@ -835,8 +942,7 @@ export function processEvent(event , state ) {
|
|
|
835
942
|
}
|
|
836
943
|
}
|
|
837
944
|
|
|
838
|
-
// Derived
|
|
839
|
-
// can never drift from the discriminated union's real literal list.
|
|
945
|
+
// Derived from SparkEventSchema itself, so this set can never drift from the union's literal list.
|
|
840
946
|
const KNOWN_EVENT_TYPES = new Set(
|
|
841
947
|
SparkEventSchema.options.map((option) => option.shape.Event.value)
|
|
842
948
|
);
|
|
@@ -849,13 +955,9 @@ export function dispatchLine(line , state , emit
|
|
|
849
955
|
state.skippedLines++;
|
|
850
956
|
return;
|
|
851
957
|
}
|
|
852
|
-
// A real Spark event log carries many event types this tool
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
// value outside our 15 modeled literals is silently ignored exactly like
|
|
856
|
-
// before, never counted toward skippedLines. Only a value that IS one of
|
|
857
|
-
// the 15 known types but fails ITS schema is a genuine, valuable signal
|
|
858
|
-
// worth surfacing as a skipped line.
|
|
958
|
+
// A real Spark event log carries many event types this tool never modeled (BlockManagerAdded,
|
|
959
|
+
// TaskStart, ...): an `Event` outside our modeled literals is silently ignored, never counted as
|
|
960
|
+
// a skipped line. Only a KNOWN type that fails ITS schema is a genuine signal worth surfacing.
|
|
859
961
|
const eventType = (parsed )?.Event;
|
|
860
962
|
if (typeof eventType !== 'string' || !KNOWN_EVENT_TYPES.has(eventType)) {
|
|
861
963
|
return;
|
|
@@ -869,11 +971,9 @@ export function dispatchLine(line , state , emit
|
|
|
869
971
|
try {
|
|
870
972
|
msg = processEvent(result.data, state);
|
|
871
973
|
} catch (err) {
|
|
872
|
-
// processEvent's switch is exhaustive over the schema-validated union
|
|
873
|
-
//
|
|
874
|
-
//
|
|
875
|
-
// in a handler, not malformed input; log it distinctly so it doesn't
|
|
876
|
-
// masquerade as an ordinary skipped-line (malformed/invalid data) count.
|
|
974
|
+
// processEvent's switch is exhaustive over the schema-validated union (assertNever default), so
|
|
975
|
+
// this is unreachable for any event that got this far. If it fires it's a handler bug, not
|
|
976
|
+
// malformed input; log it distinctly so it doesn't masquerade as an ordinary skipped-line count.
|
|
877
977
|
console.error('processEvent threw for an already-validated event:', err);
|
|
878
978
|
state.skippedLines++;
|
|
879
979
|
return;
|
|
@@ -881,10 +981,8 @@ export function dispatchLine(line , state , emit
|
|
|
881
981
|
if (msg) emit(msg);
|
|
882
982
|
}
|
|
883
983
|
|
|
884
|
-
// Gather every stage's post-completion-populated executorMetrics for a single
|
|
885
|
-
//
|
|
886
|
-
// for why the per-stage message posted at completion is empty). Returns a
|
|
887
|
-
// Map<stageId, Map<execId, metrics>>; empty when the log had no
|
|
984
|
+
// Gather every stage's post-completion-populated executorMetrics for a single re-post just before
|
|
985
|
+
// `done` (the per-stage message posted at completion is empty). Empty when the log had no
|
|
888
986
|
// spark.eventLog.logStageExecutorMetrics data.
|
|
889
987
|
export function collectStageExecutorMetrics(state ) {
|
|
890
988
|
const out = new Map ();
|