From 76079280d557233af7fcb3f9838321be01f2d2dc Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Tue, 7 Apr 2026 11:26:28 -0400 Subject: [PATCH] Address round 2 feedback from #178 (items 13-16) - Rule 3 (Serial Plan): Suppress for TRIVIAL plans and 0ms actual elapsed; demote to Info unless user explicitly forced serial (MAXDOP 1 / hint) - Statement tab warning count: Use total (statement + node) warnings to match the main warnings strip count - Parameters card: Span 2 columns when populated for more horizontal space Co-Authored-By: Claude Opus 4.6 (1M context) --- src/PlanViewer.Core/Services/PlanAnalyzer.cs | 12 +++++++++--- src/PlanViewer.Web/Pages/Index.razor | 4 ++-- src/PlanViewer.Web/wwwroot/css/app.css | 3 ++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/PlanViewer.Core/Services/PlanAnalyzer.cs b/src/PlanViewer.Core/Services/PlanAnalyzer.cs index b66d8fe..d89458d 100644 --- a/src/PlanViewer.Core/Services/PlanAnalyzer.cs +++ b/src/PlanViewer.Core/Services/PlanAnalyzer.cs @@ -124,9 +124,12 @@ private static void TryOverrideSeverity(PlanWarning warning, AnalyzerConfig cfg) private static void AnalyzeStatement(PlanStatement stmt, AnalyzerConfig cfg) { // Rule 3: Serial plan with reason - // Skip trivial statements (e.g., variable assignments, constant scans) — not worth warning about + // Skip: trivial cost (< 0.01), TRIVIAL optimization (can't go parallel anyway), + // and 0ms actual elapsed time (not worth flagging). if (!cfg.IsRuleDisabled(3) && !string.IsNullOrEmpty(stmt.NonParallelPlanReason) - && stmt.StatementSubTreeCost >= 0.01) + && stmt.StatementSubTreeCost >= 0.01 + && stmt.StatementOptmLevel != "TRIVIAL" + && !(stmt.QueryTimeStats != null && stmt.QueryTimeStats.ElapsedTimeMs == 0)) { var reason = stmt.NonParallelPlanReason switch { @@ -138,11 +141,14 @@ private static void AnalyzeStatement(PlanStatement stmt, AnalyzerConfig cfg) _ => stmt.NonParallelPlanReason }; + // Only warn (not info) when the user explicitly forced serial execution + var isExplicit = stmt.NonParallelPlanReason is "MaxDOPSetToOne" or "QueryHintNoParallelSet"; + stmt.PlanWarnings.Add(new PlanWarning { WarningType = "Serial Plan", Message = $"Query running serially: {reason}.", - Severity = PlanWarningSeverity.Warning + Severity = isExplicit ? PlanWarningSeverity.Warning : PlanWarningSeverity.Info }); } diff --git a/src/PlanViewer.Web/Pages/Index.razor b/src/PlanViewer.Web/Pages/Index.razor index c714f81..6aae734 100644 --- a/src/PlanViewer.Web/Pages/Index.razor +++ b/src/PlanViewer.Web/Pages/Index.razor @@ -74,9 +74,9 @@ else { @s.EstimatedCost.ToString("N2") } - @if (s.Warnings.Count > 0) + @if (GetAllWarnings(s).Count > 0) { - @s.Warnings.Count + @GetAllWarnings(s).Count } } diff --git a/src/PlanViewer.Web/wwwroot/css/app.css b/src/PlanViewer.Web/wwwroot/css/app.css index 05bbb7d..95b0cb4 100644 --- a/src/PlanViewer.Web/wwwroot/css/app.css +++ b/src/PlanViewer.Web/wwwroot/css/app.css @@ -373,7 +373,8 @@ textarea::placeholder { margin-bottom: 0.75rem; } -/* Wait stats card gets extra width when present */ +/* Give params and waits cards extra width when populated */ +.insight-card.params.has-items, .insight-card.waits.has-items { grid-column: span 2; }