Skip to content

Roll the per-thread stats into one collapsed breakdown per section - #4587

Merged
erikdarlingdata merged 2 commits into
devfrom
viewer/4575-per-thread-breakdown
Sep 28, 2026
Merged

erikdarlingdata merged 2 commits into
devfrom
viewer/4575-per-thread-breakdown

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4575. Part of #4511.

Why

The plan viewer's Properties panel emitted one indented "Thread N" row per
thread, inline, right under each summary row: rows, rows read, executions,
elapsed, CPU, logical reads, physical reads, scans, and read-ahead reads. A
DOP 4 hash match already produces about thirty of those rows, and DOP 8
produces hundreds, so the handful of summary numbers most people open this
panel for were buried in a scroll marathon.

Ports erikdarlingdata/PerformanceStudio@ff7f1d9d59851b2f7438bdade83d710e4528a411
(and its current dev form, which is unchanged since that commit for this
code).

What changes

  • The summary rows are untouched.
  • The per-thread numbers now live in one collapsed "Per-thread breakdown"
    sub-expander per affected section (Actual Statistics, Actual Timing,
    Actual I/O), with threads grouped under a small header per metric.
  • A metric with no per-thread data is skipped, so a section only grows a
    breakdown when there is something in it.
  • Rows and Executions list idle threads too (a thread sitting at zero while
    its siblings work is exactly what someone opens the breakdown to see);
    Rows Read, Elapsed, CPU, Logical/Physical Reads, Scans, and Read-Ahead
    Reads only list threads with a nonzero value.
  • The breakdown header carries a skew suffix when there is skew. The share
    test mirrors PlanAnalyzer's Rule 8 (Parallel Skew) exactly — same
    coordinator-thread filter, same 1,000-rows-per-worker floor, same 0.80 (≤2
    workers) / 0.50 (>2 workers) share threshold — so the header never
    contradicts the Parallel Skew warning the same plan raises. There is also
    an idle-thread check Rule 8 does not make: a thread that returned nothing
    at all while its siblings did real work reads as skew on sight even when
    the busiest thread is under Rule 8's own share threshold.
  • The shaping (which metrics to show, in what order, and the skew math)
    moves into a new pure ThreadBreakdown static class in
    PerformanceMonitor.PlanAnalysis. The shared PlanViewerControl
    code-behind's new AddPerThreadBreakdown only renders what that helper
    returns; the previous inline nine-repeat block is gone.
  • Reuses the existing OrangeBrush (0xFFB347) already used elsewhere in this
    same panel for warnings, rather than adding a new brush.

Lite: gets this too (shared control) — PlanViewerControl lives in
PerformanceMonitor.Ui, and both the Darling Viewer and Lite reference that
project, so this one change reaches both hosts.

Not ported: none. This PR is the whole ff7f1d9 diff for this file.

Test plan

New file: Darling/Darling.Tests/ViewerThreadBreakdownTests.cs, 12 pins on
ThreadBreakdown — no-more-than-one-thread returns null, an all-zero metric
returns null, idle threads included/excluded per metric flag, header text
and thread count, the skew suffix format, the 0.50 threshold at 3+ workers
(just over and just under), the 0.80 threshold at 2 workers (60/40 vs
81/19), the idle-thread-always-reads-as-skew case, the 1,000-rows-per-worker
floor, metric ordering/skip, and unit carry-through.

RED on dev (pre-fix 130bc6a9e): compile-only, since ThreadBreakdown
doesn't exist there yet — 13 CS0103 errors, confirmed in a detached
worktree at that sha with the new test file copied in.

Runtime mutation: changed the >2-worker share threshold from 0.50 to
0.90 in ThreadBreakdown.cs. Build_carries_a_skew_suffix_at_three_or_more_workers_over_the_fifty_percent_share
went RED ([FAIL]); reverted, rebuilt, GREEN again.

Ran on this rig (in-process, Darling.Tests.dll with
Microsoft.WindowsDesktop.App stripped from its runtimeconfig.json):

-class Darling.Tests.ViewerThreadBreakdownTests
-class Darling.Tests.ShowPlanParserCondAndMultiplePlanTests
-class Darling.Tests.ActualPlanRequestTests
-class Darling.Tests.ActualPlanDispatchTests
-class Darling.Tests.ActualPlanResultParseTests
-class Darling.Tests.QueryModificationDetectorTests
-class Darling.Tests.ActualPlanCaptureLoopTests
-class Darling.Tests.ActualPlanGatingTests
-class Darling.Tests.ReproScriptBuilderHardeningTests
-class Darling.Tests.DarlingAnalysisPipelineTests
-class Darling.Tests.DarlingMcpPlanToolsSurfaceAndSqlTests
-class Darling.Tests.DarlingMcpPlanToolsLivePostgresTests
-class Darling.Tests.McpPlanAnalysisEnvelopeTests
-class Darling.Tests.SerialLoopStoreSizeSourceTests
-class Darling.Tests.TsqlConventionGuardTests
-class Darling.Tests.DocCommentHygieneTests

