sparkforensics-server 0.4.0 → 0.5.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/index.js +3 -0
- package/package.json +1 -1
- package/public/assets/AutoscalingChurn-9Zo6IMGS.js +1 -0
- package/public/assets/CacheUtilization-MBDfMsKC.js +1 -0
- package/public/assets/CachingOpportunity-Cfw3wAFa.js +1 -0
- package/public/assets/ColdStart-BtS12b6_.js +1 -0
- package/public/assets/ConfigAudit-DNPV6EQg.js +1 -0
- package/public/assets/CoreUsageArea-BObEeenE.js +1 -0
- package/public/assets/Dashboard-CC_BXrNy.js +2 -0
- package/public/assets/DisclosureContext-Ds0hcZFn.js +1 -0
- package/public/assets/DuplicatePlanSubtree-vN3zqRsi.js +1 -0
- package/public/assets/ExecutorUtilization-CZObSLIj.js +1 -0
- package/public/assets/GcPressure-ByJlLOua.js +1 -0
- package/public/assets/IncompleteRun-MzqV4W8y.js +1 -0
- package/public/assets/JobFailures-LbMvAwAb.js +1 -0
- package/public/assets/MemoryUtilization-DH4jYvJK.js +1 -0
- package/public/assets/OverBroadcast-euk6HKg4.js +1 -0
- package/public/assets/PartitionHint-DThaMMRT.js +1 -0
- package/public/assets/PartitionSizing-yIN-iZzx.js +1 -0
- package/public/assets/{PlanExplorer-DdmPkKO1.js → PlanExplorer-Dd9WPK4S.js} +1 -1
- package/public/assets/PlanGraphRoute-NV5mCXRm.js +1 -0
- package/public/assets/RetryWaste-CXH4D-xH.js +1 -0
- package/public/assets/ShuffleIO-CW7U68ph.js +1 -0
- package/public/assets/Skew-PL79TppX.js +1 -0
- package/public/assets/SlowHost-GBO73hCi.js +1 -0
- package/public/assets/SmallFiles-B5cUjEav.js +1 -0
- package/public/assets/SpeculationWaste-mxi3jgb0.js +1 -0
- package/public/assets/Spill-CH1bNpxL.js +1 -0
- package/public/assets/StageFailed-YvxC11ii.js +1 -0
- package/public/assets/StageFindingGroup-B1A0nf-e.js +1 -0
- package/public/assets/{StageHeader-BSV8ALhT.js → StageHeader-BMNmUy-s.js} +1 -1
- package/public/assets/StageShape-DO3mQ6NS.js +1 -0
- package/public/assets/StageSlowness-SwRxfAEx.js +1 -0
- package/public/assets/Straggler-DHLTiBY-.js +1 -0
- package/public/assets/TaskFailures-Bj0QxiOc.js +1 -0
- package/public/assets/{TinyTask-CIPRxWfT.js → TinyTask-COUQQ5bQ.js} +1 -1
- package/public/assets/UnderBroadcast-RAUbQ6vw.js +1 -0
- package/public/assets/{finding-anchor-DF1cSHO1.js → finding-anchor-Dl8mIHiU.js} +1 -1
- package/public/assets/{impact-sort-7z0ZcBZf.js → impact-sort-Eihua9dY.js} +1 -1
- package/public/assets/index-DaC8A2FS.js +13 -0
- package/public/assets/{parser-worker-Q5KCPdKT.js → parser-worker-CfDvPkwF.js} +5 -5
- package/public/assets/{select-Cy5pmbrh.js → select-DkjFHlXR.js} +1 -1
- package/public/assets/{usePagedRows-BjPOpUU8.js → usePagedRows-Bo-aSztx.js} +1 -1
- package/public/assets/useRecentFiles-Bb4-ukVz.js +11 -0
- package/public/assets/{useSortMode-CU0vzThu.js → useSortMode-lDedD5a3.js} +1 -1
- package/public/docs/404.html +4 -3
- package/public/docs/assets/{app.C6w47a9W.js → app.yjczoibf.js} +1 -1
- package/public/docs/assets/chunks/@localSearchIndexroot.r9imX8Aq.js +1 -0
- package/public/docs/assets/chunks/{VPLocalSearchBox.BcTNvT55.js → VPLocalSearchBox.DOBbPfix.js} +1 -1
- package/public/docs/assets/chunks/{theme.D-mE6j02.js → theme.DFcAl8Ws.js} +2 -2
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.D0Kkk_r5.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.D0Kkk_r5.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_drill-down.md.CFNCE8JB.lean.js → contributor-guide_architecture_drill-down.md.CdtlKHl-.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_index.md.B9yZieYL.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_index.md.3TO9ic6w.lean.js → contributor-guide_architecture_index.md.B9yZieYL.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.DBGo-bwn.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_overview.md.DBGo-bwn.lean.js +1 -0
- package/public/docs/assets/contributor-guide_architecture_state-and-history.md.w4lwybdh.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_state-and-history.md.DOTVHjTV.lean.js → contributor-guide_architecture_state-and-history.md.w4lwybdh.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.js +1 -0
- package/public/docs/assets/{contributor-guide_architecture_widget-rendering.md.BmpARZve.lean.js → contributor-guide_architecture_widget-rendering.md.CcvqxnZI.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.js +6 -0
- package/public/docs/assets/{contributor-guide_architecture_worker-protocol.md.21pTYhFF.lean.js → contributor-guide_architecture_worker-protocol.md.CKeFttKj.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_contributing.md.CQMvGwFt.js +1 -0
- package/public/docs/assets/{contributor-guide_contributing.md.B2E9ni79.lean.js → contributor-guide_contributing.md.CQMvGwFt.lean.js} +1 -1
- package/public/docs/assets/contributor-guide_development-setup.md.D9bu4j_c.js +15 -0
- package/public/docs/assets/contributor-guide_development-setup.md.D9bu4j_c.lean.js +1 -0
- package/public/docs/assets/contributor-guide_testing.md.By6XGR9a.js +1 -0
- package/public/docs/assets/{contributor-guide_testing.md.DTwhpoN2.lean.js → contributor-guide_testing.md.By6XGR9a.lean.js} +1 -1
- package/public/docs/assets/{style.DSixAiZE.css → style.tmDhD3c8.css} +1 -1
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.js +7 -0
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.lean.js +1 -0
- package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.js +24 -0
- package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.lean.js +1 -0
- package/public/docs/assets/{user-guide_mcp-tools.md.dQwIvdTF.js → user-guide_mcp-tools.md.DcLiOa9t.js} +40 -23
- package/public/docs/assets/{user-guide_mcp-tools.md.dQwIvdTF.lean.js → user-guide_mcp-tools.md.DcLiOa9t.lean.js} +1 -1
- package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.js +1 -0
- package/public/docs/assets/{user-guide_run-comparison.md.ChLI3MK0.lean.js → user-guide_run-comparison.md.qin0a4IX.lean.js} +1 -1
- package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.js +1 -0
- package/public/docs/contributor-guide/architecture/board-widgets.html +7 -6
- package/public/docs/contributor-guide/architecture/detector-contract.html +7 -6
- package/public/docs/contributor-guide/architecture/drill-down.html +7 -6
- package/public/docs/contributor-guide/architecture/impact-estimation.html +7 -6
- package/public/docs/contributor-guide/architecture/index.html +7 -6
- package/public/docs/contributor-guide/architecture/overview.html +7 -6
- package/public/docs/contributor-guide/architecture/state-and-history.html +7 -6
- package/public/docs/contributor-guide/architecture/widget-rendering.html +7 -6
- package/public/docs/contributor-guide/architecture/worker-protocol.html +10 -9
- package/public/docs/contributor-guide/contributing.html +7 -6
- package/public/docs/contributor-guide/development-setup.html +11 -10
- package/public/docs/contributor-guide/testing.html +7 -6
- package/public/docs/hashmap.json +1 -1
- package/public/docs/index.html +5 -4
- package/public/docs/tuning-reference/anti-patterns.html +5 -4
- package/public/docs/tuning-reference/aqe.html +5 -4
- package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +5 -4
- package/public/docs/tuning-reference/bottleneck-cold-start.html +5 -4
- package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +5 -4
- package/public/docs/tuning-reference/bottleneck-failures.html +5 -4
- package/public/docs/tuning-reference/bottleneck-gc.html +5 -4
- package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +5 -4
- package/public/docs/tuning-reference/bottleneck-memory-utilization.html +5 -4
- package/public/docs/tuning-reference/bottleneck-retry-waste.html +5 -4
- package/public/docs/tuning-reference/bottleneck-shuffle.html +5 -4
- package/public/docs/tuning-reference/bottleneck-skew.html +5 -4
- package/public/docs/tuning-reference/bottleneck-slow-host.html +5 -4
- package/public/docs/tuning-reference/bottleneck-small-files.html +5 -4
- package/public/docs/tuning-reference/bottleneck-spill.html +5 -4
- package/public/docs/tuning-reference/bottleneck-straggler.html +5 -4
- package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +5 -4
- package/public/docs/tuning-reference/bottleneck-utilization.html +5 -4
- package/public/docs/tuning-reference/caching.html +5 -4
- package/public/docs/tuning-reference/cluster-config.html +5 -4
- package/public/docs/tuning-reference/config.html +5 -4
- package/public/docs/tuning-reference/data-formats.html +5 -4
- package/public/docs/tuning-reference/index.html +5 -4
- package/public/docs/tuning-reference/intro.html +5 -4
- package/public/docs/tuning-reference/joins.html +5 -4
- package/public/docs/tuning-reference/memory-model.html +5 -4
- package/public/docs/tuning-reference/metrics.html +5 -4
- package/public/docs/tuning-reference/partitioning.html +5 -4
- package/public/docs/tuning-reference/pyspark.html +5 -4
- package/public/docs/tuning-reference/shuffle.html +5 -4
- package/public/docs/tuning-reference/spark-architecture.html +5 -4
- package/public/docs/tuning-reference/table-formats.html +5 -4
- package/public/docs/user-guide/alternative-log-retrieval.html +14 -7
- package/public/docs/user-guide/getting-started.html +27 -8
- package/public/docs/user-guide/mcp-tools.html +46 -28
- package/public/docs/user-guide/run-comparison.html +7 -6
- package/public/docs/user-guide/understanding-findings.html +7 -6
- package/public/export-template.html +44 -44
- package/public/index.html +2 -2
- package/vendor-core/allocation.js +106 -0
- package/vendor-core/analyzer.js +13 -13
- package/vendor-core/cli/budgets.js +23 -9
- package/vendor-core/cli/collect-run.js +11 -4
- package/vendor-core/cli/regression-budgets.js +83 -0
- package/vendor-core/comparison-verdict.js +22 -22
- package/vendor-core/core-source-hash.txt +1 -1
- package/vendor-core/detectors.js +202 -68
- package/vendor-core/docs-content/detection/cache.md +3 -2
- package/vendor-core/docs-content/detection/cfg.md +9 -8
- package/vendor-core/docs-content/detection/chrn.md +1 -2
- package/vendor-core/docs-content/detection/cold.md +4 -2
- package/vendor-core/docs-content/detection/fail.md +3 -2
- package/vendor-core/docs-content/detection/gc.md +3 -2
- package/vendor-core/docs-content/detection/host.md +2 -1
- package/vendor-core/docs-content/detection/local.md +1 -1
- package/vendor-core/docs-content/detection/mem.md +5 -2
- package/vendor-core/docs-content/detection/plan.md +2 -1
- package/vendor-core/docs-content/detection/sfail.md +2 -1
- package/vendor-core/docs-content/detection/shape.md +5 -4
- package/vendor-core/docs-content/detection/skew.md +3 -1
- package/vendor-core/docs-content/detection/slow.md +2 -2
- package/vendor-core/docs-content/detection/spec.md +2 -3
- package/vendor-core/docs-content/detection/spill.md +1 -1
- package/vendor-core/docs-site-config.js +1 -1
- package/vendor-core/effective-conf.js +107 -0
- package/vendor-core/efficiency-model.js +8 -6
- package/vendor-core/event-handlers.js +160 -47
- package/vendor-core/event-schemas.js +2 -0
- package/vendor-core/evidence-report.js +18 -10
- package/vendor-core/finding-generic-recommendation.js +20 -1
- package/vendor-core/finding-names.js +7 -0
- package/vendor-core/finding-presentation.js +61 -26
- package/vendor-core/finding-tag-help.js +1 -1
- package/vendor-core/finding-types.js +12 -0
- package/vendor-core/format-utils.js +4 -3
- package/vendor-core/impact-estimator.js +20 -2
- package/vendor-core/impact-format.js +14 -13
- package/vendor-core/impact-model.js +27 -5
- package/vendor-core/ingest.js +4 -2
- package/vendor-core/list-runs.js +5 -2
- package/vendor-core/mcp-tools.js +1 -1
- package/vendor-core/model-assembler.js +23 -1
- package/vendor-core/parser-worker.js +1 -1
- package/vendor-core/proxy.js +3 -1
- package/vendor-core/python-stage.js +25 -0
- package/vendor-core/recommendation-rollup.js +16 -9
- package/vendor-core/redact.js +51 -10
- package/vendor-core/remediation.js +20 -0
- package/vendor-core/run-comparison.js +43 -24
- package/vendor-core/run-interpretation.js +2 -1
- package/vendor-core/run-metrics.js +198 -0
- package/vendor-core/run-totals.js +24 -0
- package/vendor-core/run-verdict.js +3 -4
- package/vendor-core/scorecard-estimates.js +1 -0
- package/vendor-core/session-snapshot.js +7 -0
- package/vendor-core/shs-schemas.js +2 -2
- package/vendor-core/spark-memory.js +17 -0
- package/vendor-core/stage-plan-nodes.js +18 -0
- package/vendor-core/stage-quantiles.js +4 -0
- package/vendor-core/types.js +49 -1
- package/vendor-core/wasted-core-hours.js +10 -7
- package/vendor-core/write-targets.js +312 -0
- package/public/assets/AutoscalingChurn-CUSIvvab.js +0 -1
- package/public/assets/CacheUtilization-Ce0K6_X2.js +0 -1
- package/public/assets/CachingOpportunity-Cn-LmRA4.js +0 -1
- package/public/assets/ColdStart-BoiYzEyV.js +0 -1
- package/public/assets/ConfigAudit-BoJc6PAw.js +0 -1
- package/public/assets/CoreUsageArea-BFfXKu7K.js +0 -1
- package/public/assets/Dashboard-Bo_1jGqH.js +0 -2
- package/public/assets/DisclosureContext-LYzHroHO.js +0 -1
- package/public/assets/DuplicatePlanSubtree-BaAuGAoo.js +0 -1
- package/public/assets/ExecutorUtilization-OE6VMFkG.js +0 -1
- package/public/assets/GcPressure-AoL_ZNrV.js +0 -1
- package/public/assets/IncompleteRun-Bv6IzgsQ.js +0 -1
- package/public/assets/JobFailures-DF3U2z1f.js +0 -1
- package/public/assets/MemoryUtilization-B65OVZlq.js +0 -1
- package/public/assets/OverBroadcast-DXbJREDl.js +0 -1
- package/public/assets/PartitionSizing-DZLnjy0d.js +0 -1
- package/public/assets/PlanGraphRoute-C-BGmC6j.js +0 -1
- package/public/assets/RetryWaste-BbEPsdMw.js +0 -1
- package/public/assets/ShuffleIO-wZB94LS8.js +0 -1
- package/public/assets/Skew-SpQE7zeJ.js +0 -1
- package/public/assets/SlowHost-DwDBCTvr.js +0 -1
- package/public/assets/SmallFiles-yjY_6sT1.js +0 -1
- package/public/assets/SpeculationWaste-73PDmj0c.js +0 -1
- package/public/assets/Spill-DEw0Kd1i.js +0 -1
- package/public/assets/StageFailed-BEfXdEJc.js +0 -1
- package/public/assets/StageFindingGroup-Bzm2TY03.js +0 -1
- package/public/assets/StageShape-CzR4jHOt.js +0 -1
- package/public/assets/StageSlowness-vMPsnAaa.js +0 -1
- package/public/assets/Straggler-Cf__OGl5.js +0 -1
- package/public/assets/TaskFailures-BZ8kLZiO.js +0 -1
- package/public/assets/UnderBroadcast-CBm8ZPoS.js +0 -1
- package/public/assets/index-CEM9bz_x.js +0 -13
- package/public/assets/useRecentFiles-BrBl-AP1.js +0 -11
- package/public/docs/assets/chunks/@localSearchIndexroot.DbZy5EtM.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.VTcYTH4d.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_board-widgets.md.VTcYTH4d.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_detector-contract.md.CsqZHz_T.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_drill-down.md.CFNCE8JB.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.HT9IJGIl.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.HT9IJGIl.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_index.md.3TO9ic6w.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.CTEqVeV4.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_overview.md.CTEqVeV4.lean.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DOTVHjTV.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BmpARZve.js +0 -1
- package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.21pTYhFF.js +0 -6
- package/public/docs/assets/contributor-guide_contributing.md.B2E9ni79.js +0 -1
- package/public/docs/assets/contributor-guide_development-setup.md.CjgZJwjK.js +0 -15
- package/public/docs/assets/contributor-guide_development-setup.md.CjgZJwjK.lean.js +0 -1
- package/public/docs/assets/contributor-guide_testing.md.DTwhpoN2.js +0 -1
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.ZHPSbBkF.js +0 -1
- package/public/docs/assets/user-guide_alternative-log-retrieval.md.ZHPSbBkF.lean.js +0 -1
- package/public/docs/assets/user-guide_getting-started.md.CEa2ZX-l.js +0 -6
- package/public/docs/assets/user-guide_getting-started.md.CEa2ZX-l.lean.js +0 -1
- package/public/docs/assets/user-guide_run-comparison.md.ChLI3MK0.js +0 -1
- package/public/docs/assets/user-guide_understanding-findings.md.D1ccs60C.js +0 -1
- /package/public/docs/assets/{contributor-guide_architecture_detector-contract.md.CsqZHz_T.lean.js → contributor-guide_architecture_detector-contract.md.caA46z-u.lean.js} +0 -0
- /package/public/docs/assets/{user-guide_understanding-findings.md.D1ccs60C.lean.js → user-guide_understanding-findings.md.DzReyCXb.lean.js} +0 -0
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
<title>Getting started | SparkForensics</title>
|
|
7
7
|
<meta name="description" content="Docs for using and contributing to SparkForensics">
|
|
8
8
|
<meta name="generator" content="VitePress v1.6.4">
|
|
9
|
-
<link rel="preload stylesheet" href="/docs/assets/style.
|
|
9
|
+
<link rel="preload stylesheet" href="/docs/assets/style.tmDhD3c8.css" as="style">
|
|
10
10
|
<link rel="preload stylesheet" href="/docs/vp-icons.css" as="style">
|
|
11
11
|
|
|
12
|
-
<script type="module" src="/docs/assets/app.
|
|
12
|
+
<script type="module" src="/docs/assets/app.yjczoibf.js"></script>
|
|
13
13
|
<link rel="preload" href="/docs/assets/inter-roman-latin.Di8DUHzh.woff2" as="font" type="font/woff2" crossorigin="">
|
|
14
|
-
<link rel="modulepreload" href="/docs/assets/chunks/theme.
|
|
14
|
+
<link rel="modulepreload" href="/docs/assets/chunks/theme.DFcAl8Ws.js">
|
|
15
15
|
<link rel="modulepreload" href="/docs/assets/chunks/framework.DSg0KOwT.js">
|
|
16
|
-
<link rel="modulepreload" href="/docs/assets/user-guide_getting-started.md.
|
|
16
|
+
<link rel="modulepreload" href="/docs/assets/user-guide_getting-started.md.BJW5oVhe.lean.js">
|
|
17
|
+
<script>(()=>{const c=`${`[${["data","shuffle","product","bar"].join("-")}]`}, .VPNav, .VPLocalNav`,a=24,d=document.documentElement,l=t=>{const e=getComputedStyle(t);return e.display==="none"||t.getClientRects().length===0?0:e.position==="fixed"?t.getBoundingClientRect().bottom:e.position==="sticky"?(parseFloat(e.top)||0)+t.offsetHeight:0},u=()=>{try{return location.hash?document.getElementById(decodeURIComponent(location.hash.slice(1))):null}catch{return null}};let r=null;const s=()=>{const t=Array.from(document.querySelectorAll(c),l),e=Math.ceil(Math.max(0,...t))+a;if(e===r)return;const n=r;r=e,d.style.setProperty("--sf-anchor-offset",`${r}px`);const o=n===null?null:u();if(!o)return;const f=(parseFloat(getComputedStyle(o).paddingTop)||0)+2;Math.abs(o.getBoundingClientRect().top-n)<=f&&o.scrollIntoView()},i=()=>{const t=document.createElement("div");t.setAttribute("data-sf-anchor-offset",""),t.setAttribute("aria-hidden","true"),document.body.appendChild(t);const e=window.ResizeObserver?new ResizeObserver(s):null,n=()=>{e&&document.querySelectorAll(c).forEach(o=>e.observe(o)),s()};n(),new MutationObserver(n).observe(document.body,{childList:!0,subtree:!0}),window.addEventListener("resize",s),window.addEventListener("load",s)};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",i,{once:!0}):i()})();</script>
|
|
17
18
|
<link rel="icon" type="image/svg+xml" href="/docs/favicon.svg">
|
|
18
19
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
19
20
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
|
@@ -22,13 +23,31 @@
|
|
|
22
23
|
<script id="check-mac-os">document.documentElement.classList.toggle("mac",/Mac|iPhone|iPod|iPad/i.test(navigator.platform));</script>
|
|
23
24
|
</head>
|
|
24
25
|
<body>
|
|
25
|
-
<div id="app"><div class="Layout" data-v-5d98c3a5><!--[--><!--]--><!--[--><span tabindex="-1" data-v-0b0ada53></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-0b0ada53>Skip to content</a><!--]--><!----><header class="VPNav" data-v-5d98c3a5 data-v-ae24b3ad><div class="VPNavBar" data-v-ae24b3ad data-v-6aa21345><div class="wrapper" data-v-6aa21345><div class="container" data-v-6aa21345><div class="title" data-v-6aa21345><div class="VPNavBarTitle has-sidebar" data-v-6aa21345 data-v-1168a8e4><a class="title" href="/docs/" data-v-1168a8e4><!--[--><!--]--><!--[--><img class="VPImage logo" src="/docs/favicon.svg" alt data-v-8426fc1a><!--]--><span data-v-1168a8e4>SparkForensics</span><!--[--><!--]--></a></div></div><div class="content" data-v-6aa21345><div class="content-body" data-v-6aa21345><!--[--><!--]--><div class="VPNavBarSearch search" data-v-6aa21345><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-6aa21345 data-v-dc692963><span id="main-nav-aria-label" class="visually-hidden" data-v-dc692963> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink active" href="/docs/user-guide/getting-started.html" tabindex="0" data-v-dc692963 data-v-e56f3d57><!--[--><span data-v-e56f3d57>User Guide</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/contributor-guide/development-setup.html" tabindex="0" data-v-dc692963 data-v-e56f3d57><!--[--><span data-v-e56f3d57>Contributor Guide</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/tuning-reference/" tabindex="0" data-v-dc692963 data-v-e56f3d57><!--[--><span data-v-e56f3d57>Tuning Reference</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-6aa21345 data-v-6c893767><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-6c893767 data-v-5337faa4 data-v-1d5665e3><span class="check" data-v-1d5665e3><span class="icon" data-v-1d5665e3><!--[--><span class="vpi-sun sun" data-v-5337faa4></span><span class="vpi-moon moon" data-v-5337faa4></span><!--]--></span></span></button></div><!----><div class="VPFlyout VPNavBarExtra extra" data-v-6aa21345 data-v-bb2aa2f0 data-v-cf11d7a2><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-cf11d7a2><span class="vpi-more-horizontal icon" data-v-cf11d7a2></span></button><div class="menu" data-v-cf11d7a2><div class="VPMenu" data-v-cf11d7a2 data-v-b98bc113><!----><!--[--><!--[--><!----><div class="group" data-v-bb2aa2f0><div class="item appearance" data-v-bb2aa2f0><p class="label" data-v-bb2aa2f0>Appearance</p><div class="appearance-action" data-v-bb2aa2f0><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-bb2aa2f0 data-v-5337faa4 data-v-1d5665e3><span class="check" data-v-1d5665e3><span class="icon" data-v-1d5665e3><!--[--><span class="vpi-sun sun" data-v-5337faa4></span><span class="vpi-moon moon" data-v-5337faa4></span><!--]--></span></span></button></div></div></div><!----><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-6aa21345 data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><div class="divider" data-v-6aa21345><div class="divider-line" data-v-6aa21345></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-5d98c3a5 data-v-a6f0e41e><div class="container" data-v-a6f0e41e><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-a6f0e41e><span class="vpi-align-left menu-icon" data-v-a6f0e41e></span><span class="menu-text" data-v-a6f0e41e>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-a6f0e41e data-v-8a42e2b4><button data-v-8a42e2b4>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-5d98c3a5 data-v-319d5ca6><div class="curtain" data-v-319d5ca6></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-319d5ca6><span class="visually-hidden" id="sidebar-aria-label" data-v-319d5ca6> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-c40bc020><section class="VPSidebarItem level-0 has-active" data-v-c40bc020 data-v-b3fd67f8><div class="item" role="button" tabindex="0" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><h2 class="text" data-v-b3fd67f8>User Guide</h2><!----></div><div class="items" data-v-b3fd67f8><!--[--><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/getting-started.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>Getting started</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/understanding-findings.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>Understanding findings</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/run-comparison.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>Run comparison mode</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/mcp-tools.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>MCP tools reference</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/alternative-log-retrieval.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>Alternative log retrieval</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-5d98c3a5 data-v-1428d186><div class="VPDoc has-sidebar has-aside" data-v-1428d186 data-v-39a288b8><!--[--><!--]--><div class="container" data-v-39a288b8><div class="aside" data-v-39a288b8><div class="aside-curtain" data-v-39a288b8></div><div class="aside-container" data-v-39a288b8><div class="aside-content" data-v-39a288b8><div class="VPDocAside" data-v-39a288b8 data-v-3f215769><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-3f215769 data-v-a5bbad30><div class="content" data-v-a5bbad30><div class="outline-marker" data-v-a5bbad30></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-a5bbad30>On this page</div><ul class="VPDocOutlineItem root" data-v-a5bbad30 data-v-b933a997><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-39a288b8><div class="content-container" data-v-39a288b8><!--[--><!--]--><main class="main" data-v-39a288b8><div style="position:relative;" class="vp-doc _docs_user-guide_getting-started" data-v-39a288b8><div><h1 id="getting-started" tabindex="-1">Getting started <a class="header-anchor" href="#getting-started" aria-label="Permalink to "Getting started""></a></h1><p>SparkForensics reads a Spark History Server event log and turns it into a dashboard of flagged bottlenecks. No account needed.</p><h2 id="local-server-mode" tabindex="-1">Run it locally <a class="header-anchor" href="#local-server-mode" aria-label="Permalink to "Run it locally {#local-server-mode}""></a></h2><p>The recommended way to run SparkForensics on your machine:</p><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> sparkforensics-server</span></span></code></pre></div><p>Open <code>http://127.0.0.1:4173</code> and drop a log in. This is <strong>local-server mode</strong>: it also fetches runs from a Spark History Server on your behalf. Port and environment-variable overrides, and what changes for a static deploy, are in the <a href="https://github.com/shuffle-works/sparkforensics#deploy-modes" target="_blank" rel="noreferrer">project README</a>.</p><p>The <a href="https://shuffle-works.github.io/sparkforensics/" target="_blank" rel="noreferrer">hosted demo</a> runs the same dashboard but can't fetch from a History Server. To work on SparkForensics itself, see <a href="./../contributor-guide/development-setup.html">Development setup</a>.</p><h2 id="load-a-run" tabindex="-1">Load a run <a class="header-anchor" href="#load-a-run" aria-label="Permalink to "Load a run""></a></h2><p>Drop a log file onto the landing page, or click <strong>Choose file</strong> to pick one. Parsing runs in a background worker, off the browser's main thread, so a multi-hundred-megabyte event stream doesn't freeze the tab. While it runs, the progress screen shows how far along it is and roughly how long is left; <strong>Cancel</strong> returns to the landing page.</p><p>No log of your own yet? Click <strong>Try a sample run</strong> on the landing page to load a bundled example run and see a populated dashboard right away. A line above its verdict says it is the sample, with <strong>Load my event log</strong> and a link to where to find one, so you can switch to your own run from there. <strong>Where do I find my event log?</strong>, under the buttons, is a short guide to turning event logging on and downloading a log from a History Server.</p><p>The app takes a newline-delimited JSON event log (one JSON event per line, the format Spark writes to <code>spark.eventLog.dir</code>), either plain or gzip/Zstandard/LZ4/Snappy-compressed. It also takes the zip a Spark History Server hands back, from the Spark UI's download link or from <code>GET /api/v1/applications/<appId>/logs</code>, as-is: drop the <code>.zip</code> and the app unwraps the log inside it, or reassembles the parts of a rolling log. The zip must hold one attempt: for an application that ran more than once, download <code>/api/v1/applications/<appId>/<attemptId>/logs</code> instead.</p><p>Click <strong>Other sources</strong> on the landing page for two more ways in:</p><ul><li><strong>Choose rolling-log folder</strong>, for an <code>eventlog_v2_*</code> rolling directory. Drop the folder or point the picker at it; the app reassembles its parts in order before parsing.</li><li><strong>Fetch from Spark History Server</strong>, to pull an application straight from a reachable History Server. This needs <strong>local-server mode</strong> (see <a href="#local-server-mode">Run it locally</a>) and an application ID in one of the forms Spark itself uses: <code>application_<timestamp>_<id></code>, <code>local-<timestamp></code>, <code>app-<id></code>, <code>spark-<id></code>, or <code>driver-<id></code>. If the fetch fails, the panel names the problem (server unreachable, application not found, local server not running, and so on) and suggests what to try next.</li></ul><p>Can't reach the History Server directly (it's only reachable through an SSH bastion)? See <a href="./alternative-log-retrieval.html">Alternative ways to get the logs</a>.</p><p>Files you have already loaded stay listed under <strong>Recent files</strong> on the landing page.</p><h2 id="reading-the-dashboard" tabindex="-1">Reading the dashboard <a class="header-anchor" href="#reading-the-dashboard" aria-label="Permalink to "Reading the dashboard""></a></h2><p>When parsing finishes, the dashboard shows a board of widgets, each in its own card with a title. Every widget that flags a problem uses the same convention: a colored impact dot (critical / warning / info) and an ALL-CAPS tag for the bottleneck category (<code>SKEW</code>, <code>SPILL</code>, <code>GC</code>, and so on: see <a href="./understanding-findings.html">Understanding findings</a>). Widgets list every affected stage, not just the worst one. The dot's color tracks how much run time the finding could save you rather than how unusual the metric looks, so a small-looking anomaly with a big payoff can outrank a dramatic one that would barely move your run time.</p><p>The board opens with a verdict: one line saying where to start, a short summary of what was found, and up to three numbered next steps. Each step explains in plain language what is happening, says what to try, and has a <strong>Show evidence</strong> button that jumps to the finding's detail widget. Findings on the same stage are folded into one step, because they usually share a cause and their savings overlap rather than add up. Each savings figure says what it counts: a time such as "58.6s of run time" is how much sooner the run could finish, while a resource figure such as "3.0 GB-h of unused executor memory" or "0.7 core-h of core time" is cluster time a fix would free up, which cuts cost but may not shorten the run. <strong>Copy next steps</strong> copies the whole plan as a plain checklist (run, verdict, and numbered steps with their stage and savings) to paste into a ticket or a message. Steps follow the same savings ranking as the rest of the board. When much of the run's executor capacity sat idle, the summary says so, without moving cluster size ahead of a bigger fix.</p><p>The verdict also says how the run ended. When every job succeeded it says so. When a job failed, the title says the run failed (or how many of its jobs did), quotes the first line of the reason Spark recorded, and puts the failure first in the next steps, ahead of any speed-up, since a job has to finish before its speed matters.</p><p>A run is called clean only when the log had everything its checks need. When something was missing, the verdict title says some checks could not run, and the Findings tab's <strong>Clean checks</strong> list says which ones and, where Spark has one, the setting to turn on for the next run (for example <code>spark.eventLog.logStageExecutorMetrics=true</code> for per-executor memory).</p><p>A run scorecard sits under the verdict: <strong>Wall-clock</strong> (total run time), <strong>Efficiency</strong> (the share of that time with a stage running; higher is better; <strong>Not measured</strong> when no stage in the log recorded an end) and <strong>Unused core time</strong> (driver idle plus executor slack across the whole run, so it can run higher than the idle capacity a verdict step reports; lower is better). A collapsed <strong>New to Spark tuning?</strong> primer in the verdict explains stages, tasks, executors, shuffle and how to read savings. Stage labels such as <strong>Stage 7</strong> open that stage's details: how long it ran and what share of the run that was, then each of its findings with what is happening, what to try and a <strong>Show evidence</strong> button, followed by its task, locality, I/O and plan sections. Below it, two tabs split the rest of the board:</p><ol><li><strong>Findings</strong>: every flagged finding and its detail widget, grouped by impact band (Critical, Warning, Info). Within a band, a recommendation row for a bottleneck type collapses into a summary row when it fires more than once; clicking the summary row expands its full list, and clicking any single row or widget jumps straight to that finding. Each band leads with its recommendation rows; its detail widgets (the charts and per-stage numbers behind them) sit under <strong>Show the evidence</strong>, and <strong>Show evidence</strong> on any finding opens them for you. Advanced view shows the widgets without that step. Widgets that found nothing fold away into a "Clean checks" disclosure. A check the log lacked the data for is listed there under <strong>Not checked on this log</strong>, not as a pass, with the reason and the setting to turn on: for example every per-stage check when no stage finished, or core usage, memory and executor churn when the log has no end-of-run record. This is the tab you land on.</li><li><strong>Full app report</strong>: the wall-clock and executor timelines, the stage table, and the reference-only cards, led by core usage by locality, which shows even on a clean run.</li></ol><p>Click a finding's documentation link (or the topbar's <strong>Docs</strong> button) to open the reference material in a slide-in panel beside the dashboard: the dashboard stays visible and interactive, so you can check a metric against the reference without losing your place. Esc closes the panel, even while you are reading or scrolling inside it.</p><h3 id="advanced-view" tabindex="-1">Advanced view <a class="header-anchor" href="#advanced-view" aria-label="Permalink to "Advanced view""></a></h3><p>The topbar has an <strong>Advanced view</strong> toggle. It's off by default, which keeps each widget to the finding itself and what to do about it. Turn it on to also show confidence levels, supporting evidence, and documentation links for each finding, plus a few extra table columns and the finding filter bar (impact, type, stage). A filter that is already active, for example from a shared link, keeps the filter bar visible either way. In the verdict, each step also says how its savings figure was estimated (measured or modeled, and whether the stage ran alone or shared the cluster, which makes the figure a range from a floor to an optimistic high), shows any confidence marker, and a line states how the steps are ordered. The scorecard switches from plain captions to the raw run and idle-time breakdown, and the newcomer primer is hidden. Advanced view also turns on single-key triage shortcuts: <code>j</code> and <code>k</code> step through the verdict's steps and then every finding row, <code>Enter</code> shows the focused finding's evidence, or expands a grouped finding, <code>f</code> jumps to the filters, and <code>1</code> and <code>2</code> switch between <strong>Findings</strong> and <strong>Full app report</strong>. They stay off in the default view, so they never surprise a first-time visitor or a screen-reader user. Your choice is remembered across runs.</p><h3 id="the-rest-of-the-topbar" tabindex="-1">The rest of the topbar <a class="header-anchor" href="#the-rest-of-the-topbar" aria-label="Permalink to "The rest of the topbar""></a></h3><p>Once a run is loaded, the topbar also carries a few more controls. The count chip ("4 critical") counts the same findings the verdict ranks; click it to jump to that band of the Findings list (a board filter hiding the band is cleared, with a notice saying so). It reads <strong>No findings</strong> only when the verdict calls the run clean, <strong>Not fully checked</strong> when the log lacked evidence for some checks, and <strong>Run failed</strong> when a job failed. For keyboard users, the first Tab stop is <strong>Skip to the verdict</strong>. <strong>New analysis</strong> goes back to the landing page to load another run. <strong>Compare with another run</strong> keeps this run as the baseline and asks only for the other one (see <a href="./run-comparison.html">Run comparison mode</a>). <strong>Plan graph</strong> opens an interactive node-and-edge view of the run's SQL execution plan, filterable down to I/O operators (scan, exchange), a broader "basic" set, or every operator. <strong>Export evidence</strong> downloads the current run's findings as a portable Markdown or JSON report, the same shape the CLI and MCP tools produce, or as <strong>Download HTML dashboard</strong>: one <code>.html</code> file holding this dashboard for the run, which opens in any browser with no server, like the CLI's <code>--export-html</code> folder. Exported dashboards carry no docs links: finding tags and "learn more" references show as plain text, so look them up in these docs yourself. An exported dashboard shows the conclusions worked out when it was exported, and its footer names the tool, core version and build that produced it; it has no <strong>Export evidence</strong> menu of its own. A file written by an incompatible release opens to a message saying so; export the run again with your current release. Turn on <strong>Redact identifiers</strong> first if the export is headed outside the environment that produced it, since that pseudonymizes the app id and any host/IP tokens in all three formats. <strong>Keyboard shortcuts</strong> (press <code>?</code> from anywhere) lists every shortcut. Rounding it out: a <strong>Docs</strong> link and a theme toggle.</p><h2 id="compare-two-runs" tabindex="-1">Compare two runs <a class="header-anchor" href="#compare-two-runs" aria-label="Permalink to "Compare two runs""></a></h2><p>To compare a baseline run against a candidate, say to check whether a tuning change helped, see <a href="./run-comparison.html">Run comparison mode</a>.</p><h2 id="ci-and-automation" tabindex="-1">CI and automation <a class="header-anchor" href="#ci-and-automation" aria-label="Permalink to "CI and automation""></a></h2><p>In a pipeline or a script, use the <code>analyze</code> CLI instead of the browser dashboard. It parses the same event logs and exits non-zero when a run crosses a threshold you set, so it can gate a build:</p><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npx</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> sparkforensics-cli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> sparkforensics-analyze</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">file</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dir</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">> [--max-runtime </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">ms]</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [--max-skew </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">ratio]</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
|
|
26
|
+
<div id="app"><div class="Layout" data-v-5d98c3a5><!--[--><!--]--><!--[--><span tabindex="-1" data-v-0b0ada53></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-0b0ada53>Skip to content</a><!--]--><!----><header class="VPNav" data-v-5d98c3a5 data-v-ae24b3ad><div class="VPNavBar" data-v-ae24b3ad data-v-6aa21345><div class="wrapper" data-v-6aa21345><div class="container" data-v-6aa21345><div class="title" data-v-6aa21345><div class="VPNavBarTitle has-sidebar" data-v-6aa21345 data-v-1168a8e4><a class="title" href="/docs/" data-v-1168a8e4><!--[--><!--]--><!--[--><img class="VPImage logo" src="/docs/favicon.svg" alt data-v-8426fc1a><!--]--><span data-v-1168a8e4>SparkForensics</span><!--[--><!--]--></a></div></div><div class="content" data-v-6aa21345><div class="content-body" data-v-6aa21345><!--[--><!--]--><div class="VPNavBarSearch search" data-v-6aa21345><!--[--><!----><div id="local-search"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><span class="vp-icon DocSearch-Search-Icon"></span><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"><kbd class="DocSearch-Button-Key"></kbd><kbd class="DocSearch-Button-Key">K</kbd></span></button></div><!--]--></div><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-6aa21345 data-v-dc692963><span id="main-nav-aria-label" class="visually-hidden" data-v-dc692963> Main Navigation </span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink active" href="/docs/user-guide/getting-started.html" tabindex="0" data-v-dc692963 data-v-e56f3d57><!--[--><span data-v-e56f3d57>User Guide</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/contributor-guide/development-setup.html" tabindex="0" data-v-dc692963 data-v-e56f3d57><!--[--><span data-v-e56f3d57>Contributor Guide</span><!--]--></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/docs/tuning-reference/" tabindex="0" data-v-dc692963 data-v-e56f3d57><!--[--><span data-v-e56f3d57>Tuning Reference</span><!--]--></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-6aa21345 data-v-6c893767><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-6c893767 data-v-5337faa4 data-v-1d5665e3><span class="check" data-v-1d5665e3><span class="icon" data-v-1d5665e3><!--[--><span class="vpi-sun sun" data-v-5337faa4></span><span class="vpi-moon moon" data-v-5337faa4></span><!--]--></span></span></button></div><!----><div class="VPFlyout VPNavBarExtra extra" data-v-6aa21345 data-v-bb2aa2f0 data-v-cf11d7a2><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-cf11d7a2><span class="vpi-more-horizontal icon" data-v-cf11d7a2></span></button><div class="menu" data-v-cf11d7a2><div class="VPMenu" data-v-cf11d7a2 data-v-b98bc113><!----><!--[--><!--[--><!----><div class="group" data-v-bb2aa2f0><div class="item appearance" data-v-bb2aa2f0><p class="label" data-v-bb2aa2f0>Appearance</p><div class="appearance-action" data-v-bb2aa2f0><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" title aria-checked="false" data-v-bb2aa2f0 data-v-5337faa4 data-v-1d5665e3><span class="check" data-v-1d5665e3><span class="icon" data-v-1d5665e3><!--[--><span class="vpi-sun sun" data-v-5337faa4></span><span class="vpi-moon moon" data-v-5337faa4></span><!--]--></span></span></button></div></div></div><!----><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-6aa21345 data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><div class="divider" data-v-6aa21345><div class="divider-line" data-v-6aa21345></div></div></div><!----></header><div class="VPLocalNav has-sidebar empty" data-v-5d98c3a5 data-v-a6f0e41e><div class="container" data-v-a6f0e41e><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-a6f0e41e><span class="vpi-align-left menu-icon" data-v-a6f0e41e></span><span class="menu-text" data-v-a6f0e41e>Menu</span></button><div class="VPLocalNavOutlineDropdown" style="--vp-vh:0px;" data-v-a6f0e41e data-v-8a42e2b4><button data-v-8a42e2b4>Return to top</button><!----></div></div></div><aside class="VPSidebar" data-v-5d98c3a5 data-v-319d5ca6><div class="curtain" data-v-319d5ca6></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-319d5ca6><span class="visually-hidden" id="sidebar-aria-label" data-v-319d5ca6> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="no-transition group" data-v-c40bc020><section class="VPSidebarItem level-0 has-active" data-v-c40bc020 data-v-b3fd67f8><div class="item" role="button" tabindex="0" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><h2 class="text" data-v-b3fd67f8>User Guide</h2><!----></div><div class="items" data-v-b3fd67f8><!--[--><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/getting-started.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>Getting started</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/understanding-findings.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>Understanding findings</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/run-comparison.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>Run comparison mode</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/mcp-tools.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>MCP tools reference</p><!--]--></a><!----></div><!----></div><div class="VPSidebarItem level-1 is-link" data-v-b3fd67f8><div class="item" data-v-b3fd67f8><div class="indicator" data-v-b3fd67f8></div><a class="VPLink link link" href="/docs/user-guide/alternative-log-retrieval.html" data-v-b3fd67f8><!--[--><p class="text" data-v-b3fd67f8>Finding your event log</p><!--]--></a><!----></div><!----></div><!--]--></div></section></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-5d98c3a5 data-v-1428d186><div class="VPDoc has-sidebar has-aside" data-v-1428d186 data-v-39a288b8><!--[--><!--]--><div class="container" data-v-39a288b8><div class="aside" data-v-39a288b8><div class="aside-curtain" data-v-39a288b8></div><div class="aside-container" data-v-39a288b8><div class="aside-content" data-v-39a288b8><div class="VPDocAside" data-v-39a288b8 data-v-3f215769><!--[--><!--]--><!--[--><!--]--><nav aria-labelledby="doc-outline-aria-label" class="VPDocAsideOutline" data-v-3f215769 data-v-a5bbad30><div class="content" data-v-a5bbad30><div class="outline-marker" data-v-a5bbad30></div><div aria-level="2" class="outline-title" id="doc-outline-aria-label" role="heading" data-v-a5bbad30>On this page</div><ul class="VPDocOutlineItem root" data-v-a5bbad30 data-v-b933a997><!--[--><!--]--></ul></div></nav><!--[--><!--]--><div class="spacer" data-v-3f215769></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-39a288b8><div class="content-container" data-v-39a288b8><!--[--><!--]--><main class="main" data-v-39a288b8><div style="position:relative;" class="vp-doc _docs_user-guide_getting-started" data-v-39a288b8><div><h1 id="getting-started" tabindex="-1">Getting started <a class="header-anchor" href="#getting-started" aria-label="Permalink to "Getting started""></a></h1><p>SparkForensics reads a Spark History Server event log and turns it into a dashboard of flagged bottlenecks. No account needed.</p><h2 id="local-server-mode" tabindex="-1">Run it locally <a class="header-anchor" href="#local-server-mode" aria-label="Permalink to "Run it locally {#local-server-mode}""></a></h2><p>The recommended way to run SparkForensics on your machine:</p><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> sparkforensics-server</span></span></code></pre></div><p>Open <code>http://127.0.0.1:4173</code> and drop a log in. This is <strong>local-server mode</strong>: it also fetches runs from a Spark History Server on your behalf. Port and environment-variable overrides, and what changes for a static deploy, are in the <a href="https://github.com/shuffle-works/sparkforensics#deploy-modes" target="_blank" rel="noreferrer">project README</a>.</p><p>The <a href="https://shuffle-works.github.io/sparkforensics/" target="_blank" rel="noreferrer">hosted demo</a> runs the same dashboard but can't fetch from a History Server. To work on SparkForensics itself, see <a href="./../contributor-guide/development-setup.html">Development setup</a>.</p><h2 id="load-a-run" tabindex="-1">Load a run <a class="header-anchor" href="#load-a-run" aria-label="Permalink to "Load a run""></a></h2><p>Drop a log file onto the landing page, or click <strong>Choose file</strong> to pick one. Parsing runs in a background worker, off the browser's main thread, so a multi-hundred-megabyte event stream doesn't freeze the tab. While it runs, the progress screen shows how far along it is and roughly how long is left; <strong>Cancel</strong> returns to the landing page.</p><p>No log of your own yet? Click <strong>Try a sample run</strong> on the landing page to load a bundled example run and see a populated dashboard right away. A line above its verdict says it is the sample, with <strong>Load my event log</strong> and a link to where to find one, so you can switch to your own run from there. <strong>Where do I find my event log?</strong>, under the buttons, is a short guide to turning event logging on and downloading a log from a History Server.</p><p>The app takes a newline-delimited JSON event log (one JSON event per line, the format Spark writes to <code>spark.eventLog.dir</code>), either plain or gzip/Zstandard/LZ4/Snappy-compressed. It also takes the zip a Spark History Server hands back, from the Spark UI's download link or from <code>GET /api/v1/applications/<appId>/logs</code>, as-is: drop the <code>.zip</code> and the app unwraps the log inside it, or reassembles the parts of a rolling log. The zip must hold one attempt: for an application that ran more than once, download <code>/api/v1/applications/<appId>/<attemptId>/logs</code> instead. Spark's <code>lzf</code> codec is not supported: set <code>spark.eventLog.compression.codec</code> to <code>zstd</code>, <code>lz4</code> or <code>snappy</code>.</p><p>Click <strong>Other sources</strong> on the landing page for two more ways in:</p><ul><li><strong>Choose rolling-log folder</strong>, for an <code>eventlog_v2_*</code> rolling directory. Drop the folder or point the picker at it; the app reassembles its parts in order before parsing.</li><li><strong>Fetch from Spark History Server</strong>, to pull an application straight from a reachable History Server. This needs <strong>local-server mode</strong> (see <a href="#local-server-mode">Run it locally</a>) and an application ID in one of the forms Spark itself uses: <code>application_<timestamp>_<id></code>, <code>local-<timestamp></code>, <code>app-<id></code>, <code>spark-<id></code>, or <code>driver-<id></code>. If the fetch fails, the panel names the problem (server unreachable, application not found, local server not running, and so on) and suggests what to try next.</li></ul><p>Can't reach the History Server directly (it's only reachable through an SSH bastion)? See <a href="./alternative-log-retrieval.html#behind-an-ssh-bastion">Behind an SSH bastion</a>.</p><p>In Chromium-based browsers, single files you open stay listed under <strong>Recent files</strong> on the landing page. Rolling-log folders, History Server fetches and the sample run are not listed.</p><h2 id="reading-the-dashboard" tabindex="-1">Reading the dashboard <a class="header-anchor" href="#reading-the-dashboard" aria-label="Permalink to "Reading the dashboard""></a></h2><p>When parsing finishes, the dashboard shows a board of widgets, each in its own card with a title. Every widget that flags a problem uses the same convention: a colored impact dot (critical / warning / info) and an ALL-CAPS tag for the bottleneck category (<code>SKEW</code>, <code>SPILL</code>, <code>GC</code>, and so on: see <a href="./understanding-findings.html">Understanding findings</a>). Widgets list every affected stage, not just the worst one. For a finding with a run-time estimate, the dot's color tracks how much run time it could save rather than how unusual the metric looks, so a small-looking anomaly with a big payoff can outrank a dramatic one that would barely move your run time. A finding with only a resource estimate, or none, keeps the level its check assigned.</p><p>The board opens with a verdict: one line saying where to start, a short summary of what was found, and up to three numbered next steps. Each step says what the finding measured and what to try, and has a <strong>Show evidence</strong> button that jumps to the finding's detail widget. Findings on the same stage are folded into one step, because they usually share a cause and their savings overlap rather than add up. Each savings figure says what it counts: a time such as "58.6s of run time" is how much sooner the run could finish, while a resource figure such as "3.0 GB-h of unused executor memory" or "0.7 core-h of core time" is cluster time a fix would free up, which cuts cost but may not shorten the run. A figure "of idle core capacity" counts allocated cores that ran no task, not work a fix removes. <strong>Copy next steps</strong> copies the whole plan as a plain checklist (run, verdict, and numbered steps with their stage and savings) to paste into a ticket or a message. Steps follow the same savings ranking as the rest of the board. When much of the run's executor capacity sat idle, the summary says so, without moving cluster size ahead of a bigger fix.</p><p>The verdict also says how the run ended. When every job succeeded it says so. When a job failed, the title says the run failed (or how many of its jobs did), quotes the first line of the reason Spark recorded, and puts the failure first in the next steps, ahead of any speed-up, since a job has to finish before its speed matters.</p><p>A run is called clean only when the log had everything its checks need. When something was missing, the run is never called clean: the Findings tab's <strong>Clean checks</strong> list says which checks could not run and, where Spark has one, the setting to turn on for the next run (for example <code>spark.eventLog.logStageExecutorMetrics=true</code> for per-executor memory). If nothing else was found, the verdict title says so too.</p><p>A run scorecard sits under the verdict: <strong>Wall-clock</strong> (total run time), <strong>Efficiency</strong> (the share of that time with a stage running; higher is better; <strong>Not measured</strong> when no stage in the log recorded an end) and <strong>Unused core time</strong> (the share of executor core time that ran no task, the same idle figure a verdict step reports; lower is better). When the log has no complete application timing, one <strong>Timing unavailable</strong> notice replaces the three tiles. A collapsed <strong>New to Spark tuning?</strong> primer in the verdict explains stages, tasks, executors, shuffle and how to read savings. Stage labels such as <strong>Stage 7</strong> open that stage's details: how long it ran and what share of the run that was, then each of its findings with what it measured, what to try and a <strong>Show evidence</strong> button, followed by its overview, task, locality, I/O, spill, GC and plan sections (spill and GC only when the stage had them). Below it, two tabs split the rest of the board:</p><ol><li><strong>Findings</strong>: every flagged finding and its detail widget, grouped by impact band (Critical, Warning, Info). Within a band, a recommendation row for a bottleneck type collapses into a summary row when it fires more than once; clicking the summary row expands its full list, and clicking any single row or widget jumps straight to that finding. Each band leads with its recommendation rows; its detail widgets (the charts and per-stage numbers behind them) sit under <strong>Show the evidence</strong>, and <strong>Show evidence</strong> on any finding opens them for you. Advanced view shows the widgets without that step. Widgets that found nothing fold away into a "Clean checks" disclosure. A check the log lacked the data for is listed there under <strong>Not checked on this log</strong>, not as a pass, with the reason and the setting to turn on: for example every per-stage check when no stage finished, or core usage, memory and executor churn when the log has no end-of-run record. This is the tab you land on.</li><li><strong>Full app report</strong>: the wall-clock and executor timelines, the stage table, and the reference-only cards, led by core usage by locality, which shows even on a clean run.</li></ol><p>Click a finding's tag to open its reference material in a slide-in panel beside the dashboard: the dashboard stays visible and interactive, so you can check a metric against the reference without losing your place. Esc closes the panel, even while you are reading or scrolling inside it. The topbar's <strong>Docs</strong> button opens these docs in a new tab.</p><h3 id="advanced-view" tabindex="-1">Advanced view <a class="header-anchor" href="#advanced-view" aria-label="Permalink to "Advanced view""></a></h3><p>The topbar has an <strong>Advanced view</strong> toggle. It's off by default, which keeps each widget to the finding itself and what to do about it. Turn it on to also show confidence levels, supporting evidence, and a page icon beside each finding's tag that opens its entry in these docs, plus a few extra table columns and the finding filter bar (impact, type, stage). A filter that is already active, for example from a shared link, keeps the filter bar visible either way. In the verdict, each step also says how its savings figure was estimated (measured or modeled, and whether the stage ran alone or shared the cluster, which makes the figure a range from a floor to an optimistic high), shows any confidence marker, and a line states how the steps are ordered. The scorecard switches from plain captions to the raw run and idle-time breakdown, and the newcomer primer is hidden. Advanced view also turns on single-key triage shortcuts: <code>j</code> and <code>k</code> step through the verdict's steps and then every finding row, <code>Enter</code> shows the focused finding's evidence, or expands a grouped finding, <code>f</code> jumps to the filters, and <code>1</code> and <code>2</code> switch between <strong>Findings</strong> and <strong>Full app report</strong>. They stay off in the default view, so they never surprise a first-time visitor or a screen-reader user. Your choice is remembered across runs.</p><h3 id="the-rest-of-the-topbar" tabindex="-1">The rest of the topbar <a class="header-anchor" href="#the-rest-of-the-topbar" aria-label="Permalink to "The rest of the topbar""></a></h3><p>Once a run is loaded, the topbar also carries a few more controls. The run name opens a menu of recent files, to switch to another run or load a new file. When the parser skipped malformed lines, a warning beside the name says how many. The count chip ("4 critical") counts the same findings the verdict ranks; click it to jump to that band of the Findings list (a board filter hiding the band is cleared, with a notice saying so). It reads <strong>Run failed</strong> when a job failed and there is no finding to count, <strong>No findings</strong> only when the verdict calls the run clean, and <strong>Not fully checked</strong> otherwise. For keyboard users, the first Tab stop is <strong>Skip to the verdict</strong>. <strong>New analysis</strong> goes back to the landing page to load another run; dropping a file onto the dashboard loads it too. <strong>Compare with another run</strong> keeps this run as the baseline and asks only for the other one (see <a href="./run-comparison.html">Run comparison mode</a>). <strong>Plan graph</strong> opens an interactive node-and-edge view of the run's SQL execution plan, filterable down to I/O operators (scan, exchange), a broader "basic" set, or every operator. <strong>Export evidence</strong> downloads the current run's findings as a portable Markdown or JSON report, the same report the CLI produces, or as <strong>Download HTML dashboard</strong>: one <code>.html</code> file holding this dashboard for the run, which opens in any browser with no server, like the CLI's <code>--export-html</code> folder. Exported dashboards carry no docs links: finding tags and "learn more" references show as plain text, so look them up in these docs yourself. An exported dashboard shows the conclusions worked out when it was exported, and its footer names the tool, core version and build that produced it; it has no <strong>Export evidence</strong> menu of its own. A file written by an incompatible release opens to a message saying so; export the run again with your current release. Turn on <strong>Redact identifiers</strong> first if the export is headed outside the environment that produced it, since that pseudonymizes the app id, the app name and any host/IP tokens in all three formats. <strong>Keyboard shortcuts</strong> (press <code>?</code> from anywhere) lists every shortcut. Rounding it out: a <strong>Docs</strong> link and a theme toggle.</p><h2 id="compare-two-runs" tabindex="-1">Compare two runs <a class="header-anchor" href="#compare-two-runs" aria-label="Permalink to "Compare two runs""></a></h2><p>To compare a baseline run against a candidate, say to check whether a tuning change helped, see <a href="./run-comparison.html">Run comparison mode</a>.</p><h2 id="ci-and-automation" tabindex="-1">CI and automation <a class="header-anchor" href="#ci-and-automation" aria-label="Permalink to "CI and automation""></a></h2><p>In a pipeline or a script, use the <code>analyze</code> CLI instead of the browser dashboard. It parses the same event logs and exits non-zero when a run crosses a threshold you set, so it can gate a build:</p><div class="language-sh vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sh</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npx</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -p</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> sparkforensics-cli</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> sparkforensics-analyze</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> <</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">file</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">|</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">dir</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">> [--max-runtime </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">ms]</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [--max-skew </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">ratio]</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
|
|
26
27
|
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [--max-spill </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">gb]</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [--max-failed-task-rate </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">pct]</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [--min-efficiency </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">pct]</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
|
|
27
|
-
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [--out </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">path]</span></span></code></pre></div><p><code>--min-efficiency</code> checks busy core time, the share of executor core time that ran tasks (100 minus the dashboard's Unused core time). It is not the dashboard's Efficiency tile, which is the share of wall-clock with a stage running, so the two can differ widely on the same run.</p><p>The command ships in the <code>sparkforensics-cli</code> package. To install it once: <code>npm i -g sparkforensics-cli</code>, then run <code>sparkforensics-analyze</code> directly.</p><p>Point it at a single event-log file, or at one run's <code>eventlog_v2_*</code> rolling-log directory; any other directory is rejected. Output goes to stdout; pass <code>--out <path></code> to write it to that file instead.</p><p>Pass <code>--export-html <dir></code> to write a self-contained HTML dashboard for the run into <code><dir></code> (which must not already exist or must be empty). Open <code><dir>/index.html</code> directly in a browser over <code>file://</code>, with no server, to get the same interactive dashboard offline, without the docs links. This makes it easy to archive or share a run. <code>--redact</code> applies to the exported report too.</p><
|
|
28
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> [--out </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">path]</span></span></code></pre></div><p>Output is JSON by default; <code>--format md</code> writes the Markdown report instead. Budget violations and inconclusive budgets print to stderr as <code>[violation] ...</code> and <code>[inconclusive] ...</code> lines. The exit code is 0 when every budget passes, 1 when one is violated, 3 when none is violated but one is inconclusive, 2 for a usage error (bad flags or arguments, an invalid <code>--thresholds</code> or <code>--budgets</code> file), 4 when the candidate log can't be read or parsed or its History Server fetch fails, 5 when the <code>--baseline</code> log can't be read or parsed, and 6 for an internal error, including a failed <code>--export-html</code>. When several of these apply, the worst wins in this order: 6, 5, 4, 1, 3, 0. A usage error exits 2 before any log is read.</p><p><code>--min-efficiency</code> checks busy core time, the share of executor core time that ran tasks (100 minus the dashboard's Unused core time). It is not the dashboard's Efficiency tile, which is the share of wall-clock with a stage running, so the two can differ widely on the same run.</p><p>The command ships in the <code>sparkforensics-cli</code> package. To install it once: <code>npm i -g sparkforensics-cli</code>, then run <code>sparkforensics-analyze</code> directly. <code>npx sparkforensics-analyze <file></code> is a shorter way to run the same command.</p><p>Point it at a single event-log file, or at one run's <code>eventlog_v2_*</code> rolling-log directory; any other directory is rejected. Output goes to stdout; pass <code>--out <path></code> to write it to that file instead.</p><p>Pass <code>--export-html <dir></code> to write a self-contained HTML dashboard for the run into <code><dir></code> (which must not already exist or must be empty). Open <code><dir>/index.html</code> directly in a browser over <code>file://</code>, with no server, to get the same interactive dashboard offline, without the docs links. This makes it easy to archive or share a run. <code>--redact</code> applies to the exported report too.</p><h3 id="machine-readable-fixes-and-costs" tabindex="-1">Machine-readable fixes and costs <a class="header-anchor" href="#machine-readable-fixes-and-costs" aria-label="Permalink to "Machine-readable fixes and costs""></a></h3><p>Each row of the JSON report's <code>findings</code> array carries two fields for a script or tuning loop that acts on the output without reading prose.</p><p><code>remediation</code> is an array of the property changes the row's <code>recommendation</code> names, in structured form. It is empty when the recommendation names no Spark property.</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"remediation"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span></span>
|
|
29
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"kind"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"conf"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"key"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"spark.sql.shuffle.partitions"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"direction"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"increase"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"suggested"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">800</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
30
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span></code></pre></div><ul><li><code>kind</code> is always <code>"conf"</code>: a Spark property.</li><li><code>direction</code> is <code>"increase"</code> or <code>"decrease"</code> (move the current value that way) or <code>"set"</code> (take the <code>suggested</code> value, for a switch or a class name).</li><li><code>suggested</code> is the value the detector computed, or <code>null</code> when it computes none. Counts are numbers, switches are booleans, sizes carry a Spark unit suffix (<code>"1024m"</code>).</li></ul><p><code>impactEstimate.coreTimeMs</code> is the busy core time the fix removes: the executor task time, in core-milliseconds, next to the <code>wallClock</code> range (elapsed time). It is a <code>{ "low": ..., "high": ... }</code> range with <code>low</code> equal to <code>high</code>, or <code>null</code> when the detector measures no such figure: <code>null</code> means unknown, never zero. Only figures read from the log count: GC time, the executor time of retried and discarded speculative attempts, and for skew and straggler findings the task time the fix removes from the slow tasks. These are set even when the log has no executor core data. A finding with only a wall-clock claim has <code>coreTimeMs: null</code>: its elapsed time is not converted to core time. Task run time is used, not <code>executorCpuTime</code>, so Python worker CPU is not missed.</p><p>Findings whose only figure is bytes or memory-time have <code>coreTimeMs: null</code>, as do findings whose core figure rests on an assumed constant: <code>coreLocality</code>'s per-task fetch penalty and the executor-hours or job-hours of <code>autoscalingChurn</code> and <code>jobFailureRate</code>. Their <code>rawWaste</code> is unchanged.</p><p><code>coreTimeMs</code> never includes idle capacity. A finding whose waste is allocated cores that ran no task (<code>utilization</code>, <code>stageShape</code>'s low parallelism and task/stage skew rows) has <code>coreTimeMs: null</code> and keeps its figure in <code>impactEstimate.rawWaste</code> with <code>"idle": true</code>; every surface labels it "of idle core capacity", not "of core time". A stage's slow tail is counted once: when <code>skew</code> and <code>straggler</code> both flag the same stage, <code>skew</code> carries the removed task time and <code>straggler</code> has <code>null</code>.</p><p>When the logged <code>spark.sql.shuffle.partitions</code> is already at or above the count a low-parallelism shuffle stage needs, the property is not what limits that stage: the recommendation points at the stage's own partitioning (<code>repartition(n)</code> or RDD parallelism) and <code>remediation</code> is empty.</p><p>A <code>remediation</code> that sets a property to a fixed value (for example <code>spark.sql.adaptive.skewJoin.enabled</code>, <code>spark.speculation</code> or <code>spark.dynamicAllocation.enabled</code>) is left out when the run's logged conf already has that value; booleans compare case-insensitively. The recommendation then stops naming that property and points at the remedy left (for example "AQE skew-join handling is already on, so salt the key or repartition on a better key"), so the text and <code>remediation</code> never disagree. The dashboard's one-line fix for a group of such findings follows the same logged conf. Only properties the event log records count: Spark's unlogged version defaults (such as skew-join handling being on by default with AQE in Spark 3.2+) are not modeled, so such a run can still get the suggestion.</p><p>Two logged settings change which fix is offered. Skew-join handling counts as already on only when <code>spark.sql.adaptive.enabled</code> is not logged <code>false</code>; with AQE logged off, the skew findings suggest setting <code>spark.sql.adaptive.enabled</code> to <code>true</code> instead. When <code>spark.sql.autoBroadcastJoinThreshold</code> is logged <code>-1</code> (auto-broadcast disabled), an over-broadcast finding has an empty <code>remediation</code> and points at removing the <code>broadcast()</code> hint.</p><p>The CLI also supports fetching a run directly from a reachable Spark History Server (<code>--shs-base-url</code>/<code>--app-id</code>/<code>--attempt-id</code>) instead of a local file, comparing a candidate run against a baseline with regression gating (<code>--baseline</code>/<code>--max-regression-pct</code>/<code>--regression-metric</code>/ <code>--fail-on-introduced</code>; the baseline is a local file or rolling-log directory, no History Server), several regression budgets and several candidates in one call (<code>--regression-budget</code>/<code>--budgets</code>, see <a href="#several-budgets-and-several-candidates">Several budgets and several candidates</a>), redacting the app id, the app name and any host/IP tokens before sharing output (<code>--redact</code>), and narrowing the findings to certain impact bands, types, or a stage (<code>--impact</code>/<code>--type</code>/<code>--stage</code>), and tuning detector thresholds from a file (<code>--thresholds</code>, below). Run it with <code>--help</code> for the full flag list.</p><p>Same caveat as above: if the History Server is only reachable through an SSH bastion, <code>--shs-base-url</code> can't reach it either: see <a href="./alternative-log-retrieval.html#behind-an-ssh-bastion">Behind an SSH bastion</a>.</p><p>Running in Airflow instead of a plain CI pipeline? See <a href="https://github.com/shuffle-works/sparkforensics-operator" target="_blank" rel="noreferrer">sparkforensics-operator</a>, an Airflow operator that wraps the CLI and acts on the result after each Spark job, so you don't have to wire up the call yourself.</p><p>Want an AI assistant to diagnose a run directly, without the dashboard or a CI gate? See <a href="./mcp-tools.html">MCP tools reference</a>.</p><h3 id="write-targets" tabindex="-1">Write targets <a class="header-anchor" href="#write-targets" aria-label="Permalink to "Write targets {#write-targets}""></a></h3><p>The JSON report has a top-level <code>writeTargets</code> object listing every SQL write in the run, so a script can check where a job wrote without reading the plan. It has no Markdown counterpart.</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
|
|
31
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "writes"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span></span>
|
|
32
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
33
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "sqlExecutionId"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
34
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "nodeId"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"e3:n0"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
35
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "command"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"InsertIntoHadoopFsRelationCommand"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
36
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "recognized"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
37
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "kind"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"path"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
38
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "target"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"hdfs://nn/sandbox/out/t1"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
39
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "outputRows"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5000000</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
40
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "raw"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"Execute InsertIntoHadoopFsRelationCommand hdfs://nn/sandbox/out/t1, false, Parquet, ..."</span></span>
|
|
41
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
42
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ],</span></span>
|
|
43
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "executionsWithoutPlan"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [{ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"sqlExecutionId"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">4</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"reason"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"noPlan"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }],</span></span>
|
|
44
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "skippedLines"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0</span></span>
|
|
45
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><ul><li><p><code>writes</code> holds one row per write node, ordered by SQL execution id and then by position in the plan. <code>command</code> is the plan node's name without the <code>Execute </code> prefix or an <code>Exec</code> suffix, <code>nodeId</code> is the plan node's id, and <code>raw</code> is the node's full plan string as the log recorded it.</p></li><li><p><code>kind</code> says how to read <code>target</code>:</p><ul><li><code>path</code> is a filesystem location.</li><li><code>table</code> is a table name whose catalog the command states or implies: a Hive or V1 command's <code>database.table</code>, or a DataSource V2 name with a catalog part (<code>catalog.namespace.table</code>).</li><li><code>unqualifiedTable</code> is a DataSource V2 name with no catalog part, such as <code>default.events</code> or <code>events</code>. The log does not say which catalog it lives in, so a check on its database part alone can match the wrong catalog.</li><li><code>jdbcTable</code> is a <code>dbtable</code> of a JDBC write. It names a table in an external database, not in a Spark catalog, and the database itself (the JDBC url) is not reported.</li></ul><p>Every target is copied verbatim from the plan string: nothing is resolved, so a relative path, an unexpanded <code>${var}</code> placeholder or a bare table name appears exactly as the log states it. Backticks around table name parts are dropped.</p></li><li><p><code>target</code> and <code>kind</code> are <code>null</code> when the target cannot be determined. That covers a node whose plan string has no target (Spark omits it for some commands, and a Delta write made through <code>SaveIntoDataSourceCommand</code> often has none), a redacted option value, a plan string that names more than one candidate target, a Delta <code>MERGE</code> (its plan string also prints the source), a Delta command whose first argument is not a <code>delta.</code> path, and a plan string Spark cut short. A target is reported only when its whole text is followed by a delimiter in the plan string: a path cut by <code>spark.sql.maxMetadataStringLength</code> or a name with a <code>...</code> marker in it is <code>null</code>, never a partial path. A trailing <code>... N more fields</code> after the target, such as the column list of <code>InsertIntoHadoopFsRelationCommand</code>, does not affect the target before it. Treat a <code>null</code> target as unknown, not as inside or outside any location.</p></li><li><p><code>outputRows</code> is the node's own <code>number of output rows</code> SQL metric. It is <code>null</code> when the log has no such metric for that node, which is the case for the DataSource V2 and Delta write nodes that report other metrics.</p></li><li><p><code>recognized</code> is <code>true</code> for the commands below and <code>false</code> for a write-like node that is not in that list. An unrecognized write always has <code>target</code> and <code>kind</code> <code>null</code>, and <code>raw</code> is the only information about it.</p></li><li><p><code>executionsWithoutPlan</code> lists the SQL executions whose writes the report cannot see, each with a <code>reason</code>. <code>noPlan</code> means the execution started but the log has no plan for it, such as an execution still running when the log was cut off (a plan is read when its execution ends). <code>unreadableStart</code> means the execution's start event was skipped because it could not be read, for example a plan nested deeper than 500 nodes or a start line cut off at the end of the log. A write in one of these executions is not in <code>writes</code>, so a non-empty list means the report may be incomplete.</p></li><li><p><code>skippedLines</code> is the number of log lines the parser could not read. A line cut off before its execution id is not in <code>executionsWithoutPlan</code>, so a non-zero count means a write may be missing even when that list is empty. It is <code>null</code> when the count is unknown.</p></li></ul><p>Recognized commands and where their target comes from:</p><table tabindex="0"><thead><tr><th>Command</th><th>Target</th></tr></thead><tbody><tr><td><code>InsertIntoHadoopFsRelationCommand</code></td><td><code>path</code>: the first argument, including for an <code>INSERT OVERWRITE ... PARTITION</code> that prints a static-partition map after it</td></tr><tr><td><code>InsertIntoHiveTable</code>, <code>CreateDataSourceTableAsSelectCommand</code>, <code>CreateHiveTableAsSelectCommand</code>, <code>OptimizedCreateHiveTableAsSelectCommand</code></td><td><code>table</code>: the first argument</td></tr><tr><td><code>SaveIntoDataSourceCommand</code></td><td><code>path</code> from the <code>path</code> option, or <code>jdbcTable</code> from the JDBC <code>dbtable</code>/<code>table</code> option</td></tr><tr><td><code>AppendData</code>, <code>OverwriteByExpression</code>, <code>OverwritePartitionsDynamic</code>, <code>ReplaceData</code>, <code>WriteDelta</code>, <code>WriteToDataSourceV2</code>, <code>AppendDataExecV1</code>, <code>OverwriteByExpressionExecV1</code></td><td><code>table</code> or <code>unqualifiedTable</code>: the <code>table=</code> of the connector's write object, for example Iceberg's <code>IcebergWrite(table=..., ...)</code></td></tr><tr><td><code>CreateTableAsSelect</code>, <code>AtomicCreateTableAsSelect</code>, <code>ReplaceTableAsSelect</code>, <code>AtomicReplaceTableAsSelect</code></td><td><code>table</code> or <code>unqualifiedTable</code>: the identifier after the catalog object, which does not name the catalog</td></tr><tr><td><code>WriteIntoDelta</code>, <code>WriteIntoDeltaCommand</code>, <code>UpdateCommand</code>, <code>DeleteCommand</code>, <code>CreateDeltaTableCommand</code>, <code>OptimizeTableCommand</code>, <code>RestoreTableCommand</code>, <code>DeltaReorgTableCommand</code></td><td><code>path</code>: the first argument when it is a <code>delta.</code> path table, otherwise <code>null</code></td></tr><tr><td><code>MergeIntoCommand</code></td><td>always <code>null</code></td></tr></tbody></table><p>A plan node not in the table is a write when its operator name (the first word of the node name after any leading <code>Execute</code>, so not the relation or table a scan prints after it), split into CamelCase words, contains <code>Write</code>, <code>Insert</code>, <code>Save</code>, <code>Overwrite</code>, <code>Append</code>, <code>Merge</code>, <code>Update</code>, <code>Delete</code>, <code>Truncate</code>, <code>Replace</code>, <code>Drop</code>, <code>Load</code>, <code>Vacuum</code>, <code>Convert</code>, <code>Clone</code>, <code>Restore</code>, <code>Optimize</code>, <code>Alter</code>, <code>Reorg</code>, <code>Rename</code> or <code>Call</code>, or contains <code>TableAsSelect</code>, <code>AddPartition</code>, <code>DropPartition</code>, <code>RenamePartition</code> or <code>RecoverPartitions</code>. That covers table DDL and <code>ADD PARTITION ... LOCATION</code>, and an Iceberg procedure call (<code>Call</code>), whose target is a procedure argument and not a path, so it is reported with a <code>null</code> target. A name containing <code>Join</code> (<code>SortMergeJoin</code>) and these operators that share a word with a write but write nothing are not: <code>WriteFiles</code> (the child of a write command), <code>AppendColumns</code>, <code>AppendColumnsWithObject</code>, <code>MergeRows</code>, <code>StateStoreSave</code>, <code>StateStoreRestore</code>, <code>SessionWindowStateStoreSave</code>, <code>SessionWindowStateStoreRestore</code> and <code>UpdateEventTimeWatermarkColumn</code>. The classification leans toward reporting too much: a node that matches by name is listed with a <code>null</code> target rather than dropped.</p><p>Only SQL writes are covered. Writes made outside Spark SQL, such as an RDD <code>saveAsTextFile</code> or a direct filesystem call from the driver, do not appear in the event log and are not in <code>writeTargets</code>.</p><h3 id="tuning-detector-thresholds" tabindex="-1">Tuning detector thresholds <a class="header-anchor" href="#tuning-detector-thresholds" aria-label="Permalink to "Tuning detector thresholds""></a></h3><p>Every check fires at a fixed default threshold. When your normal workload trips one on purpose, such as a join you skew deliberately, pass <code>--thresholds <file></code> to run that check with your own value. The file is JSON, keyed by detector, then by threshold name:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
|
|
28
46
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "skew"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"ratioWarn"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">6</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
29
47
|
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "shuffle"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"minBytes"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1073741824</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
30
|
-
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>The names, units and defaults are the <code>detectors</code> catalog in the CLI's JSON report (<code>thresholds</code> on each row): bytes are raw byte counts, times are milliseconds, and <code>*Pct</code>/<code>*Rate</code> values are fractions (<code>0.05</code> is 5%). A tier list such as <code>slowHost.ratioTiers</code> takes the same number of values, in ascending order. The <code>configAudit</code> checks can't be tuned: they compare your Spark settings against Spark's own defaults.</p><p>The CLI refuses to run, with exit code 2 and a message naming the problem, when the file can't be read, isn't valid JSON, or names an unknown detector or threshold, or a value of the wrong shape. It never falls back to the defaults silently.</p><p>A tuned run says so wherever it reports:</p><ul><li>Each finding from a tuned detector carries <code>tunedThresholds</code> (each overridden threshold's <code>value</code> and <code>default</code>), and its <code>validationRequired</code> text names them and, when the finding has an impact estimate, says that estimate is unvalidated. The estimates are calibrated against the default thresholds, so they were never checked for a finding your override lets through. Tuning <code>slowHost</code> also labels <code>stageSlowness</code> findings (as <code>slowHost.<name></code>), because a slow host hides a stage's slowness finding, so the override decides which of those you see.</li><li>A clean check measured against a tuned threshold carries <code>tunedThresholds</code> too, and its <code>thresholdSummary</code> states the tuned value.</li><li><code>summary.tunedThresholds</code> lists every tuned detector, and each tuned row of the <code>detectors</code> catalog shows the thresholds the run used.</li><li>The Markdown report adds a <code>Tuned thresholds</code> line to its header and a <code>tuned thresholds</code> line to each affected finding.</li></ul><p>An override equal to the default changes nothing and is not labeled. <code>--baseline</code> runs the baseline with the same overrides, so the comparison compares like with like. The budget flags are separate gates computed from the run's own figures; the one that recomputes a detector's figure, <code>--max-skew</code>, uses the file's <code>skew.minTasksForP95</code>, so it measures the same ratio the skew finding reports. <code>--export-html</code> writes the dashboard with the default thresholds, because the dashboard never tunes, and prints a note to stderr saying so. The browser dashboard has no tuning.</p><h3 id="regression-metric-keys" tabindex="-1">Regression metric keys <a class="header-anchor" href="#regression-metric-keys" aria-label="Permalink to "Regression metric keys""></a></h3><p><code>--regression-metric</code> (and the MCP <code>evaluate_budgets</code> tool's <code>regressionMetric</code>) takes one of these keys; the default is <code>wallClock</code>:</p><ul><li><code>wallClock</code>: wall-clock duration</li><li><code>executorRunTime</code>: summed executor run time</li><li><code>shuffleSpill</code>: shuffle spill</li><li><code>diskSpill</code>: disk spill</li><li><code>gcTime</code>: JVM GC time</li><li><code>taskSkew</code>: p95 task skew</li><li><code>failedTaskRate</code>: failed-task rate</li></ul><p>Four more keys, <code>inputBytes</code>, <code>outputBytes</code>, <code>taskCount</code> and <code>executorsAdded</code>, measure workload volume rather than performance. They have no better or worse direction, so a regression budget on one of them reports <code>inconclusive</code> whenever the value changes, and passes when it doesn't.</p><p>Same caveat as above: if the History Server is only reachable through an SSH bastion, <code>--shs-base-url</code> can't reach it either: see <a href="./alternative-log-retrieval.html">Alternative ways to get the logs</a>.</p><p>Running in Airflow instead of a plain CI pipeline? See <a href="https://github.com/shuffle-works/sparkforensics-operator" target="_blank" rel="noreferrer">sparkforensics-operator</a>, an Airflow operator that wraps the CLI and acts on the result after each Spark job, so you don't have to wire up the call yourself.</p><p>Want an AI assistant to diagnose a run directly, without the dashboard or a CI gate? See <a href="./mcp-tools.html">MCP tools reference</a>.</p></div></div></main><footer class="VPDocFooter" data-v-39a288b8 data-v-e257564d><!--[--><!--]--><!----><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-e257564d><span class="visually-hidden" id="doc-footer-aria-label" data-v-e257564d>Pager</span><div class="pager" data-v-e257564d><!----></div><div class="pager" data-v-e257564d><a class="VPLink link pager-link next" href="/docs/user-guide/understanding-findings.html" data-v-e257564d><!--[--><span class="desc" data-v-e257564d>Next page</span><span class="title" data-v-e257564d>Understanding findings</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
|
|
31
|
-
|
|
48
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>The names, units and defaults are the <code>detectors</code> catalog in the CLI's JSON report (<code>thresholds</code> on each row): bytes are raw byte counts, times are milliseconds, and <code>*Pct</code>/<code>*Rate</code> values are fractions (<code>0.05</code> is 5%). A tier list such as <code>slowHost.ratioTiers</code> takes the same number of values, in ascending order. The <code>configAudit</code> checks can't be tuned: they compare your Spark settings against Spark's own defaults.</p><p>The CLI refuses to run, with exit code 2 and a message naming the problem, when the file can't be read, isn't valid JSON, or names an unknown detector or threshold, or a value of the wrong shape. It never falls back to the defaults silently.</p><p>A tuned run says so wherever it reports:</p><ul><li>Each finding from a tuned detector carries <code>tunedThresholds</code> (each overridden threshold's <code>value</code> and <code>default</code>), and its <code>validationRequired</code> text names them and, when the finding has an impact estimate, says that estimate is unvalidated. The estimates are calibrated against the default thresholds, so they were never checked for a finding your override lets through. Tuning <code>slowHost</code> also labels <code>stageSlowness</code> findings (as <code>slowHost.<name></code>), because a slow host hides a stage's slowness finding, so the override decides which of those you see.</li><li>A clean check measured against a tuned threshold carries <code>tunedThresholds</code> too, with the tuned value and the default. Its <code>thresholdSummary</code> shows the tuned numbers for most detectors (for example <code>skew</code>, <code>gc</code>, <code>spill</code>, <code>slowHost</code>). A few summaries are fixed text with no number to tune.</li><li><code>summary.tunedThresholds</code> lists every tuned detector, and each tuned row of the <code>detectors</code> catalog shows the thresholds the run used.</li><li>The Markdown report adds a <code>Tuned thresholds</code> line to its header and a <code>tuned thresholds</code> line to each affected finding.</li></ul><p>An override equal to the default changes nothing and is not labeled. <code>--baseline</code> runs the baseline with the same overrides, so the comparison compares like with like. The budget flags are separate gates computed from the run's own figures; the one that recomputes a detector's figure, <code>--max-skew</code>, uses the file's <code>skew.minTasksForP95</code>, so it measures the same ratio the skew finding reports. <code>--export-html</code> writes the dashboard with the default thresholds, because the dashboard never tunes, and prints a note to stderr saying so. The browser dashboard has no tuning.</p><h3 id="metrics-block" tabindex="-1">Metrics block <a class="header-anchor" href="#metrics-block" aria-label="Permalink to "Metrics block""></a></h3><p>The CLI's JSON output carries a <code>metrics</code> block next to the report, for scripts that act on a run without reading the report. With <code>--baseline</code> it is inside <code>candidate</code>. It has its own <code>schemaVersion</code>, separate from the report's. A figure the log cannot provide is <code>null</code>, never <code>0</code>: a log from a Spark version that records no CPU time has <code>executorCpuTimeMs: null</code>, and a log cut off before any executor joined has null allocation. The time, data and task figures, run-level and per row, include the work of every attempt of a resubmitted stage, failed attempts too, and the tasks of a failed attempt that end after it (killed or still running when it failed); skew describes the latest attempt.</p><table tabindex="0"><thead><tr><th>Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>runComplete</code></td><td><code>true</code> when the log has an application-end record. <code>false</code> means the log was cut off and every total covers only what it recorded.</td></tr><tr><td><code>time.wallClockMs</code></td><td>Application start to end; null without both.</td></tr><tr><td><code>time.executorCpuTimeMs</code></td><td>Summed CPU time of every task attempt, including failed attempts, retries and speculative copies that lost. Null when no task recorded any. Misses Python worker CPU, see <code>python</code>.</td></tr><tr><td><code>time.executorRunTimeMs</code>, <code>time.gcTimeMs</code></td><td>Summed run time and JVM GC time of every task attempt, counted like the CPU time.</td></tr><tr><td><code>data.memorySpillBytes</code>, <code>data.diskSpillBytes</code></td><td>Summed spill.</td></tr><tr><td><code>data.shuffleReadBytes</code>, <code>data.shuffleWriteBytes</code></td><td>Summed shuffle bytes, local plus remote on the read side.</td></tr><tr><td><code>data.inputBytes</code>, <code>data.outputBytes</code>, <code>data.outputRows</code></td><td>Summed input, output and rows written. <code>outputRows</code> is null when no task reported rows.</td></tr><tr><td><code>data.peakExecutionMemoryBytes</code></td><td>The largest per-task peak execution memory. Null when every task reports 0.</td></tr><tr><td><code>shape.taskCount</code>, <code>shape.stageCount</code></td><td>Distinct task records and stages.</td></tr><tr><td><code>shape.failedStageAttempts</code></td><td>Stage attempts that ended with a failure reason, including an attempt Spark then resubmitted (a fetch failure, for example).</td></tr><tr><td><code>shape.retriedStages</code></td><td>Stages submitted more than once.</td></tr><tr><td><code>shape.failedTasks</code>, <code>shape.retriedTasks</code></td><td>Task-level counts: tasks whose final attempt failed, and task attempts superseded by a retry.</td></tr><tr><td><code>shape.maxSkew</code></td><td>The largest stage skew ratio, the figure <code>--max-skew</code> checks (P95 over median, or max over median for a stage with few tasks; <code>skew.minTasksForP95</code> from <code>--thresholds</code> applies).</td></tr><tr><td><code>allocation.coreHours</code>, <code>allocation.memoryGbHours</code></td><td>See <a href="#allocation">Allocation</a>.</td></tr><tr><td><code>python.shareOfTaskRunTime</code></td><td>See <a href="#python-share">Python share</a>.</td></tr><tr><td><code>stages</code></td><td>The per-stage rows.</td></tr></tbody></table><p><code>stages</code> holds one row per stage fingerprint, keyed by the same fingerprint the run comparison matches stages on (the normalized stage name plus the stage's plan nodes), so the same stage has the same key in a baseline and a candidate run. A stage repeated in a loop shares one key: its row sums the repeats and lists them in <code>stageIds</code>. A row carries <code>durationMs</code>, <code>executorCpuTimeMs</code>, <code>executorRunTimeMs</code>, <code>gcTimeMs</code>, the spill, shuffle, input and output figures, <code>outputRows</code>, <code>peakExecutionMemoryBytes</code>, <code>taskCount</code>, <code>failedTasks</code>, <code>retriedTasks</code> and <code>skew</code> (the largest skew ratio among the repeats) as above, plus <code>failed</code> (an attempt of the stage failed), <code>retried</code> (the stage was submitted more than once) and <code>python</code> flags. The stage-attempt figures, run-level and per row, are null for a stage record that carries no attempt count. With <code>--redact</code> the <code>metrics</code> and <code>effectiveConf</code> blocks are built from the redacted run the report uses, so they carry the same <code>host-N</code> and <code>app-N</code> pseudonyms: the keys come from redacted stage names.</p><h4 id="allocation" tabindex="-1">Allocation <a class="header-anchor" href="#allocation" aria-label="Permalink to "Allocation""></a></h4><p><code>allocation.coreHours</code> is the sum over executors of cores times hours alive. <code>allocation.memoryGbHours</code> is the sum of memory times hours alive, in GiB (1024 MiB). An executor is alive from its executor-added event to its first later executor-removed event. One with no removal event closes at the application end when the log has one. In a cut-off log (<code>runComplete</code> false) it closes at the last timestamp the log records (the latest application, stage or executor event), so the figure is a lower bound.</p><p>Cores per executor are the executor-added event's total cores. Under dynamic allocation or YARN defaults, where <code>spark.executor.cores</code> is not set, that is the core count the cluster manager actually granted; when the event carries none, <code>spark.executor.cores</code> is used. If neither exists the core-hours are null.</p><p>Memory per executor is the container size Spark requests: <code>spark.executor.memory</code> (1g when unset), plus the overhead, plus <code>spark.memory.offHeap.size</code> when <code>spark.memory.offHeap.enabled</code> is <code>true</code>, plus <code>spark.executor.pyspark.memory</code>. The overhead is <code>spark.executor.memoryOverhead</code>, else the legacy <code>spark.yarn.executor.memoryOverhead</code>, else the larger of 384 MiB and <code>spark.executor.memoryOverheadFactor</code> (default 0.1) times the executor memory. Memory GB-hours are null when the log records no Spark properties at all, or a memory property cannot be read. Driver resources are not included.</p><h4 id="python-share" tabindex="-1">Python share <a class="header-anchor" href="#python-share" aria-label="Permalink to "Python share""></a></h4><p><code>python.shareOfTaskRunTime</code> is the summed task run time of Python stages divided by the summed task run time of all stages; null when no task run time was recorded. It is <code>0</code> for a run with task time and no Python stage. Executor CPU time counts only the JVM task thread, so on a run with a high share it misses the CPU of the Python worker processes; treat <code>executorCpuTimeMs</code> as unreliable there.</p><p>A stage is a Python stage when either signal holds:</p><ul><li>a plan node attributed to it is a Python operator (<code>PythonRDD</code>, <code>BatchEvalPython</code>, <code>ArrowEvalPython</code>, <code>PythonMapInArrow</code>, or a pandas/Arrow grouped or map operator such as <code>FlatMapGroupsInPandas</code>, including suffixed variants such as <code>BatchEvalPythonUDTF</code> and <code>FlatMapGroupsInPandasWithState</code> and the Spark 4.1 <code>ArrowAggregatePython</code> and <code>ArrowWindowPython</code>), which catches Python UDFs and UDTFs inside SQL;</li><li>its name or call site names <code>PythonRDD</code> or <code>org.apache.spark.api.python</code>, which catches RDD lambdas that have no plan and stages that cannot be matched to one.</li></ul><p>The "tasks mostly idle" check that keeps a low-CPU stage from being treated as waiting on an external system uses the same test.</p><h4 id="other-surfaces" tabindex="-1">Other surfaces <a class="header-anchor" href="#other-surfaces" aria-label="Permalink to "Other surfaces""></a></h4><p>Every figure the metrics block shares with another surface reads the same core code as that surface. The run comparison (the dashboard's comparison view, the CLI's <code>--baseline</code> and the MCP <code>compare_runs</code> tool) uses the same sums for run time, CPU time, GC time, spill, input, output and task count, so a comparison metric equals the matching <code>metrics</code> field for the same run, including the work of failed and speculative task attempts. Per-stage views in the dashboard (the stage table and stage detail) describe the stage's latest attempt and the task attempts that won, so a stage's own figures can be smaller than its row in <code>metrics.stages</code>. The dashboard's Efficiency card reports available capacity (peak concurrent cores times run time), which differs from <code>allocation.coreHours</code> (cores times the hours each executor was alive) under dynamic allocation. The utilization finding's <code>cpuUtilizationPct</code> is null, as <code>time.executorCpuTimeMs</code> is, when the log recorded no CPU time.</p><h3 id="effective-conf" tabindex="-1">Effective conf <a class="header-anchor" href="#effective-conf" aria-label="Permalink to "Effective conf""></a></h3><p>The JSON output also carries <code>effectiveConf</code> (with its own <code>schemaVersion</code>), the Spark properties the run started with, so a script can check that a <code>--conf</code> overlay took effect. It is null when the log records no Spark properties. With <code>--baseline</code> it is inside <code>candidate</code>.</p><ul><li><code>values</code>: property to value, for every property that is not withheld.</li><li><code>maskedKeys</code>: properties present in the log whose value is withheld.</li><li><code>absentKeys</code>: with <code>--conf-keys</code>, the requested properties the log does not contain.</li></ul><p>A value is withheld when its key or value matches Spark's default secret pattern (<code>(?i)secret|password|token|access[.]?key</code>), as Spark's own redaction does, when its key names another credential form (<code>passwd</code>, <code>pwd</code>, <code>pass</code>, <code>apiKey</code>, <code>accountKey</code> as in <code>fs.azure.account.key.*</code>, <code>privateKey</code>, <code>sas</code>, <code>sig</code>, <code>credential</code>), when the key or value matches the job's own <code>spark.redaction.regex</code> (when the log records one; if that pattern uses syntax JavaScript cannot evaluate, every value is withheld), or when the key or value matches <code>--conf-redact-regex <pattern></code>. A withheld property shows that it is present, not what it is set to, so the output cannot tell you whether a masked property has the value you expected. No hash or other derivative of a withheld value is emitted.</p><p>In every other value, credentials are replaced with <code>[redacted]</code>: in a value that looks like a URL, <code>user:password@host</code> userinfo and the Oracle thin <code>user/password@host</code> form; in any value, <code>name=value</code> parameters named <code>password</code>, <code>passwd</code>, <code>pwd</code>, <code>pass</code>, <code>apikey</code>, <code>accountkey</code>, <code>sas</code>, <code>sig</code>, <code>signature</code> and similar (JDBC and ODBC strings, query strings, JVM options), including signature parameters such as Azure SAS <code>sig=</code> and <code>X-Amz-Signature=</code>. <code>--conf-keys a,b</code> narrows <code>values</code> and <code>maskedKeys</code> to the named properties.</p><p>With <code>--redact</code>, the host names the report pseudonymizes become <code>host-N</code> in every value: the values of <code>*.host</code> and <code>*.hostname</code> properties, executor hosts, and IP or EC2-style addresses. <code>spark.app.name</code> becomes the application's <code>app-N</code> pseudonym. A host name that appears only in some other property, such as <code>spark.yarn.historyServer.address</code>, is left as is; withhold it with <code>--conf-redact-regex</code> or leave it out with <code>--conf-keys</code>.</p><h3 id="regression-metric-keys" tabindex="-1">Regression metric keys <a class="header-anchor" href="#regression-metric-keys" aria-label="Permalink to "Regression metric keys""></a></h3><p><code>--regression-metric</code>, <code>--regression-budget</code> and the <code>--budgets</code> file (and the MCP <code>evaluate_budgets</code> tool's <code>regressionMetric</code>) take one of these keys; the default for <code>--regression-metric</code> is <code>wallClock</code>:</p><ul><li><code>wallClock</code>: wall-clock duration</li><li><code>executorRunTime</code>: summed executor run time</li><li><code>shuffleSpill</code>: memory spill (Spark's <code>memoryBytesSpilled</code>)</li><li><code>diskSpill</code>: disk spill (Spark's <code>diskBytesSpilled</code>)</li><li><code>gcTime</code>: JVM GC time</li><li><code>taskSkew</code>: p95 task skew</li><li><code>failedTaskRate</code>: failed-task rate</li><li><code>executorCpuTime</code>: summed executor CPU time, in milliseconds (unavailable for a run whose log never recorded it)</li><li><code>allocatedCoreHours</code>: executor cores times hours alive, see <a href="#allocation">Allocation</a></li></ul><p>Four more keys, <code>inputBytes</code>, <code>outputBytes</code>, <code>taskCount</code> and <code>executorsAdded</code>, measure workload volume rather than performance. They have no better or worse direction, so a regression budget on one of them reports <code>inconclusive</code> whenever the value changes, and passes when it doesn't.</p><h3 id="several-budgets-and-several-candidates" tabindex="-1">Several budgets and several candidates <a class="header-anchor" href="#several-budgets-and-several-candidates" aria-label="Permalink to "Several budgets and several candidates""></a></h3><h4 id="several-regression-budgets" tabindex="-1">Several regression budgets <a class="header-anchor" href="#several-regression-budgets" aria-label="Permalink to "Several regression budgets""></a></h4><p><code>--max-regression-pct</code> checks one metric. To gate on several in one call, repeat <code>--regression-budget <metric>:<pct></code>, or list them in a file passed with <code>--budgets <file></code>. Both need <code>--baseline</code>, and each budget takes a <a href="#regression-metric-keys">metric key</a> and a percentage of zero or more:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">sparkforensics-analyze</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> candidate</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --baseline</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> baseline</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> \</span></span>
|
|
49
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --regression-budget</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> wallClock:10</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> --regression-budget</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> gcTime:25</span></span></code></pre></div><p>The <code>--budgets</code> file is JSON with one key, <code>regression</code>, mapping a metric key to its percentage:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{ </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"regression"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"wallClock"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">10</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"gcTime"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">25</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"failedTaskRate"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">0</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } }</span></span></code></pre></div><p>The CLI refuses to run, with exit code 2 and a message naming the problem, for an unknown top-level key, an unknown metric key, a percentage that is not a non-negative number (in the file a JSON number, not a string; on the flag plain digits such as <code>10</code> or <code>2.5</code>), or a file that can't be read or isn't valid JSON.</p><p>The budgets combine like this:</p><ul><li><code>--max-regression-pct</code> with <code>--regression-metric</code> (default <code>wallClock</code>) still works and counts as one more budget next to the new ones.</li><li>Each metric can be budgeted once across the legacy pair, the repeated flag and the file. A metric named twice is a usage error (exit 2), even when both give the same percentage.</li><li>Each budget is checked on its own and gives one <code>max-regression</code> result that carries <code>metric</code>, the key it checks. Any violated budget makes the run exit <code>1</code>; otherwise an inconclusive one makes it exit <code>3</code>. A budget on a metric the baseline or the candidate log can't provide is inconclusive, never a pass.</li></ul><h4 id="several-candidates" tabindex="-1">Several candidates <a class="header-anchor" href="#several-candidates" aria-label="Permalink to "Several candidates""></a></h4><p>Pass two or more logs as positional arguments, with <code>--baseline</code>, to compare each against the same baseline. The baseline is parsed once. Output is NDJSON: one line per candidate, in argument order, written as each one finishes. <code>--out <path></code> writes the lines to a file instead of stdout, and <code>--format ndjson</code> selects this output for a single candidate too. The mode can't be combined with <code>--export-html</code>, <code>--shs-base-url</code>, <code>--format json</code> or <code>--format md</code> (exit 2). <code>--redact</code>, <code>--thresholds</code>, <code>--impact</code>, <code>--type</code>, <code>--stage</code>, <code>--conf-keys</code>, <code>--conf-redact-regex</code> and every budget flag apply to each candidate.</p><p>Each line is one JSON object:</p><table tabindex="0"><thead><tr><th>Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>log</code></td><td>The candidate path, as given on the command line. With <code>--redact</code>, <code>candidate-<n></code> instead, <code>n</code> being the candidate's 1-based position.</td></tr><tr><td><code>status</code></td><td><code>pass</code>, <code>violation</code>, <code>inconclusive</code> or <code>error</code>.</td></tr><tr><td><code>exitCode</code></td><td>The exit code this line alone would give: <code>0</code> for <code>pass</code>, <code>1</code> for <code>violation</code>, <code>3</code> for <code>inconclusive</code>, and for <code>error</code> <code>4</code> (the log can't be read or parsed) or <code>6</code> (an internal failure while analyzing it).</td></tr><tr><td><code>error</code></td><td>The message when <code>status</code> is <code>error</code>; otherwise <code>null</code>. With <code>--redact</code>, a generic message that names no path.</td></tr><tr><td><code>budgets</code></td><td>This candidate's budget results, each with <code>name</code>, <code>status</code> (<code>pass</code>, <code>violation</code> or <code>inconclusive</code>) and <code>detail</code>, plus <code>metric</code> on <code>max-regression</code>. Empty for an <code>error</code> line.</td></tr><tr><td><code>candidate</code></td><td>The candidate's report with its <code>metrics</code> and <code>effectiveConf</code> blocks, the same object the single-candidate JSON output carries under <code>candidate</code>. <code>null</code> for an <code>error</code> line.</td></tr><tr><td><code>comparison</code></td><td><code>verdict</code>, <code>confidence</code>, <code>reason</code>, <code>matchedCoverage</code>, <code>metrics</code> and <code>findings</code>, the same object the single-candidate JSON output carries under <code>comparison</code>. <code>null</code> for an <code>error</code> line.</td></tr></tbody></table><p>A line's <code>status</code> follows the single-candidate rules: <code>violation</code> if any budget result is a violation, else <code>inconclusive</code> if any is inconclusive (including the <code>run-complete</code> check for a log with no <code>ApplicationEnd</code>), else <code>pass</code>.</p><p>A candidate that can't be read or parsed doesn't stop the others. Its line has <code>status: "error"</code>, <code>exitCode: 4</code>, the message in <code>error</code>, and <code>null</code> for <code>candidate</code> and <code>comparison</code>, as a single-candidate run exits <code>4</code> for a candidate it can't parse. The process exit code is the worst line, in the order <code>6</code>, <code>5</code>, <code>4</code>, <code>1</code>, <code>3</code>, <code>0</code>, so an unreadable candidate outranks a violation and a violation outranks an inconclusive result.</p><p>Two failures stop the batch before any line is written:</p><ul><li>A usage error (bad flags or arguments, an invalid <code>--thresholds</code> or <code>--budgets</code> file) exits <code>2</code>.</li><li>A baseline that can't be read or parsed exits <code>5</code>. Fix the baseline and run the batch again.</li></ul><p>An internal failure that isn't tied to one candidate, such as an unwritable <code>--out</code> path, exits <code>6</code>.</p><p>With <code>--redact</code>, no candidate path is written anywhere, since event-log file names usually carry the app id. <code>log</code> and the <code>stderr</code> line prefixes name each candidate by its 1-based position (<code>candidate-1</code>, <code>candidate-2</code>, ...), and an <code>error</code> line carries a generic message instead of the parser's, which may quote the path: <code>Candidate 2 could not be read or parsed.</code>, or <code>could not be analyzed</code> for an internal failure. An unreadable baseline prints <code>The baseline could not be read or parsed.</code> to <code>stderr</code>. A single-candidate run does the same, naming <code>The baseline</code> or <code>The candidate</code>.</p></div></div></main><footer class="VPDocFooter" data-v-39a288b8 data-v-e257564d><!--[--><!--]--><!----><nav class="prev-next" aria-labelledby="doc-footer-aria-label" data-v-e257564d><span class="visually-hidden" id="doc-footer-aria-label" data-v-e257564d>Pager</span><div class="pager" data-v-e257564d><!----></div><div class="pager" data-v-e257564d><a class="VPLink link pager-link next" href="/docs/user-guide/understanding-findings.html" data-v-e257564d><!--[--><span class="desc" data-v-e257564d>Next page</span><span class="title" data-v-e257564d>Understanding findings</span><!--]--></a></div></nav></footer><!--[--><!--]--></div></div></div><!--[--><!--]--></div></div><!----><!--[--><!--]--></div></div>
|
|
50
|
+
<script>window.__VP_HASH_MAP__=JSON.parse("{\"contributor-guide_architecture_board-widgets.md\":\"D0Kkk_r5\",\"contributor-guide_architecture_detector-contract.md\":\"caA46z-u\",\"contributor-guide_architecture_drill-down.md\":\"CdtlKHl-\",\"contributor-guide_architecture_impact-estimation.md\":\"BNp14LtL\",\"contributor-guide_architecture_index.md\":\"B9yZieYL\",\"contributor-guide_architecture_overview.md\":\"DBGo-bwn\",\"contributor-guide_architecture_state-and-history.md\":\"w4lwybdh\",\"contributor-guide_architecture_widget-rendering.md\":\"CcvqxnZI\",\"contributor-guide_architecture_worker-protocol.md\":\"CKeFttKj\",\"contributor-guide_contributing.md\":\"CQMvGwFt\",\"contributor-guide_development-setup.md\":\"D9bu4j_c\",\"contributor-guide_testing.md\":\"By6XGR9a\",\"index.md\":\"CHJVslga\",\"tuning-reference_anti-patterns.md\":\"D08Uaod7\",\"tuning-reference_aqe.md\":\"C1Ae6A8u\",\"tuning-reference_bottleneck-broadcast-sizing.md\":\"6wjKuJNE\",\"tuning-reference_bottleneck-cold-start.md\":\"BVQbsuHP\",\"tuning-reference_bottleneck-duplicate-plan-subtree.md\":\"DtaYnaiF\",\"tuning-reference_bottleneck-failures.md\":\"Z1Tdmdd3\",\"tuning-reference_bottleneck-gc.md\":\"BCnXLf1n\",\"tuning-reference_bottleneck-job-failure-rate.md\":\"CcvQj2g9\",\"tuning-reference_bottleneck-memory-utilization.md\":\"B-C0-8h-\",\"tuning-reference_bottleneck-retry-waste.md\":\"DCnhWiqj\",\"tuning-reference_bottleneck-shuffle.md\":\"B5OMJCd3\",\"tuning-reference_bottleneck-skew.md\":\"Cvo6NQm_\",\"tuning-reference_bottleneck-slow-host.md\":\"ja5qr3wq\",\"tuning-reference_bottleneck-small-files.md\":\"Crjtb0i2\",\"tuning-reference_bottleneck-spill.md\":\"Bl8KtSRg\",\"tuning-reference_bottleneck-straggler.md\":\"B0VN8reT\",\"tuning-reference_bottleneck-tiny-tasks.md\":\"DQCftGhu\",\"tuning-reference_bottleneck-utilization.md\":\"BXDl30f9\",\"tuning-reference_caching.md\":\"CcaiMn_7\",\"tuning-reference_cluster-config.md\":\"DbWG1yZq\",\"tuning-reference_config.md\":\"YUqSpwFB\",\"tuning-reference_data-formats.md\":\"C5DX5xL4\",\"tuning-reference_index.md\":\"ZVIpQ6Dk\",\"tuning-reference_intro.md\":\"BSSaDlqx\",\"tuning-reference_joins.md\":\"BVhNCDnE\",\"tuning-reference_memory-model.md\":\"CfjFdgbl\",\"tuning-reference_metrics.md\":\"C64VNEL4\",\"tuning-reference_partitioning.md\":\"CtLK0xsS\",\"tuning-reference_pyspark.md\":\"D-92XgF8\",\"tuning-reference_shuffle.md\":\"Iq2v1oHJ\",\"tuning-reference_spark-architecture.md\":\"_qwnE_Sc\",\"tuning-reference_table-formats.md\":\"BSYEK-eN\",\"user-guide_alternative-log-retrieval.md\":\"z-8npxNk\",\"user-guide_getting-started.md\":\"BJW5oVhe\",\"user-guide_mcp-tools.md\":\"DcLiOa9t\",\"user-guide_run-comparison.md\":\"qin0a4IX\",\"user-guide_understanding-findings.md\":\"DzReyCXb\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"SparkForensics\",\"description\":\"Docs for using and contributing to SparkForensics\",\"base\":\"/docs/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"logo\":\"/favicon.svg\",\"nav\":[{\"text\":\"User Guide\",\"link\":\"/user-guide/getting-started\"},{\"text\":\"Contributor Guide\",\"link\":\"/contributor-guide/development-setup\"},{\"text\":\"Tuning Reference\",\"link\":\"/tuning-reference/\"}],\"sidebar\":{\"/tuning-reference/\":[{\"text\":\"Getting Started\",\"items\":[{\"text\":\"Introduction\",\"link\":\"/tuning-reference/intro\"}]},{\"text\":\"Optimization Guide\",\"items\":[{\"text\":\"Spark Execution Model\",\"link\":\"/tuning-reference/spark-architecture\"},{\"text\":\"Memory Management\",\"link\":\"/tuning-reference/memory-model\"},{\"text\":\"Partitioning\",\"link\":\"/tuning-reference/partitioning\"},{\"text\":\"Join Optimization\",\"link\":\"/tuning-reference/joins\"},{\"text\":\"Shuffle\",\"link\":\"/tuning-reference/shuffle\"},{\"text\":\"Data Formats\",\"link\":\"/tuning-reference/data-formats\"},{\"text\":\"Table Formats\",\"link\":\"/tuning-reference/table-formats\"},{\"text\":\"Caching & Persistence\",\"link\":\"/tuning-reference/caching\"},{\"text\":\"PySpark Specifics\",\"link\":\"/tuning-reference/pyspark\"},{\"text\":\"Adaptive Query Execution\",\"link\":\"/tuning-reference/aqe\"},{\"text\":\"Cluster Tuning\",\"link\":\"/tuning-reference/cluster-config\"},{\"text\":\"Anti-Patterns\",\"link\":\"/tuning-reference/anti-patterns\"}]},{\"text\":\"Detector Catalog\",\"items\":[{\"text\":\"Task Skew\",\"link\":\"/tuning-reference/bottleneck-skew\"},{\"text\":\"Shuffle I/O\",\"link\":\"/tuning-reference/bottleneck-shuffle\"},{\"text\":\"Memory / Disk Spill\",\"link\":\"/tuning-reference/bottleneck-spill\"},{\"text\":\"GC Pressure\",\"link\":\"/tuning-reference/bottleneck-gc\"},{\"text\":\"Cold Start\",\"link\":\"/tuning-reference/bottleneck-cold-start\"},{\"text\":\"Executor Utilization\",\"link\":\"/tuning-reference/bottleneck-utilization\"},{\"text\":\"Slow Host\",\"link\":\"/tuning-reference/bottleneck-slow-host\"},{\"text\":\"Task Failures\",\"link\":\"/tuning-reference/bottleneck-failures\"},{\"text\":\"Stragglers\",\"link\":\"/tuning-reference/bottleneck-straggler\"},{\"text\":\"Retry Waste\",\"link\":\"/tuning-reference/bottleneck-retry-waste\"},{\"text\":\"Tiny Tasks\",\"link\":\"/tuning-reference/bottleneck-tiny-tasks\"},{\"text\":\"Job Failure Rate\",\"link\":\"/tuning-reference/bottleneck-job-failure-rate\"},{\"text\":\"Memory Utilization\",\"link\":\"/tuning-reference/bottleneck-memory-utilization\"},{\"text\":\"Duplicate Plan Subtree\",\"link\":\"/tuning-reference/bottleneck-duplicate-plan-subtree\"},{\"text\":\"Small Files\",\"link\":\"/tuning-reference/bottleneck-small-files\"},{\"text\":\"Broadcast Sizing\",\"link\":\"/tuning-reference/bottleneck-broadcast-sizing\"}]},{\"text\":\"Reference\",\"items\":[{\"text\":\"Metrics Glossary\",\"link\":\"/tuning-reference/metrics\"},{\"text\":\"Spark Config Quick-Reference\",\"link\":\"/tuning-reference/config\"}]}],\"/user-guide/\":[{\"text\":\"User Guide\",\"items\":[{\"text\":\"Getting started\",\"link\":\"/user-guide/getting-started\"},{\"text\":\"Understanding findings\",\"link\":\"/user-guide/understanding-findings\"},{\"text\":\"Run comparison mode\",\"link\":\"/user-guide/run-comparison\"},{\"text\":\"MCP tools reference\",\"link\":\"/user-guide/mcp-tools\"},{\"text\":\"Finding your event log\",\"link\":\"/user-guide/alternative-log-retrieval\"}]}],\"/contributor-guide/\":[{\"text\":\"Contributor Guide\",\"items\":[{\"text\":\"Development setup\",\"link\":\"/contributor-guide/development-setup\"},{\"text\":\"Architecture\",\"link\":\"/contributor-guide/architecture/\",\"collapsed\":true,\"items\":[{\"text\":\"Overview\",\"link\":\"/contributor-guide/architecture/overview\"},{\"text\":\"Worker protocol\",\"link\":\"/contributor-guide/architecture/worker-protocol\"},{\"text\":\"State & history intake\",\"link\":\"/contributor-guide/architecture/state-and-history\"},{\"text\":\"Detector contract\",\"link\":\"/contributor-guide/architecture/detector-contract\"},{\"text\":\"Impact estimation\",\"link\":\"/contributor-guide/architecture/impact-estimation\"},{\"text\":\"Widget rendering\",\"link\":\"/contributor-guide/architecture/widget-rendering\"},{\"text\":\"Board widgets\",\"link\":\"/contributor-guide/architecture/board-widgets\"},{\"text\":\"Drill-down\",\"link\":\"/contributor-guide/architecture/drill-down\"}]},{\"text\":\"Testing & verification\",\"link\":\"/contributor-guide/testing\"},{\"text\":\"Contributing\",\"link\":\"/contributor-guide/contributing\"}]}]},\"search\":{\"provider\":\"local\"}},\"locales\":{},\"scrollOffset\":{\"selector\":\"[data-sf-anchor-offset]\",\"padding\":0},\"cleanUrls\":false}");</script>
|
|
32
51
|
|
|
33
52
|
</body>
|
|
34
53
|
</html>
|