Skip to content

The WPF Viewer computes the fleet deadlock total a third time, with the same structural-zero defect — and sharing the coverage type crosses an assembly boundary #3029

Description

@erikdarlingdata

The WPF Viewer computes the fleet deadlock total a third time, independently of the MCP tool and the web dashboard, and it carries the same structural-zero defect #3017 was filed about. Fixing it is not a rendering change — it crosses an assembly boundary — which is why it is filed here rather than folded into #3027.

The finding

Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.Fleet.cs's FleetTotalsSql runs its own:

SELECT COUNT(*) FROM v_deadlocks WHERE deadlock_time >= $1 AND deadlock_time <= $2

That reaches the WPF Overview through FleetRollup.Build → MainWindow.xaml:876, as a bare binding:

Text="{Binding TotalDeadlocks, StringFormat='Deadlocks: {0}'}"

On a PostgreSQL fleet it is structurally zero forever, exactly as get_fleet_overview was — v_deadlocks is written only by the SQL Server extended-event collector, and no v_pg_deadlocks exists. Same defect, same argument, different assembly.

And the same ambiguity: zero is also what a genuinely quiet SQL Server fleet reports, so the reading that needs no action and the reading that means "this total does not cover your fleet" are indistinguishable.

Why this is not the client-rendering change #3027 already shipped

Checked rather than assumed:

  • PerformanceMonitor.Darling.Viewer does not reference PerformanceMonitor.Darling.Service. Its ProjectReferences are Common, Ui, PlanAnalysis, Alerting, Analysis, Notifications, Darling.Analysis, Collectors, Darling.Storage. So FleetDeadlockSource, FleetDeadlockCoverage and ClassifyDeadlockSource are unreachable from it.

    Sharing them means moving a public enum, a public DTO and a classify method out of the Service into Common. That is arguably the right home — "banding lives once" is this codebase's own line — but it is a public type crossing assemblies, and the Service's JSON-shape pins have to be re-verified afterward.

  • The Viewer's Overview read selects no engine_kind at all. engine_kind appears only in ViewerDataService.MonitoredServers.cs and ViewerDataService.cs, never on the Overview path, and ServerSummaryItem carries no PostgreSQL flag. Both the read and the DTO have to grow one.

  • The Viewer's summary path keeps only a FAILING count, not the per-collector band, so the deadlocks collector's band has to be retained through it the way the service reader now does.

  • Plus MainWindow.xaml and ViewerFleetRollupTests.cs (626 lines).

Roughly five more files including the cross-assembly move.

Why it was split rather than folded

#3027 was already at review shape on a ~1000-line dual-gate diff, and its routing instruction was explicitly "no server-side change" — which this cannot honour. The cross-assembly public-type relocation is a different review class and deserves its own reviewers' full attention rather than a rider slot on a diff that size. Same call as #3017 item 3, which also went to its own lane.

The costing above is carried over verbatim so whoever picks this up starts from the ProjectReference facts rather than rediscovering them.

What a fix has to decide

  • Where the shared types live. Common is the obvious home if "banding lives once" holds, but the move has to leave the Service's existing JSON-shape pins green.
  • Whether the Viewer needs the full coverage object or a narrower signal. The web tile takes servers_read/servers_total and renders a sub-line; the WPF Overview is a single bound string today and may want something different.
  • What the tile shows when coverage is complete, which Fixes #3017 (items 1 and 2) #3027 had to decide for the web surface — a permanent "12 of 12" trains people to ignore it, while showing something only on incomplete coverage means absence carries meaning.

Priority

Same defect family as #3017, so it inherits that priority rather than urgency. Nothing is newly broken; the number has read this way for as long as the Viewer has had the tile.

Activity

  1. added a commit that references this issue on Sep 5, 2026
    473a806
  2. erikdarlingdata commented on Sep 5, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling

    Closed by #3042, merged to dev at 3d9b049a53da3f78bfe7e36a45a752dcdefb20cf. Closing by hand because a closing keyword does not fire against dev.

    The WPF Overview's Deadlocks: N now carries its coverage, so a structural zero and a genuinely quiet fleet stop reading the same. The shared types moved to PerformanceMonitor.Common and the service's wire shape did not change — the emitted JSON is byte-identical across the move, md5 c6cfb86cfd50ca3e418016d592fdaf7a, measured on both branches and staleness-controlled by mutating a moved constant and watching the hash move and return.

    Two corrections to this issue's costing, both in the cheaper direction. The Overview read does not need to grow an engine_kind select: DarlingServer already carries IsPostgres through both registry reads and LoadOverviewAsync already stamps ServerName from it, so no summary query changed. The collector band is likewise free — it comes out of the collection-health rows already being enumerated to count HEALTHY and FAILING.

    A fifth uncovered cause exists that the service has no equivalent of, and it deliberately keeps its own words. The viewer's summaries silently drops a server whose per-server read failed this cycle (#2753). That server is uncovered for the same reason a null band is, but folding it into the silent-collector cause would send the reader to a collector when the thing that failed was the viewer's own read. So the denominator is the registered fleet, the four causes deliberately do not sum to it, and the shortfall stays UnknownCount and is named in the tooltip. No always-zero fifth field was added to the wire.

    Not verified, stated rather than implied: nothing was rendered. Wrap behaviour, the two brushes against the dark theme and tooltip length on screen are all unobserved — only that the bindings compiled into BAML. No PostgreSQL-only fleet was loaded in the viewer, so the measured case is asserted over constructed cards.

    One side finding, chipped rather than filed: PgDeadlocksGrid sits under the "Vacuum" tab while its own comment says "Activity".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions