Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

.nornir/databricks-spark-ui-parity-reasoning.md

Databricks / Apache Spark UI parity — a facett build map

Status: RESEARCH + DESIGN (no code). This doc catalogues everything in the open-source Apache Spark Web UI and the Databricks Spark/SQL observability surfaces, then maps each view onto facett's component family — what already covers it, what needs an extension, and what is net-new. The goal is "we want that class of UI in facett": a self-hosted, egui-native, headless-testable rebuild of the Spark/Databricks monitoring experience, fed by engine-agnostic view-models (the same source-agnostic seam as facett-pipeline and facett-grid).

facett owns only the picture. A host (knut / korp / nornir viz) maps the Spark REST API / event log / Databricks system.query.history onto facett view-models; facett never learns what a "stage" is. This mirrors the existing PipelineAdapter split (engine → adapter → normalized model → view).

Companion docs: components.md (the crate list this cites), facett-grid.md, facett-pipeline.md, facett-graphview.md, facett-syschart.md, roadmap.md.


0. Executive read

The Spark/Databricks UI is, structurally, five visual primitives repeated across ~12 tabs:

  1. Big sortable metric tables (Jobs, Stages, Tasks, Executors, Storage, Environment, Query History, JDBC/Connect sessions) — facett already nails this with facett-grid (1M-row virtualized, multi-sort, pinned cols, Σ footer, in-cell bars/sparklines/chips, conditional-format heat).
  2. Operator/RDD DAGs (job DAG, stage DAG, SQL query plan, Databricks Query Profile graph) — partly covered by facett-pipeline::PipelineView + facett-graph::DepGraphView; the gap is per-node metric bodies + cost-heat + AQE initial/final + collapse.
  3. Timeline / Gantt swimlanes (Jobs event timeline, per-stage task timeline with stacked phase segments) — net-new; nothing in facett draws a horizontal time-axis Gantt today (facett-plot is x/y only).
  4. Percentile / distribution rows (Summary Metrics for Tasks: Min/25/Median/ 75/Max; skew detection) — partly covered by facett-stat::StatGridView sparklines; needs a box/percentile cell.
  5. Time-series line+histogram pairs (Structured Streaming stats, Databricks compute metrics) — facett-plot covers the line/ bar half; the paired histogram + stacked "operation duration" band is an extension.

Plus two profiling artifacts (thread dump → flame graph, plan text tree) that are minor/long-tail.

The three highest-ROI builds (see §3) are: a facett-timeline Gantt (net-new, unlocks Jobs + Task timelines + Lakeflow run Gantt), a plan-DAG extension to facett-pipeline/DepGraphView for metric-bearing, cost-heated, collapsible nodes (the SQL plan / Query Profile graph), and wiring the existing facett-grid to the Stage/Task/Executor tables (near-zero-cost, huge coverage).


1. The full Spark / Databricks UI inventory

Legend for visual form: table · gantt (horizontal time-axis swimlane) · dag (node/edge operator graph) · plan-tree (collapsible text) · percentile (Min/25/Median/75/Max distribution row) · line+hist (timeline chart + paired histogram) · bar (progress / segmented bar) · flame (stacked-sample flame graph) · kv (key/value table).

1A. Open-source Apache Spark Web UI

