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
26 changes: 10 additions & 16 deletions Darling/PerformanceMonitor.Darling.Service/DarlingWebEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,6 @@ public static class DarlingWebEndpoints
"get_mute_rules",
"get_notification_routes",
"get_sweep_reports",
"get_database_config_changes",
"get_database_scoped_config",
"get_server_config_changes",
"get_trace_flag_changes",
"get_collection_health",
"get_collection_log",
"get_current_waits_trend",
Expand Down Expand Up @@ -218,11 +214,9 @@ public static class DarlingWebEndpoints
"get_memory_grants",
"get_memory_pressure_events",
"get_resource_semaphore",
"get_database_sizes",

"get_pvs_stats",
"get_index_usage",
"get_object_locking",
"get_table_index_sizes",

"get_cpu_scheduler_pressure",
"get_plan_cache_bloat",
"get_running_jobs",
Expand Down Expand Up @@ -2986,11 +2980,11 @@ budget cut does not silently shrink what the viewer renders. */
["get_database_config"] = (c, pg, an) => DarlingMcpConfigTools.GetDatabaseConfig(pg, Server(c), Str(c, "database_name"), c.RequestAborted),
["get_server_config"] = (c, pg, an) => DarlingMcpConfigTools.GetServerConfig(pg, Server(c), c.RequestAborted),
["get_trace_flags"] = (c, pg, an) => DarlingMcpConfigTools.GetTraceFlags(pg, Server(c), c.RequestAborted),
["get_database_config_changes"] = (c, pg, an) => DarlingMcpConfigHistoryTools.GetDatabaseConfigChanges(pg, Server(c), Hours(c, 168), as_of: AsOf(c)),
["get_database_scoped_config"] = (c, pg, an) => DarlingMcpConfigHistoryTools.GetDatabaseScopedConfig(pg, Server(c), Str(c, "database_name")),
["get_database_config_changes"] = (c, pg, an) => DarlingMcpConfigHistoryTools.GetDatabaseConfigChanges(pg, Server(c), Hours(c, 168), as_of: AsOf(c), cancellationToken: c.RequestAborted),
["get_database_scoped_config"] = (c, pg, an) => DarlingMcpConfigHistoryTools.GetDatabaseScopedConfig(pg, Server(c), Str(c, "database_name"), cancellationToken: c.RequestAborted),
["get_query_store_health"] = (c, pg, an) => DarlingMcpConfigHistoryTools.GetQueryStoreHealth(pg, Server(c), Str(c, "database_name"), c.RequestAborted),
["get_server_config_changes"] = (c, pg, an) => DarlingMcpConfigHistoryTools.GetServerConfigChanges(pg, Server(c), Hours(c, 168), as_of: AsOf(c)),
["get_trace_flag_changes"] = (c, pg, an) => DarlingMcpConfigHistoryTools.GetTraceFlagChanges(pg, Server(c), Hours(c, 168), as_of: AsOf(c)),
["get_server_config_changes"] = (c, pg, an) => DarlingMcpConfigHistoryTools.GetServerConfigChanges(pg, Server(c), Hours(c, 168), as_of: AsOf(c), cancellationToken: c.RequestAborted),
["get_trace_flag_changes"] = (c, pg, an) => DarlingMcpConfigHistoryTools.GetTraceFlagChanges(pg, Server(c), Hours(c, 168), as_of: AsOf(c), cancellationToken: c.RequestAborted),

/* ── core data reads ── */
/* #4198: full_detail=true keeps the web viewer's payload exactly what it was before the default
Expand Down Expand Up @@ -3147,18 +3141,18 @@ sizing the points itself (the OptionalDouble rule). */
["get_resource_semaphore"] = (c, pg, an) => DarlingMcpMemoryGrantTools.GetResourceSemaphore(pg, Server(c), Hours(c, 24), as_of: AsOf(c)),

/* ── object / index stats ── */
["get_database_sizes"] = (c, pg, an) => DarlingMcpObjectStatsTools.GetDatabaseSizes(pg, Server(c)),
["get_database_sizes"] = (c, pg, an) => DarlingMcpObjectStatsTools.GetDatabaseSizes(pg, Server(c), cancellationToken: c.RequestAborted),
["get_pvs_stats"] = (c, pg, an) => DarlingMcpPvsTools.GetPvsStats(pg, Server(c), QueryInt(c, "trend_hours_back", null, 0)),
["get_index_usage"] = (c, pg, an) => DarlingMcpObjectStatsTools.GetIndexUsage(pg, Server(c), Str(c, "database_name"), Rows(c, "limit", 200)),
["get_index_usage"] = (c, pg, an) => DarlingMcpObjectStatsTools.GetIndexUsage(pg, Server(c), Str(c, "database_name"), Rows(c, "limit", 200), cancellationToken: c.RequestAborted),
/* #4258: limit defaults to 75 on the MCP signature now (was an uncapped-looking 200-row hard
fetch with no parameter at all), sized under the shared response budget. The web viewer has
always effectively received that old 200-row fetch (there was no smaller cap anywhere in the
path), so the row pins its OWN limit at 200 - the same value get_index_usage's row above pins
for the identical reason - rather than silently dropping to the new MCP default. 200 is well
under both McpHelpers.MaxTop and MaxRowLimit (1000 each), so the value is never refused or
reclamped by either validation layer. */
["get_object_locking"] = (c, pg, an) => DarlingMcpObjectStatsTools.GetObjectLocking(pg, Server(c), Rows(c, "limit", 200)),
["get_table_index_sizes"] = (c, pg, an) => DarlingMcpObjectStatsTools.GetTableIndexSizes(pg, Server(c)),
["get_object_locking"] = (c, pg, an) => DarlingMcpObjectStatsTools.GetObjectLocking(pg, Server(c), Rows(c, "limit", 200), cancellationToken: c.RequestAborted),
["get_table_index_sizes"] = (c, pg, an) => DarlingMcpObjectStatsTools.GetTableIndexSizes(pg, Server(c), cancellationToken: c.RequestAborted),

