-
Notifications
You must be signed in to change notification settings - Fork 28
Fix operator self-time through Compute Scalar children + one-decimal benefit % (#215 C5, C2) #255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -407,7 +407,7 @@ private static void WriteWaitStatsCard(StringBuilder sb, StatementResult stmt, b | |
| { | ||
| var barPct = maxWait > 0 ? (double)w.WaitTimeMs / maxWait * 100 : 0; | ||
| var benefitTag = benefitLookup.TryGetValue(w.WaitType, out var pct) | ||
| ? $" <span class=\"warn-benefit\">up to {pct:N0}%</span>" | ||
| ? $" <span class=\"warn-benefit\">up to {(pct >= 100 ? pct.ToString("N0") : pct.ToString("N1"))}%</span>" | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Generated by Claude Code |
||
| : ""; | ||
| sb.AppendLine("<div class=\"wait-row\">"); | ||
| sb.AppendLine($"<span class=\"wait-type\">{Encode(w.WaitType)}</span>"); | ||
|
|
@@ -458,7 +458,7 @@ private static void WriteWarnings(StringBuilder sb, StatementResult stmt) | |
| sb.AppendLine($"<span class=\"warn-op\">{Encode(w.Operator)}</span>"); | ||
| sb.AppendLine($"<span class=\"warn-type\">{Encode(w.Type)}</span>"); | ||
| if (w.MaxBenefitPercent.HasValue) | ||
| sb.AppendLine($"<span class=\"warn-benefit\">up to {w.MaxBenefitPercent:N0}% benefit</span>"); | ||
| sb.AppendLine($"<span class=\"warn-benefit\">up to {(w.MaxBenefitPercent.Value >= 100 ? w.MaxBenefitPercent.Value.ToString("N0") : w.MaxBenefitPercent.Value.ToString("N1"))}% benefit</span>"); | ||
| sb.AppendLine($"<span class=\"warn-msg\">{Encode(w.Message)}</span>"); | ||
| if (!string.IsNullOrEmpty(w.ActionableFix)) | ||
| sb.AppendLine($"<span class=\"warn-fix\">{Encode(w.ActionableFix)}</span>"); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -353,7 +353,7 @@ | |
|
|
||
| if (unsnifffedParams.Count > 0) | ||
| { | ||
| var hasRecompile = stmt.StatementText.Contains("RECOMPILE", StringComparison.OrdinalIgnoreCase); | ||
|
Check warning on line 356 in src/PlanViewer.Core/Services/PlanAnalyzer.cs
|
||
| if (!hasRecompile) | ||
| { | ||
| var names = string.Join(", ", unsnifffedParams.Select(p => p.Name)); | ||
|
|
@@ -1204,7 +1204,7 @@ | |
| // Rule 28: Row Count Spool — NOT IN with nullable column | ||
| // Pattern: Row Count Spool with high rewinds, child scan has IS NULL predicate, | ||
| // and statement text contains NOT IN | ||
| if (!cfg.IsRuleDisabled(28) && node.PhysicalOp.Contains("Row Count Spool")) | ||
|
Check warning on line 1207 in src/PlanViewer.Core/Services/PlanAnalyzer.cs
|
||
| { | ||
| var rewinds = node.HasActualStats ? (double)node.ActualRewinds : node.EstimateRewinds; | ||
| if (rewinds > 10000 && HasNotInPattern(node, stmt)) | ||
|
|
@@ -1593,26 +1593,45 @@ | |
| } | ||
|
|
||
| /// <summary> | ||
| /// Serial row mode self-time: subtract all direct children's elapsed. | ||
| /// Exchange children are skipped through to their real child. | ||
| /// Serial row mode self-time: subtract all direct children's effective elapsed. | ||
| /// Pass-through operators (Compute Scalar, etc.) don't carry runtime stats — | ||
| /// look through them to the first descendant that does. Exchange children | ||
| /// use max-child elapsed because exchange times are unreliable. | ||
| /// </summary> | ||
| private static long GetSerialOwnElapsed(PlanNode node) | ||
| { | ||
| var totalChildElapsed = 0L; | ||
| foreach (var child in node.Children) | ||
| { | ||
| var childElapsed = child.ActualElapsedMs; | ||
|
|
||
| // Exchange operators have unreliable times — skip to their child | ||
| if (child.PhysicalOp == "Parallelism" && child.Children.Count > 0) | ||
| childElapsed = child.Children.Max(c => c.ActualElapsedMs); | ||
|
|
||
| totalChildElapsed += childElapsed; | ||
| } | ||
| totalChildElapsed += GetEffectiveChildElapsedMs(child); | ||
|
|
||
| return Math.Max(0, node.ActualElapsedMs - totalChildElapsed); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns the elapsed time a child contributes to its parent's subtree. | ||
| /// Looks through pass-through operators (Compute Scalar, Parallelism exchange) | ||
| /// that don't carry reliable runtime stats. | ||
| /// </summary> | ||
| private static long GetEffectiveChildElapsedMs(PlanNode child) | ||
| { | ||
| // Exchange operators: unreliable times, use max child | ||
| if (child.PhysicalOp == "Parallelism" && child.Children.Count > 0) | ||
| return child.Children.Max(GetEffectiveChildElapsedMs); | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a scoring-rule change — Generated by Claude Code |
||
|
|
||
| // Child has its own stats: use them | ||
| if (child.ActualElapsedMs > 0) | ||
| return child.ActualElapsedMs; | ||
|
|
||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Behavior change for Parallelism children worth calling out: the old code did Generated by Claude Code |
||
| // No stats (Compute Scalar and similar): look through to descendants | ||
| if (child.Children.Count == 0) | ||
| return 0; | ||
|
|
||
| var sum = 0L; | ||
| foreach (var grandchild in child.Children) | ||
| sum += GetEffectiveChildElapsedMs(grandchild); | ||
| return sum; | ||
| } | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Summing over all grandchildren works for the common case (Compute Scalar with a single input) but could over-subtract if a pass-through-without-stats ever has multiple input branches — the parent's elapsed includes those branches sequentially in row mode, but the two sibling branches' elapsed are not additive in wall time. Compute Scalar is always unary so this is theoretical today; worth either a comment stating the assumption or a guard that only recurses when Generated by Claude Code |
||
|
|
||
| /// <summary> | ||
| /// Calculates a Parallelism (exchange) operator's own elapsed time. | ||
| /// Exchange times are unreliable — they accumulate wait time caused by | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -302,7 +302,7 @@ else | |
| @w.WaitTimeMs.ToString("N0") ms | ||
| @if (benefitPct > 0) | ||
| { | ||
| <span class="wait-benefit">up to @benefitPct.ToString("N0")%</span> | ||
| <span class="wait-benefit">up to @(benefitPct >= 100 ? benefitPct.ToString("N0") : benefitPct.ToString("N1"))%</span> | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inline ternary inside the Razor expression is hard to read and duplicates the logic from Generated by Claude Code |
||
| } | ||
| </span> | ||
| </div> | ||
|
|
@@ -346,7 +346,7 @@ else | |
| <span class="warning-type">@w.Type</span> | ||
| @if (w.MaxBenefitPercent.HasValue) | ||
| { | ||
| <span class="warn-benefit">up to @w.MaxBenefitPercent.Value.ToString("N0")% benefit</span> | ||
| <span class="warn-benefit">up to @(w.MaxBenefitPercent.Value >= 100 ? w.MaxBenefitPercent.Value.ToString("N0") : w.MaxBenefitPercent.Value.ToString("N1"))% benefit</span> | ||
| } | ||
| <span class="warning-msg">@w.Message</span> | ||
| @if (!string.IsNullOrEmpty(w.ActionableFix)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edge case:
pctin the 99.95–99.99 range will format as"100.0"underN1(rounds up) but the ternary only switches toN0at>= 100. You'll get strings like"up to 100.0%"which reads weird next to the"100"case the branch is trying to produce. If the intent was "show integer only at exactly 100", consider rounding before the compare:var r = Math.Round(pct, 1); return r >= 100 ? $"{r:N0}" : $"{r:N1}";.Generated by Claude Code