View Data it shows Visual form Why useful
Jobs — landing header (User, Uptime, Scheduling Mode, Active/Completed/Failed counts); jobs table: Job Id, Description, Submitted, Duration, Stages succeeded/total, Tasks progress table + bar fastest triage — spot the failed/slow job, the stalled task bar
Jobs — event timeline executor add/remove markers + each job as a bar on a shared wall-clock axis gantt correlate executor churn (dynamic allocation, lost executors) with job timing
Job detail header (status, stage counts active/pending/completed/skipped/failed, SQL link); stages table (Stage Id, Desc, Submitted, Duration, Tasks bar, Input, Output, Shuffle R/W) table + bar which stage dominates; skipped = cache/shuffle reuse
Job detail — RDD DAG RDD/DataFrame vertices, op edges, boxed by stage (boundaries = shuffles); green = cached, grey = skipped dag how the computation split into stages, where shuffles force boundaries
Job detail — event timeline executor track + each stage as a bar gantt stage serialization vs overlap, gaps between stages
Stages — landing per-status stage tables (same cols as job detail); Fair Scheduler Pools table (name, min share, weight, active stages, running tasks, mode) table cross-job stage overview; pool fairness
Stage detail — header Total Time Across Tasks; Locality Summary (PROCESS/NODE/RACK/ANY); Input/Output/Shuffle R/W size+records; associated Job Ids kv stage-level totals + locality quality
Stage detail — operator DAG physical operators in the stage (BatchScan, WholeStageCodegen[id], Exchange, HashAggregate, Sort, ShuffledRowRDD) dag ties the stage back to exact operators
Stage detail — Summary Metrics for Tasks per-metric distribution at Min/25th/Median/75th/Max: Duration, GC, Scheduler Delay, (De)serialization, Getting Result, Peak Exec Memory, Input, Output, Shuffle Read (+ fetch wait, remote), Shuffle Write (+ time), Spill (mem), Spill (disk) percentile the #1 skew/straggler tool — Median≪Max ⇒ skew; high spill ⇒ low memory
Stage detail — Aggregated Metrics by Executor per-executor rollup: Exec Id, Address, Task Time, Total/Failed/Killed/Succeeded Tasks, excluded flag, Input/Output/Shuffle R/W, Spill mem/disk table skew (one task) vs bad node (one executor slow across all tasks)
Stage detail — Accumulators named accumulators + aggregated values table user counters
Stage detail — Tasks table per-task: Index, ID, Attempt, Status, Locality, Exec Id, Host, Launch Time, Duration, Scheduler Delay, Deser Time, GC, Result Ser, Getting Result, Peak Exec Mem, Accumulators, Input, Output, Shuffle Read fetch-wait/size/remote, Shuffle Write time/size, Spill mem/disk, Errors table row-level forensics — the exact straggler, its host, its error
Stage detail — task event timeline one bar per task, segmented by phase: Scheduler Delay, Task Deser, Shuffle Read, Executor Computing, Shuffle Write, Result Ser, Getting Result gantt (stacked) where time actually goes — lots of non-compute ⇒ over-partitioning/GC/scheduling
Storage — landing cached RDD/DF: Name/Id, Storage Level, Cached Partitions, Fraction Cached, Size in Memory, Size on Disk table confirm full materialization; spotted spill
Storage — detail per-partition: Block Name, Storage Level, Size mem/disk, hosting executor(s); data-distribution-on-cluster table table uneven partition distribution, eviction
Environment Runtime Info (Java/Scala), Spark Properties, Hadoop Properties, System Properties, Classpath Entries, Resource Profiles kv ground truth for "what config was actually in effect"
Executors per-executor + driver: Exec Id, Address, Status, RDD Blocks, Storage Mem (on/off heap), Disk Used, Cores, Active/Failed/Complete/Total Tasks, Task Time (GC Time), Input, Shuffle R/W, Logs; per-row Thread Dump / Heap Histogram / Flame Graph table (+ flame) cluster health/balance; dead nodes, GC-bound execs, hot threads
SQL/DataFrame — query list ID, Description, Submitted, Duration, Job IDs, status table bridge logical query → physical jobs
SQL/DataFrame — query plan DAG operator boxes grouped in blue WholeStageCodegen[id] boxes; per-node metrics: output rows, peak memory, spill, data size, scan/metadata time, shuffle bytes/records/time, fetch wait, sort time, hash-probe, build-side size, commit time, Python UDF bytes dag (metric-bearing) where rows explode, where memory peaks/spills, which operator dominates
SQL/DataFrame — plan text Parsed → Analyzed → Optimized logical → Physical plan (codegen *(id) markers, AdaptiveSparkPlan/isFinalPlan) plan-tree confirm Catalyst/AQE choices (pushdown, join type, coalescing)
Structured Streaming — query list Name, Status, ID, Run ID, Submitted, Duration, Avg Input/sec, Avg Process/sec, Latest Batch, Error table which stream is failing / falling behind
Structured Streaming — statistics Input Rate, Process Rate, Input Rows, Batch Duration (each line+hist); Operation Duration stacked (addBatch/getBatch/latestOffset/queryPlanning/walCommit); state metrics (total/updated rows, state memory, dropped-by-watermark, watermark gap) line+hist + gantt(stacked) input>process ⇒ backlog; batch-dur→trigger ⇒ falling behind; state growth ⇒ OOM risk
Spark Connect sessions table (User, session id, Start/Finish, Duration, executed requests); request/SQL stats linking to jobs/SQL table attribute jobs back to a remote client session
JDBC/ODBC (Thrift) sessions (User, IP, Session id, Start/Finish, Duration, Total Execute); SQL stats (User, Job id, Group id, Start/Finish/Close, Exec/Duration time, Statement, State, Detail) table BI admin: who ran what; compile vs exec vs fetch
(legacy) DStream Streaming per-batch Scheduling Delay, Processing Time, Total Delay; batches table line+hist + table superseded by Structured Streaming tab

