Wait stats as warnings (#215, Joe feedback a1/b4/b5)#244
Conversation
Each wait stat recorded on a statement now emits a first-class PlanWarning alongside the existing wait-stats card, so waits sort by benefit % in the warnings list next to operator findings instead of living only in their own panel. - WaitStatsKnowledge: per-wait description + how-to-fix content covering PAGEIOLATCH_*, PAGELATCH_*, LATCH_*, LCK_M_*, MEMORY_ALLOCATION_EXT, RESOURCE_SEMAPHORE*, CX*/HT*/BMPBUILD parallel + batch-mode families, SOS_SCHEDULER_YIELD, THREADPOOL, ASYNC_NETWORK_IO, WRITELOG, IO_COMPLETION, LOGBUFFER, EXECSYNC, PREEMPTIVE_OS_WRITEFILEGATHER, with prefix fallbacks. - PAGEIOLATCH_SH/EX/UP/DT warnings include effective latency per wait (wait_ms / wait_count) in the message. - Severity tiers off benefit %: >=50 Critical, >=10 Warning, else Info. - Flows through existing PlanWarnings pipeline so web viewer, HTML export, JSON/text, and Avalonia Plan Warnings expander all pick it up with no renderer changes. Wait-stats chart stays as a complementary view. - Version bump 1.6.0 -> 1.7.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
erikdarlingdata
left a comment
There was a problem hiding this comment.
Review: PR #244 — Wait stats as warnings
What this does
Adds WaitStatsKnowledge (a static wait-type → description/fix/latency-hint table) and BenefitScorer.EmitWaitStatWarnings, which emits one PlanWarning per wait stat entry after ScoreWaitStats runs. Severity tiers from the existing benefit % (>=50 Critical, >=10 Warning, else Info). Wires the new file into PlanViewer.Web.csproj's compile-link list and bumps PlanViewer.App to 1.7.0.
What's good
- Base branch is
dev— correct. - No Avalonia/XAML changes, so none of the usual Avalonia gotchas (AvaloniaEdit style include,
PlaceholderTextvsWatermark,:pointerover /template/selectors, MVVM creep) apply here. - No new brushes or inline colors, so no text-contrast concerns.
AppandClipick up the new file viaProjectReference;Webcorrectly got an explicit<Compile Include>entry.FormatLatencypicks reasonable units across the range.- Knowledge-library content is well-curated with a sensible default fallback.
What needs attention
-
Cross-repo sync — scoring change. This touches
BenefitScorer, which is Studio's plan-analysis/scoring surface. The equivalent wait-as-warning behavior (or an explicit decision to skip it) needs to land in PerformanceMonitor's Dashboard and Lite copies to keep the rule set in sync. Not blocking this PR, but file a tracking task. -
Test coverage.
WaitStatsKnowledge.Lookuphas non-trivial branching (exact → 7 prefix fallbacks →Contains("MEMORY_ALLOCATION")→ default) andEmitWaitStatWarningshas severity tiering, latency-hint formatting, and the guard-asymmetry case flagged inline. Nothing intests/PlanViewer.Core.Tests/covers any of it. At minimum:Lookupreturns exact for known keys, the right prefix entry for unknowns in each family,Defaultfor""/null/unknowns with no matching prefix.EmitWaitStatWarningsseverity tiers at the 50 / 10 / below-10 boundaries.- Effective-latency suffix appears only for entries with
ShowEffectiveLatency = trueandWaitCount > 0.
-
Inline findings (see comments on the diff):
BenefitScorer.cs:43— guard asymmetry:EmitWaitStatWarningsruns whenQueryTimeStatsis null, so benefit % is always missing and everything isInfo.BenefitScorer.cs:91—WarningType = "Wait: " + wait.WaitTypeproduces unbounded distinct warning types; confirm no downstream switch/grouping breaks.WaitStatsKnowledge.cs:265—StartsWith("HT")is overly broad as a family fallback.WaitStatsKnowledge.cs:286—Exact["MEMORY_ALLOCATION_EXT"]throws if the key is ever renamed; useTryGetValueor inline the entry.
No approval / no changes requested — leaving the call to the maintainer.
Generated by Claude Code
| ScoreWaitStats(stmt); | ||
|
|
||
| if (stmt.WaitStats.Count > 0) | ||
| EmitWaitStatWarnings(stmt); |
There was a problem hiding this comment.
Guard asymmetry worth confirming: ScoreWaitStats on line 40 only runs when stmt.QueryTimeStats != null, but EmitWaitStatWarnings on line 43 runs whenever WaitStats.Count > 0.
When QueryTimeStats is null (e.g. estimated-only plans with recorded wait stats), stmt.WaitBenefits stays empty, so benefitByType.TryGetValue always misses and every wait is emitted at Info severity with MaxBenefitPercent = null. The plan may have a real 99%-elapsed CXPACKET problem that silently shows up as Info.
Intentional degrade? If so, fine — but it's worth a comment at line 42 saying so. Otherwise consider matching the QueryTimeStats != null guard.
Generated by Claude Code
| Description = "Waiting to read a data page from disk into the buffer pool.", | ||
| HowToFix = "Reduce physical reads (better indexes, fewer pages touched, fix cardinality estimates) or move to faster storage.", | ||
| ShowEffectiveLatency = true | ||
| }; |
There was a problem hiding this comment.
StartsWith("HT") is overly broad — any future or unrelated wait name that happens to begin with HT gets labeled as a batch-mode hash synchronization wait. The known batch-mode HT* waits (HTBUILD, HTREPARTITION, HTDELETE, HTMEMO, HTREINIT) are already in Exact, so this prefix fallback only fires on unknowns.
Consider tightening to a known-prefix allowlist (e.g. check HTBUILD/HTREPARTITION/HTDELETE/HTMEMO/HTREINIT via Exact only) or narrowing the prefix to something more specific. Same concern applies to StartsWith("CX") on line 272 — less likely to misfire since real CX* waits are all exchange-related, but the same shape of issue.
Generated by Claude Code
| { | ||
| Description = "Batch-mode hash operation synchronization wait.", | ||
| HowToFix = "Reduce input rows and skew for the batch-mode hash. Confirm the smaller side is on the build, and check that batch mode is the right choice for this plan shape." | ||
| }; |
There was a problem hiding this comment.
Exact["MEMORY_ALLOCATION_EXT"] uses the indexer, which throws KeyNotFoundException if someone later renames or removes that exact key. Cheap fix: return Exact.TryGetValue("MEMORY_ALLOCATION_EXT", out var m) ? m : Default; — or just inline the Entry literal here so the fallback doesn't secretly depend on another dictionary entry existing.
Generated by Claude Code
|
|
||
| stmt.PlanWarnings.Add(new PlanWarning | ||
| { | ||
| WarningType = "Wait: " + wait.WaitType, |
There was a problem hiding this comment.
WarningType = "Wait: " + wait.WaitType creates one unique WarningType per wait type (e.g. "Wait: CXPACKET", "Wait: PAGEIOLATCH_SH"). Any downstream code keying on WarningType — filters, grouping in the web viewer / HTML export / Avalonia Plan Warnings expander, rule-count metrics — now sees an unbounded set of warning types instead of a single "Wait Statistics" bucket. Worth a quick scan of the ResultMapper / exporters / Avalonia warnings view to confirm none of them switch/dictionary on WarningType.
Generated by Claude Code
Summary
PlanWarningwith per-wait description, how-to-fix guidance, and the benefit % already calculated byBenefitScorer. Joe's a1/b4/b5 asks from [FEATURE] Add system to assign a "maximum benefit" for plan analysis rules #215 — integrate waits into the warnings list instead of leaving them in their own card.Details
New:
WaitStatsKnowledge.csStatic content library mapping wait type → description + how-to-fix. Covers:
PAGEIOLATCH_SH/EX/UP/DT,WRITELOG,IO_COMPLETION,ASYNC_IO_COMPLETION,LOGBUFFERMEMORY_ALLOCATION_EXT,RESOURCE_SEMAPHORE,RESOURCE_SEMAPHORE_QUERY_COMPILE,SOS_PHYS_PAGE_CACHESOS_SCHEDULER_YIELD,THREADPOOL,DISPATCHER_QUEUE_SEMAPHORECXPACKET,CXCONSUMER,CXSYNC_PORT,CXSYNC_CONSUMERHTBUILD,HTREPARTITION,HTDELETE,HTMEMO,HTREINIT,BPSORT,BMPBUILDPAGELATCH_SH/EX/UP,LATCH_EX,LATCH_SHLCK_M_S/X/U/IS/IX/SCH_S/SCH_M/RS_S/RS_U/RX_XASYNC_NETWORK_IOEXECSYNC,PREEMPTIVE_OS_WRITEFILEGATHERPrefix fallbacks for
LCK_M_,PAGEIOLATCH_,PAGELATCH_,LATCH_,HT,CX,PREEMPTIVE_, plus aMEMORY_ALLOCATIONcontains-match (catchesRESERVED_MEMORY_ALLOCATION_EXT).BenefitScorer.EmitWaitStatWarningsRuns after
ScoreWaitStats. For eachWaitStatInfoit builds a warning:WarningType = "Wait: {WaitType}"Message={WaitType}: {description}. Observed N ms across M waits.PAGEIOLATCH_*also appendsEffective latency: X ms/µs per wait(wait_ms / wait_count)MaxBenefitPercentfrom the existing wait-benefit calculationActionableFix= how-to-fix from the knowledge library>=50 Critical, >=10 Warning, else InfoRenderers
No changes needed — warnings flow through
stmt.PlanWarnings→ResultMapper→ web viewer, HTML export, JSON/text, Avalonia Plan Warnings expander.Version bump
1.6.0 → 1.7.0.
Test plan
.internal/examples/20260415_1.sqlplan— CXPACKET 99% Critical, ASYNC_NETWORK_IO 93% Critical, HT*/LATCH_EX/MEMORY_ALLOCATION_EXT as Info.internal/examples/NXfso4jC0L.sqlplan— PAGEIOLATCH_SH showsEffective latency: 102 µs per wait, MEMORY_ALLOCATION_EXT 77.7% Critical, RESERVED_MEMORY_ALLOCATION_EXT picked up by fallback🤖 Generated with Claude Code