Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
302 changes: 302 additions & 0 deletions Darling/Darling.Tests/DarlingMcpBlockingToolsTests.cs

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Darling/Darling.Tests/McpPayloadContractCensusTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1540,16 +1540,16 @@ public static readonly (string Key, string[] Files, string WhatWasCut)[] SecondB
"the dedup_key fingerprint scan's ceiling (FingerprintScanCeiling), observed off a ceiling + 1 fetch, beside the page's own truncated — two bounds in one payload, the second spelled <bound>_truncated"),
("processes_truncated", ["DarlingMcpBlockingTools.cs"],
"get_deadlock_detail's processes[] is cut by two bounds, the per-deadlock cap DarlingDeadlockProcessRows.MaxProcessesPerDeadlock (12) and the default page's shared row budget (DefaultPageRowBudget, 24); the rows cut from one deadlock are counted beside it. Unlike the boolean <bound>_truncated keys around it, processes_truncated is a COUNT (0 when nothing was cut), kept under this name because it is the per-deadlock twin of the page's truncated and a reader looks for it beside processes"),
("window_truncated", ["DarlingMcpConfigHistoryTools.cs", "DarlingMcpDataTools.cs", "DarlingMcpJobTools.cs", "DarlingMcpMemoryGrantTools.cs", "DarlingMcpPlanCorrectionTools.cs", "DarlingMcpQueryHeatmapTools.cs", "DarlingMcpQueryStoreClutterTools.cs", "DarlingMcpQueryStoreRegressionTools.cs", "DarlingMcpSessionTools.cs", "DarlingMcpTrendTools.cs", "McpBlockingTools.cs", "McpConfigHistoryTools.cs", "McpHealthTools.cs", "McpLongQueryTools.cs", "McpMemoryTools.cs", "McpPlanCorrectionTools.cs", "McpQueryTools.cs", "McpSessionTools.cs", "McpWaitTools.cs"],
"the #2364 / #2353 WINDOW floor (#3653 item 17): the tier that answered did not hold the whole requested window, so the served series begins later than asked — beside effective_start / effective_hours_back, observed off the served head against the shared ninety-minute TruncationSlack, no cap involved; get_query_trend and get_query_store_top write it in their initializers, the duration-trend trio through TrendDisclosure.WriteTo (Darling) and WriteDisclosure (Lite); get_query_store_clutter (#3797) writes it in its initializer for its plan-churn and wait arms, which read the same raw tier get_query_store_top does, off the same window-floor read and the same ninety-minute slack; Lite's get_active_queries and get_waiting_tasks (QuerySnapshots / WaitingTasks, read by coverage), get_query_store_regressions (checked against the baseline's start, the earlier of its two windows) and get_query_heatmap (#4966) write it in their initializers through McpQueryTools.WindowNotice, beside a page cut of their own (truncated), so they publish effective_start without effective_hours_back, which the window-floor rule below holds apart for the window floor; Darling's get_server_config_changes, get_database_config_changes and get_trace_flag_changes (#4966) write them after hours_back from the coverage probe of server_config, database_config and trace_flags (the same rule as the viewer's and the web's Config Changes notes), and on their empty answer inside the hints; Darling's get_collection_log (one server), get_plan_corrections and get_memory_pressure_events (#4966) write them after hours_back too: the log from its own coverage probe, which ignores the collector, status and duration filters so the note says where the log starts, plan corrections from plan_correction's coverage, where an answer of only automatic-tuning rows is a data answer, and memory pressure from the events' sample_time with its edge at the purge cutoff, each on its empty answer inside the hints; Darling's get_active_queries, get_waiting_tasks, get_query_heatmap and get_query_store_regressions (#4966) write the same three keys after hours_back through DarlingMcpWindowNotice, with the same wording and the same absence of effective_hours_back (the regressions tool checks its baseline's start, as Lite's does); Darling's get_active_queries (DarlingMcpSessionTools.cs) writes them only when the window was cut, so a covered default call stays inside the response budget and a missing key there means covered (Lite's writes them always), and each of the four leaves them off when its coverage probe failed, since a failed probe costs the notice and never the rows; and Lite's config-change, collection-log, plan-correction and memory-pressure tools (#4966) write it as well"),
("window_truncated", ["DarlingMcpBlockingTools.cs", "DarlingMcpConfigHistoryTools.cs", "DarlingMcpDataTools.cs", "DarlingMcpJobTools.cs", "DarlingMcpLongQueryTools.cs", "DarlingMcpMemoryGrantTools.cs", "DarlingMcpPlanCorrectionTools.cs", "DarlingMcpQueryHeatmapTools.cs", "DarlingMcpQueryStoreClutterTools.cs", "DarlingMcpQueryStoreRegressionTools.cs", "DarlingMcpSessionTools.cs", "DarlingMcpTrendTools.cs", "McpBlockingTools.cs", "McpConfigHistoryTools.cs", "McpHealthTools.cs", "McpLongQueryTools.cs", "McpMemoryTools.cs", "McpPlanCorrectionTools.cs", "McpQueryTools.cs", "McpSessionTools.cs", "McpWaitTools.cs"],
"the #2364 / #2353 WINDOW floor (#3653 item 17): the tier that answered did not hold the whole requested window, so the served series begins later than asked — beside effective_start / effective_hours_back, observed off the served head against the shared ninety-minute TruncationSlack, no cap involved; get_query_trend and get_query_store_top write it in their initializers, the duration-trend trio through TrendDisclosure.WriteTo (Darling) and WriteDisclosure (Lite); get_query_store_clutter (#3797) writes it in its initializer for its plan-churn and wait arms, which read the same raw tier get_query_store_top does, off the same window-floor read and the same ninety-minute slack; Lite's get_active_queries and get_waiting_tasks (QuerySnapshots / WaitingTasks, read by coverage), get_query_store_regressions (checked against the baseline's start, the earlier of its two windows) and get_query_heatmap (#4966) write it in their initializers through McpQueryTools.WindowNotice, beside a page cut of their own (truncated), so they publish effective_start without effective_hours_back, which the window-floor rule below holds apart for the window floor; Darling's get_server_config_changes, get_database_config_changes and get_trace_flag_changes (#4966) write them after hours_back from the coverage probe of server_config, database_config and trace_flags (the same rule as the viewer's and the web's Config Changes notes), and on their empty answer inside the hints; Darling's get_collection_log (one server), get_plan_corrections and get_memory_pressure_events (#4966) write them after hours_back too: the log from its own coverage probe, which ignores the collector, status and duration filters so the note says where the log starts, plan corrections from plan_correction's coverage, where an answer of only automatic-tuning rows is a data answer, and memory pressure from the events' sample_time with its edge at the purge cutoff, each on its empty answer inside the hints; Darling's get_active_queries, get_waiting_tasks, get_query_heatmap and get_query_store_regressions (#4966) write the same three keys after hours_back through DarlingMcpWindowNotice, with the same wording and the same absence of effective_hours_back (the regressions tool checks its baseline's start, as Lite's does); Darling's get_active_queries (DarlingMcpSessionTools.cs) writes them only when the window was cut, so a covered default call stays inside the response budget and a missing key there means covered (Lite's writes them always), and each of the four leaves them off when its coverage probe failed, since a failed probe costs the notice and never the rows; and Lite's config-change, collection-log, plan-correction and memory-pressure tools (#4966) write it as well; Darling's get_blocking, get_deadlocks, get_deadlock_detail, get_blocked_process_xml and get_long_query_completions (#4966) write the three keys after hours_back through DarlingMcpWindowNotice.ReadEventAsync, beside a page cut of their own (truncated), and leave them off when the coverage probe failed"),
("findings_truncated", ["DarlingMcpTools.cs", "McpAnalysisTools.cs"],
"#4198: get_analysis_findings' GROUP PAGE cut — limit caps the collapsed per-chain groups returned (default 18), independent of the pre-existing truncated above (the raw WindowCoveringLimit occurrence read, beside truncation_note): truncated warns occurrence stats may under-report, findings_truncated warns other diagnostic chains exist but are not on this page at all"),
];

public static readonly (string Key, string[] Files, string WhatItExplains)[] CutNoteKeys =
[
("truncation_note", ["DarlingMcpConfigHistoryTools.cs", "DarlingMcpDataTools.cs", "DarlingMcpMemoryGrantTools.cs", "DarlingMcpPlanCorrectionTools.cs", "DarlingMcpQueryHeatmapTools.cs", "DarlingMcpQueryStoreClutterTools.cs", "DarlingMcpQueryStoreRegressionTools.cs", "DarlingMcpSessionTools.cs", "DarlingMcpTools.cs", "McpAnalysisTools.cs", "McpBlockingTools.cs", "McpConfigHistoryTools.cs", "McpHealthTools.cs", "McpLongQueryTools.cs", "McpMemoryTools.cs", "McpPlanCorrectionTools.cs", "McpQueryTools.cs", "McpSessionTools.cs", "McpWaitTools.cs"],
"the prose beside the flag: on get_analysis_findings (both SKUs) beside truncated, the WindowCoveringLimit read cap observed off a cap + 1 fetch; on get_query_store_top and get_query_store_clutter beside window_truncated, the #2364 window floor — the store's raw retention did not reach the whole requested window (on the clutter view, for the two arms that read the raw tier); on Lite's get_top_queries_by_cpu / get_top_procedures_by_cpu / get_query_store_top (#4231), the same window-floor note, from LocalDataService.GetQueryWindowFloorAsync; and on Lite's get_active_queries, get_waiting_tasks, get_query_store_regressions and get_query_heatmap (#4966), the same note beside window_truncated, built by McpQueryTools.WindowNotice with the table each one read; and on Darling's get_active_queries, get_waiting_tasks, get_query_heatmap and get_query_store_regressions (#4966), the same note beside window_truncated, built by DarlingMcpWindowNotice with the table each one read (query_snapshots, waiting_tasks, query_stats, query_store_stats); and Lite's config-change, collection-log, plan-correction and memory-pressure tools (#4966), the same note from the coverage probe"),
("truncation_note", ["DarlingMcpBlockingTools.cs", "DarlingMcpConfigHistoryTools.cs", "DarlingMcpDataTools.cs", "DarlingMcpLongQueryTools.cs", "DarlingMcpMemoryGrantTools.cs", "DarlingMcpPlanCorrectionTools.cs", "DarlingMcpQueryHeatmapTools.cs", "DarlingMcpQueryStoreClutterTools.cs", "DarlingMcpQueryStoreRegressionTools.cs", "DarlingMcpSessionTools.cs", "DarlingMcpTools.cs", "McpAnalysisTools.cs", "McpBlockingTools.cs", "McpConfigHistoryTools.cs", "McpHealthTools.cs", "McpLongQueryTools.cs", "McpMemoryTools.cs", "McpPlanCorrectionTools.cs", "McpQueryTools.cs", "McpSessionTools.cs", "McpWaitTools.cs"],
"the prose beside the flag: on get_analysis_findings (both SKUs) beside truncated, the WindowCoveringLimit read cap observed off a cap + 1 fetch; on get_query_store_top and get_query_store_clutter beside window_truncated, the #2364 window floor — the store's raw retention did not reach the whole requested window (on the clutter view, for the two arms that read the raw tier); on Lite's get_top_queries_by_cpu / get_top_procedures_by_cpu / get_query_store_top (#4231), the same window-floor note, from LocalDataService.GetQueryWindowFloorAsync; and on Lite's get_active_queries, get_waiting_tasks, get_query_store_regressions and get_query_heatmap (#4966), the same note beside window_truncated, built by McpQueryTools.WindowNotice with the table each one read; and on Darling's get_active_queries, get_waiting_tasks, get_query_heatmap and get_query_store_regressions (#4966), the same note beside window_truncated, built by DarlingMcpWindowNotice with the table each one read (query_snapshots, waiting_tasks, query_stats, query_store_stats); and Lite's config-change, collection-log, plan-correction and memory-pressure tools (#4966), the same note from the coverage probe; and Darling's get_blocking, get_deadlocks, get_deadlock_detail, get_blocked_process_xml and get_long_query_completions (#4966), the same note, built by DarlingMcpWindowNotice with the collector table each one read"),
("findings_truncated_note", ["DarlingMcpTools.cs", "McpAnalysisTools.cs"],
"#4198: the prose beside findings_truncated — how many diagnostic chains were active in the window and that raising limit or narrowing hours_back would show more of them"),
];
Expand Down
4 changes: 2 additions & 2 deletions Darling/Darling.Tests/SeparatelyMonitoredListNoteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ public void TheWebPanels_DeclareTheNoteKey_AndTheHostsPassTheRegistry()
var end = js.IndexOf("),", at, StringComparison.Ordinal);
Assert.Contains("\"separately_monitored_note\"", js.Substring(at, end - at), StringComparison.Ordinal);
}
Assert.Contains("WebSqlTextPreviewLength, registryState, c.RequestAborted)", web, StringComparison.Ordinal);
Assert.Contains("as_of: AsOf(c), registryState: registryState, cancellationToken: c.RequestAborted)", web, StringComparison.Ordinal);
Assert.Contains("WebSqlTextPreviewLength, registryState, logger, c.RequestAborted)", web, StringComparison.Ordinal);
Assert.Contains("as_of: AsOf(c), registryState: registryState, logger: logger, cancellationToken: c.RequestAborted)", web, StringComparison.Ordinal);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4366,20 +4366,20 @@ logger is the tool's logger seat — the web host's SERVICE logger when MapAll b
["get_sweep_reports"] = (c, pg, an) => DarlingMcpFleetSweepTools.GetSweepReports(pg, logger, Hours(c, 1), AsOf(c), Str(c, "sweep_id"), Str(c, "watch_state"), c.RequestAborted),

/* ── blocking / deadlocks ── */
["get_blocked_process_xml"] = (c, pg, an) => DarlingMcpBlockingTools.GetBlockedProcessXml(pg, Server(c), Hours(c, 24), Rows(c, "limit", 5), as_of: AsOf(c), cancellationToken: c.RequestAborted),
["get_blocked_process_xml"] = (c, pg, an) => DarlingMcpBlockingTools.GetBlockedProcessXml(pg, Server(c), Hours(c, 24), Rows(c, "limit", 5), as_of: AsOf(c), logger: logger, cancellationToken: c.RequestAborted),
/* #4198: the internal overload, not the MCP tool wrapper — pins the OLD row limit (30) and the
OLD 2000-char text cap (WebSqlTextPreviewLength) explicitly, so this page does not change even
though the tool's own MCP defaults (limit 15, 150-char preview) did. Same shape #3897's trend
tools use to pass TrendBudget.Chart here instead of their own MCP point budget. */
["get_blocking"] = (c, pg, an) => DarlingMcpBlockingTools.GetBlocking(pg, Server(c), Hours(c, 24), Rows(c, "limit", 30), null, false, AsOf(c), DarlingMcpBlockingTools.WebSqlTextPreviewLength, registryState, c.RequestAborted),
["get_blocking"] = (c, pg, an) => DarlingMcpBlockingTools.GetBlocking(pg, Server(c), Hours(c, 24), Rows(c, "limit", 30), null, false, AsOf(c), DarlingMcpBlockingTools.WebSqlTextPreviewLength, registryState, logger, c.RequestAborted),
["get_blocking_trend"] = (c, pg, an) => DarlingMcpBlockingTools.GetBlockingTrend(pg, Server(c), Hours(c, 24), as_of: AsOf(c), cancellationToken: c.RequestAborted),
/* #4254: full_graph defaults false on the MCP signature (a preview keeps a busy production
store's tools/list-driven call under the shared response budget), but the web viewer has
always shown the whole graph. The row pins its OWN default to true so #4198's MCP-side
budget cut does not silently shrink what the viewer renders. */
["get_deadlock_detail"] = (c, pg, an) => DarlingMcpBlockingTools.GetDeadlockDetail(pg, Server(c), Hours(c, 24), Rows(c, "limit", 5), full_graph: QueryBool(c, "full_graph", true), as_of: AsOf(c), cancellationToken: c.RequestAborted),
["get_deadlock_detail"] = (c, pg, an) => DarlingMcpBlockingTools.GetDeadlockDetail(pg, Server(c), Hours(c, 24), Rows(c, "limit", 5), full_graph: QueryBool(c, "full_graph", true), as_of: AsOf(c), logger: logger, cancellationToken: c.RequestAborted),
["get_deadlock_trend"] = (c, pg, an) => DarlingMcpBlockingTools.GetDeadlockTrend(pg, Server(c), Hours(c, 24), as_of: AsOf(c), cancellationToken: c.RequestAborted),
["get_deadlocks"] = (c, pg, an) => DarlingMcpBlockingTools.GetDeadlocks(pg, Server(c), Hours(c, 24), Rows(c, "limit", 20), as_of: AsOf(c), registryState: registryState, cancellationToken: c.RequestAborted),
["get_deadlocks"] = (c, pg, an) => DarlingMcpBlockingTools.GetDeadlocks(pg, Server(c), Hours(c, 24), Rows(c, "limit", 20), as_of: AsOf(c), registryState: registryState, logger: logger, cancellationToken: c.RequestAborted),
["get_lock_wait_trend"] = (c, pg, an) => OptionalInt(c, "bucket_minutes", out var bucketMinutes)
? DarlingMcpBlockingTools.GetLockWaitTrend(pg, Server(c), Hours(c, 24), AsOf(c), bucketMinutes, TrendBudget.Chart, c.RequestAborted)
: UnparseableParam("bucket_minutes"),
Expand Down Expand Up @@ -4438,7 +4438,7 @@ budget cut does not silently shrink what the viewer renders. */
still lets an operator ask for the whole statement via ?full_text=true, but the default (no
query override) reproduces the page exactly as it always rendered. */
["get_query_store_top"] = (c, pg, an) => DarlingMcpDataTools.GetQueryStoreTop(pg, Server(c), Hours(c, 24), Rows(c, "top", 20), Str(c, "database_name"), as_of: AsOf(c), execution_type: Str(c, "execution_type"), module_name: Str(c, "module_name"), full_text: QueryBool(c, "full_text", false), previewLength: 2000, cancellationToken: c.RequestAborted),
["get_long_query_completions"] = (c, pg, an) => DarlingMcpLongQueryTools.GetLongQueryCompletions(pg, Server(c), Hours(c, 24), Rows(c, "limit", 30), as_of: AsOf(c), cancellationToken: c.RequestAborted),
["get_long_query_completions"] = (c, pg, an) => DarlingMcpLongQueryTools.GetLongQueryCompletions(pg, Server(c), Hours(c, 24), Rows(c, "limit", 30), as_of: AsOf(c), logger: logger, cancellationToken: c.RequestAborted),
["get_server_properties"] = (c, pg, an) => DarlingMcpDataTools.GetServerProperties(pg, Server(c), c.RequestAborted),
["get_tempdb_trend"] = (c, pg, an) => OptionalInt(c, "bucket_minutes", out var bucketMinutes)
? DarlingMcpDataTools.GetTempDbTrend(pg, Server(c), Hours(c, 24), AsOf(c), bucketMinutes, TrendBudget.Chart, c.RequestAborted)
Expand Down
Loading
Loading