1B. Databricks-specific additions / changes

View Data it shows Visual form Why useful
Embedded Spark UI (compute page) the full Spark tab set, proxied through control plane; live on running clusters, history-server replay (~last 1k jobs) on terminated; executor loss reason (decommission); Master worker-decommission surface (all of 1A) same Spark UI without driver network access; gated by workspace ACLs
Query Profile — graph operator DAG, leaves→results; each node shows switchable Time / Memory peak / Rows; highlight most-expensive nodes; zoom/search/filter; preview card dag (metric-bearing, cost-heat) visual hotspot finder for a single query
Query Profile — tree condensed hierarchy ordered slowest-first plan-tree slowest operators at a glance
Query Profile — overview status; user/compute/runtime; UUID; SQL; wall-clock "where time went" (waiting-for-compute / in-queue / scheduling / optimizing+pruning / executing / result-fetch); aggregated task time; rows returned/read; bytes read (+from cache); files/partitions read; files/bytes pruned; bytes spilled to disk; % task time in Photon; Top operators ranked by cost; Performance Insights (beta) bar (segmented) + table one-screen diagnosis: spill, skew, pruning effectiveness, Photon coverage
Query Profile — per-node metrics Time spent, Memory peak, Rows, Cumulative time, Tasks total time, Source/Sink rows; scan/IO (files read/pruned, sizes, bytes read/cache, metadata/scan time, partitions); Bytes spilled; # tasks; verbose CPU/cloud metrics dag node body attribute cost to the relational operator
Query History list (query text, status, start, duration, end, warehouse, user, rows, statement id); filters (user/date/compute/duration/status/type/tag); detail panel table + detail find slow/expensive/failed queries across a warehouse
Query History — phase breakdown segmented bar: Waiting-for-compute / Waiting-at-capacity (queue) / Scheduling / Compilation+pruning / Execution / Result-fetch (*_duration_ms) bar (stacked) is slowness provisioning, queuing, planning, execution, or fetch?
Photon indicators Photon* operator nodes + adapter boundary; % task time in Photon; coloring (Spark UI: orange=Photon/blue=Spark; Query Profile: purple=Photon/grey=Spark) dag coloring + metric is the query vectorized; where did it fall back to JVM
AQE adaptive plan AdaptiveSparkPlan root w/ isFinalPlan; initial vs current/final both shown; DAG mutates live; runtime stats isRuntime; AQEShuffleRead/CustomShuffleReader (Coalesced / skewed partition counts); dynamic SMJ→BHJ visible as plan diff dag (versioned) proof AQE re-planned; quantifies coalescing/skew-split
Lakeflow Jobs run views Matrix (runs×tasks status cells, bar-height=duration), Graph (task DAG, click=metrics+query-history links), Timeline (Gantt) (task bars + deps + perf insights), List (sortable) table/dag/gantt multi-task workflow monitoring; click-through to Query Profile
Compute Metrics (replaces Ganglia) Hardware: CPU util+active nodes (stacked by mode), container memory, JVM heap, network rx/tx, free FS, mem+swap. Spark: server-load distribution heatmap/tiles, task metrics, shuffle bytes, task duration. GPU: per-GPU util/mem/fb/enc/dec line/bar/heatmap I/O-bound (iowait), memory pressure/swap, skew (uneven tiles), GPU saturation
Thread dump → flame graph per-executor/driver JVM thread snapshot rendered as async-profiler stacked samples (frame width ∝ time) flame hot CPU paths / hung tasks without an external profiler
Streaming (notebook live + UI) live line charts per micro-batch + Raw StreamingQueryProgress JSON; numInputRows, input/processed rows/sec, durationMs phases, stateOperators (rows/memory/rocksdb), per-source lag (Kafka offsets-behind, Kinesis ms-behind, Auto Loader files-outstanding) line+hist + kv backlog, batch-dur trend, state growth, per-source lag