/* ── plan cache / scheduler ── */
["get_cpu_scheduler_pressure"] = (c, pg, an) => DarlingMcpPlanCacheSchedulerTools.GetCpuSchedulerPressure(pg, Server(c), Hours(c, 24), as_of: AsOf(c)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ public static async Task<string> GetServerConfigChanges(
NpgsqlDataSource postgres,
[Description("Server name or display name.")] string? server_name = null,
[Description("Hours of history to retrieve. Default 168 (7 days).")] int hours_back = 168,
[Description(McpHelpers.AsOfDescription)] string? as_of = null)
[Description(McpHelpers.AsOfDescription)] string? as_of = null,
CancellationToken cancellationToken = default)
{
var (resolved, error) = await DarlingServerResolver.ResolveOrErrorAsync(postgres, server_name);
var (resolved, error) = await DarlingServerResolver.ResolveOrErrorAsync(postgres, server_name, cancellationToken);
if (error != null) return error;

var validation = McpHelpers.ValidateWindow(hours_back, as_of, out var windowEnd);
Expand All @@ -58,13 +59,13 @@ public static async Task<string> GetServerConfigChanges(
{
var windowEndNaive = NaiveUtc(windowEnd);
var windowStart = windowEndNaive.AddHours(-hours_back);
var snapshots = await DarlingConfigHistoryReader.GetServerConfigSnapshotsAsync(postgres, resolved.ServerId);
var snapshots = await DarlingConfigHistoryReader.GetServerConfigSnapshotsAsync(postgres, resolved.ServerId, cancellationToken);
/* Unanchored, the tool still reads the full history and only lower-bounds — see UpperEdge, which
keeps DateTime.MaxValue as the (no-op) upper edge so the shared both-edges diff reproduces the
prior behaviour exactly. An as_of anchor is what closes the upper edge. */
var changes = ConfigChangeDiff.DiffServerConfigChanges(snapshots, windowStart, UpperEdge(as_of, windowEndNaive));
if (changes.Count == 0)
return await DarlingEngineCapability.NotCollectedStatusAsync(postgres, resolved.ServerId, resolved.ServerName, "server_config")
return await DarlingEngineCapability.NotCollectedStatusAsync(postgres, resolved.ServerId, resolved.ServerName, "server_config", cancellationToken)
?? NoChanges(resolved.ServerName, hours_back, DistinctCaptures(snapshots.Select(s => s.CaptureTime)));

var result = changes.Select(c => new
Expand All @@ -87,7 +88,7 @@ prior behaviour exactly. An as_of anchor is what closes the upper edge. */
changes = result
}, McpHelpers.JsonOptions);
}
catch (Exception ex)
catch (Exception ex) when (ex is not OperationCanceledException)
{
return McpHelpers.FormatError("get_server_config_changes", ex);
}
Expand All @@ -98,9 +99,10 @@ public static async Task<string> GetDatabaseConfigChanges(
NpgsqlDataSource postgres,
[Description("Server name or display name.")] string? server_name = null,
[Description("Hours of history to retrieve. Default 168 (7 days).")] int hours_back = 168,
[Description(McpHelpers.AsOfDescription)] string? as_of = null)
[Description(McpHelpers.AsOfDescription)] string? as_of = null,
CancellationToken cancellationToken = default)
{
var (resolved, error) = await DarlingServerResolver.ResolveOrErrorAsync(postgres, server_name);
var (resolved, error) = await DarlingServerResolver.ResolveOrErrorAsync(postgres, server_name, cancellationToken);
if (error != null) return error;

var validation = McpHelpers.ValidateWindow(hours_back, as_of, out var windowEnd);
Expand All @@ -110,10 +112,10 @@ public static async Task<string> GetDatabaseConfigChanges(
{
var windowEndNaive = NaiveUtc(windowEnd);
var windowStart = windowEndNaive.AddHours(-hours_back);
var snapshots = await DarlingConfigHistoryReader.GetDatabaseConfigSnapshotsAsync(postgres, resolved.ServerId);
var snapshots = await DarlingConfigHistoryReader.GetDatabaseConfigSnapshotsAsync(postgres, resolved.ServerId, cancellationToken);
var changes = ConfigChangeDiff.DiffDatabaseConfigChanges(snapshots, windowStart, UpperEdge(as_of, windowEndNaive));
if (changes.Count == 0)
return await DarlingEngineCapability.NotCollectedStatusAsync(postgres, resolved.ServerId, resolved.ServerName, "database_config")
return await DarlingEngineCapability.NotCollectedStatusAsync(postgres, resolved.ServerId, resolved.ServerName, "database_config", cancellationToken)
?? NoChanges(resolved.ServerName, hours_back, DistinctCaptures(snapshots.Select(s => s.CaptureTime)));

var result = changes.Select(c => new
Expand All @@ -133,7 +135,7 @@ public static async Task<string> GetDatabaseConfigChanges(
changes = result
}, McpHelpers.JsonOptions);
}
catch (Exception ex)
catch (Exception ex) when (ex is not OperationCanceledException)
{
return McpHelpers.FormatError("get_database_config_changes", ex);
}
Expand All @@ -144,9 +146,10 @@ public static async Task<string> GetTraceFlagChanges(
NpgsqlDataSource postgres,
[Description("Server name or display name.")] string? server_name = null,
[Description("Hours of history to retrieve. Default 168 (7 days).")] int hours_back = 168,
[Description(McpHelpers.AsOfDescription)] string? as_of = null)
[Description(McpHelpers.AsOfDescription)] string? as_of = null,
CancellationToken cancellationToken = default)
{
var (resolved, error) = await DarlingServerResolver.ResolveOrErrorAsync(postgres, server_name);
var (resolved, error) = await DarlingServerResolver.ResolveOrErrorAsync(postgres, server_name, cancellationToken);
if (error != null) return error;

var validation = McpHelpers.ValidateWindow(hours_back, as_of, out var windowEnd);
Expand All @@ -156,10 +159,10 @@ public static async Task<string> GetTraceFlagChanges(
{
var windowEndNaive = NaiveUtc(windowEnd);
var windowStart = windowEndNaive.AddHours(-hours_back);
var snapshots = await DarlingConfigHistoryReader.GetTraceFlagSnapshotsAsync(postgres, resolved.ServerId);
var snapshots = await DarlingConfigHistoryReader.GetTraceFlagSnapshotsAsync(postgres, resolved.ServerId, cancellationToken);
var changes = ConfigChangeDiff.DiffTraceFlagChanges(snapshots, windowStart, UpperEdge(as_of, windowEndNaive));
if (changes.Count == 0)
return await DarlingEngineCapability.NotCollectedStatusAsync(postgres, resolved.ServerId, resolved.ServerName, "trace_flags")
return await DarlingEngineCapability.NotCollectedStatusAsync(postgres, resolved.ServerId, resolved.ServerName, "trace_flags", cancellationToken)
?? NoChanges(resolved.ServerName, hours_back, DistinctCaptures(snapshots.Select(s => s.CaptureTime)));

var result = changes.Select(c => new
Expand All @@ -182,7 +185,7 @@ public static async Task<string> GetTraceFlagChanges(
changes = result
}, McpHelpers.JsonOptions);
}
catch (Exception ex)
catch (Exception ex) when (ex is not OperationCanceledException)
{
return McpHelpers.FormatError("get_trace_flag_changes", ex);
}
Expand All @@ -192,16 +195,17 @@ public static async Task<string> GetTraceFlagChanges(
public static async Task<string> GetDatabaseScopedConfig(
NpgsqlDataSource postgres,
[Description("Server name or display name.")] string? server_name = null,
[Description("Filter to a specific database. Omit for all databases.")] string? database_name = null)
[Description("Filter to a specific database. Omit for all databases.")] string? database_name = null,
CancellationToken cancellationToken = default)
{
var (resolved, error) = await DarlingServerResolver.ResolveOrErrorAsync(postgres, server_name);
var (resolved, error) = await DarlingServerResolver.ResolveOrErrorAsync(postgres, server_name, cancellationToken);
if (error != null) return error;

try
{
var snapshot = await DarlingConfigHistoryReader.GetLatestDatabaseScopedConfigAsync(postgres, resolved.ServerId);
var snapshot = await DarlingConfigHistoryReader.GetLatestDatabaseScopedConfigAsync(postgres, resolved.ServerId, cancellationToken);
if (snapshot.IsEmpty)
return await DarlingEngineCapability.NotCollectedStatusAsync(postgres, resolved.ServerId, resolved.ServerName, "database_scoped_config")
return await DarlingEngineCapability.NotCollectedStatusAsync(postgres, resolved.ServerId, resolved.ServerName, "database_scoped_config", cancellationToken)
?? McpHelpers.Status(
"unavailable",
"No database-scoped configuration data available. The config collector may not have run yet.");
Expand Down Expand Up @@ -232,7 +236,7 @@ public static async Task<string> GetDatabaseScopedConfig(
databases = grouped
}, McpHelpers.JsonOptions);
}
catch (Exception ex)
catch (Exception ex) when (ex is not OperationCanceledException)
{
return McpHelpers.FormatError("get_database_scoped_config", ex);
}
Expand Down
Loading
Loading