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) {