Skip to content

get_latch_stats moves reading guidance off tools/list (#3898) - #4119

Merged
erikdarlingdata merged 4 commits into
devfrom
feature/3898-content-latch
Sep 24, 2026
Merged

erikdarlingdata merged 4 commits into
devfrom
feature/3898-content-latch

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Refs #3898.

Why

get_latch_stats served 1,027 characters on Darling and 1,372 on Lite before a client asked its first question.
This lane moves the reading guidance off tools/list, into the per-tool guide get_tool_guide serves on demand.
The guidance covers which fields are floors, which zeros are measurements, and which windows are fixed. Nothing is deleted.

What changes

  • Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpLatchSpinlockTools.cs: get_latch_stats's
    Description(...) now carries a terse head, the <<GUIDE>> marker, then the entire original description
    verbatim as the tail.
  • Lite/Mcp/McpLatchSpinlockTools.cs: same split, with Lite's own original text as its tail. The head text is
    byte-identical to Darling's (enforced by McpToolGuideTests.EverySharedToolName_CarriesTheMarkerOnBothSkus_OrNeither_WithByteIdenticalHeads).
  • New head pins: Darling/Darling.Tests/McpToolGuideHeads.Latch.cs (McpToolGuideHeadsLatchTests) and
    Lite.Tests/McpToolGuideHeads.Latch.cs (same class name, Lite.Tests namespace).
  • Budget: updated only the get_latch_stats lines in Darling/Darling.Tests/McpToolsListBudget/DarlingMcpLatchSpinlockTools.txt
    and Lite.Tests/McpToolsListBudget/McpLatchSpinlockTools.txt. get_spinlock_stats's lines in the same two files
    are lane e5's, untouched here.

Darling and Lite genuinely differ in how this tool reads (D6). Darling sums each class's waits over the whole
hours_back window and ranks the top classes by that window total. Lite returns only the single newest snapshot
found within hours_back, paged to limit classes, heaviest last-interval wait first. The shared head states
both facts in one clause instead of picking one product's framing.

Darling-only fields (severity, severity_banded_from) and Lite-only fields (truncated, captured_at,
age_seconds) stay off the shared head. They live only in each product's own tail, the way list_servers
already does this for its Darling-only fleet fields.

Served characters

Tool Darling before Darling after Lite before Lite after
get_latch_stats 1,027 549 1,372 549

Status routes (both products never return status: "empty" for this tool)

Status Condition Darling Lite
not_collected rows.Count == 0 and the target's engine cannot produce sys.dm_os_latch_stats (a non-SQL-Server target) DarlingMcpLatchSpinlockTools.cs:78 McpLatchSpinlockTools.cs:73
unavailable rows.Count == 0 and the engine can produce it, but nothing was collected in the requested window DarlingMcpLatchSpinlockTools.cs:79 McpLatchSpinlockTools.cs:74
error an exception was thrown DarlingMcpLatchSpinlockTools.cs:129 McpLatchSpinlockTools.cs:116

Corrections

One sentence was wrong on all three products, and the lane had copied it into the head as "means TempDB allocation contention".

  • Darling and the Dashboard said: "High LATCH_EX on ACCESS_METHODS_DATASET_PARENT or FGCB_ADD_REMOVE indicates TempDB allocation contention."
  • Lite said: "High LATCH_EX on ACCESS_METHODS_DATASET_PARENT or a page-latch class indicates allocation/page contention (often TempDB)."

Microsoft's sys.dm_os_latch_stats page says ACCESS_METHODS_DATASET_PARENT is "Used to synchronize child dataset access to the parent dataset during parallel operations." It says FGCB_ADD_REMOVE is used "to synchronize access to filegroups for add, drop, grow, and shrink file operations." It also says BUFFER "covers all possible uses of page latches", so this DMV has no separate page-latch class. The product's own latch-class text in install/47_create_reporting_views.sql says the same. LATCH_EX is a wait type, not a latch class, and this DMV reports no latch mode.

The head no longer makes the claim. The tails now say:

  • Darling and the Dashboard: "ACCESS_METHODS_DATASET_PARENT synchronizes child dataset access to the parent dataset during parallel operations, and FGCB_ADD_REMOVE synchronizes filegroup add, drop, grow and shrink file operations."
  • Lite: "ACCESS_METHODS_DATASET_PARENT synchronizes child dataset access to the parent dataset during parallel operations, and every page latch reports under the one BUFFER class, where contention can mean hot pages or slow I/O."

The Dashboard's LATCH_EX next-tool hint also dropped its "(ACCESS_METHODS_DATASET_PARENT / FGCB_ADD_REMOVE point at TempDB allocation contention)" note. The dropped-sentence check now reports missing=1 on each product: the corrected sentence, and nothing else.

D4 removals

None. This tool's description carried no issue references and no anecdotes.

Re-pointed pins

None. git grep for get_latch_stats across Darling/Darling.Tests and Lite.Tests found no test asserting a
literal substring of the old head text. Every hit reads the tool body or SQL, which this PR did not touch. The rest
read the whole Description attribute for a substring that still exists in the preserved tail
(McpLatestSnapshotStampTests, Lite.Tests/McpLatestSnapshotStampTests).

The lane's own head-pin files changed in the coordinator pass. They follow the new head. A new test, LatchClasses_KeepMicrosoftsMeaning_NotATempDbReading, checks that the TempDB reading is gone from the head and the tail on both products.

D9 traps

  • Q: get_latch_stats on Darling ranks classes by their most recent interval's wait time.
    Correct: No. Darling ranks by the SUM of waits across the whole hours_back window, not the latest interval.
    Prevents it: "Darling sums waits over the whole hours_back window and returns the top classes by that total"
  • Q: get_latch_stats on Lite aggregates every collection inside hours_back into one number per class, the way Darling does.
    Correct: No. Lite returns only the single newest snapshot found within hours_back. The window only bounds the search for that snapshot.
    Prevents it: "Lite: LATEST IS A TIME, only the newest snapshot within hours_back, paged to limit, heaviest last-interval wait first"
  • Q: On Lite, the returned classes are sorted by their cumulative lifetime wait_time_ms.
    Correct: No. The page is ordered heaviest last-interval wait first, not by the cumulative total.
    Prevents it: "Lite: LATEST IS A TIME, only the newest snapshot within hours_back, paged to limit, heaviest last-interval wait first"
  • Q: On Lite, the newest latch snapshot inside hours_back is 20 hours old. Does the tool skip it as stale and return nothing?
    Correct: No. Lite returns the newest snapshot found within hours_back, however old it is inside that window. LATEST IS A TIME: the snapshot is the moment it was captured, and the payload's captured_at says when.
    Prevents it: "Lite: LATEST IS A TIME, only the newest snapshot within hours_back"
  • Q: severity is derived the same way as the window total, from every collection in hours_back.
    Correct: No. severity (Darling only) comes from the LATEST interval, the same single interval the per-second rates come from, never the window sum.
    Prevents it: "interval_seconds, both per-second rates and (on Darling) severity come from the LATEST interval only"
  • Q: Lite's twin exposes a severity field too, since the head mentions it.
    Correct: No. The head marks severity "(on Darling)" specifically. Lite's payload has no severity or severity_banded_from field at all.
    Prevents it: "(on Darling) severity come from the LATEST interval only"
  • Q: A row with waits_per_second: 0.00 on a restart means the class was momentarily quiet.
    Correct: No. When the interval is unknowable (a restart or first sample, stored as 0), the rates are null, never a real 0.
    Prevents it: "on a restart or first sample (interval unknowable, stored 0) they are null, never 0 or LOW"
  • Q: Darling's severity can legitimately read LOW on a restart row.
    Correct: No. severity is also null on that row, never a LOW banded from an unmeasured zero.
    Prevents it: "they are null, never 0 or LOW"
  • Q: An empty-looking result (no latch classes returned) means status empty, the way many other tools report it.
    Correct: No. This tool never returns status empty. Zero rows is either not_collected or unavailable.
    Prevents it: "Zero rows: not_collected on a non-SQL Server target, else unavailable, never empty"
  • Q: not_collected on this tool can mean the collector is merely behind schedule on a supported target.
    Correct: No. not_collected here means the target's engine cannot produce this data at all, because it is not a SQL Server engine. A supported target with nothing collected yet answers unavailable instead.
    Prevents it: "Zero rows: not_collected on a non-SQL Server target, else unavailable, never empty"
  • Q: Because the head is identical on both products, top and limit bound the same thing.
    Correct: No. Darling's top bounds how many classes the window ranking returns. Lite's limit bounds how many classes the snapshot page returns.
    Prevents it: "Darling sums waits over the whole hours_back window and returns the top classes by that total. Lite: LATEST IS A TIME, only the newest snapshot within hours_back, paged to limit"
  • Q: A class with a high window total this period must also show a high waits_per_second right now.
    Correct: Not necessarily. The window total (Darling only) and the per-second rates come from different sources: the whole window versus the latest interval only. A class can be high on one and unremarkable on the other.
    Prevents it: "interval_seconds, both per-second rates and (on Darling) severity come from the LATEST interval only"

Test plan

  • dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug - Build succeeded, 0 Warning(s), 0 Error(s).
  • dotnet build Lite.Tests/Lite.Tests.csproj -c Debug - Build succeeded, 0 Warning(s), 0 Error(s).
  • Targeted classes on both products (McpToolGuide*, McpToolsListBudget*, McpDescription*, McpZeroIsAMeasurement*,
    McpPayloadContractCensus*, DarlingMcpLatchSpinlockTools*, McpLatestSnapshotStamp*, McpPageContract*,
    DeltaFamilyUnknowableRowRead*): Darling 312 total, 0 failed. Lite 106 total, 0 failed.
  • Red-watch: broke the head's null/LOW clause, saw McpToolGuideHeadsLatchTests fail, restored it, reran green.
  • dropcheck.py origin/dev HEAD get_latch_stats: Darling sentences=6 missing=0, Lite sentences=6 missing=0.
  • git merge origin/dev (clean, no conflicts), then full suites once:
    • Darling.Tests: Total 13338, Failed 0, Skipped 636, Not Run 1. That one test is unrelated to this change, and
      it is not a failure, so it was not investigated further.
    • Lite.Tests: Total 5271, Failed 1 on first run
      (AnalysisPassTokenThreadingTests.TheReadLockWaitIsAbandonableWhileAWriterHoldsIt, a lock/threading test
      unrelated to MCP descriptions). Re-ran alone: passed. Treated as a timing flake, not filed.
  • Plain-English checker on this PR body: run once, real hits fixed. Remaining hits are the D9 labels
    (**Q:**, **Correct:**, **Prevents it:**) and two exact-quoted head phrases in "Prevents it" fields, both
    allowed by the brief.

Coordinator verification

  • I merged origin/dev (90d6af4a) into the branch. No parameter description changed.
  • I checked every head fact against the code. Darling's LatchStatsTopNSql sums delta_wait_time_ms over the window and returns LIMIT top classes by that total. Rates, interval_seconds and severity come from each class's newest row, and a stored interval of 0 becomes NULL. Lite reads the one newest snapshot inside the window, ordered by delta_wait_time_ms DESC, and pages it to limit. Both products return not_collected or unavailable on zero rows and never empty. The latch_stats collector runs on every SQL Server edition, so not_collected only happens on a non-SQL Server target.
  • Head facts fixed: 3. (1) The TempDB claim was false (see Corrections). (2) "not_collected off SQL Server engines" read as "the collector is turned off" in D9, so it now says "on a non-SQL Server target". (3) The head never named Darling's top or Lite's "LATEST IS A TIME" (lesson 5), and D9 read top and limit as the same bound. It now says Darling "returns the top classes by that total" and "Lite: LATEST IS A TIME, only the newest snapshot within hours_back". Trap Q4 asked about the false claim, so I replaced it with a trap on the Lite snapshot.
  • D9 round 1, on the lane's head: 12 questions, 1 misread (Q11: top and limit "bound the same thing"). Q10 gave the right answer for the wrong reason ("the collector is turned off").
  • D9 round 2, on the new head: 12 questions, 0 misreads. Q10 came back as "can't tell".
  • The served size went from 614 to 549 on both products. I updated both head-pin files and both budget lines. The changelog buffer entry now says "fewer than 620 characters" and describes the correction.
  • After the edits, the Darling, Lite and Dashboard builds have 0 warnings. Targeted classes: Darling 310 tests (9 skipped) and Lite 125 tests, 0 failed. CI runs the full suites before the merge.

Handoff

None. get_latch_stats is fully converted on both products, green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC

erikdarlingdata and others added 4 commits September 23, 2026 22:56
Darling served 1,027 chars and Lite served 1,372 for this tool; both now
serve a 614-char head, with get_tool_guide serving the rest by name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
…eanings follow Microsoft's docs (#3898)

The head and the old description said high ACCESS_METHODS_DATASET_PARENT / FGCB_ADD_REMOVE latch waits mean
TempDB allocation contention. Per Microsoft's sys.dm_os_latch_stats page, the first synchronizes child dataset
access during parallel operations and the second covers filegroup add, drop, grow and shrink file operations.
The corrected sentence replaces the old one in the Darling, Lite and Dashboard descriptions, and in the
Dashboard's LATCH_EX next-tool hint. The head also names Darling's top and Lite's LATEST IS A TIME snapshot,
and says not_collected means a non-SQL Server target. Served head 614 -> 549 on both products.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 24, 2026 03:41
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 24, 2026 03:41
@erikdarlingdata
erikdarlingdata merged commit 192a0a4 into dev Sep 24, 2026
16 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/3898-content-latch branch September 24, 2026 04:13
erikdarlingdata added a commit that referenced this pull request Sep 24, 2026
* Add 48 buffered CHANGELOG entries in one splice

Adds 48 entries and 48 link refs (#4057, #4077, #4079, #4081, #4082, #4083, #4084, #4085, #4086, #4087, #4088, #4089, #4090, #4091, #4092, #4093, #4095, #4096, #4099, #4100, #4101, #4103, #4105, #4106, #4107, #4108, #4109, #4110, #4111, #4113, #4114, #4115, #4116, #4117, #4118, #4119, #4120, #4121, #4122, #4123, #4124, #4125, #4126, #4127, #4131, #4133, #4136, #4141). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran.

Entries found under a bare section line (none unless the old script ran again) move into the matching ### section, below the new entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC

* Changelog splice: add #4137's entry, which merged before the splice but was missed

#4137 (pg_plan_capture reads csvlog) merged at 05:57Z with a CHANGELOG entry section in its
body. It was neither spliced nor listed as needing no entry. Its entry goes under Fixed, right
after #4136's, and #4136's last sentence now points to it instead of saying plan capture is
unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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