2. facett mapping — what covers it, the gap, what to build

Per Spark/Databricks view: the facett crate that covers it today, the gap, and what to build. Crates cited are real (see components.md and the workspace Cargo.toml members).

Spark/DBX view facett component today Gap What to build
Jobs / Stages list, Tasks table, Aggregated-by-Executor, Executors, Storage, Environment, Accumulators, Query History list, JDBC/Connect sessions ✅ facett-grid Grid (virtualized, multi-sort, pinned cols, Σ footer, in-cell progress bar for Tasks-done, sparkline, chips for status/locality, color-scale heat for duration/spill); facett-table for tiny lists none structural — pure wiring. Need a host adapter Spark REST/JSON → RecordBatch. Minor: a "Tasks succeeded/total" dual-segment progress cell (grid has single-value bar) Wire it + a tiny condfmt "stacked progress" cell variant. ~zero new component.
Environment (kv), Storage detail, streaming Raw JSON ✅ facett-grid/facett-table; facett-card CardView for the kv detail; facett-docview for big text none wire kv into a CardView or 2-col grid
Job DAG, Stage operator DAG 🟡 facett-pipeline PipelineView (L→R layered DAG, status colour, animated flow edges, job-progress bar, throughput edge labels) + facett-graph::DepGraphView (toposort columns, boxes/arrows, sublabel, click→drill-down) DAGs are stage-boxed (nested grouping), nodes need green=cached / grey=skipped semantics, and the job DAG groups RDDs inside stage boxes Extend PipelineView/DepGraphView: node grouping boxes (WholeStageCodegen / stage clusters) + per-node cached/skipped state colour. Most of the layout already exists.
SQL query plan DAG, Query Profile graph, Photon coloured plan, AQE adaptive plan 🟡 DepGraphView gives boxes+arrows+drill-down; PipelineView gives themed status colour The big gap: (a) per-node metric body (multi-line: rows / peak mem / spill / time), (b) cost-heat node fill (Time/Memory/Rows switch like Query Profile), (c) collapsible WholeStageCodegen/Photon groups, (d) initial-vs-final / isFinalPlan toggle showing two plan versions, (e) Photon orange/purple palette per node NET-NEW facett-plandag (or a heavy DepGraphView mode): metric-bearing, cost-heated, collapsible, versioned operator-DAG. See §4.
Plan text tree (parsed/analyzed/optimized/physical), Query Profile tree view 🟡 facett-jobview (hierarchical tree with ASCII branch guides, fold/unfold, depth) is the closest shape; facett-docview for static plan text jobview rows are job entries, not generic indented metric rows; needs slowest-first ordering + per-node metric column Extend facett-jobview into a generic indented metric tree (depth + guides + a value/heat column) — reuses its guide renderer + fold tween.
Jobs event timeline (executors + jobs/stages bars), Job-detail timeline ❌ nothing — facett-plot is x/y line/bar only, no horizontal time-axis Gantt whole primitive missing: wall-clock axis, stacked swimlanes (executor lifecycle / job / stage / task bars), zoom/pan, colour-by-status NET-NEW facett-timeline — a swimlane Gantt. See §4. (Tab label already exists in facett-tabbar: "🧵 Timeline".)
Per-stage task event timeline (stacked phase segments per task) ❌ none same Gantt primitive, but each bar is internally segmented into phases (scheduler delay / deser / shuffle read / compute / shuffle write / ser / getting-result) with a phase legend facett-timeline with segmented bars + legend (one component covers both timeline kinds).
Lakeflow run Gantt + Matrix + Graph Matrix → 🟡 facett-matrix (repo×aspect coloured cells — same shape as runs×tasks!); Graph → DepGraphView/PipelineView; Timeline → facett-timeline (new); List → facett-grid matrix bar-height-by-duration; timeline = the new component reuse facett-matrix for the run matrix (cell colour + a height/intensity tweak); rest covered above
Summary Metrics for Tasks (Min/25/Median/75/Max percentiles) 🟡 facett-stat StatGridView (label + value + inline bar + sparkline + verdict) no box/percentile cell; can't show 5-number distribution or the Median≪Max skew signal Extend facett-stat with a percentile/box-plot cell (5-number horizontal box). Small, high value (skew detection).
Structured Streaming statistics (Input/Process Rate, Input Rows, Batch Duration line+hist) 🟡 facett-plot PlotView (line/bars via egui_plot) missing the paired histogram beside each timeline, and the stacked "Operation Duration" band Extend facett-plot: a line+hist paired layout + a stacked-area kind (egui_plot supports both).
Compute Metrics (CPU/mem/heap/network/FS line charts) ✅ facett-plot line/area; ✅ facett-syschart for node badges server-load heatmap/tiles by node facett-plot covers the lines; the per-node load heatmap → facett-matrix (node×time coloured cells) or a facett-grid heat column
Streaming query list, Connect/JDBC sessions, state-operator metrics ✅ facett-grid none wire
Thread dump → flame graph ❌ none flame graph (stacked samples, width∝time) is its own primitive NET-NEW facett-flame (lower priority) — recursive stacked rects, zoom-on-click. Could share the facett-timeline painter (both are nested horizontal rects).
Executors thread dump (raw), driver/executor logs ✅ facett-log (ring log, kind-coloured) + facett-console (monospace terminal, ANSI, scrollback) none wire stderr/stdout/thread-dump text in
The whole multi-tab Spark UI shell ✅ facett-tabbar (tab bar; already ships a "Timeline / DepGraph / Release" demo) + facett-core::FacetDeck (ui tabbed + wall_ui multi-pane wall, palette/zoom/clipboard routed) none compose: one FacetDeck per Spark tab, exactly like facett-warehousedeck composes panes from existing widgets

Coverage tally: of the ~25 distinct views, ~12 are already covered by facett-grid/table/card/log/console/tabbar/deck (pure wiring), ~7 need extensions to existing crates (facett-pipeline/DepGraphView, facett-stat, facett-plot, facett-jobview, facett-matrix), and ~3 are net-new (facett-timeline, facett-plandag, facett-flame).


3. Prioritized build roadmap (highest ROI first)

Ordered by (views unlocked) × (how Spark-defining it is) ÷ (build cost).

P0 — Wire the tables onto facett-grid (days, no new component)

The cheapest, biggest single win. A host adapter SparkRestApi/EventLog → Arrow RecordBatch plus Grid::from_record_batch(...) immediately delivers: Jobs, Stages, Tasks, Aggregated-by-Executor, Executors, Storage, Environment, Query History list, Connect/JDBC sessions — ~half the entire UI. facett-grid already has the in-cell progress bar (task progress), sparkline, status chips (locality/status), and colour-scale heat (duration/spill) these tables want. Only addition: a dual-segment "succeeded/total" progress cell (Spark's task bar shows succeeded+running+failed). Deliverable: a knut-side SparkGridAdapter

P1 — facett-timeline (net-new): the Gantt swimlane

The single most Spark-defining visual that facett lacks. One component covers three views: the Jobs event timeline (executor lifecycle + job/stage bars), the per-stage task timeline (segmented phase bars), and the Lakeflow run Gantt. Wall-clock x-axis, stacked named lanes, segmented bars (per-phase colours from the active Theme), drag-pan + scroll-zoom (reuse facett-core::scroll_engine::SmoothScroll), click-select a bar → detail, hover tooltip. Facet + state_json like everything else. The "🧵 Timeline" tab already exists in facett-tabbar. See §4.

P2 — facett-plandag (net-new) / heavy DepGraphView mode: metric-bearing operator DAG

The SQL query plan / Databricks Query Profile graph — the other signature Databricks surface. Builds on the existing DepGraphView toposort + PipelineView theming, adding: (a) per-node metric body (rows / peak mem / spill / time), (b) cost-heat fill with a Time/Memory/Rows switch (Query Profile's core interaction), (c) collapsible WholeStageCodegen/Photon groups, (d) Photon orange/purple node palette, (e) initial-vs-final isFinalPlan toggle (two plan versions). High value, higher cost — schedule after the Gantt. See §4.

P3 — Targeted extensions (small, each unlocks one signature view)

P4 — facett-flame (net-new, long-tail): the thread-dump flame graph

Lowest frequency, but the only true flame surface (Executors → Thread Dump → Flame Graph). Recursive stacked rects, width∝samples, zoom-on-click. Cheap if it shares the facett-timeline nested-rect painter (a flame graph is a depth-stacked Gantt without a time axis) — which is a reason to build P1 first.

P5 — Compose the shell

A SparkUiDeck à la facett-warehousedeck: one facett-tabbar across Spark tabs, each tab a FacetDeck of the widgets above, in wall_ui (multi-pane) or tabbed mode. Zero new components — pure composition of P0–P4, exactly the warehousedeck pattern.


4. The 2–3 net-new facett components this implies

NEW #1 — facett-timeline (a swimlane Gantt) — P1, build first

Why new: facett has no horizontal time-axis view. facett-plot is x/y line/bar; PipelineView/DepGraphView are left-to-right topological (not temporal) DAGs; facett-jobview is a vertical list. A Gantt — bars positioned and sized by wall-clock time across stacked lanes — is a genuinely new primitive.

Model (source-agnostic, mirrors the PipelineAdapter seam):

TimelineModel { lanes: Vec<Lane>, t0, t1 }     // wall-clock window
Lane  { label, rows: Vec<Bar> }                 // e.g. "executor-3", "stage 7"
Bar   { start, end, status, label,              // status → Theme colour
        segments: Vec<Segment> }                // optional intra-bar phases
Segment { kind, fraction }                       // scheduler-delay/compute/shuffle…

Covers: Jobs event timeline, Job-detail timeline, per-stage task timeline (segmented bars = the phase legend), Lakeflow run Gantt. Reuses: Theme colours, effects::glow_rect for the selected bar, scroll_engine::SmoothScroll for pan/zoom, the Facet+state_json+FacetCaps contract. Caps: selectable

NEW #2 — facett-plandag (metric-bearing, cost-heated, collapsible operator DAG) — P2

Why new (vs extending): the SQL plan / Query Profile graph wants four things no current graph facet has at once: a multi-line metric body inside each node, cost-heat fill with a Time/Memory/Rows switch, collapsible operator groups (WholeStageCodegen / Photon), and a two-version isFinalPlan (AQE) toggle. DepGraphView gives the toposort columns + boxes + drill-down; PipelineView gives themed status. This composes both (the established "compose facett_graph, paint our own" pattern from facett-pipeline.md) and adds the metric/heat/collapse/version layer. Could ship as a heavy DepGraphView mode instead of a crate — decide at build time; the capabilities are what matter. Covers: SQL query plan DAG, Databricks Query Profile graph, Photon plan, AQE adaptive plan. For huge plans, it is the natural first consumer of facett-graphview's vello render engine + its stubbed LOD/culling/clustering (collapse dense subgraphs into super-nodes).

NEW #2b — the constraint overlay (bottleneck badge · downstream emphasis · critical path)

Design (what shipped). The operator DAG is a DAG, so it has the same constraint structure a funnel Plan-DAG has — a PlanDagView now surfaces where a plan is constrained, additively, over the metric/heat/collapse/version layer. A pure analysis on the resolved graph (facett-plandag/src/model.rs):

This mirrors funnel_core::PlanDag::{bottlenecks, critical_path}() — not a funnel-core dependency: facett-plandag renders a host-fed generic PlanModel (≠ a funnel Funnel) and is pinned to the buildable dependency-island of facett-core/facett-graph (the same reason it inlines facett-pipeline's status colours — see its Cargo.toml). The nornir-viz layer maps a funnel PlanDag → this PlanModel; the ranking rule (reach → fan_out → fan_in → id) is byte-identical so the two agree by construction.

The picture (facett-plandag/src/lib.rs, render), all gated on state.show_constraints (subtle + on by default; a header ⚑ constraints toggle / Msg::SetConstraints turns it off — see the SVG below):

Guide (host / robot). The overlay reads the SAME resolve() the painter and state_json read, so the pixels ARE the data: state_json gains bottlenecks (the full ranking), top_bottleneck (the badged id), critical_path (the chain) and show_constraints; top_bottleneck + show_constraints also join portable_state/load_state. A robot never touches egui — it asserts state_json["top_bottleneck"], or (render-proof) get_by_label the badge in the AccessKit tree. facett-plandag/tests/render.rs does exactly that on a wide-vs-deep DAG: the Deep node (fan_out 1, downstream 4) is badged over the Wide node (fan_out 3, downstream 3) — RED-when-broken if the reach ranking ever flips to fan-out-primary — plus overlay-off drops the badge.

For-idiots. Which box do I fix first? The one with the little ⚑ flag — it has the most work piled up behind it. The brighter a box's outer ring, the more the plan is waiting on it; the blue arrows trace the longest road through the plan (its schedule length). Flip the ⚑ constraints switch off if you just want the raw metrics.

NEW #3 — facett-flame (flame graph) — P4, optional / long-tail

Why new: depth-stacked sample rectangles (width∝time) with zoom-on-click — no facett component does this. Cheap if it reuses facett-timeline's nested-rect painter (a flame graph ≈ a depth-stacked Gantt with no time axis). Covers: Executors → Thread Dump → Flame Graph (the only true flame surface). Lowest priority — build only after the timeline painter exists to share.

Net-new count: 2 essential (facett-timeline, facett-plandag) + 1 optional (facett-flame). Everything else is facett-grid wiring or a bounded extension to facett-stat / facett-plot / facett-jobview / facett-matrix / facett-pipeline+DepGraphView, all composed under the existing facett-tabbar + FacetDeck shell.


Sources

Apache Spark Web UI: spark.apache.org/docs/latest/web-ui.html; apache/spark core/.../ui/jobs/StagePage.scala (verbatim task columns + the TIMELINE_LEGEND phase labels); The Internals of Spark Core (japila). Databricks: docs.databricks.com Query Profile, Query History (+ system.query.history schema), Photon, AQE (optimizations/aqe), cluster / compute metrics, Spark-UI debugging guide, Lakeflow job monitoring; the Photon SIGMOD 2022 paper (operator class names); Databricks engineering blogs (Structured Streaming UI in Spark 3.0, DBSQL Query Profile, Faster SQL with AQE). Caveats carried from research: Photon operator class names are from the SIGMOD paper not the docs; two Photon palettes (Spark UI orange/blue vs Query Profile purple/grey); AQE node naming drift (CustomShuffleReader in DBX docs vs AQEShuffleRead in OSS); Query-History column set partly inferred from filters + detail panel; ~1k-job replay limit is community-sourced, not an SLA.