sparkforensics-cli 0.1.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/bin/sparkforensics-analyze.mjs +113 -48
- package/export-template/docs/404.html +25 -0
- package/export-template/docs/assets/app.DQTZyGL1.js +1 -0
- package/export-template/docs/assets/aqe-loop.IwQSATHw.svg +1 -0
- package/export-template/docs/assets/aqe-loop.dark.DGbaxqJE.svg +1 -0
- package/export-template/docs/assets/broadcast-vs-shuffle.Db4WY1XK.svg +1 -0
- package/export-template/docs/assets/broadcast-vs-shuffle.dark.C7Bxs0mG.svg +1 -0
- package/export-template/docs/assets/cache-lifecycle.dark.B-hS7AgU.svg +1 -0
- package/export-template/docs/assets/cache-lifecycle.rEOVYQNU.svg +1 -0
- package/export-template/docs/assets/chunks/@localSearchIndexroot.DppnXnDE.js +1 -0
- package/export-template/docs/assets/chunks/VPLocalSearchBox.BkBIPFs6.js +9 -0
- package/export-template/docs/assets/chunks/duplicate-plan-subtree.dark.Cdp70QhV.js +1 -0
- package/export-template/docs/assets/chunks/framework.DSg0KOwT.js +20 -0
- package/export-template/docs/assets/chunks/retry-escalation-ladder.dark.DHipdJgZ.js +1 -0
- package/export-template/docs/assets/chunks/theme.DP0u1AUq.js +2 -0
- package/export-template/docs/assets/cold-start-timeline.DxC_Sc7w.svg +1 -0
- package/export-template/docs/assets/cold-start-timeline.dark.CZ17YcAG.svg +1 -0
- package/export-template/docs/assets/columnar-layout.PghGeOEA.svg +1 -0
- package/export-template/docs/assets/columnar-layout.dark.BVNlz0ff.svg +1 -0
- package/export-template/docs/assets/container-memory.DIO0AnIm.svg +1 -0
- package/export-template/docs/assets/container-memory.dark.CP-5zuCl.svg +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_board-widgets.md.CWpj01WU.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_board-widgets.md.CWpj01WU.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.CgzUsQ6W.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_detector-contract.md.CgzUsQ6W.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_drill-down.md.BtPdlM7r.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_drill-down.md.BtPdlM7r.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_impact-estimation.md.CooslVJt.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_impact-estimation.md.CooslVJt.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_overview.md.CehiRmGn.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_overview.md.CehiRmGn.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_state-and-history.md.C-xxn0q7.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_state-and-history.md.C-xxn0q7.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.R27gQrgY.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_widget-rendering.md.R27gQrgY.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.IbnfNrV3.js +6 -0
- package/export-template/docs/assets/contributor-guide_architecture_worker-protocol.md.IbnfNrV3.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_contributing.md.CvRsdr6J.js +1 -0
- package/export-template/docs/assets/contributor-guide_contributing.md.CvRsdr6J.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_development-setup.md.DvAN_9mK.js +12 -0
- package/export-template/docs/assets/contributor-guide_development-setup.md.DvAN_9mK.lean.js +1 -0
- package/export-template/docs/assets/contributor-guide_testing.md.6rIKqSyY.js +1 -0
- package/export-template/docs/assets/contributor-guide_testing.md.6rIKqSyY.lean.js +1 -0
- package/export-template/docs/assets/dag-stages.DSz_S937.svg +1 -0
- package/export-template/docs/assets/dag-stages.dark.F72UzxH4.svg +1 -0
- package/export-template/docs/assets/driver-executor.D5pQ7YN1.svg +1 -0
- package/export-template/docs/assets/driver-executor.dark.BmX9cPvh.svg +1 -0
- package/export-template/docs/assets/duplicate-plan-subtree.B4cvN6fj.svg +1 -0
- package/export-template/docs/assets/duplicate-plan-subtree.dark.Dw8wS0Ag.svg +1 -0
- package/export-template/docs/assets/index.md.CHJVslga.js +1 -0
- package/export-template/docs/assets/index.md.CHJVslga.lean.js +1 -0
- package/export-template/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/export-template/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/export-template/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/export-template/docs/assets/join-strategy.C_FvrCEo.svg +1 -0
- package/export-template/docs/assets/join-strategy.dark.ChMLnNII.svg +1 -0
- package/export-template/docs/assets/memory-borrowing.BqQRJg0u.svg +1 -0
- package/export-template/docs/assets/memory-borrowing.dark.Yhh20O9C.svg +1 -0
- package/export-template/docs/assets/memory-regions.XHvO7jHG.svg +1 -0
- package/export-template/docs/assets/memory-regions.dark.D4TP9_08.svg +1 -0
- package/export-template/docs/assets/repartition-vs-coalesce.BovLRrpj.svg +1 -0
- package/export-template/docs/assets/repartition-vs-coalesce.dark.BhAczKZQ.svg +1 -0
- package/export-template/docs/assets/retry-escalation-ladder.DyTKJJmZ.svg +1 -0
- package/export-template/docs/assets/retry-escalation-ladder.dark.BdsabtU3.svg +1 -0
- package/export-template/docs/assets/shuffle-map-reduce.KuOEZVmg.svg +1 -0
- package/export-template/docs/assets/shuffle-map-reduce.dark.BgQZnFSb.svg +1 -0
- package/export-template/docs/assets/spill-classification.BU2euYDO.svg +1 -0
- package/export-template/docs/assets/spill-classification.dark.D7i1M40d.svg +1 -0
- package/export-template/docs/assets/style.DSixAiZE.css +1 -0
- package/export-template/docs/assets/tuning-reference_anti-patterns.md.Df1YMIHu.js +1 -0
- package/export-template/docs/assets/tuning-reference_anti-patterns.md.Df1YMIHu.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_aqe.md.BIsCtLzm.js +1 -0
- package/export-template/docs/assets/tuning-reference_aqe.md.BIsCtLzm.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-broadcast-sizing.md.CEstB3Ia.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-broadcast-sizing.md.CEstB3Ia.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-cold-start.md.CEuy-72y.js +7 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-cold-start.md.CEuy-72y.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-duplicate-plan-subtree.md.CIohQDfn.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-duplicate-plan-subtree.md.CIohQDfn.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-failures.md.4z5BXGJ2.js +6 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-failures.md.4z5BXGJ2.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-gc.md.DSxzZRK7.js +6 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-gc.md.DSxzZRK7.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-job-failure-rate.md.BaJl__1W.js +8 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-job-failure-rate.md.BaJl__1W.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-memory-utilization.md.DbP-SJZc.js +7 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-memory-utilization.md.DbP-SJZc.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-retry-waste.md.D5JMjVOt.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-retry-waste.md.D5JMjVOt.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-shuffle.md.CM-nTmIH.js +12 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-shuffle.md.CM-nTmIH.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-skew.md.BdUwiDhn.js +14 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-skew.md.BdUwiDhn.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-slow-host.md.BlIo6UDW.js +7 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-slow-host.md.BlIo6UDW.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-small-files.md.B8kloyx8.js +5 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-small-files.md.B8kloyx8.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-spill.md.PNH7mITt.js +6 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-spill.md.PNH7mITt.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-straggler.md.DY36fHN5.js +7 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-straggler.md.DY36fHN5.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-tiny-tasks.md.QTV7O8kU.js +8 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-tiny-tasks.md.QTV7O8kU.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-utilization.md.DTiueZC3.js +5 -0
- package/export-template/docs/assets/tuning-reference_bottleneck-utilization.md.DTiueZC3.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_caching.md.B7aQ8asB.js +1 -0
- package/export-template/docs/assets/tuning-reference_caching.md.B7aQ8asB.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_cluster-config.md.ZVmDGsQ3.js +1 -0
- package/export-template/docs/assets/tuning-reference_cluster-config.md.ZVmDGsQ3.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_config.md.UvveiWG3.js +1 -0
- package/export-template/docs/assets/tuning-reference_config.md.UvveiWG3.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_data-formats.md.bjCAWH3N.js +1 -0
- package/export-template/docs/assets/tuning-reference_data-formats.md.bjCAWH3N.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_index.md.BQ_NooMV.js +1 -0
- package/export-template/docs/assets/tuning-reference_index.md.BQ_NooMV.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_intro.md.CobD-lGB.js +1 -0
- package/export-template/docs/assets/tuning-reference_intro.md.CobD-lGB.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_joins.md.BtKs_CuW.js +1 -0
- package/export-template/docs/assets/tuning-reference_joins.md.BtKs_CuW.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_memory-model.md.DhT-n4y3.js +1 -0
- package/export-template/docs/assets/tuning-reference_memory-model.md.DhT-n4y3.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_metrics.md.mLOh7Apj.js +1 -0
- package/export-template/docs/assets/tuning-reference_metrics.md.mLOh7Apj.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_partitioning.md.q0zKF_8X.js +1 -0
- package/export-template/docs/assets/tuning-reference_partitioning.md.q0zKF_8X.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_pyspark.md.DDCfvN9t.js +6 -0
- package/export-template/docs/assets/tuning-reference_pyspark.md.DDCfvN9t.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_shuffle.md.BZZ7R4Ix.js +1 -0
- package/export-template/docs/assets/tuning-reference_shuffle.md.BZZ7R4Ix.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_spark-architecture.md.Dwzm5avO.js +1 -0
- package/export-template/docs/assets/tuning-reference_spark-architecture.md.Dwzm5avO.lean.js +1 -0
- package/export-template/docs/assets/tuning-reference_table-formats.md.D6wj-2dX.js +1 -0
- package/export-template/docs/assets/tuning-reference_table-formats.md.D6wj-2dX.lean.js +1 -0
- package/export-template/docs/assets/udf-execution-models.BUFDICuG.svg +1 -0
- package/export-template/docs/assets/udf-execution-models.dark.YTNS6GDq.svg +1 -0
- package/export-template/docs/assets/user-guide_alternative-log-retrieval.md.B4tPGIal.js +1 -0
- package/export-template/docs/assets/user-guide_alternative-log-retrieval.md.B4tPGIal.lean.js +1 -0
- package/export-template/docs/assets/user-guide_getting-started.md.BJvwLEIM.js +3 -0
- package/export-template/docs/assets/user-guide_getting-started.md.BJvwLEIM.lean.js +1 -0
- package/export-template/docs/assets/user-guide_mcp-tools.md.Vi3RoflJ.js +125 -0
- package/export-template/docs/assets/user-guide_mcp-tools.md.Vi3RoflJ.lean.js +1 -0
- package/export-template/docs/assets/user-guide_run-comparison.md.CQc1aoU8.js +1 -0
- package/export-template/docs/assets/user-guide_run-comparison.md.CQc1aoU8.lean.js +1 -0
- package/export-template/docs/assets/user-guide_understanding-findings.md.DL1UDhvR.js +1 -0
- package/export-template/docs/assets/user-guide_understanding-findings.md.DL1UDhvR.lean.js +1 -0
- package/export-template/docs/contributor-guide/architecture/board-widgets.html +25 -0
- package/export-template/docs/contributor-guide/architecture/detector-contract.html +25 -0
- package/export-template/docs/contributor-guide/architecture/drill-down.html +25 -0
- package/export-template/docs/contributor-guide/architecture/impact-estimation.html +25 -0
- package/export-template/docs/contributor-guide/architecture/index.html +25 -0
- package/export-template/docs/contributor-guide/architecture/overview.html +25 -0
- package/export-template/docs/contributor-guide/architecture/state-and-history.html +25 -0
- package/export-template/docs/contributor-guide/architecture/widget-rendering.html +25 -0
- package/export-template/docs/contributor-guide/architecture/worker-protocol.html +30 -0
- package/export-template/docs/contributor-guide/contributing.html +25 -0
- package/export-template/docs/contributor-guide/development-setup.html +36 -0
- package/export-template/docs/contributor-guide/testing.html +25 -0
- package/export-template/docs/favicon.svg +4 -0
- package/export-template/docs/hashmap.json +1 -0
- package/export-template/docs/index.html +25 -0
- package/export-template/docs/package.json +1 -0
- package/export-template/docs/tuning-reference/anti-patterns.html +25 -0
- package/export-template/docs/tuning-reference/aqe.html +25 -0
- package/export-template/docs/tuning-reference/bottleneck-broadcast-sizing.html +25 -0
- package/export-template/docs/tuning-reference/bottleneck-cold-start.html +31 -0
- package/export-template/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +25 -0
- package/export-template/docs/tuning-reference/bottleneck-failures.html +30 -0
- package/export-template/docs/tuning-reference/bottleneck-gc.html +30 -0
- package/export-template/docs/tuning-reference/bottleneck-job-failure-rate.html +32 -0
- package/export-template/docs/tuning-reference/bottleneck-memory-utilization.html +31 -0
- package/export-template/docs/tuning-reference/bottleneck-retry-waste.html +25 -0
- package/export-template/docs/tuning-reference/bottleneck-shuffle.html +36 -0
- package/export-template/docs/tuning-reference/bottleneck-skew.html +38 -0
- package/export-template/docs/tuning-reference/bottleneck-slow-host.html +31 -0
- package/export-template/docs/tuning-reference/bottleneck-small-files.html +29 -0
- package/export-template/docs/tuning-reference/bottleneck-spill.html +30 -0
- package/export-template/docs/tuning-reference/bottleneck-straggler.html +31 -0
- package/export-template/docs/tuning-reference/bottleneck-tiny-tasks.html +32 -0
- package/export-template/docs/tuning-reference/bottleneck-utilization.html +29 -0
- package/export-template/docs/tuning-reference/caching.html +25 -0
- package/export-template/docs/tuning-reference/cluster-config.html +25 -0
- package/export-template/docs/tuning-reference/config.html +25 -0
- package/export-template/docs/tuning-reference/data-formats.html +25 -0
- package/export-template/docs/tuning-reference/index.html +25 -0
- package/export-template/docs/tuning-reference/intro.html +25 -0
- package/export-template/docs/tuning-reference/joins.html +25 -0
- package/export-template/docs/tuning-reference/memory-model.html +25 -0
- package/export-template/docs/tuning-reference/metrics.html +25 -0
- package/export-template/docs/tuning-reference/partitioning.html +25 -0
- package/export-template/docs/tuning-reference/pyspark.html +30 -0
- package/export-template/docs/tuning-reference/shuffle.html +25 -0
- package/export-template/docs/tuning-reference/spark-architecture.html +25 -0
- package/export-template/docs/tuning-reference/table-formats.html +25 -0
- package/export-template/docs/user-guide/alternative-log-retrieval.html +25 -0
- package/export-template/docs/user-guide/getting-started.html +27 -0
- package/export-template/docs/user-guide/mcp-tools.html +149 -0
- package/export-template/docs/user-guide/run-comparison.html +25 -0
- package/export-template/docs/user-guide/understanding-findings.html +25 -0
- package/export-template/docs/vp-icons.css +0 -0
- package/export-template/favicon.svg +4 -0
- package/export-template/index.html +111 -0
- package/export-template/parser-worker-DyjiQvfP.js +112 -0
- package/export-template/sample-runs/sample-run.ndjson.gz +0 -0
- package/package.json +20 -6
- package/vendor-core/analyzer.js +74 -74
- package/vendor-core/cli/budgets.js +13 -27
- package/vendor-core/cli/collect-run.js +43 -19
- package/vendor-core/core-count.js +25 -27
- package/vendor-core/core-locality-ratio.js +4 -11
- package/vendor-core/core-time-series.js +6 -12
- package/vendor-core/core-usage-locality.js +3 -4
- package/vendor-core/detectors.js +395 -389
- package/vendor-core/docs-config.js +69 -21
- package/vendor-core/docs-content/chapters/01-intro.md +32 -0
- package/vendor-core/docs-content/chapters/02-spark-architecture.md +76 -0
- package/vendor-core/docs-content/chapters/03-memory-model.md +73 -0
- package/vendor-core/docs-content/chapters/04-partitioning.md +65 -0
- package/vendor-core/docs-content/chapters/05-joins.md +62 -0
- package/vendor-core/docs-content/chapters/06-shuffle.md +59 -0
- package/vendor-core/docs-content/chapters/07-data-formats.md +81 -0
- package/vendor-core/docs-content/chapters/07b-table-formats.md +56 -0
- package/vendor-core/docs-content/chapters/08-caching.md +58 -0
- package/vendor-core/docs-content/chapters/09-pyspark.md +78 -0
- package/vendor-core/docs-content/chapters/10-aqe.md +167 -0
- package/vendor-core/docs-content/chapters/11-cluster-config.md +170 -0
- package/vendor-core/docs-content/chapters/12-anti-patterns.md +171 -0
- package/vendor-core/docs-content/chapters/14-metrics.md +87 -0
- package/vendor-core/docs-content/chapters/15-config.md +93 -0
- package/vendor-core/docs-content/chapters/nav-index.json +370 -0
- package/vendor-core/docs-content/detection/cache.md +7 -0
- package/vendor-core/docs-content/detection/cfg.md +15 -0
- package/vendor-core/docs-content/detection/chrn.md +9 -0
- package/vendor-core/docs-content/detection/cold.md +4 -0
- package/vendor-core/docs-content/detection/cstor.md +4 -0
- package/vendor-core/docs-content/detection/fail.md +5 -0
- package/vendor-core/docs-content/detection/gc.md +4 -0
- package/vendor-core/docs-content/detection/host.md +5 -0
- package/vendor-core/docs-content/detection/incmp.md +6 -0
- package/vendor-core/docs-content/detection/jobs.md +4 -0
- package/vendor-core/docs-content/detection/local.md +6 -0
- package/vendor-core/docs-content/detection/mem.md +10 -0
- package/vendor-core/docs-content/detection/part.md +5 -0
- package/vendor-core/docs-content/detection/plan.md +14 -0
- package/vendor-core/docs-content/detection/retry.md +4 -0
- package/vendor-core/docs-content/detection/sfail.md +5 -0
- package/vendor-core/docs-content/detection/shape.md +5 -0
- package/vendor-core/docs-content/detection/shfl.md +4 -0
- package/vendor-core/docs-content/detection/skew.md +6 -0
- package/vendor-core/docs-content/detection/slow.md +6 -0
- package/vendor-core/docs-content/detection/spec.md +8 -0
- package/vendor-core/docs-content/detection/spill.md +7 -0
- package/vendor-core/docs-content/detection/strag.md +5 -0
- package/vendor-core/docs-content/detection/tiny.md +4 -0
- package/vendor-core/docs-content/detection/util.md +4 -0
- package/vendor-core/docs-content/diagrams/aqe-loop.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/aqe-loop.svg +1 -0
- package/vendor-core/docs-content/diagrams/broadcast-vs-shuffle.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/broadcast-vs-shuffle.svg +1 -0
- package/vendor-core/docs-content/diagrams/cache-lifecycle.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/cache-lifecycle.svg +1 -0
- package/vendor-core/docs-content/diagrams/cold-start-timeline.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/cold-start-timeline.svg +1 -0
- package/vendor-core/docs-content/diagrams/columnar-layout.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/columnar-layout.svg +1 -0
- package/vendor-core/docs-content/diagrams/container-memory.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/container-memory.svg +1 -0
- package/vendor-core/docs-content/diagrams/dag-stages.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/dag-stages.svg +1 -0
- package/vendor-core/docs-content/diagrams/driver-executor.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/driver-executor.svg +1 -0
- package/vendor-core/docs-content/diagrams/duplicate-plan-subtree.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/duplicate-plan-subtree.svg +1 -0
- package/vendor-core/docs-content/diagrams/join-strategy.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/join-strategy.svg +1 -0
- package/vendor-core/docs-content/diagrams/memory-borrowing.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/memory-borrowing.svg +1 -0
- package/vendor-core/docs-content/diagrams/memory-regions.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/memory-regions.svg +1 -0
- package/vendor-core/docs-content/diagrams/repartition-vs-coalesce.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/repartition-vs-coalesce.svg +1 -0
- package/vendor-core/docs-content/diagrams/retry-escalation-ladder.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/retry-escalation-ladder.svg +1 -0
- package/vendor-core/docs-content/diagrams/shuffle-map-reduce.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/shuffle-map-reduce.svg +1 -0
- package/vendor-core/docs-content/diagrams/spill-classification.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/spill-classification.svg +1 -0
- package/vendor-core/docs-content/diagrams/udf-execution-models.dark.svg +1 -0
- package/vendor-core/docs-content/diagrams/udf-execution-models.svg +1 -0
- package/vendor-core/docs-content/tuning/broadcast-sizing.md +78 -0
- package/vendor-core/docs-content/tuning/cold-start.md +81 -0
- package/vendor-core/docs-content/tuning/duplicate-plan-subtree.md +45 -0
- package/vendor-core/docs-content/tuning/failures.md +124 -0
- package/vendor-core/docs-content/tuning/gc.md +110 -0
- package/vendor-core/docs-content/tuning/job-failure-rate.md +101 -0
- package/vendor-core/docs-content/tuning/memory-utilization.md +58 -0
- package/vendor-core/docs-content/tuning/retry-waste.md +90 -0
- package/vendor-core/docs-content/tuning/shuffle.md +154 -0
- package/vendor-core/docs-content/tuning/skew.md +123 -0
- package/vendor-core/docs-content/tuning/slow-host.md +117 -0
- package/vendor-core/docs-content/tuning/small-files.md +99 -0
- package/vendor-core/docs-content/tuning/spill.md +114 -0
- package/vendor-core/docs-content/tuning/straggler.md +103 -0
- package/vendor-core/docs-content/tuning/tiny-tasks.md +94 -0
- package/vendor-core/docs-content/tuning/utilization.md +90 -0
- package/vendor-core/docs-site-config.js +10 -17
- package/vendor-core/efficiency-model.js +7 -13
- package/vendor-core/etl-phases.js +3 -5
- package/vendor-core/event-handlers.js +232 -134
- package/vendor-core/event-schemas.js +48 -114
- package/vendor-core/evidence-availability.js +5 -10
- package/vendor-core/evidence-report.js +73 -123
- package/vendor-core/export-data.js +48 -0
- package/vendor-core/finding-action-label.js +4 -10
- package/vendor-core/finding-filter-predicate.js +3 -7
- package/vendor-core/finding-generic-recommendation.js +112 -0
- package/vendor-core/finding-names.js +51 -0
- package/vendor-core/format-utils.js +112 -38
- package/vendor-core/impact-band.js +18 -24
- package/vendor-core/impact-estimator.js +38 -74
- package/vendor-core/ingest.js +7 -13
- package/vendor-core/job-groups.js +3 -6
- package/vendor-core/list-runs.js +278 -0
- package/vendor-core/load-vendored.js +6 -12
- package/vendor-core/log-header-peek.js +81 -0
- package/vendor-core/lz4-block.js +4 -6
- package/vendor-core/mcp-server-factory.js +38 -8
- package/vendor-core/mcp-tools.js +105 -76
- package/vendor-core/model-assembler.js +8 -16
- package/vendor-core/occupancy.js +5 -9
- package/vendor-core/parser-worker.js +20 -29
- package/vendor-core/plan-dot.js +2 -5
- package/vendor-core/plan-duration-attribution.js +78 -29
- package/vendor-core/plan-graph-model.js +126 -69
- package/vendor-core/plan-node-detail.js +31 -17
- package/vendor-core/plan-summary.js +19 -8
- package/vendor-core/recommendation-rollup.js +35 -39
- package/vendor-core/redact.js +72 -16
- package/vendor-core/rolling-log-reassembly.js +4 -6
- package/vendor-core/run-comparison.js +86 -72
- package/vendor-core/scaling-sim.js +5 -7
- package/vendor-core/session-snapshot.js +1 -1
- package/vendor-core/shs-fetch.js +4 -6
- package/vendor-core/shs-load.js +9 -13
- package/vendor-core/shs-request.js +1 -1
- package/vendor-core/stage-quantiles.js +14 -0
- package/vendor-core/types.js +78 -18
- package/vendor-core/wasted-core-hours.js +7 -12
package/vendor-core/redact.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// Deterministic (sorted assignment), idempotent (pseudonyms map to themselves),
|
|
8
8
|
// and non-mutating (returns a fresh, deep-copied tree).
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
|
|
10
12
|
// Host / IP identifier patterns. Used to enumerate host names that surface only
|
|
11
13
|
// inside free text: recommendation strings, `stageFailed`'s failure-reason
|
|
12
14
|
// value, SQL relation/node names, never as a structured `host` field, so
|
|
@@ -23,7 +25,7 @@ const HOST_PATTERNS = [
|
|
|
23
25
|
// Spark application id (e.g. application_1690000000000_0001). Besides
|
|
24
26
|
// redactReport's structured summary.app.id field, an app id can also surface
|
|
25
27
|
// as a residual free-text token (e.g. embedded in a finding's evidence or
|
|
26
|
-
// recommendation string)
|
|
28
|
+
// recommendation string), same as a host/IP token embedded in a stage name
|
|
27
29
|
// or plan detail. redactComparison has no structured app-id field at all
|
|
28
30
|
// (baselineLabel/candidateLabel are caller-supplied labels, not Spark app
|
|
29
31
|
// ids), so free text is its *only* source of app ids.
|
|
@@ -57,19 +59,58 @@ function scanHostTokens(node , hosts ) {
|
|
|
57
59
|
scanTokens(node, [{ patterns: HOST_PATTERNS, out: hosts }]);
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
// Recursively collects every string value found under a key literally named
|
|
63
|
+
// `host`, anywhere in the tree. Host names surface at several depths, a
|
|
64
|
+
// finding's own `host`, `evidence.host`, and now
|
|
65
|
+
// `evidence.failedTaskDetails[].host` / `evidence.retriedTaskDetails[].host`,
|
|
66
|
+
// and only some of them are host-*pattern*-shaped (a plain FQDN like
|
|
67
|
+
// `worker-3.internal` matches neither HOST_PATTERN). Walking by key name rather
|
|
68
|
+
// than enumerating known paths keeps any future nested `host` field covered.
|
|
69
|
+
function collectHostFields(node , hosts ) {
|
|
70
|
+
if (Array.isArray(node)) {
|
|
71
|
+
for (const n of node) collectHostFields(n, hosts);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (node && typeof node === 'object') {
|
|
75
|
+
for (const [k, v] of Object.entries(node)) {
|
|
76
|
+
if (k === 'host' && typeof v === 'string' && v.length > 0) hosts.add(v);
|
|
77
|
+
else collectHostFields(v, hosts);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Spark config keys ending in `host`/`hostname` (e.g. spark.driver.host,
|
|
83
|
+
// spark.yarn.am.hostname) carry plain FQDN host names that neither
|
|
84
|
+
// HOST_PATTERNS matches (no IP/EC2 shape) nor collectHostFields's by-key-name
|
|
85
|
+
// walk catches (the literal key is the dotted Spark property name, never
|
|
86
|
+
// `host` itself). app.config is a flat Record<string, string> unique to
|
|
87
|
+
// redactExportData: no other redact* export ships a raw Spark config dict.
|
|
88
|
+
// Known gap: a hostname value under a differently-named key isn't caught by
|
|
89
|
+
// this suffix check. Confirmed against a real cluster config: spark.master,
|
|
90
|
+
// spark.yarn.historyServer.address, and the plural YARN proxy/HA keys
|
|
91
|
+
// (...AmIpFilter.param.PROXY_HOSTS, ...RM_HA_URLS) all carry real hostnames
|
|
92
|
+
// through un-redacted today.
|
|
93
|
+
function collectConfigHostValues(config , hosts ) {
|
|
94
|
+
if (!config) return;
|
|
95
|
+
for (const [key, value] of Object.entries(config)) {
|
|
96
|
+
const lastSegment = key.slice(key.lastIndexOf('.') + 1).toLowerCase();
|
|
97
|
+
if ((lastSegment === 'host' || lastSegment === 'hostname') && value) hosts.add(value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
60
101
|
// Known locations of the identifiers, so we don't have to guess which strings
|
|
61
|
-
// are sensitive: the app id lives at summary.app.id; host names live
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
// values (e.g. a finding's evidence/recommendation text).
|
|
102
|
+
// are sensitive: the app id lives at summary.app.id; host names live under any
|
|
103
|
+
// `host` key in the findings tree (see collectHostFields), plus a free-text
|
|
104
|
+
// scan for hosts/IPs and app ids that appear only inside string values (e.g. a
|
|
105
|
+
// finding's evidence/recommendation text).
|
|
66
106
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
107
|
+
|
|
108
|
+
|
|
71
109
|
|
|
72
|
-
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
73
114
|
|
|
74
115
|
|
|
75
116
|
function collectIds(report ) {
|
|
@@ -77,11 +118,7 @@ function collectIds(report )
|
|
|
77
118
|
const hosts = new Set ();
|
|
78
119
|
const appId = report?.summary?.app?.id;
|
|
79
120
|
if (typeof appId === 'string' && appId.length > 0) appIds.add(appId);
|
|
80
|
-
|
|
81
|
-
for (const h of [f?.host, f?.evidence?.host]) {
|
|
82
|
-
if (typeof h === 'string' && h.length > 0) hosts.add(h);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
121
|
+
collectHostFields(report?.findings, hosts);
|
|
85
122
|
// Free-text scan for both host/IP tokens and app-id tokens: an app id can
|
|
86
123
|
// surface in a finding's evidence/recommendation text (e.g. "retry app
|
|
87
124
|
// application_1690000000000_0001 failed") same as redactComparison's scan.
|
|
@@ -173,3 +210,22 @@ export function redactComparison (comparison ) {
|
|
|
173
210
|
scanTokens(comparison, [{ patterns: HOST_PATTERNS, out: hosts }, { patterns: APP_ID_PATTERNS, out: appIds }]);
|
|
174
211
|
return applyReplacements(comparison, { appIds, hosts });
|
|
175
212
|
}
|
|
213
|
+
|
|
214
|
+
// HTML-export counterpart to redactReport/redactComparison. Unlike
|
|
215
|
+
// redactReport (scoped to EvidenceReportJson's summary.app.id + findings),
|
|
216
|
+
// this also walks executors.added/removed for their literal `host` field
|
|
217
|
+
// (ExecutorAddedEvent.host), since raw executor records: not just findings
|
|
218
|
+
//: reach data.js.
|
|
219
|
+
export function redactExportData(data ) {
|
|
220
|
+
const appIds = new Set ();
|
|
221
|
+
const hosts = new Set ();
|
|
222
|
+
const appId = data.app?.id;
|
|
223
|
+
if (typeof appId === 'string' && appId.length > 0) appIds.add(appId);
|
|
224
|
+
collectHostFields(data.executors.added, hosts);
|
|
225
|
+
collectHostFields(data.executors.removed, hosts);
|
|
226
|
+
collectHostFields(data.catalog, hosts);
|
|
227
|
+
collectHostFields(data.configFindings, hosts);
|
|
228
|
+
collectConfigHostValues(data.app?.config, hosts);
|
|
229
|
+
scanTokens(data, [{ patterns: HOST_PATTERNS, out: hosts }, { patterns: APP_ID_PATTERNS, out: appIds }]);
|
|
230
|
+
return applyReplacements(data, { appIds, hosts });
|
|
231
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
// Rolling event-log directory/zip member-name reassembly.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// wider parser-worker.ts module graph) depend on. Keep this file free of any
|
|
6
|
-
// import beyond plain JS/TS built-ins.
|
|
1
|
+
// Rolling event-log directory/zip member-name reassembly. Import-free (plain
|
|
2
|
+
// JS/TS built-ins only) so DropZone.tsx's drag-drop path can use it without
|
|
3
|
+
// pulling in the Zod schemas and vendored zstd/gzip decompressors that the rest
|
|
4
|
+
// of shs-fetch.ts depends on. Keep it that way.
|
|
7
5
|
|
|
8
6
|
export function naturalCompare(a , b ) {
|
|
9
7
|
const tokenize = (s ) => s.match(/(\d+)|(\D+)/g) ?? [s];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// src/run-comparison.ts
|
|
2
1
|
import { computeWallClock } from './wall-clock.js';
|
|
3
2
|
import { normalizeDetail } from './detectors.js';
|
|
4
3
|
import { captureSnapshot } from './session-snapshot.js';
|
|
@@ -20,7 +19,7 @@ import { captureSnapshot } from './session-snapshot.js';
|
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
|
|
@@ -36,16 +35,9 @@ export function normalizeStageName(name ) {
|
|
|
36
35
|
.trim();
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
// cyrb53: fast, non-cryptographic 53-bit string hash
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// small stable-hash helper (`fnv1a`, 32-bit) for deterministic finding ids,
|
|
43
|
-
// but that runs once per finding; planTreeIdentity's `visit` below runs once
|
|
44
|
-
// per PLAN NODE, for every stage, in both runs of a comparison: orders of
|
|
45
|
-
// magnitude more hash calls, so the smaller 32-bit space's birthday-collision
|
|
46
|
-
// risk is a real concern here in a way it isn't for finding ids. 53 bits keeps
|
|
47
|
-
// collision probability negligible at that scale; adequate for a same-run
|
|
48
|
-
// identity key, not a security boundary.
|
|
38
|
+
// cyrb53: fast, non-cryptographic, deterministic 53-bit string hash. Not a
|
|
39
|
+
// security boundary; 53 bits keeps collision risk negligible at the per-node,
|
|
40
|
+
// per-stage call volume planTreeIdentity/sqlNodeIdentity put through it.
|
|
49
41
|
function cyrb53(str , seed = 0) {
|
|
50
42
|
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
|
51
43
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -59,38 +51,16 @@ function cyrb53(str , seed = 0) {
|
|
|
59
51
|
}
|
|
60
52
|
|
|
61
53
|
// Bottom-up, order-independent structural identity of a resolved plan tree:
|
|
62
|
-
// each node folds its
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
// join on one column and a join on another, with identical operator names,
|
|
73
|
-
// collided) and ignored nesting (so two differently-shaped trees sharing a
|
|
74
|
-
// node-name multiset also collided).
|
|
75
|
-
//
|
|
76
|
-
// Each node is encoded via JSON.stringify of its [name, detail, childDigests]
|
|
77
|
-
// triple rather than an ad hoc delimiter-joined string: real Spark `detail`
|
|
78
|
-
// text routinely contains its own `<`/`>`/`{`/`}`/`,` characters (e.g.
|
|
79
|
-
// `ReadSchema: struct<id:bigint>`), so a hand-rolled delimiter scheme could
|
|
80
|
-
// let two structurally different plans serialize to the same string;
|
|
81
|
-
// JSON.stringify escapes/quotes each component so the encoding is
|
|
82
|
-
// unambiguous. Critically, that JSON payload is then folded down to a fixed-
|
|
83
|
-
// length `cyrb53` digest before being returned: the payload only ever
|
|
84
|
-
// contains a node's own (short) name/detail plus an array of already-hashed,
|
|
85
|
-
// fixed-length child digests, never a raw nested JSON blob. Embedding full
|
|
86
|
-
// child identity STRINGS instead (this function's first version) made each
|
|
87
|
-
// level re-stringify-and-escape the level below, so identity length grew
|
|
88
|
-
// ~2^depth; real Spark plans routinely nest 20-60+ operators deep
|
|
89
|
-
// (Scan→Filter→Project→HashAggregate→Exchange→Sort→Join, repeated per
|
|
90
|
-
// join/aggregate), which blew up to megabytes-per-stage or a
|
|
91
|
-
// `RangeError: Invalid string length` well within that range. Digest-folding
|
|
92
|
-
// keeps per-node output size bounded by name+detail length plus a small
|
|
93
|
-
// constant per child, independent of subtree depth.
|
|
54
|
+
// each node folds its normalized name/detail with its children's digests
|
|
55
|
+
// (children sorted, so AQE picking a different broadcast side still matches),
|
|
56
|
+
// so two plans collide only when their whole shape and every node's detail
|
|
57
|
+
// agree. `normalizeDetail` strips the run-to-run noise (expr ids, `plan_id=`,
|
|
58
|
+
// codegen numbers, AQE build-side choice, commutative-operand order). Each
|
|
59
|
+
// node folds to a fixed-length cyrb53 digest (JSON.stringify-encoded, so
|
|
60
|
+
// detail text containing `<`/`>`/`{`/`,` can't collide two different plans)
|
|
61
|
+
// instead of embedding full child identity strings, which would re-escape
|
|
62
|
+
// every level below and blow identity size up to ~2^depth on the 20-60+
|
|
63
|
+
// operator-deep plans real Spark produces.
|
|
94
64
|
function planTreeIdentity(root ) {
|
|
95
65
|
if (!root) return null;
|
|
96
66
|
function visit(node ) {
|
|
@@ -100,12 +70,27 @@ function planTreeIdentity(root ) {
|
|
|
100
70
|
return visit(root);
|
|
101
71
|
}
|
|
102
72
|
|
|
103
|
-
// Plan identity for the stage's SQL execution,
|
|
104
|
-
//
|
|
73
|
+
// Plan identity for the stage's SQL execution, scoped to only the plan nodes
|
|
74
|
+
// this stage actually ran (`node.stageIds`), not the whole tree: two stages
|
|
75
|
+
// sharing one SQL execution (e.g. a self-join's two Exchange stages) otherwise
|
|
76
|
+
// collapse onto one identity regardless of which part of the plan each ran.
|
|
77
|
+
// Falls back to the coarser whole-tree identity when the stage has no
|
|
78
|
+
// attributed nodes (hand-built snapshots without `stageIds`, or unmatched
|
|
79
|
+
// accumulables).
|
|
105
80
|
function sqlNodeIdentity(stage , snapshot ) {
|
|
106
81
|
const execId = stage.sqlExecutionId;
|
|
107
82
|
if (execId == null) return '';
|
|
108
|
-
|
|
83
|
+
const root = snapshot.sql.get(execId)?.planTree ?? null;
|
|
84
|
+
if (!root) return '';
|
|
85
|
+
const fingerprints = [];
|
|
86
|
+
(function collect(node ) {
|
|
87
|
+
if (node.stageIds?.includes(stage.id)) {
|
|
88
|
+
fingerprints.push(JSON.stringify([normalizeStageName(node.name ?? ''), normalizeDetail(node.detail ?? '')]));
|
|
89
|
+
}
|
|
90
|
+
for (const child of node.children ?? []) collect(child);
|
|
91
|
+
})(root);
|
|
92
|
+
if (fingerprints.length === 0) return planTreeIdentity(root) ?? '';
|
|
93
|
+
return cyrb53(JSON.stringify(fingerprints.sort()));
|
|
109
94
|
}
|
|
110
95
|
|
|
111
96
|
export function stageIdentity(stage , snapshot ) {
|
|
@@ -113,7 +98,7 @@ export function stageIdentity(stage , snapshot ) {
|
|
|
113
98
|
}
|
|
114
99
|
|
|
115
100
|
function identityIndex(snapshot ) {
|
|
116
|
-
const byIdentity = new Map ();
|
|
101
|
+
const byIdentity = new Map ();
|
|
117
102
|
for (const [id, stage] of snapshot.stages) {
|
|
118
103
|
const key = stageIdentity(stage, snapshot);
|
|
119
104
|
const ids = byIdentity.get(key);
|
|
@@ -134,24 +119,35 @@ export function matchStages(baseSnap , candSnap
|
|
|
134
119
|
const pairs = [];
|
|
135
120
|
const matchedIdentities = new Set ();
|
|
136
121
|
const collisionIdentities = new Set ();
|
|
137
|
-
// A collision is a single-run property:
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
122
|
+
// A collision is a single-run property: more than one stage in the SAME run
|
|
123
|
+
// shares an identity. Recorded per-run regardless of whether the other run
|
|
124
|
+
// shares it too. Some collisions get resolved into `pairs` below and some
|
|
125
|
+
// don't, so this set means "was ambiguous", not "stayed unpaired".
|
|
141
126
|
for (const idx of [baseIdx, candIdx])
|
|
142
127
|
for (const [identity, ids] of idx) if (ids.length > 1) collisionIdentities.add(identity);
|
|
143
|
-
//
|
|
144
|
-
//
|
|
128
|
+
// An identity colliding equally on both sides has no genuine ambiguity about
|
|
129
|
+
// *count*, so pair its stages off positionally by sorted id rather than
|
|
130
|
+
// dropping them. This is exact when the identity actually distinguishes
|
|
131
|
+
// stages (e.g. self-comparing a run: every stage matches itself). It's a
|
|
132
|
+
// best-effort guess when the identity is coarse (no SQL/attribution) and
|
|
133
|
+
// the two sides are genuinely different runs -- two unrelated same-named
|
|
134
|
+
// stages could get cross-paired. Accepted tradeoff: dropping them instead
|
|
135
|
+
// would also sacrifice the exact-self-comparison case, which matters more.
|
|
145
136
|
for (const identity of [...baseIdx.keys()].sort()) {
|
|
146
|
-
|
|
137
|
+
const candIds = candIdx.get(identity);
|
|
138
|
+
if (!candIds) continue;
|
|
147
139
|
const baseIds = baseIdx.get(identity) ;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
140
|
+
if (baseIds.length !== candIds.length) continue;
|
|
141
|
+
const sortedBaseIds = [...baseIds].sort((a, b) => a - b);
|
|
142
|
+
const sortedCandIds = [...candIds].sort((a, b) => a - b);
|
|
143
|
+
for (let i = 0; i < sortedBaseIds.length; i++) {
|
|
144
|
+
pairs.push({ identity, baseId: sortedBaseIds[i], candId: sortedCandIds[i] });
|
|
145
|
+
}
|
|
151
146
|
matchedIdentities.add(identity);
|
|
152
147
|
}
|
|
153
148
|
const total = baseSnap.stages.size + candSnap.stages.size;
|
|
154
|
-
|
|
149
|
+
// Both runs stage-less: nothing to compare, not "nothing matched".
|
|
150
|
+
const coverage = total === 0 ? 1 : (2 * pairs.length) / total;
|
|
155
151
|
return { pairs, matchedIdentities, collisionIdentities, coverage };
|
|
156
152
|
}
|
|
157
153
|
|
|
@@ -329,18 +325,19 @@ function namesConflict(a , b )
|
|
|
329
325
|
return a?.name != null && b?.name != null && a.name !== b.name;
|
|
330
326
|
}
|
|
331
327
|
|
|
332
|
-
// A pair's skew ratio is null when the stage's duration wasn't measurable
|
|
333
|
-
//
|
|
334
|
-
//
|
|
328
|
+
// A pair's skew ratio is null when the stage's duration wasn't measurable (see
|
|
329
|
+
// `stageSkewRatio`). `baseId`/`candId` are carried through so a consumer can
|
|
330
|
+
// key rows uniquely: two pairs can share one `identity` (a same-run collision
|
|
331
|
+
// resolved positionally in matchStages), but never the same baseId+candId.
|
|
335
332
|
function stageSkewDeltas(
|
|
336
333
|
baseSnap ,
|
|
337
334
|
candSnap ,
|
|
338
335
|
match ,
|
|
339
|
-
)
|
|
336
|
+
) {
|
|
340
337
|
return match.pairs.map((p) => {
|
|
341
338
|
const b = stageSkewRatio(baseSnap.stages.get(p.baseId) );
|
|
342
339
|
const c = stageSkewRatio(candSnap.stages.get(p.candId) );
|
|
343
|
-
return { identity: p.identity, baseline: b, candidate: c, delta: b != null && c != null ? c - b : null };
|
|
340
|
+
return { identity: p.identity, baseId: p.baseId, candId: p.candId, baseline: b, candidate: c, delta: b != null && c != null ? c - b : null };
|
|
344
341
|
}).sort((x, y) => x.identity < y.identity ? -1 : x.identity > y.identity ? 1 : 0);
|
|
345
342
|
}
|
|
346
343
|
|
|
@@ -379,15 +376,13 @@ function stageList(snap )
|
|
|
379
376
|
return [...snap.stages].map(([id, s]) => ({ id, name: s.name ?? `Stage ${id}`, metrics: perStageMetrics(s) }));
|
|
380
377
|
}
|
|
381
378
|
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
// reference, so one shared empty Map is safe in place of a fresh allocation
|
|
385
|
-
// per call.
|
|
379
|
+
// captureSnapshot copies this into a fresh Map internally and never mutates the
|
|
380
|
+
// caller's reference, so one shared empty Map is safe here.
|
|
386
381
|
const EMPTY_TASK_DATA = new Map ();
|
|
387
382
|
|
|
388
383
|
// Wraps the analyze()-to-compareRuns() snapshot-building sequence shared by
|
|
389
384
|
// the CLI's --baseline path and mcp-tools.ts's compareRuns tool: both need
|
|
390
|
-
// captureSnapshot (with an empty taskDataCache
|
|
385
|
+
// captureSnapshot (with an empty taskDataCache, the interactive drill-down
|
|
391
386
|
// cache, never read by compareRuns/matchStages/metricDeltas/findingsDelta)
|
|
392
387
|
// for each side before diffing them.
|
|
393
388
|
export function buildComparison(
|
|
@@ -400,6 +395,14 @@ export function buildComparison(
|
|
|
400
395
|
);
|
|
401
396
|
}
|
|
402
397
|
|
|
398
|
+
// matchStages' coverage is a Dice coefficient: (2 * pairs.length) / (baseCount
|
|
399
|
+
// + candCount). Below 0.5, more than half of each run's stages went unpaired,
|
|
400
|
+
// so the stage-level rows (stageSkew, baseStages/candStages) mostly show
|
|
401
|
+
// unrelated work side by side rather than the same stage before/after -- the
|
|
402
|
+
// comparison is dominated by guesswork, not genuine pairing. 0.5 is thus the
|
|
403
|
+
// natural midpoint for "more matched than not," not an arbitrary tuning knob.
|
|
404
|
+
const LOW_COVERAGE_THRESHOLD = 0.5;
|
|
405
|
+
|
|
403
406
|
export function compareRuns(
|
|
404
407
|
baseline ,
|
|
405
408
|
candidate ,
|
|
@@ -410,10 +413,21 @@ export function compareRuns(
|
|
|
410
413
|
// is the normal way to label an A/B experiment, so a mismatch must not block
|
|
411
414
|
// the (matching-free, name-independent) deltas. Surface it as `low` instead.
|
|
412
415
|
const namesDiffer = namesConflict(baseSnap.app, candSnap.app);
|
|
416
|
+
// Coverage is the other half of the signal: identical names on two runs that
|
|
417
|
+
// barely share any stages are just as misleading as differing names on two
|
|
418
|
+
// runs that match well, so either condition alone drops confidence to `low`.
|
|
419
|
+
const lowCoverage = match.coverage < LOW_COVERAGE_THRESHOLD;
|
|
420
|
+
const reason = namesDiffer && lowCoverage
|
|
421
|
+
? `Run names differ and only ${(match.coverage * 100).toFixed(0)}% of stages matched, so deltas may compare different work.`
|
|
422
|
+
: namesDiffer
|
|
423
|
+
? 'Run names differ, so deltas may compare different work.'
|
|
424
|
+
: lowCoverage
|
|
425
|
+
? `Only ${(match.coverage * 100).toFixed(0)}% of stages matched between runs, so per-stage rows mostly compare unrelated work.`
|
|
426
|
+
: null;
|
|
413
427
|
return {
|
|
414
428
|
baselineLabel: baseline.label, candidateLabel: candidate.label,
|
|
415
|
-
confidence: namesDiffer ? 'low' : 'ok',
|
|
416
|
-
reason
|
|
429
|
+
confidence: namesDiffer || lowCoverage ? 'low' : 'ok',
|
|
430
|
+
reason,
|
|
417
431
|
matchedCoverage: match.coverage,
|
|
418
432
|
metrics: metricDeltas(baseSnap, candSnap),
|
|
419
433
|
findings: findingsDelta(baseSnap, candSnap),
|
|
@@ -440,7 +454,7 @@ function renderFindingsSection(title , findings )
|
|
|
440
454
|
export function renderComparisonMarkdown(comparison ) {
|
|
441
455
|
const lines = ['', '## Comparison to baseline', ''];
|
|
442
456
|
if (comparison.confidence === 'low') {
|
|
443
|
-
lines.push(`- confidence: low
|
|
457
|
+
lines.push(`- confidence: low, ${comparison.reason}`);
|
|
444
458
|
lines.push('');
|
|
445
459
|
}
|
|
446
460
|
lines.push(`- matched stage coverage: ${(comparison.matchedCoverage * 100).toFixed(1)}%`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// §4 What-if executor-scaling simulator
|
|
1
|
+
// §4 What-if executor-scaling simulator.
|
|
2
2
|
// DESIGN SPIKE: the makespan model is a first-cut approximation and is NOT
|
|
3
3
|
// validated against ground truth (a single event log only ever observes one
|
|
4
4
|
// scale). Every prediction ships with a Model Error confidence indicator.
|
|
@@ -42,13 +42,11 @@ export function simulateScaling({ app, stages, runAggregates, executorsAdded }
|
|
|
42
42
|
|
|
43
43
|
{
|
|
44
44
|
const perStage = runAggregates?.perStage ?? {};
|
|
45
|
-
// `app ?? {}`: computeTotalCores
|
|
46
|
-
// `resources` is absent, and
|
|
47
|
-
//
|
|
45
|
+
// `app ?? {}`: computeTotalCores falls back to the executor-derived core sum
|
|
46
|
+
// when `resources` is absent, and a null `app` (malformed log, no
|
|
47
|
+
// ApplicationStart) is real here; `app!` would crash on `app.resources`.
|
|
48
48
|
// `executorsAdded` cast: computeTotalCores only reads `totalCores`, present on
|
|
49
|
-
// ExecutorAddedEvent (the only kind
|
|
50
|
-
// so the union as a whole is a structural mismatch against computeTotalCores's
|
|
51
|
-
// `{totalCores?}` shape.
|
|
49
|
+
// ExecutorAddedEvent (the only kind passed here) but not on the union type.
|
|
52
50
|
const baselineCores = computeTotalCores(app ?? {}, executorsAdded );
|
|
53
51
|
const wc = computeWallClock(app, stages );
|
|
54
52
|
const observedActiveMs = wc.stagesActive;
|
|
@@ -67,7 +67,7 @@ export function applySnapshot(
|
|
|
67
67
|
appModel.jobs = new Map(snapshot.jobs);
|
|
68
68
|
appModel.runAggregates = snapshot.runAggregates ?? null;
|
|
69
69
|
// Fail-closed on unknown schema versions: a future incompatible ledger is
|
|
70
|
-
// never trusted as V1 data
|
|
70
|
+
// never trusted as V1 data.
|
|
71
71
|
appModel.evidenceAvailability = isSupportedEvidenceAvailability(snapshot.evidenceAvailability)
|
|
72
72
|
? snapshot.evidenceAvailability
|
|
73
73
|
: null;
|
package/vendor-core/shs-fetch.js
CHANGED
|
@@ -31,10 +31,9 @@ export function sniffCodec(bytes )
|
|
|
31
31
|
// it (fzstd's `decompress()`, fflate's `gunzipSync()`) allocates that entire
|
|
32
32
|
// size as a single ArrayBuffer up front, which can exceed what the browser
|
|
33
33
|
// will allocate for a multi-GB event log ("Array buffer allocation failed").
|
|
34
|
-
// fflate's Gunzip and fzstd's Decompress are untyped vendor JS
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// the two call sites below without touching the vendored files.
|
|
34
|
+
// fflate's Gunzip and fzstd's Decompress are untyped vendor JS, so TS can't
|
|
35
|
+
// infer a construct signature for them; this local shape types the call sites
|
|
36
|
+
// without touching the vendored files.
|
|
38
37
|
|
|
39
38
|
|
|
40
39
|
|
|
@@ -68,8 +67,7 @@ export async function runParseFromUrl(
|
|
|
68
67
|
state ,
|
|
69
68
|
{ fetchImpl = fetch, emit = (msg ) => self.postMessage(msg) } = {}
|
|
70
69
|
) {
|
|
71
|
-
// buildProxyRequestUrl is
|
|
72
|
-
// untyped .js sibling); `request`'s real shape isn't pinned down here.
|
|
70
|
+
// buildProxyRequestUrl is untyped JS; `request`'s real shape isn't pinned here.
|
|
73
71
|
const url = buildProxyRequestUrl(request );
|
|
74
72
|
|
|
75
73
|
let res;
|
package/vendor-core/shs-load.js
CHANGED
|
@@ -61,12 +61,10 @@ async function readArchiveBytes(upstream , maxBytes , idleTimeou
|
|
|
61
61
|
const reader = upstream.body .getReader();
|
|
62
62
|
// Grown geometrically instead of preallocated at `maxBytes`: a typical
|
|
63
63
|
// archive is nowhere near the cap, so starting small (or at the declared
|
|
64
|
-
// content-length, when trustworthy) and doubling on demand keeps peak
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// freshly allocated same-size output buffer, both live during the final
|
|
69
|
-
// copy) no longer happens.
|
|
64
|
+
// content-length, when trustworthy) and doubling on demand keeps peak memory
|
|
65
|
+
// close to the actual download size. Chunks are copied straight into this
|
|
66
|
+
// buffer as they arrive rather than buffered in an array and copied once at
|
|
67
|
+
// the end, which would keep ~2x-of-total live during the final copy.
|
|
70
68
|
let buf = new Uint8Array(Number.isFinite(declared) && declared > 0 ? Math.min(declared, maxBytes) : 65536);
|
|
71
69
|
let total = 0;
|
|
72
70
|
for (;;) {
|
|
@@ -96,11 +94,9 @@ async function readArchiveBytes(upstream , maxBytes , idleTimeou
|
|
|
96
94
|
return buf.subarray(0, total);
|
|
97
95
|
}
|
|
98
96
|
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// discriminated union (either branch, never a mix), so it's asserted here
|
|
103
|
-
// once at the boundary rather than widening every downstream read.
|
|
97
|
+
// fetchShsEventLog is untyped JS; its real contract is this discriminated union
|
|
98
|
+
// (either branch, never a mix), asserted here once at the boundary rather than
|
|
99
|
+
// widening every downstream read.
|
|
104
100
|
|
|
105
101
|
|
|
106
102
|
export async function resolveFromShs(
|
|
@@ -108,7 +104,7 @@ export async function resolveFromShs(
|
|
|
108
104
|
appId ,
|
|
109
105
|
attemptId ,
|
|
110
106
|
opts = {},
|
|
111
|
-
)
|
|
107
|
+
) {
|
|
112
108
|
const { fetchImpl = fetch, maxArchiveBytes = DEFAULT_MAX_ARCHIVE_BYTES, idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS } = opts;
|
|
113
109
|
const validated = validateShsRequest({ baseUrl: shsBaseUrl, appId, attemptId: attemptId ?? '' });
|
|
114
110
|
if (!validated.request) throw mcpError('access-or-upstream-failure', 'Invalid SHS request parameters.');
|
|
@@ -117,5 +113,5 @@ export async function resolveFromShs(
|
|
|
117
113
|
const zipBytes = await readArchiveBytes(fetched.upstream, maxArchiveBytes, idleTimeoutMs);
|
|
118
114
|
const { appModel, skippedLines } = await collectShsAppModel(zipBytes);
|
|
119
115
|
appModel.evidenceAvailability = deriveEvidenceAvailability(appModel, { skippedLines });
|
|
120
|
-
return appModel;
|
|
116
|
+
return { appModel, skippedLines };
|
|
121
117
|
}
|
|
@@ -19,7 +19,7 @@ function trimString(value) {
|
|
|
19
19
|
return typeof value === 'string' ? value.trim() : '';
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function normalizeBaseUrl(value) {
|
|
22
|
+
export function normalizeBaseUrl(value) {
|
|
23
23
|
const baseUrl = trimString(value);
|
|
24
24
|
if (!baseUrl || baseUrl.includes('?') || baseUrl.includes('#')) return null;
|
|
25
25
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
1
3
|
// Single source of truth for the packed per-task numeric array: FIELDS (offset
|
|
2
4
|
// constants), TASK_FIELD_NAMES (display labels), and the finalizeStage hot-loop
|
|
3
5
|
// push order are all derived from this list. `prop` is the task record's real
|
|
@@ -22,6 +24,10 @@ const TASK_FIELD_DESCRIPTORS = [
|
|
|
22
24
|
|
|
23
25
|
const STRIDE = TASK_FIELD_DESCRIPTORS.length;
|
|
24
26
|
|
|
27
|
+
// Mirrors event-handlers.ts's MAX_TASK_SAMPLES (kept as a separate constant to avoid a circular
|
|
28
|
+
// value import between the two modules: see that file's comment).
|
|
29
|
+
const MAX_TASK_SAMPLES = 20;
|
|
30
|
+
|
|
25
31
|
export const FIELDS = Object.freeze({
|
|
26
32
|
...Object.fromEntries(TASK_FIELD_DESCRIPTORS.map((d, i) => [d.key, i])),
|
|
27
33
|
STRIDE,
|
|
@@ -67,6 +73,7 @@ export function finalizeStage(
|
|
|
67
73
|
const hostStats = new Map();
|
|
68
74
|
const executorStats = new Map();
|
|
69
75
|
const failureReasons = new Map();
|
|
76
|
+
const failedTaskSamples = [];
|
|
70
77
|
const localityStats = new Map();
|
|
71
78
|
let peakExecutionMemoryMax = 0;
|
|
72
79
|
|
|
@@ -75,6 +82,12 @@ export function finalizeStage(
|
|
|
75
82
|
if (t.failed) {
|
|
76
83
|
failedTasks++;
|
|
77
84
|
if (t.reason) failureReasons.set(t.reason, (failureReasons.get(t.reason) ?? 0) + 1);
|
|
85
|
+
if (failedTaskSamples.length < MAX_TASK_SAMPLES) {
|
|
86
|
+
failedTaskSamples.push({
|
|
87
|
+
taskId: t.taskId, attemptNumber: t.attemptNumber, host: t.host, executorId: t.executorId,
|
|
88
|
+
reason: t.reason, peakExecMem: t.peakExecMem, memSpilled: t.memSpilled, shuffleWrite: t.shuffleWrite,
|
|
89
|
+
} );
|
|
90
|
+
}
|
|
78
91
|
}
|
|
79
92
|
if (t.speculative) speculativeTasks++;
|
|
80
93
|
if (t.host) {
|
|
@@ -148,6 +161,7 @@ export function finalizeStage(
|
|
|
148
161
|
const data = {
|
|
149
162
|
...stage,
|
|
150
163
|
hostStats: hostStatsArr, executorStats: executorStatsArr, failureReasons: failureReasonsArr, localityStats: localityStatsArr, stragglerCount,
|
|
164
|
+
failedTaskSamples,
|
|
151
165
|
peakExecutionMemoryMax,
|
|
152
166
|
taskDurationP50: p50,
|
|
153
167
|
taskDurationP95: p95,
|