Total: 238, Errors: 0, Failed: 0, Skipped: 2, Not Run: 0

Also ran every Viewer* class discovered in Darling.Tests (160 classes,
1,655 tests): 86 failures, all pre-existing and all FileNotFoundException: PresentationFramework — WPF types this Mac can't load, unrelated to this
change (the same classes fail the same way with this branch's changes
reverted). No PlanSync* classes exist in this tree.

Builds (Release, -p:EnableWindowsTargeting=true), each 0 warnings / 0
errors: PerformanceMonitor.PlanAnalysis, PerformanceMonitor.Ui,
Darling.Tests, Lite/PerformanceMonitorLite.csproj, Lite.Tests,
Darling/PerformanceMonitor.Darling.Viewer.

Screenshot plan (for the Darling Viewer and Lite — same control):

  1. Capture an actual plan with a parallel hash match or sort at DOP 4+
    (rows in the thousands per worker, so the skew math has something to
    grade). Open the node's Properties panel.
  2. Actual Statistics, Actual Timing, and Actual I/O each show their summary
    rows exactly as before, no inline "Thread N" rows under any of them.
  3. Each affected section shows one "Per-thread breakdown (N threads)"
    sub-expander, collapsed by default, right where the old inline rows used
    to start.
  4. Expand a breakdown: metric names appear as small headers (Rows, Rows
    Read, Executions for Actual Statistics; Elapsed, CPU for Actual Timing;
    Logical Reads, Physical Reads, Scans, Read-Ahead Reads for Actual I/O),
    each with its own indented "Thread N: value" list underneath.
  5. If the sample plan has real skew, the breakdown header for Actual
    Statistics shows an amber "(skewed: N max / M min)" suffix next to the
    thread count, in the same amber this panel already uses for warnings.
    If balanced, no suffix.
  6. A metric with no per-thread data anywhere (e.g. Physical Reads on an
    all-in-memory plan) doesn't appear in the breakdown at all.

CHANGELOG

SECTION: Changed
ENTRY: - The plan viewer rolls per-thread stats into one collapsed breakdown ([#4587]) - For an actual parallel plan, the per-thread stats in each properties section collapse into one "Per-thread breakdown (N threads)" section, as in PerformanceStudio, with a skew note in its header when the work is unbalanced, instead of an inline row per thread per metric.
REF: [#4587]: #4587

Every actual metric emitted one indented Thread N row per thread, inline,
right under its own summary row: rows, rows read, executions, elapsed, CPU,
logical reads, physical reads, scans, and read-ahead reads. A DOP 4 hash
match already produced about thirty of those rows and DOP 8 produces
hundreds, so the handful of summary numbers people actually open this panel
for were buried in a scroll marathon.

The summary rows are untouched. The per-thread numbers now live in one
collapsed Per-thread breakdown sub-expander per affected section (Actual
Statistics, Actual Timing, Actual I/O), with the threads grouped under a
small header per metric. A metric with no per-thread data is skipped, so a
section only grows a breakdown when there is something in it. Rows and
executions list idle threads too, since a thread sitting at zero while its
siblings work is exactly what someone opens the breakdown to see.

The breakdown header carries a skew suffix when there is skew: the share
test mirrors PlanAnalyzer's Rule 8 (Parallel Skew) exactly (same coordinator
filter, same 1,000-rows-per-worker floor, same 0.80/0.50 share threshold) so
the header never contradicts the warning the same plan raises, plus an
idle-thread test Rule 8 does not make, since a thread that returned nothing
at all while its siblings did real work reads as skew on sight even when the
busiest thread is under Rule 8's share threshold.

The shaping (which metrics have data, in what order, and the skew math)
lives in a new pure ThreadBreakdown helper in PerformanceMonitor.PlanAnalysis
so it can be pinned without WPF. The shared PlanViewerControl code-behind
only renders what that helper returns, so Lite and the Darling Viewer both
get the collapsed breakdown from this one change.
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 28, 2026 15:42
@erikdarlingdata
erikdarlingdata merged commit 72cf4e5 into dev Sep 28, 2026
15 of 16 checks passed
@erikdarlingdata
erikdarlingdata deleted the viewer/4575-per-thread-breakdown branch September 28, 2026 15:42
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