From ef63c64eeeef88f1180b217908fd3adc67671a36 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Fri, 24 Apr 2026 16:01:12 -0400 Subject: [PATCH] Estimated plan info on Runtime card (#215 E4, E5, E6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - E6: MemoryGrantResult now exposes DesiredKB (optimizer's pre-execution parallel-adjusted desired grant) and SerialRequiredKB. When QueryTime is null (estimated plan) but DesiredKB > 0, the Runtime card shows a "Memory (estimated)" row with the desired grant, plus a "Serial required" row when it differs. Applied to HTML export + web viewer. - E4: compile time already renders on estimated plans when CompileTimeMs > 0 (existing behavior). Joe's cursor plan legitimately has compile_time=0 on its inner cursor ops, so nothing displays there. No code change needed. - E5: predicted DOP already shows on estimated plans when DegreeOfParallelism > 0 (existing behavior). Plans with no DOP show the Serial reason. Host EstimatedAvailableDOP from OptimizerHardwareDependentProperties intentionally not surfaced — it describes the host, not the plan, and is misleading for plans the optimizer explicitly kept serial. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/PlanViewer.Core/Output/AnalysisResult.cs | 14 ++++++++++++++ src/PlanViewer.Core/Output/HtmlExporter.cs | 7 +++++++ src/PlanViewer.Core/Output/ResultMapper.cs | 4 +++- src/PlanViewer.Web/Pages/Index.razor | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/PlanViewer.Core/Output/AnalysisResult.cs b/src/PlanViewer.Core/Output/AnalysisResult.cs index c9c1a26..04f6f4e 100644 --- a/src/PlanViewer.Core/Output/AnalysisResult.cs +++ b/src/PlanViewer.Core/Output/AnalysisResult.cs @@ -167,6 +167,20 @@ public class MemoryGrantResult [JsonPropertyName("estimated_available_memory_grant_kb")] public long EstimatedAvailableMemoryGrantKB { get; set; } + + /// + /// Optimizer's pre-execution "desired" grant (parallel-adjusted). + /// Non-zero on estimated plans; pairs with DesiredKB serial-required as fallback + /// when no runtime-granted memory exists (#215 E6). + /// + [JsonPropertyName("desired_kb")] + public long DesiredKB { get; set; } + + /// + /// Optimizer's pre-execution serial-required grant (memory minimum before DOP scaling). + /// + [JsonPropertyName("serial_required_kb")] + public long SerialRequiredKB { get; set; } } public class QueryTimeResult diff --git a/src/PlanViewer.Core/Output/HtmlExporter.cs b/src/PlanViewer.Core/Output/HtmlExporter.cs index 5d87f26..cf8ac8a 100644 --- a/src/PlanViewer.Core/Output/HtmlExporter.cs +++ b/src/PlanViewer.Core/Output/HtmlExporter.cs @@ -335,6 +335,13 @@ private static void WriteRuntimeCard(StringBuilder sb, StatementResult stmt) var spillTag = hasSpill ? " ⚠ spill" : ""; sb.AppendLine($"
Used{FormatKB(stmt.MemoryGrant.MaxUsedKB)} ({pctUsed:N0}%){spillTag}
"); } + else if (isEstimated && stmt.MemoryGrant != null && stmt.MemoryGrant.DesiredKB > 0) + { + // #215 E6: estimated plans — show the optimizer's pre-execution desired grant + WriteRow(sb, "Memory (estimated)", FormatKB(stmt.MemoryGrant.DesiredKB) + " desired"); + if (stmt.MemoryGrant.SerialRequiredKB > 0 && stmt.MemoryGrant.SerialRequiredKB != stmt.MemoryGrant.DesiredKB) + WriteRow(sb, "Serial required", FormatKB(stmt.MemoryGrant.SerialRequiredKB)); + } if (stmt.OptimizationLevel != null) WriteRow(sb, "Optimization", Encode(stmt.OptimizationLevel)); if (stmt.CardinalityEstimationModel > 0) diff --git a/src/PlanViewer.Core/Output/ResultMapper.cs b/src/PlanViewer.Core/Output/ResultMapper.cs index f73dd06..35d1f51 100644 --- a/src/PlanViewer.Core/Output/ResultMapper.cs +++ b/src/PlanViewer.Core/Output/ResultMapper.cs @@ -105,7 +105,9 @@ private static StatementResult MapStatement(PlanStatement stmt) MaxUsedKB = stmt.MemoryGrant.MaxUsedMemoryKB, GrantWaitMs = stmt.MemoryGrant.GrantWaitTimeMs, FeedbackAdjusted = stmt.MemoryGrant.IsMemoryGrantFeedbackAdjusted, - EstimatedAvailableMemoryGrantKB = stmt.HardwareProperties?.EstimatedAvailableMemoryGrant ?? 0 + EstimatedAvailableMemoryGrantKB = stmt.HardwareProperties?.EstimatedAvailableMemoryGrant ?? 0, + DesiredKB = stmt.MemoryGrant.DesiredMemoryKB, + SerialRequiredKB = stmt.MemoryGrant.SerialRequiredMemoryKB }; } diff --git a/src/PlanViewer.Web/Pages/Index.razor b/src/PlanViewer.Web/Pages/Index.razor index 652583c..8fbfdac 100644 --- a/src/PlanViewer.Web/Pages/Index.razor +++ b/src/PlanViewer.Web/Pages/Index.razor @@ -215,6 +215,20 @@ else } + else if (isEstimatedRuntime && ActiveStmt!.MemoryGrant != null && ActiveStmt!.MemoryGrant.DesiredKB > 0) + { +
+ Memory (estimated) + @FormatKB(ActiveStmt!.MemoryGrant.DesiredKB) desired +
+ @if (ActiveStmt!.MemoryGrant.SerialRequiredKB > 0 && ActiveStmt!.MemoryGrant.SerialRequiredKB != ActiveStmt!.MemoryGrant.DesiredKB) + { +
+ Serial required + @FormatKB(ActiveStmt!.MemoryGrant.SerialRequiredKB) +
+ } + } @if (ActiveStmt!.OptimizationLevel != null) {