Repository navigation
feat(dag-viewer): plan through stage_pipeline in the live viewer; delete dag_export - #686
Merged
Merged
Conversation
… with --table Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
server.py's /api/plan runs stage_pipeline (one language per plan; SQL tables via --table) and returns its asap-stage-pipeline/v1 document; stage-workload.js shows it as Stage 0 before and the Stage 3 selected plan after, with per-node costs. generate-sample.sh writes dag.example.json with stage_pipeline; render.py embeds a stage document unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
stage_pipeline and the viewer now cover the live path. CostUnit, the only asap_types::cost item still used, moves into asap-aware-mapping's cost_model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
stage_pipeline takes --promql and --sql in any order, each optionally named with --name; the Query planner sends every checked row with its name, and the sample is its default mixed workload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
Drop the old export format (pre/post-ASAP WorkloadDAG, translation cards, baseline/benefit and edge cost annotations, multi-file merge, render.py's IR labeling) and show Stage 3's candidate table from the stage document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W7qG9aFyPij5uWsyAJCxDW
A workload whose Stage 1 Cartesian product exceeds --max-combinations (default 10^4) is refused before enumeration: exit status 3 with the count and the limit, no document written. Planning itself still ranks every candidate; only --max-displayed caps the display. The count comes from Pass 1 alone (planning_stages::combinations). Also reject a --table declared twice, and say when a duplicate query name is a query's default name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…checks server.py plans under a lock, checks field types (request object, query name/language/text, selected schemas, epsilon and interval_ms not booleans), says which query already uses a duplicate name, and returns stage_pipeline's combination-limit refusal as 422 with its message. The editor shows server errors without the start-with-server.py hint and says "query-language frontends". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion errors
stageCandidateRows lists Stage 1 candidates that did not compose
("composition error") with their errors, after the pruned ones.
stageCandidateHeader adds the Stage 3 selection error to the table
header. Lane labels say "query-language frontends".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When Pass 1 fails, stage_pipeline still writes the document: the Stage 0 DAG, the error under stage1_logical_asap_optimization.error, and no selection. The combination limit no longer re-runs Pass 1: the library splits run_stage1_to_stage3 at Pass 1 (run_stage1_to_stage3_from_pass1, PerComputationCandidates::combinations), and planning_stages checks the limit on that one inventory (PlanningStagesError). A --table time_index that is not a column index is an error rather than no time column. Adds examples/scalar-queries.json, a stage_pipeline document with standalone scalar queries first, in the middle and last. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0 without a plan stageWorkload mapped query i to roots[i], but the physical DAG keeps standalone scalar queries in scalar_roots, so scalar(v), time() or 1 + 2 crashed the viewer. Operator queries now take the next operator root; a scalar query's root is a Scalar node showing its expression over the operator nodes it reads, in both the Stage 0 and the selected-plan view. The viewer shows the Stage 0 lanes when no plan was selected, and the candidate table header shows a Stage 1 (Pass 1) error. server.py checks column entries and time_index (400 rather than a stage_pipeline failure). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 tasks
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.
Why
The dag-viewer's live path (
server.py,generate-sample.sh, the Query planner's "Plan selected workload" button) still ran the legacydag_exportbin. That bin uses the legacy search, not the planner on main (Stage 1 → Stage 3,run_stage1_to_stage3). This PR moves the live path ontostage_pipeline. Once nothing needsdag_exportandasap_types::{dag_export, cost}, it deletes them.What
Capability mapping (
dag_export→stage_pipeline/viewer)dag_exportcapability--promql,--epsilon,--data-ingestion-interval-ms)server.py→stage_pipeline --promql … --epsilon … --interval-ms …--sql,--table-schema)stage_pipeline --sql … --table '<json>'(same column types; no built-in tables, the editor sends its declared ones)stage_pipelinetakes--promql/--sqlin any order. Each query is lowered in its own language, then planned as one workload (planning_stages::run_planning_stages_over).--name)stage_pipeline --nameafter each query setsworkload.queries[].id, which defaults toq<n>. The editor sends each row's name through/api/plan.stage0_query_language_frontends.dag. It is still written when Pass 1 fails: Stage 1 then carrieserrorand no candidates, nothing is selected, and the viewer shows the Stage 0 lanes with the error in the candidate table header.stage2_physical_asap_optimization.candidates[].dag(PhysicalASAPDAG), with the Stage 3 selected planstage3_plan_selection.costs[id].per_node; the viewer labels each post-ASAP nodecost X [× multiplier]and shows the entry in the node detailstage3_plan_selection(stageCandidateRows): the selected candidate, the other costed ones cheapest first, then the rejected and Stage 2 pruned ones with their reasons, then the Stage 1 candidates that did not compose (composition error) with their errors. The table header (stageCandidateHeader) gives the candidate count, the shown count when capped, and the Stage 3 selection error when there is one. The editor status line shows the selected id, total and candidate count.stage-workload.jsturns a stage document into the viewer's per-query pre (Stage 0) / post (selected Stage 2 plan) DAGs, using stage node ids asworkload_node_idso shared nodes merge. A standalone scalar query (scalar(v),time(),1 + 2) is read from the physical DAG'sscalar_rootsby query position; other queries take the next entry ofroots. In both views its root is aScalarnode showing the expression, over the operator nodes it reads.render.pytake one stage document, embedded unchanged and read bystage-workload.jsNot covered, dropped (accepted by the user):
--planner-cost-json/--default-cost/--topk-margin-json. These ranked the legacy search with a deployment physical-evidence document. Stage 3 prices candidates with its own built-in model. Evidence-driven Stage 3 cost is being redone in Calibrate plan selection's costs from measured sketch costs #679/Stage 3: evidence-driven cost (storage I/O, hand-off bytes) #615. The library types (PhysicalEvidenceSnapshot,PhysicalPlanCostModel) stay.dag_exportput on Stage 0 nodes fromasap_aware_mapping::explain_replacements(which ASAP replacement could apply there). The library function stays; the viewer does not show them yet.--progress:dag_export's per-phase progress and timing lines on stderr.stage_pipelineprints nothing while it runs.render.pyread onlyasap-stage-pipeline/v1documents. Removed: the WorkloadDAG pre/post reader, multi-file merge,render.py's IR labeling (prepare_workload,_semantic_label), the cost-annotation code and CSS, andpost_asap_fixture.json.test_render.pynow covers the stage-document path.Deleted:
crates/devtools/src/bin/dag_export.rs,crates/types/src/dag_export.rs,crates/types/src/cost.rs.Kept:
CostUnit. It is the onlyasap_types::costitem used elsewhere, so it moves intoasap_aware_mapping::cost_model, which already re-exported it. Its serde stays the same.How
stage_pipeline:--sql/--table(repeatable). Addsplanning_stages::lower_sql_roots, andworkload.queries[].languagenow records the real language.stage_pipeline: a workload with more than--max-combinations(default 10^4) whole-workload candidates is refused before Stage 1 enumerates any. It exits with status 3, the count and the limit, and writes no document. It is never truncated. Pass 1 runs once: the library splitsrun_stage1_to_stage3at Pass 1 (run_stage1_to_stage3_from_pass1,PerComputationCandidates::combinations), andplanning_stageschecks the limit on that inventory (PlanningStagesError). The library still enumerates and ranks every candidate; only--max-displayedcaps the display. A Pass 1 failure still writes the document (Stage 0 and the error). A--tabletime_indexthat is not a column index ("0",-1, out of range) is an error. A--tabledeclared twice is rejected, and a duplicate query name says when it is a query's default name.stage_pipeline/planning_stages: mixed-language ad-hoc runs and--name.run_planning_stages_over(roots, targets, data)is the language-agnostic core;run_planning_stages(workload, roots)calls it.server.py:/api/planplans one request at a time (a lock aroundstage_pipeline). It validates the editor payload: field types (the request is an object; query name, language and text; table column entries andtime_index; selected schemas are declared table names;epsilonandinterval_msare numbers, not booleans), unique table names, and unique query names (the error says which query already uses the name). SQL tables are passed only when there is a SQL query. It runsstage_pipeline(honoringCARGO_TARGET_DIR) and returns theasap-stage-pipeline/v1document. The combination-limit refusal comes back as 422 withstage_pipeline's message, which the editor shows as is./api/exampleservesdag.example.json. The editor's default rows are all checked again.stage-workload.js(new, DOM-free) is loaded beforeviewer.js.viewer.jsreads stage documents through it.generate-sample.shwritesdag.example.jsonwithstage_pipeline: the editor's default mixed, named workload (3 SQL + 2 PromQL), with--max-displayed 4. That flag is display-only, so Stage 3 still selects among all 144 candidates.node_hashtest now compares againststructural_hashdirectly.exact_compositionkeeps its physical-export timing assertions and drops the WorkloadDAG ones.dag_exportbin), chore(types): delete the dag_export module #617 (delete thedag_exportmodule) and the server/generate-sample/editor parts of feat(dag-viewer): redesign the viewer around the stage pipeline #574.Before
The editor and
server.pyrandag_export --post-asap(the legacy search).dag.example.jsonwas adag_exportWorkloadDAG.After
The editor and
server.pyrunstage_pipeline(Stage 0 → Stage 3). The viewer shows Stage 0 as pre-ASAP and the Stage 3 selected physical plan as post-ASAP, with per-node Stage 3 costs.dag_export,asap_types::dag_exportandasap_types::costare gone.Verification
cargo fmt --all -- --check: OKcargo clippy --workspace --all-targets -- -D warnings: OKcargo nextest run --workspace: 1556 passed, 50 skipped (including the combination limit at 3^9 = 19683 > 10^4, duplicate tables, default-name messages,time_indexchecks, the Pass 1 failure document, and thatexamples/scalar-queries.jsonis current)python3 test_render.py(fromtools/dag-viewer/): 27 OK. These are stage-document only: render,stageWorkload(including scalar queries first, in the middle and last, and a Pass 1 failure document),stageCandidateRows(with composition-error rows),stageCandidateHeader(with the Stage 1 and selection errors), rejection of other JSON, and selection logic, run with py_mini_racer.python3 -m unittest test_server: 11 OK, including mixed-language order with names, request type checks (with columns andtime_index), duplicate table and query names, one planning request at a time, the 422 combination-limit refusal, and/api/planreturning a stage_pipeline document whose query keeps its editor namebash tools/verify_metricsql_external_consumer.sh: OKgit grep -w dag_export,asap_types::dag_exportandasap_types::costfind matches only in Markdown docs (below)Limitations
tools/dag-viewer/README.md,docs/user_guide_docs/run-a-query.md,docs/develop_docs/{library-api, physical-handoff-costs, storage-operation-costs, end-to-end-accuracy-guarantees, asap-aware-mapping-architecture}.md,docs/design_docs/decisions/{concat-unique-keys, cse-cost-model}.md,old_docs/README.md.q5), not by the editor's names.Human review — do not complete with an agent
🤖 Generated with Claude Code