Repository navigation
feat(analyze): correct metrics and evidence-based findings - #21
Merged
Merged
Conversation
Implements plans/improvements-2026-09/03-analysis.md (all three increments). Metric contract: - new sparkparse/metrics.py: canonical registry mapping verified classic and Photon/Connect raw metric names onto canonical names with unit, scope, aggregation, coverage and derivation. Unmapped metrics are preserved with canonical=null rather than guessed. - exact integer counts end to end: accumulator_totals carries value_exact (Int64) beside the Float64 value in both ingestion paths, so counts above 2**53 survive parse -> clean -> summary -> JSON. - a missing metric is null and a measured zero is 0; node_duration_minutes is null when a node reported no timing metric instead of claiming zero. Rules: - cartesian joins mean CartesianProduct, unconditional nested loops, or join_type=Cross, matched per (query_id, node_id). Conditional nested loops are reported separately and are no longer called cartesian. - row expansion is computed from a join's immediate inputs, descending only through row-preserving operators; scans are lineage only. - repeated scans count occurrences, including repeats inside one query. - largest scans label their byte source and fall back to rows; an unobserved scan is never presented as the largest. - new rules: scan efficiency, task stragglers (data skew only with size evidence), GC overhead, shuffle volume, AQE runtime adjustments. - every rule returns a RuleAssessment (evaluated / unsupported / insufficient_data) and verifies its counters are populated before claiming it evaluated anything. Presentation: - findings carry evidence with units, thresholds, confidence, caveats and a next investigation, and stay separate from the raw plan summary. - analyze CLI gains --compact, --top-n, --redact, --findings; redaction covers node names, which embed server operator text for unmapped Connect operators. - the dashboard shows confidence, caveats and a "Checks not run" panel so missing telemetry is visible.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements brief 03, all three increments.
Metric contract
sparkparse/metrics.py: a canonical registry mapping verified classic and Photon/Connect raw metric names onto canonical names with unit, scope, aggregation, coverage and derivation. Unmapped metrics are preserved withcanonical: nullrather than guessed —numRowsScanned≠numOutputRows,data sizeis not scan bytes, operator spill is not task memory/disk spill, and Photon'snumBytesRead(shuffle on an exchange, files on a scan) stays unmapped.accumulator_totalsnow carriesvalue_exact(Int64) beside theFloat64valuein both ingestion paths, populated for every metric that is not rescaled. A count of2**53 + 1makes it from the event log or Connect server through to the JSON export.null, a measured zero is0.node_duration_minutesis nownullwhen a node reported no timing metric, instead of claiming it ran instantly.nulltotals, not zeros.Rules
find_cartesian_joinsmeans cartesian:CartesianProduct, nested loops with no condition, orjoin_type=Cross, matched per(query_id, node_id). Conditional nested loops moved tofind_nested_loop_joins— thenested_loop_joinfixture's BNLJ isLeftOuterwith a condition and is no longer reported as cartesian.find_largest_scanslabelsbytes_sourceand falls back to rows; nothing observed means nothing is presented as "largest".RuleAssessment(evaluated/unsupported/insufficient_data) and checks its counters are actually populated before reportingevaluated, so a null column can't read as a confident zero.Presentation
sparkparse analyzegains--compact,--top-n,--redact,--findings. Redaction covers node names, which embed the server's operator text for unmapped Connect operators.Known limits, stated rather than papered over
ScanDetaildoes not retainPushedFilters/PartitionFilters, so scan efficiency reports rows read but discarded — not a pruning verdict.parse.pykeeps onlyisFinalPlan=truesnapshots, soaqe_plan_changereports optimizer-recorded adjustments on the surviving plan and says so in its assessment reason.Validation
Offline only — no live Spark or Databricks run.
just ci: 377 passed, 2 skipped; ruff clean; pyrefly unchanged at the two pre-existing errors (pages/home.py:132,pages/summary.py:172).New tests:
tests/test_metrics.py, synthetic-fixture and Photon-fixture coverage intests/test_analyze.py(reused node IDs across queries,CartesianProduct, conditional nested loops, zero-row inputs, filters before joins, reused exchange, repeated scan within one query, incomplete scan detail, plan spill without task rows, all-null scan ranking, unsupported/insufficient statuses, redaction of an unknown operator carrying a literal, and ingestion-to-JSON precision above 2**53). Helpers live intests/synthetic.py.Expected-output fixtures were regenerated; the only field changes are
node_duration_minutes(0.0→null) and the newvalue_exact.