Repository navigation
get_query_store_duration_trend/get_perfmon_trend move reading guidance off tools/list (#3898) - #4101
Merged
Merged
Conversation
…e off tools/list (#3898) Splits both tools' descriptions into a short head (served by tools/list) and a full reading-guide tail (served by get_tool_guide), per #3898. Nothing is deleted: the original prose moves to the tail verbatim. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
The head used a literal "/" right before sample_interval_seconds
("delta_value / sample_interval_seconds"), which MeasurementContractCensusTests'
BareIntervalDenominator regex reads as an unguarded SQL division, since the
scanner treats any string literal containing "interval" as SQL to check.
Spelled out as "divided by", matching the tail's own prose, on both products.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
get_perfmon_trend: unavailable means no counter at all was collected in the window, not "nothing was collected here yet" (collected.Count == 0 over the window, both products). get_query_store_duration_trend: each interval is counted once, at the hour it ran; a point is not always an hour (Lite rates per collection, Darling raw points are Query Store intervals). Both facts pinned on both products; budgets 599 -> 616 and 609 -> 615. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
erikdarlingdata
marked this pull request as ready for review
September 24, 2026 01:31
erikdarlingdata
enabled auto-merge (squash)
September 24, 2026 01:31
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
A client that loads Darling's whole MCP catalog pays for every tool description before it asks its first
question.
get_query_store_duration_trendandget_perfmon_trendcarried long reading guidance in theirtools/listdescriptions. This PR moves that guidance offtools/listand intoget_tool_guide, which servesit on demand by tool name. Nothing is deleted.
What changes
Both tools now carry a short head (served by
tools/list, at most 620 characters including theReading guide: get_tool_guide.pointer) and a full tail (the original description, unchanged, served only byget_tool_guide).get_query_store_duration_trendhas a Lite twin inLite/Mcp/McpQueryTools.cs.get_perfmon_trendhas a Litetwin in
Lite/Mcp/McpPerfmonTools.cs. Both twins convert in this PR with byte-identical heads, per D6.Files touched:
Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpTrendTools.csLite/Mcp/McpQueryTools.csLite/Mcp/McpPerfmonTools.csDarling/Darling.Tests/McpToolGuideHeads.Trend.cs(new head pins)Lite.Tests/McpToolGuideHeads.Trend.cs(new head pins)Darling/Darling.Tests/McpToolsListBudget/DarlingMcpTrendTools.txtLite.Tests/McpToolsListBudget/McpQueryTools.txtLite.Tests/McpToolsListBudget/McpPerfmonTools.txtNo topic file was needed: nothing new is duplicated between these two tools' heads or tails.
The first draft of get_perfmon_trend's head wrote the rate formula as "delta_value / sample_interval_seconds".
The full Lite suite caught
MeasurementContractCensusTests.NoReaderDividesByTheStoredIntervalBare. This censusreads every string literal containing the word "interval" as SQL. It flags a bare, unguarded division by
sample_interval_seconds. The literal slash in the head's prose matched that pattern, on both products. Thetail's own original prose spells the same fact as "divided by", so the head now does too, on both products.
Served-chars table
Numbers are from
McpToolsListBudgetTestson each product. The "after" number includes theReading guide: get_tool_guide.pointer.Corrections
None. Both tools' original prose held up against their bodies.
D4 removals
None. Neither tool's original description carried an issue reference or an anecdote.
Re-pointed pins
McpPayloadContractCensusTests.EveryWindowFloorTool_CarriesTheSharedClause_AndNoOtherToolDoes(Darling) checksevery tool on its
WindowFloorToolsroster by reflection.get_query_store_duration_trendis on that roster onboth products. A converted head must still state the
window_truncatedkey. It must also carry the words "nota page cut". My first draft head said "window_truncated marks the retention floor" without that exact phrase,
and the pin caught it. I added ", not a page cut" to the head. Served length went from 583 to 599 characters.
The pin checks the converted head's own wording, not text that moved elsewhere, and it passed once the head
carried the phrase the pin requires.
Status routes
get_perfmon_trend. Same branches, same order, on both products. It never returns
empty:get_query_store_duration_trend:
On both tools, the engine gate (
not_collected) is checked first. No empty or unavailable determination happensbefore it. So "no points" never by itself proves a quiet server or a missing collector.
D9 traps
Correct: No. This tool never returns status empty. A miss is always not_collected or unavailable.
Prevents it:
No points never returns empty: not_collected covers a gated engine, Page Life Expectancy, or an unknown counter name; unavailable means nothing was collected here yet.Correct: No. unavailable means no counters at all were collected in the window. A wrong name, when other
counters were collected, returns not_collected instead, with the real names listed.
Prevents it:
not_collected covers a gated engine, Page Life Expectancy, or an unknown counter name; unavailable means nothing was collected here yet.Correct: No. Divide by sample_interval_seconds. Never divide when that interval is 0.
Prevents it:
rate - per-second is delta_value divided by sample_interval_seconds, never delta_value alone or when the interval is 0.Correct: No. A level never carries a delta. Null delta_value and null sample_interval_seconds are the
gauge's normal shape.
Prevents it:
gauge - value is the bucket average, delta_value and sample_interval_seconds null (no delta for a level).Correct: No. Classify it by name instead. A name ending in /sec is a rate. Null just means the row
predates the stored type, or the instances mix types.
Prevents it:
null - classify by name (ends in /sec = rate).Correct: No. 'other' is a non-rate change, never divided by sample_interval_seconds.
Prevents it:
other - delta_value is a non-rate change.quiet?
Correct: Not necessarily. On Darling, empty can also mean the window is older than the corrected rollup's
covered range. That is a coverage gap needing a backfill, not a quiet server.
Prevents it:
empty: quiet on Lite always; on Darling, empty can also be a rollup coverage gap (window predates the corrected rollup, run --backfill-rollups).Correct: No. Lite has no rollup tier, so its empty is always a genuinely quiet window.
Prevents it:
empty: quiet on Lite always; on Darling, empty can also be a rollup coverage gap...Correct: Not exactly. unavailable means this server has never been sampled at all. Query Store being off
everywhere is the likely cause, but the status itself marks "never sampled," not "currently off."
Prevents it:
unavailable: never sampled here.between empty and unavailable?
Correct: No. not_collected is checked first, before any empty or unavailable determination.
Prevents it:
not_collected: engine cannot run Query Store.Correct: No. Null rates are never reported as 0. unrated_points counts them, and unrated_note says why.
Prevents it:
A point with no earlier point to rate against carries null rates, never 0 (unrated_points, unrated_note says why).it?
Correct: No. window_truncated marks a retention-floor start, not a page cut. No limit changes it.
Prevents it:
window_truncated marks the retention floor, not a page cut; effective_start says where the answer begins.are?
Correct: No. This tool has no bucket_minutes parameter. Every point is one Query Store interval, one point
per hour worked.
Prevents it:
one point per hour worked.empty for that same quiet server and window?
Correct: No. get_perfmon_trend never returns status empty for any reason. Its zero-point answer is always
not_collected or unavailable, regardless of what the paired trend tool says.
Prevents it:
No points never returns empty(get_perfmon_trend) versusempty:appearing inget_query_store_duration_trend's own head.
Test plan
dotnet build Darling/Darling.Tests/Darling.Tests.csproj: Build succeeded, 0 Warning(s), 0 Error(s).dotnet build Lite.Tests/Lite.Tests.csproj: Build succeeded, 0 Warning(s), 0 Error(s).McpDescription*, McpZeroIsAMeasurement*, McpPayloadContractCensusTests, plus every class
git grepfoundmentioning either tool. Result: 110 total, 0 failed, 4 skipped (live PostgreSQL, no rig here).
PerfmonCounterTypeTests, PerformanceTrendsToolTests. Result: 67 total, 0 failed, 0 skipped.
McpToolGuideHeadsTrendTestsfailed on both itsassertions, restored the text, confirmed green again.
dropcheck.py origin/dev HEAD get_query_store_duration_trend get_perfmon_trend): all four rows(Darling and Lite, both tools) report
missing=0.pass caught nothing new. The regression below only showed up on Lite's copy of the same census.
(
MeasurementContractCensusTests.NoReaderDividesByTheStoredIntervalBare, flagging both products' newget_perfmon_trend head), 0 skipped. Fixed by spelling the rate formula as "divided by" instead of "/".
missing=0.pr_body_trend.md: 38 hit(s), doc mode. Every remaining hit is anallowed exception: a D9 label, a semicolon or dash inside a quoted literal head phrase (code span) or a
table cell, or the attribution line's emoji.
Coordinator verification
missing=0in all four rows. paramcheck: no parameter changed.get_perfmon_trendsaid thatunavailablemeans "nothing was collected here yet". The code returnsunavailablewhen no counter at all was collected in the window (collected.Count == 0,DarlingMcpTrendTools.cs:225-226andMcpPerfmonTools.cs:117-118). A server with history before thewindow gets the same answer. The head now says "no counter at all collected in the window", which matches
the Status routes table above.
get_query_store_duration_trendsaid "one point per hour worked". The original text says that each intervalis counted once, at the hour the work ran. Lite rates one point per collection, and Darling's raw points are
Query Store intervals, so a point is not always an hour. The head now says "each interval counted once, at
the hour it ran".
get_query_store_duration_trendgoes from 599to 616, and
get_perfmon_trendfrom 609 to 615, on both products.unavailable): 0 misreads. Six answers were"not stated", which is not a misread.
McpPayloadContractCensusandMeasurementContractCensusincluded. Darling: 327 total, 0 failed, 6 skipped. Lite: 98 total, 0 failed. The lane's full suites ran
before these text edits, and CI runs both suites again.
Handoff
None. Both tools in this lane's brief are converted and green.
🤖 Generated with Claude Code
https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC