Skip to content

Comments

Native graphical query plan viewer (#220)#243

Merged
erikdarlingdata merged 11 commits intodevfrom
feature/query-plan-viewer-220
Feb 23, 2026
Merged

Native graphical query plan viewer (#220)#243
erikdarlingdata merged 11 commits intodevfrom
feature/query-plan-viewer-220

Conversation

@erikdarlingdata
Copy link
Owner

Summary

  • Adds a full graphical query plan viewer to both Dashboard and Lite, matching SSMS visual style with operator icons, cost percentages, edge arrows, and rich tooltips
  • Supports both estimated and actual execution plans with runtime statistics
  • Implements tabbed plan interface — open multiple plans side by side, each with independent zoom/scroll
  • Includes an 11-rule plan analysis engine that flags common performance anti-patterns with actionable suggestions
  • Parser covers ~95% of the SSMS 22 ShowPlan XSD v1.599 (2611-line schema), including SQL Server 2022+ features like PSP Dispatcher and Cardinality Feedback

Closes #220

Changes

Plan Viewer UI (both apps)

  • PlanViewerControl.xaml/.cs — graphical canvas with zoom/pan, node rendering, edge drawing, properties panel, collapsible warnings/missing indexes sections
  • PlanLayoutEngine.cs — right-to-left tree layout algorithm
  • PlanIconMapper.cs — maps 120+ operator types to SSMS-sourced PNG icons
  • Resources/PlanIcons/ — 120 operator icon PNGs

Plan Parser & Models (both apps)

  • ShowPlanParser.cs (~1600 lines) — full XDocument-based XML parser covering statements, QueryPlan metadata, RelOp tree, operator-specific elements, warnings, per-thread runtime stats, scalar UDF detection, Dispatcher/PSP, memory grants, spill details, and more
  • PlanModels.cs (~515 lines) — complete data model: ParsedPlan → PlanBatch → PlanStatement → PlanNode tree with all XSD attributes

Plan Analysis Engine (both apps)

  • PlanAnalyzer.cs — 11 post-parse rules:
    1. Filter operators discarding rows late
    2. Eager Index Spools with suggested CREATE INDEX
    3. Serial plan with translated reason
    4. UDF timing (Critical at 1s+)
    5. Row estimate mismatch (10x warning, 100x critical)
    6. Scalar UDF references (works on estimated plans)
    7. Spill severity promotion (large tempdb writes → Critical)
    8. Parallel thread skew detection
    9. Memory grant issues (overestimate + grant waits)
    10. Key Lookup with residual predicate
    11. Scan with residual predicate

Actual Plan Execution (both apps)

  • ActualPlanExecutor.cs — executes queries with SET STATISTICS XML ON, cancellable, no timeout
  • ReproScriptBuilder.cs — extracts SET options from plan XML for faithful repro
  • Loading indicator with Cancel button on Plan Viewer tab during execution

Integration

  • ServerTab.xaml/.cs — tabbed plan container with empty state, loading state, close buttons
  • QueryPerformanceContent.xaml.cs (Dashboard) — right-click context menu wiring, ActualPlanStarted/Finished events
  • ServerTab.xaml.cs (Lite) — inline actual plan wiring

Test Plan

  • Load estimated plans from Active Queries, Query Stats, Query Store rows via right-click → View Plan
  • Execute actual plans via right-click → Get Actual Plan, verify loading spinner + Cancel button
  • Open multiple plan tabs, close individual tabs, verify independent zoom/scroll per tab
  • Verify tooltips show cost, rows, predicates, I/O stats on hover
  • Check warning badges appear for nodes with issues (spills, implicit conversions, etc.)
  • Verify missing index suggestions display with CREATE INDEX statements
  • Test eager spool plans — confirm suggested CREATE INDEX appears in warning
  • Verify older SQL Server versions (2016/2017) don't crash on missing XML elements
  • Test cancel during long-running actual plan execution

Generated with Claude Code

erikdarlingdata and others added 11 commits February 22, 2026 19:48
Phase 1: ShowPlanXML rendering as interactive operator graph in a new
Plan Viewer tab. Right-click any query row → View Estimated Plan to
parse and display the plan inline (fetches on-demand when needed).

- LINQ-to-XML parser with cost computation, warnings, missing indexes
- SSMS-style layout: left-to-right with first-child horizontal spine
- 113 MIT-licensed operator icons from vscode-mssql
- Synthetic SELECT/INSERT/UPDATE/DELETE root node
- Rich tooltips, zoom/pan, save .sqlplan, multi-statement selector
- Missing index banner with CREATE INDEX suggestions
- Warnings banner (spills, implicit conversions, no join predicate)
- View Actual Plan menu item stubbed for Phase 2 (query execution)

Tested on sql2022 with TPC-H plans. Both apps build clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add collapsible properties panel (right-side, GridSplitter) with node selection
- Add rich tooltips with costs, rows, I/O, predicates, output columns
- Parse ~30 new operator properties: OrderBy, GroupBy, DefinedValues,
  OuterReferences, join columns, adaptive join, extended I/O stats,
  scan hints (ForcedIndex/ForceScan/ForceSeek/NoExpandHint), etc.
- Fix XML scoping bug: ScopedDescendants() prevents child RelOp property
  leakage (seek predicates, object names no longer bleed to parent operators)
- Fix EstimatedRowsRead/TableCardinality parsing (attributes are on RelOp,
  not the physical operator element)
- Fix DefinedValues to include column-only entries (no scalar expression)
- Gate Statement Memory Grant and Statement Info to root node only
- Always show boolean properties (Ordered, ForceScan, etc.) for scan/seek ops
- Always show I/O/CPU costs, rebinds/rewinds, estimated executions
- Add node context menus (Properties, Copy Operator/Object/Predicate)
- Statement-level metadata: CE version, DOP, compile stats, query hashes

Both Dashboard and Lite updated and tested against SSMS properties output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse all operator-specific attributes per ShowPlan XSD schema:
- RelOp: Partitioned, IsAdaptive/AdaptiveThresholdRows/EstimatedJoinType
  (fixed: these are on <RelOp>, not the physical operator element)
- IndexScan: Lookup, DynamicSeek
- NestedLoops: Optimized, WithOrderedPrefetch, WithUnorderedPrefetch
- Sort: Distinct
- Filter: StartupExpression
- Top: WithTies
- Hash: BitmapCreator
- Parallelism: Remoting, LocalParallelism

Fix Lite ViewEstimatedPlan_Click missing switch cases:
- ProcedureStatsRow: fetch via FetchProcedurePlanOnDemandAsync
- QueryStoreRow: fetch via FetchQueryStorePlanAsync

Both apps build clean, properties panel displays all new attributes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wave 1 bug fixes: StatementEstRows int→double, IsMemoryGrantFeedbackAdjusted
bool→string (shows actual feedback status), CE version read from StmtSimple.

Wave 2 high priority: Merge/NL residual+PassThru predicates, stale stats warning,
per-operator memory grants, MaxQueryMemory, Parallelism HashKeys, Top offset/rows,
QueryPlan-level warnings.

Wave 3 medium priority: EffectiveDOP, MemoryFractions, RunTimePartitionSummary,
PlanGuide/UsePlan, ParameterizedText, QS hints, enhanced spill details, Spool
stack/PrimaryNodeId, Update DML sort/ActionColumn, columnstore segment stats,
UDF timing, TraceFlags, IndexedViewInfo, EstimatedDOP per-operator.

Wave 4 structural: StmtCond (IF/ELSE recursive parsing), StmtCursor with cursor
metadata and operation sub-plans.

All changes applied to both Dashboard and Lite. Both build clean, 0 errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Issue #233: ReproScriptBuilder extracts StatementSetOptions from plan XML
  and emits matching SET statements (ANSI_NULLS, ARITHABORT, etc.) in repro
  scripts. Added isAzureSqlDb parameter to skip USE [database] for Azure SQL DB.
  Removed SET STATISTICS XML OFF from repro scripts (interfered with SSMS plans).

- Smart plan selection: "View Plan" now prefers live_query_plan (from
  dm_exec_query_statistics_xml) when available, falls back to estimated plan.
  Renamed menu item from "View Estimated Plan" to "View Plan".

- ActualPlanExecutor service: Wraps repro script with SET STATISTICS XML ON/OFF,
  executes query, iterates result sets capturing plan XML, discards all data rows.
  Supports cancellation via CancellationToken -> SqlCommand.Cancel().

- GetActualPlan_Click handlers in both apps: Confirmation dialog warning about
  query execution, 120-second timeout, loads actual plan into plan viewer.

- Actual stats on plan nodes: Duration, CPU time, and actual vs estimated rows
  (with accuracy %) displayed on each operator when actual stats are available.

- Per-node layout heights: PlanLayoutEngine calculates each node's height based
  on content (actual stats lines, object name) to prevent vertical overlap.
  Nodes auto-size visually with MinHeight for estimated plans.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Tabbed plans: multiple plans open simultaneously for comparison, close individual tabs
- Query text: collapsible read-only expander showing the source query
- Statement selector: hide label/cost text for single-statement plans
- Node text: white by default, OrangeRed for elapsed >= 1s, CPU >= 1s, row estimate off by 10x+
- Indicator badges: orange triangle for warnings, amber circle for parallelism (both visible simultaneously)
- Properties panel: collapsible Expander sections for easier navigation
- Dashboard ViewPlanRequested event extended to pass queryText

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full XSD audit against SSMS 22 schema (2611 lines) with systematic
gap closure across all major complex types. Parser now covers ~95%
of non-PDW/non-Hyperscale attributes and elements.

New parsing:
- Dispatcher/PSP with OptionalParameterPredicate (SQL 2022+/2025)
- Per-thread RunTimeCountersPerThread (23 fields for parallel skew)
- Sort/Hash/Exchange SpillDetails as structured warning data
- CardinalityFeedback, UDF/StoredProc sub-plans, ScalarUDF detection
- Remote operator metadata, StarJoinInfo, RollupInfo, PartitionId
- NamedParameterList, TieColumns, UDXName, IndexedViewInfo per-operator
- OptimizationReplay, QueryCompilationReplay, RowRequalifications

PlanAnalyzer (new):
- 5 analysis rules: Filter operators, Eager Index Spools, Serial plan
  detection, UDF timing thresholds, row estimate mismatch (10x/100x)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l index suggestion

Loading indicator: Plan Viewer tab now shows a progress bar and Cancel button
during actual plan execution. Removed the 120-second timeout — queries run
until complete or cancelled. Dashboard uses ActualPlanStarted/ActualPlanFinished
events; Lite wires inline.

New PlanAnalyzer rules:
- Rule 6: Scalar UDF references (works on estimated plans)
- Rule 7: Spill severity promotion (>1000 pages → Critical)
- Rule 8: Parallel thread skew (one thread handling 90%+ of rows)
- Rule 9: Memory grant issues (10x+ overestimate, grant wait time)
- Rule 10: Key Lookup with residual predicate
- Rule 11: Scan with residual predicate

Eager spool enhancement: Parser now extracts SeekPredicateNew key columns
and OutputList includes to generate a suggested CREATE INDEX statement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The badge Grid elements had their own ToolTip strings that spawned
blank-looking popups over the parent node's rich tooltip. Removing
them lets the parent border's detailed tooltip show through instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@erikdarlingdata erikdarlingdata merged commit 1d1490a into dev Feb 23, 2026
3 checks passed
@erikdarlingdata erikdarlingdata deleted the feature/query-plan-viewer-220 branch February 23, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant