Repository navigation
get_spinlock_stats moves reading guidance off tools/list (#3898) - #4127
Merged
Merged
Conversation
Converts the get_spinlock_stats twin: a 597-char head (pointer included) that states both products' shape in one clause (Darling sums the window, Lite serves the latest snapshot) so Darling's already-terse original does not grow. Every original sentence rides each product's own tail verbatim. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
…unters standing (#3898) The window-summed totals (Darling) and the cumulative counters (Lite) are unaffected when the latest interval is unknowable and its own rates go null; the head said the rates go null but not that the rest of the row stands, so state it, and raise the budget lines to the new 584-char served length (the lane's 597-char cap still holds). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
Wave E lesson 5: a latest-snapshot read keeps the catalog phrase in its head. Lite's get_spinlock_stats reads only the newest snapshot within hours_back (LocalDataService.LatchSpinlock.cs), so its clause now reads "Lite: LATEST IS A TIME, only the newest snapshot within hours_back", matching get_latch_stats. Head 564, served 595 on both products, under the 597 cap. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K
erikdarlingdata
marked this pull request as ready for review
September 24, 2026 04:29
erikdarlingdata
enabled auto-merge (squash)
September 24, 2026 04:29
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #3898.
Why
get_spinlock_statsis a twin tool whose two served descriptions differed a lot: Darling's was already terseat 597 characters, but Lite's ran to 1,306. Wave E converts it: the reading guidance moves off
tools/listintoget_tool_guide, and the served head stays true on both products.Lane e5 had one extra constraint. The head plus the 31-character pointer had to stay at or under 597 characters,
so Darling's
tools/listentry does not grow by even one byte.What changes
Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpLatchSpinlockTools.cs:get_spinlock_statsconverted. Every original sentence moved to the tail, verbatim.
Lite/Mcp/McpLatchSpinlockTools.cs: same tool, same head (byte-identical), Lite's own original sentences inits own tail.
Darling/Darling.Tests/McpToolsListBudget/DarlingMcpLatchSpinlockTools.txtandLite.Tests/McpToolsListBudget/McpLatchSpinlockTools.txt: only theget_spinlock_statslines changed. Lanee4's
get_latch_statslines in the same two files are untouched.Darling/Darling.Tests/McpToolGuideHeads.Spinlock.csandLite.Tests/McpToolGuideHeads.Spinlock.cs.The two products do not do the same thing here. The head says so in one clause instead of picking either
product's shape (D6). Darling sums every collection in
hours_backand ranks the top N by that total. Litealways returns the single newest snapshot found within
hours_back: cumulative counters plus the lastinterval's deltas, bounded by
limit.Both products still derive
interval_secondsand the two per-second rates from the latest interval only. Bothgo null, never a quiet 0, when that interval is unknowable. The window totals (Darling) or cumulative counters
(Lite) stand regardless.
Served characters, before and after
Both heads are byte-identical at 595 served characters (a 564-character head plus the 31-character pointer),
2 characters under the lane's 597 cap.
Corrections
None. Every fact in the original text checked out against
DarlingLatchSpinlockReader.GetSpinlockStatsTopNAsync(the window-sum SQL) and
LocalDataService.GetSpinlockStatsSnapshotAsync(the latest-snapshot SQL), plus thePerSecond/KnownIntervalhelpers inMcpLatchSpinlockTools.cs.D4 removals
None. Neither product's wire text carried an issue reference or an anecdote to begin with. This family was
already written in the terse, code-cited style #3898 asks for.
Re-pointed pins
None. Every existing test that mentions
get_spinlock_statspassed unmodified:DarlingMcpLatchSpinlockToolsTests,McpPageContractTestson both products,McpLatestSnapshotStampTestson both products, andCrossAppMcpToolInventoryPinTests. None of them pinned a sentence by its position in the old, single-blockdescription.
D9 traps
get_spinlock_statsanswersstatus unavailablewith nospinlocksarray. Is that proof the server haszero spinlock contention right now?
Correct: No.
unavailablemeans nothing was collected in the requested window, or the collector cannotrun on this engine at all, reported as
not_collectedfirst. It says nothing about whether contention exists.Prevents it:
No rows: unavailable (or not_collected first).total_delta_collisionsand Lite'sdelta_collisionsthe same kind of number?Correct: No. Darling's is a sum over every collection in
hours_back. Lite's is just the newestcollection's own last-interval delta.
Prevents it:
Darling sums every collection in hours_back, top N by total collisions; Lite returns only the latest snapshot within hours_back, cumulative counters plus last-interval deltasget_spinlock_statsaggregate contention over the wholehours_backwindow, the wayDarling's does?
Correct: No. Lite always serves one moment: the newest snapshot found within
hours_back, never a windowsum.
Prevents it:
Lite returns only the latest snapshot within hours_backcollisions_per_secondread0.00?Correct: No, it reads
null. A0.00reads as a measured, quiet rate.nullreads as an unmeasuredinterval, not a quiet one.
Prevents it:
null - never 0 - when unknowable (restart/first sample)also go null or drop that collection?
Correct: No. Only
interval_secondsand the two per-second rates go null on that row. The window sums andthe cumulative counters beside them are unaffected.
Prevents it:
totals/counters still stand.get_spinlock_statshave atruncatedflag, like Lite's?Correct: No. Darling's
topreturns up to that many rows. There is no signal for how many more existbeyond it.
Prevents it:
bounded by limit (truncated flags more)is written inside the sentence that starts "Litereturns," not as a claim true of both products.
truncatedistrue. Does that mean more spinlocks existed somewhere across the wholehours_backwindow than are shown?Correct: No. It is a single-snapshot page bound. The one latest collection held more rows than
limit.That has nothing to do with how far back
hours_backreaches.Prevents it:
Lite returns only the latest snapshot within hours_back ... bounded by limit (truncated flags more)interval_secondsand the per-second rates ever reflect an average across the whole window, even onDarling, where the other fields are window sums?
Correct: No. On both products they always come from the single latest interval.
Prevents it:
interval_seconds and the rates reflect the latest interval, never a window totalCorrect: No, the opposite is true. This is exactly the contention wait stats cannot see, which is why the
tool exists as a separate read.
Prevents it:
High contention is CPU-bound, not in wait stats.top/limitrank spinlocks by the latest interval's collisions on both products?Correct: No. Darling ranks by the total collisions summed over
hours_back. Only Lite's default order usesthe latest interval's delta.
Prevents it:
Darling sums every collection in hours_back, top N by total collisionshours_backmean the same thing on both products?Correct: Not quite. On Darling it is the period that gets summed. On Lite it is only how far back to
search for the newest snapshot. Nothing across it gets aggregated.
Prevents it:
Darling sums every collection in hours_back ... Lite returns only the latest snapshot within hours_backnot_collectedstatus the same thing asunavailable: no data in the window?Correct: No.
not_collectedmeans the collector cannot run on this server's engine or edition at all, andthat is checked first.
unavailablemeans this engine supports the collector, and nothing landed in thisparticular window.
Prevents it:
No rows: unavailable (or not_collected first).The word "first" marks the check order.Coordinator verification
D9 verification pass by a second lane, on the same branch.
git fetch origin devthengit merge --no-edit origin/devmerged clean (merge commit4ca0c599). It picked up 5 dev commits, including #4111, #4112, #4115and #4116. No conflicts, and no neighboring
McpToolsListBudgetline was touched.Size and identity
desc.py get_spinlock_statsafter the merge showed Darling head 553, served 584, and Lite the same. After thecoordinator's lesson 5 fix below, both are head 564, served 595, still byte-identical and under the 597 cap.
dropcheck.py origin/dev HEAD get_spinlock_statsshows Darlingsentences=4 missing=0and Litesentences=6 missing=0.paramcheck.py origin/dev HEAD get_spinlock_statsprinted no findings.
Every head/tail fact, re-checked against the tool body and its reader SQL on both products
This is this lane's own independent read, separate from the original lane's citations above.
hours_backand ranks the top N by that total. Confirmed inDarlingLatchSpinlockReader.cs'sSpinlockStatsTopNSql: theaggCTE sumsdelta_collisionsand friends(lines 189-198), then
ORDER BY a.total_delta_collisions DESC LIMIT $4(lines 221-224). AlsoDarlingMcpLatchSpinlockTools.cs:133-189.LocalDataService.LatchSpinlock.cs:263-295: alatestCTE takesMAX(collection_time), then the main queryfilters
WHERE collection_time = (SELECT mx FROM latest). AlsoMcpLatchSpinlockTools.cs:120-179, wherecaptured_atreadsrows[0].CollectionTime. This is the same asymmetric shape as get_latch_stats moves reading guidance off tools/list (#3898) #4119's latch twin, thebrief's own comparison case.
interval_secondsand the two per-second rates always come from the latest interval on both products, nevera window total. Darling's
latestCTE (DarlingLatchSpinlockReader.cs:200-211) is a separateDISTINCT ON (spinlock_name) ... ORDER BY collection_time DESC, apart from theaggCTE that builds the totals. Lite'sPerSecond/KnownIntervalhelpers (McpLatchSpinlockTools.cs:38-46) read only the newest row'ssample_interval_seconds.NULLIF(sample_interval_seconds, 0)(DarlingLatchSpinlockReader.cs:180-183) feeds aCASE WHEN interval_seconds > 0gate that only touches the rate columns (lines 204-208). The restart row's owndelta_collisionsvalue is 0. It sums into the total the same as any other row, so the total itself isnever nulled. Lite's
DeltaSeriesShaping.ReadableDelta(PerformanceMonitor.Common/DeltaSeriesShaping.cs:320-321)nulls the delta only when
intervalSeconds == 0. It leaves the delta standing, as a real value, when theinterval is
NULLinstead, on a row from before the interval column existed. That matches the tail's ownextra clause about a row "collected before the interval was stored."
truncatedfield and no signal for rows beyondtop(the response shape atDarlingMcpLatchSpinlockTools.cs:177-183has no such key). Lite fetcheslimit + 1rows and setstruncated = rows.Count > limit(McpLatchSpinlockTools.cs:141,146), ordereddelta_collisions DESC, collisions DESC(LocalDataService.LatchSpinlock.cs:294). The head's "bounded by limit (truncated flagsmore)" clause sits inside the Lite half of the sentence only. It is not claimed for Darling.
not_collectedis checked first, withunavailableas the fallback, on both products.DarlingEngineCapability.cs:62-82returnsStatus("not_collected", ...)at line 81, andMcpEngineCapability.cs:43-68does the same at line 68. Both tool bodies call it before falling back toMcpHelpers.Status("unavailable", ...).captured_atandage_secondson Lite.age_secondsiswindowEnd - capturedAt, clamped at zero(
McpLatestSnapshotStamp.cs:26-27). That is its distance from the window's end, not from wall-clock now.LATEST IS A TIMEphrase: the verifying lane left it in Lite's tail only, reasoning that a byte-identicalhead cannot carry a Lite-only fact. The coordinator overruled that. Wave E lesson 5 puts the phrase in the head
of a latest-snapshot read. A twin head can name one product's behavior:
get_latch_stats(get_latch_stats moves reading guidance off tools/list (#3898) #4119, merged)already says "Lite: LATEST IS A TIME, only the newest snapshot within hours_back". See the coordinator fix
below. The generic census
Lite.Tests/McpLatestSnapshotStampTests.cs:294-304checks the whole description, head and tailtogether, so it passed either way. Lesson 5 asks for the head.
Folklore
Traced "High spinlock contention indicates CPU-bound internal contention that doesn't appear in wait stats" to
its origin at
deprecated/Dashboard/Mcp/McpLatchSpinlockTools.cs:77. Darling's tail carries it verbatim, andLite's tail carries the same claim with light rewording. Checked it against two Microsoft Learn pages.
sys.dm_os_spinlock_stats
says contention can cause "significant CPU utilization." The
Whitepaper: Diagnose & Resolve Spinlock Contention
says "the symptom primarily associated with spinlock contention is high CPU consumption." Both support the
"CPU-bound" half of the claim. The whitepaper's diagnostic-tools table also lists a wait-statistics row. Starting
with SQL Server 2025 (17.x),
sys.dm_os_wait_statsandsys.dm_exec_session_wait_statscan show spinlock timeunder the
SPINLOCK_EXTwait type, gated behind trace flag 8134.That table row is a documented, narrow exception to the "doesn't appear in wait stats" claim. On 17.x, with a non-default
trace flag turned on, spinlock time does surface as a wait type. By default, and on every version before 17.x,
it still does not. The brief's own standard is "a claim that is only imprecise, not false, stays." This
sentence is not the kind of flatly wrong attribution #4119 corrected for
ACCESS_METHODS_DATASET_PARENT/TempDB.It is a true general-case statement with one opt-in, version-gated exception. Judged imprecise rather than
false on that standard, so the head, both tails and the deprecated Dashboard copy are all unchanged. Recording
the exception here for the coordinator's own call.
No corrections, no re-pointed pins
Checked the changelog buffer entry's size claims (597 and 1,306 characters before, 584 after, on both products)
against
dropcheck.py's own counts. They already matched, so the file is unchanged.Coordinator fix (lesson 5)
TIME, only the newest snapshot within hours_back". Lite's read is
LocalDataService.LatchSpinlock.cs, whichtakes only the newest snapshot. Head 553 to 564, served 584 to 595, on both products. The head pins, the two
budget lines and the changelog buffer entry now say 595.
clean.
wait stats): 0 misreads. Q2, Q6, Q7, Q11, Q12 and Q13 were answered "can't tell". Every other answer was
correct.
were the list below plus
*Spinlock*and*LatchSpinlock*.Targeted tests, re-run on the merged tree
Darling.Tests.exeandLite.Tests.exe, filtered by every class this PR's files touch. Also every classgit grep -l get_spinlock_statsfinds in the test projects:*McpToolGuide*,*McpToolsListBudget*,*McpDescription*,*McpZeroIsAMeasurement*,*McpPayloadContractCensus*,*MeasurementContractCensus*,*McpPageContract*,DarlingMcpLatchSpinlockToolsSurfaceAndSqlTests,DarlingMcpLatchSpinlockToolsLivePostgresTests,DarlingMcpMemoryGrantToolsSurfaceAndSqlTests,DarlingMcpMemoryGrantToolsLivePostgresTests,McpLatestSnapshotStampTests.0 is not pinnedflake.dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debuganddotnet build Lite.Tests/Lite.Tests.csproj -c Debugboth reportedBuild succeeded, 0 Warning(s), 0 Error(s).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).McpToolGuide*,McpToolsListBudget*,McpDescription*,McpZeroIsAMeasurement*,McpPayloadContractCensus*,DarlingMcpLatchSpinlockToolsTests,McpPageContractTests,McpLatestSnapshotStampTests,CrossAppMcpToolInventoryPinTests,SpinlockStatsCollectorDefinitionTests): Darling 293 total, 0 failed, 4 skipped (live-Postgres, no rig).Lite 110 total, 0 failed.
McpToolGuideHeadsSpinlockTestsfail with the exactAssert.Containsmiss, restored it, watched it passagain.
python dropcheck.py origin/dev HEAD get_spinlock_stats: Darlingsentences=4 missing=0, Litesentences=6 missing=0.git merge origin/devbrought in get_blocking/get_pg_replication_slots move reading guidance off tools/list (#3898) #4108, get_query_store_regressions moves reading guidance off tools/list (#3898) #4109, DarlingInstallLocationTests: the #4052 checks pass when run non-elevated #4111, and get_plan_corrections moves reading guidance off tools/list (#3898) #4105. There were no conflicts, and none ofthose PRs touch this lane's files.
Darling.Tests.exe, no filter): 13,337 total, 0 failed, 636 skipped, 1 not run.The 636 skips are the usual live-Postgres/pg-runtime classes with no rig. The 1 "not run" is unchanged
across two separate runs of the full suite in this worktree. It is unrelated to this PR's files and was
not investigated further, per the brief's token-economy guidance. The two
DarlingInstallLocationTestsfailures the brief calls out as known-until-DarlingInstallLocationTests: the #4052 checks pass when run non-elevated #4111 do not reproduce here. DarlingInstallLocationTests: the #4052 checks pass when run non-elevated #4111 merged into
devbeforethis branch's
git merge origin/dev, and both tests now pass.Lite.Tests.exe, no filter): 5,270 total, 0 failed on the final run. An earlier run(before the last content commit) showed 1 failure in
AnalysisPassTokenThreadingTests.TheReadLockWaitIsAbandonableWhileAWriterHoldsIt, a lock-timing test withno connection to this PR's files. Re-run alone, it passed. The final full run then passed clean too,
confirming a pre-existing flake, not a regression from this change.
fixed. The final pass found 26:
pr_body_3898-content-spinlock.md: 26 hit(s), doc mode. 24 are the exempt**Correct:**/**Prevents it:**bold-lead-ins, and 2 are a semicolon and a word count inside one**Prevents it:**code span, which quotes the head's exact text (also exempt) and cannot be rewordedwithout misquoting it.
Handoff
None.
get_spinlock_statswas this lane's only tool, and it converted cleanly on both products within the597-character cap.