Skip to content

Port PerformanceStudio's wait-row layout: an Auto benefit column that never clips - #4595

Merged
erikdarlingdata merged 3 commits into
devfrom
viewer/4576-wait-rows
Sep 28, 2026
Merged

erikdarlingdata merged 3 commits into
devfrom
viewer/4576-wait-rows

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4576. Part of #4511.

Why

PerformanceStudio (erikdarlingdata/PerformanceStudio@78a3370, refined @80de6fc) fixed the Wait Stats card's rows so the flexible column is the duration text, not the bar; the wait-type name is capped at 150px with an ellipsis and a tooltip; and the trailing "up to N%" benefit sits in an Auto column so it is never the thing that gets clipped. Every row also carries a tooltip now.

PerformanceMonitor's wait grid (PlanViewerControl.Properties.cs's ShowWaitStats, shared by the Darling Viewer and Lite) had none of this: a fixed name column sized off the longest wait-type string, a fixed 300px bar column, and no benefit column at all — the benefit percentage the analyzer already scores per wait type (BenefitScorer.EmitWaitStatWarnings) was never surfaced next to the row it belongs to.

What changes

  • ShowWaitStats's grid columns are now *, Auto, *, Auto (name, bar, duration, benefit) instead of three fixed-width columns. The name and duration are the two star columns that ellipsize (TextTrimming.CharacterEllipsis) under pressure; the bar (60px) and benefit are Auto so neither is ever clipped.
  • The wait-type name is capped at 150px (MaxWidth) with an ellipsis and a "{type} — {category} wait" tooltip.
  • The duration text gets a "{ms} ms across {n} waits" tooltip.
  • The header's total-ms text now also carries a tooltip ("{total} ms of waits across {n} wait types"), cleared when a statement has no waits so a hover doesn't answer with a stale count.
  • New: a benefit column reading "up to N%" for any wait type the analyzer scored (BenefitScorer.EmitWaitStatWarnings's "Wait: {type}" finding), with a "Up to N% of this statement's runtime could be recovered by removing {type} waits" tooltip. Absent when there's no matching finding, no score, or the score is zero.
  • New pure helper: PerformanceMonitor.PlanAnalysis.WaitRowText.Benefit(waitType, statementWarnings) does the join and formatting (case-insensitive match on "Wait: {type}", reusing PlanWarningDisplay.FormatBenefitPercent's rounding, now internal). The WPF code-behind calls it; nothing else changed in the join.
  • ShowWaitStats's signature grew a List<PlanWarning> parameter (the statement's PlanWarnings); the one call site (PlanViewerControl.Rendering.cs) was updated.

Lite: gets this too (shared control)

Lite hosts the same PerformanceMonitor.Ui.PlanViewerControl, so this lands there with no separate change.

Not ported / PM-ahead

Test plan

New pin file: Darling/Darling.Tests/ViewerWaitRowBenefitTests.cs — pins WaitRowText.Benefit: a positive match, case-insensitive match, the shared 100%-and-above whole-number format, no matching finding, a null score, and a zero/negative score all resolving to null.

  • RED on dev (compile-only, all-new helper): copying the test file into a detached worktree at origin/dev (49c5b6a) fails with 7× CS0103: The name 'WaitRowText' does not exist in the current context — the type doesn't exist there yet.
  • Mutation RED (runtime, on the fix branch): changed the lookup key from "Wait: " + waitType to "Wait " + waitType (dropping the colon) — 3 of 7 tests failed (Benefit_ReturnsUpToNPercent_WhenAMatchingFindingHasAPositiveScore, Benefit_MatchesCaseInsensitively_OnTheWaitType, Benefit_UsesTheSharedWholeNumberFormat_AtAndAbove100). Reverted; rebuilt; all 7 green again.
  • GREEN, -class Darling.Tests.ViewerWaitRowBenefitTests: Total: 7, Errors: 0, Failed: 0, Skipped: 0.
  • GREEN, the full required class list (ViewerWaitRowBenefitTests + ShowPlanParserCondAndMultiplePlanTests, ActualPlanRequestTests, ActualPlanDispatchTests, ActualPlanResultParseTests, QueryModificationDetectorTests, ActualPlanCaptureLoopTests, ActualPlanGatingTests, ReproScriptBuilderHardeningTests, DarlingAnalysisPipelineTests, DarlingMcpPlanToolsSurfaceAndSqlTests, DarlingMcpPlanToolsLivePostgresTests, McpPlanAnalysisEnvelopeTests, SerialLoopStoreSizeSourceTests, TsqlConventionGuardTests, DocCommentHygieneTests): Total: 238, Errors: 0, Failed: 0, Skipped: 2 (the two live-Postgres tests, gated on DARLING_TEST_PG, skip on this rig as expected).
  • Builds, all -p:EnableWindowsTargeting=true, analyzer-warnings grep empty on every log: Darling.Tests (Release), PerformanceMonitor.Ui, Lite/PerformanceMonitorLite.csproj, Lite.Tests, Darling/PerformanceMonitor.Darling.Viewer — all 0 warnings / 0 errors.
  • PerformanceMonitor.PlanAnalysis itself builds 0/0 (the internal visibility change to FormatBenefitPercent doesn't break anything outside the assembly; it's still only called from within PerformanceMonitor.PlanAnalysis).

Screenshot plan (pure layout, can't assert visually from here)

Open a captured plan with wait stats and at least one wait type the analyzer scored a benefit for (e.g. one with meaningful CXPACKET or PAGEIOLATCH_SH time), in both the Darling Viewer and Lite (shared control):

  1. Narrow the window/panel to roughly its minimum width. The wait-type name and duration text should each shrink and end in an ellipsis rather than either wrapping, clipping mid-word, or growing a horizontal scrollbar (there is none — HorizontalScrollBarVisibility="Disabled" on WaitStatsHeader's ScrollViewer was already in place).
  2. The "up to N%" benefit text at the row's right edge should be fully visible at every width, never truncated.
  3. Hover the wait-type name: tooltip reads "{type} — {category} wait". Hover the duration: tooltip reads "{ms} ms across {n} waits". Hover the benefit text: tooltip reads "Up to N% of this statement's runtime could be recovered by removing {type} waits". Hover the "Wait Stats — Nms total" header: tooltip reads "{N} ms of waits across {n} wait types".
  4. A wait type the analyzer didn't score (or scored at 0%) shows no benefit text in that row — no blank cell artifact, no "up to 0%".
  5. Colors are unchanged from before this PR (that's Wait-category colours: PerformanceStudio's contrast-checked palette #4578/Wait-category colours: a laid-out, contrast-checked palette #4583's territory).

Follow-up: the benefit text is now a whole percent (a13ed6e)

PerformanceStudio's desktop plan viewer formats the wait-row benefit as $"up to {benefitPct:N0}%" — a whole number, no decimal. WaitRowText.Benefit was formatting with PlanWarningDisplay.FormatBenefitPercent, which keeps one decimal below 100%. Fixed: WaitRowText.Benefit now formats with N0 directly, matching PerformanceStudio's rounding at every value, not just at and above 100%. FormatBenefitPercent goes back to private (it's used only by the plan-warning header's own display, a different format).

Pins updated in ViewerWaitRowBenefitTests.cs: 42.6 → "up to 43%", 12.0 → "up to 12%", 100.0 → "up to 100%" (unchanged), and a new case, 0.4 → "up to 0%" (PerformanceStudio still shows the row when the score is positive but rounds to zero, since the > 0 gating condition is on the raw score, not the rounded text).

RED on the pre-fix commit (7fdbd52): copying the updated test file into a detached worktree at that commit and running -class Darling.Tests.ViewerWaitRowBenefitTests fails 3 of 8: Benefit_ReturnsUpToNPercent_WhenAMatchingFindingHasAPositiveScore ("up to 43%" expected, "up to 42.6%" actual), Benefit_MatchesCaseInsensitively_OnTheWaitType" ("up to 12%" vs "up to 12.0%"), and Benefit_RoundsAFractionalScore_ToTheNearestWholePercent` ("up to 0%" vs "up to 0.4%").

GREEN on the fix: -class Darling.Tests.ViewerWaitRowBenefitTests → Total: 8, Errors: 0, Failed: 0, Skipped: 0.

Census family, run on this rig: CommentFilterAdoptionTests, DocCommentHygieneTests, and every *Census*Tests class that doesn't need a local Postgres server (EntraProviderPackageCensusTests, FileGrowthRiseUnitCensusTests, McpPayloadContractCensusTests, McpServiceParameterDiSeatCensusTests, PgSettingScrubCandidateCensusTests, SameStatementPileupSourceCensusTests, WebExceptionTextCensusTests) all pass alongside the updated pin: Total: 285, Errors: 0, Failed: 38, Skipped: 0 where every failure traces to LivePostgresStoreFixture.InitializeAsync (no Postgres server on this rig) — a pre-existing environment gap, not a regression from this change. DailyDeadlockWindowCensusTests, MeasurementContractCensusTests, PlanForceActionDetailCensusTests, RemoteCollectorServiceCancellationCensusTests, StoreApplicationNameCensusTests, and StoreSessionTimeZonePinCensusTests weren't run separately for the same reason (they also need a live Postgres fixture); CI decides them.

Builds, -p:EnableWindowsTargeting=true, analyzer-warnings grep empty: Darling.Tests (Release) and PerformanceMonitor.Ui — both 0 warnings / 0 errors.

CHANGELOG

SECTION: Changed
ENTRY: - The plan viewer's Wait Stats rows show each wait's potential benefit and never clip ([#4595]) - As in PerformanceStudio's desktop viewer, each wait row gains an "up to N%" benefit column taken from the plan's matching wait finding, the name and duration columns take the spare width with an ellipsis and a tooltip, and the bar and benefit columns size to their content so the percentage is never cut off.
REF: [#4595]: #4595

… never clips

Fixes #4576. Part of #4511.

PerformanceStudio (erikdarlingdata/PerformanceStudio@78a3370, refined
@80de6fc) fixed the Wait Stats card's rows so the flexible column is the
duration text, not the bar; the wait-type name is capped at 150px with an
ellipsis and a tooltip; and the trailing "up to N%" benefit sits in an
Auto column so it is never the thing that gets clipped. Every row also
carries a tooltip now.

PerformanceMonitor's wait grid (PlanViewerControl.Properties.cs's
ShowWaitStats, shared by the Darling Viewer and Lite) had none of this: a
fixed name column sized off the longest wait-type string, a fixed 300px
bar column, and no benefit column at all — the benefit percentage the
analyzer already scores per wait type (BenefitScorer.EmitWaitStatWarnings)
was never surfaced next to the row it belongs to.

This ports PS's row layout (star name/duration, Auto bar/benefit,
CharacterEllipsis + tooltip on the name and duration text) and adds the
benefit column, joining each wait row to its "Wait: {type}" finding
through a new pure helper, WaitRowText.Benefit, so the join and the
formatting are pinned outside WPF.

Not ported from PS: the theme-brush wait colors (tracked separately under
#4578) and the header text's own benefit suffix (tracked under #4571) —
this touches only the row layout inside ShowWaitStats.

## CHANGELOG entry
SECTION: Changed
ENTRY: The plan viewer's Wait Stats rows no longer clip their trailing
benefit percentage in a narrow window, and now show one for every wait
type the analyzer could score. ([#PR])
[#PR]: https://github.com/erikdarlingdata/PerformanceMonitor/pull/PR
The wait-row benefit column (#4576) showed one decimal below 100%.
PerformanceStudio's desktop plan viewer formats this value as a whole
number, so PM now matches: 42.6 becomes "up to 43%", and a benefit
below 1 rounds to "up to 0%" rather than showing a decimal.

FormatBenefitPercent (used only by the plan-warning header, a
different display) goes back to private.
# Conflicts:
#	PerformanceMonitor.Ui/PlanViewerControl.Properties.cs
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 28, 2026 16:10
@erikdarlingdata
erikdarlingdata merged commit 4b8b748 into dev Sep 28, 2026
17 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the viewer/4576-wait-rows branch September 28, 2026 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant