diff --git a/Darling/Darling.Tests/DarlingMcpHealthParserToolsTests.cs b/Darling/Darling.Tests/DarlingMcpHealthParserToolsTests.cs
index 6f9f769a5a..d6ac043ba0 100644
--- a/Darling/Darling.Tests/DarlingMcpHealthParserToolsTests.cs
+++ b/Darling/Darling.Tests/DarlingMcpHealthParserToolsTests.cs
@@ -208,12 +208,12 @@ viewer entry point still delegates rather than growing an independent (drift-pro
[Fact]
public void Significance_SchedulerIssue_MatchesViewer()
{
- foreach (var status in new[] { "WARNING", "OK", "", null })
+ foreach (var (sqlCpu, other, mem) in new (int?, int?, int?)[] { (90, 0, 100), (89, 0, 100), (0, 50, 100), (0, 49, 100), (0, 0, 50), (0, 0, 51), (null, null, null) })
{
- var r = new SchedulerIssueRecord { Status = status };
+ var r = new SchedulerIssueRecord { SqlCpuUtilization = sqlCpu, OtherProcessCpu = other, MemoryUtilization = mem };
Assert.Equal(SystemEventSignificance.IsSignificant(r), SystemHealthSignificance.IsSignificant(r));
}
- Assert.True(SystemHealthSignificance.IsSignificant(new SchedulerIssueRecord { Status = "WARNING" }));
+ Assert.True(SystemHealthSignificance.IsSignificant(new SchedulerIssueRecord { SqlCpuUtilization = 90 }));
}
[Fact]
@@ -311,7 +311,7 @@ public void RoundTrip_ShredFixturesThenGate_KeepsOnlySignificantRows()
// SystemHealthSignificance keeps the significant rows. Assert the same significant set the
// viewer's System Events tab surfaces.
Assert.True(SystemHealthSignificance.IsSignificant(
- SystemHealthParser.ParseSchedulerIssue(LoadFixture("scheduler_monitor.xml"))!)); // WARNING
+ SystemHealthParser.ParseSchedulerIssue(LoadFixture("scheduler_monitor_high_sql_cpu.xml"))!)); // SQL CPU 94 >= 90
Assert.True(SystemHealthSignificance.IsSignificant(
SystemHealthParser.ParseSevereError(LoadFixture("error_reported.xml"))!)); // severity 24
Assert.True(SystemHealthSignificance.IsSignificant(
@@ -398,7 +398,7 @@ await DarlingMcpTestData.ExecAsync(connection, ct,
VALUES ($1,$2,$3,$4,$5,$6,$7)",
CollectionIdGenerator.Next(), t, ServerId, ServerName, t, eventType, LoadFixture(fixture));
- await PlantEvent(SystemHealthParser.SchedulerMonitorEvent, "scheduler_monitor.xml");
+ await PlantEvent(SystemHealthParser.SchedulerMonitorEvent, "scheduler_monitor_high_sql_cpu.xml");
await PlantEvent(SystemHealthParser.ErrorReportedEvent, "error_reported.xml");
await PlantEvent(SystemHealthParser.SpServerDiagnosticsEvent, "sp_server_diagnostics_system.xml");
await PlantEvent(SystemHealthParser.SpServerDiagnosticsEvent, "sp_server_diagnostics_query_processing_warning.xml");
@@ -441,10 +441,20 @@ await DarlingMcpTestData.ExecAsync(connection, ct,
Assert.Contains("the absence is a measurement", broker.GetProperty("message").GetString()!, StringComparison.Ordinal);
/* The data envelope carries the same witness pair. */
- var scheduler = JsonDocument.Parse(await DarlingMcpHealthParserTools.GetSchedulerIssues(postgres, ServerName)).RootElement;
+ var schedulerJson = await DarlingMcpHealthParserTools.GetSchedulerIssues(postgres, ServerName);
+ var scheduler = JsonDocument.Parse(schedulerJson).RootElement;
Assert.True(scheduler.GetProperty("source_observed").GetBoolean());
Assert.Equal(t.ToString("o"), scheduler.GetProperty("last_captured_at").GetString());
+ /* #4452: the utilization fields the planted high-CPU sample carries — SQL CPU pinned at 94,
+ other-process CPU 4, idle 2, memory 100 — come back on the tool's own output text. This is
+ the port's product-path pin: it asserts on the served JSON text (not typed properties) so it
+ also compiles, unchanged, against the pre-port code, where these fields are null or absent. */
+ Assert.Contains("\"sql_cpu_utilization\":94", schedulerJson, StringComparison.Ordinal);
+ Assert.Contains("\"other_process_cpu\":4", schedulerJson, StringComparison.Ordinal);
+ Assert.Contains("\"system_idle\":2", schedulerJson, StringComparison.Ordinal);
+ Assert.Contains("\"memory_utilization\":100", schedulerJson, StringComparison.Ordinal);
+
/* an unknown server resolves to the listing error. */
Assert.StartsWith("Could not resolve server.", McpHelpers.ErrorMessageOf(await DarlingMcpHealthParserTools.GetSystemHealth(postgres, "darling-no-such-server")), StringComparison.Ordinal);
diff --git a/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor.xml b/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor.xml
deleted file mode 100644
index bae3a49deb..0000000000
--- a/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
- 10
- 3
- 2WARNING
- 1true
- 0false
- 1true
- 4500
- 4
-
diff --git a/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_high_sql_cpu.xml b/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_high_sql_cpu.xml
new file mode 100644
index 0000000000..798be8e0df
--- /dev/null
+++ b/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_high_sql_cpu.xml
@@ -0,0 +1,16 @@
+
+
+ 0
+ 0
+ 94
+ 2
+ 2233593750
+ 30312500
+ 505
+ 4096
+ 100
+
diff --git a/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_low_memory.xml b/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_low_memory.xml
new file mode 100644
index 0000000000..0bd959d45d
--- /dev/null
+++ b/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_low_memory.xml
@@ -0,0 +1,16 @@
+
+
+ 0
+ 0
+ 3
+ 95
+ 176250000
+ 7187500
+ 99
+ 77824
+ 50
+
diff --git a/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_normal.xml b/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_normal.xml
new file mode 100644
index 0000000000..7f0f5c74be
--- /dev/null
+++ b/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_normal.xml
@@ -0,0 +1,16 @@
+
+
+ 0
+ 0
+ 3
+ 95
+ 176250000
+ 7187500
+ 99
+ 77824
+ 100
+
diff --git a/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_other_process_cpu.xml b/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_other_process_cpu.xml
new file mode 100644
index 0000000000..13dad6fca4
--- /dev/null
+++ b/Darling/Darling.Tests/Fixtures/SystemHealth/scheduler_monitor_other_process_cpu.xml
@@ -0,0 +1,16 @@
+
+
+ 0
+ 0
+ 3
+ 40
+ 176250000
+ 7187500
+ 99
+ 77824
+ 100
+
diff --git a/Darling/Darling.Tests/McpToolGuideHeads.HealthParser.cs b/Darling/Darling.Tests/McpToolGuideHeads.HealthParser.cs
index 641963dd93..72044483d7 100644
--- a/Darling/Darling.Tests/McpToolGuideHeads.HealthParser.cs
+++ b/Darling/Darling.Tests/McpToolGuideHeads.HealthParser.cs
@@ -42,7 +42,7 @@ private static readonly (string Tool, string Fact)[] GateFacts =
("get_health_parser_memory_node_oom", "Ungated: every recorded OOM is returned."),
("get_health_parser_severe_errors", "Gated: severity 19 or higher only, benign connection-reset error numbers excluded"),
("get_health_parser_io_issues", "Gated: WARNING-state results only."),
- ("get_health_parser_scheduler_issues", "Gated: WARNING-state results only."),
+ ("get_health_parser_scheduler_issues", "Gated: only samples where SQL Server's own CPU is pinned, other processes are using half the box or more, or memory utilization has dropped to half or below."),
("get_health_parser_memory_conditions", "Gated: only snapshots whose last notification is RESOURCE_MEMPHYSICAL_LOW."),
("get_health_parser_cpu_tasks", "Gated: WARNING-state results with at least 10 pending tasks only."),
("get_health_parser_memory_broker", "Gated: RESOURCE_MEMPHYSICAL_LOW notifications only."),
diff --git a/Darling/Darling.Tests/McpToolsListBudget/DarlingMcpHealthParserTools.txt b/Darling/Darling.Tests/McpToolsListBudget/DarlingMcpHealthParserTools.txt
index 10e99f6ce5..0f65baaecd 100644
--- a/Darling/Darling.Tests/McpToolsListBudget/DarlingMcpHealthParserTools.txt
+++ b/Darling/Darling.Tests/McpToolsListBudget/DarlingMcpHealthParserTools.txt
@@ -29,7 +29,7 @@ param get_health_parser_memory_node_oom.hours_back 41
param get_health_parser_memory_node_oom.limit 38
param get_health_parser_memory_node_oom.server_name 28
-tool get_health_parser_scheduler_issues 446
+tool get_health_parser_scheduler_issues 536
param get_health_parser_scheduler_issues.as_of 167
param get_health_parser_scheduler_issues.hours_back 41
param get_health_parser_scheduler_issues.limit 38
diff --git a/Darling/Darling.Tests/McpToolsListBudgetTests.cs b/Darling/Darling.Tests/McpToolsListBudgetTests.cs
index 1698829bc5..278d2e0a35 100644
--- a/Darling/Darling.Tests/McpToolsListBudgetTests.cs
+++ b/Darling/Darling.Tests/McpToolsListBudgetTests.cs
@@ -184,7 +184,10 @@ shared 32 KB budget and is not a served description either. */
this PR's own get_store_host (+616) and dev's #4279 Lite-parity head change (+181). Constant set to the
value McpToolsListBudgetTests itself measured on the merged tree, not the two deltas added by hand. */
// #4442: get_read_latency's served block.
- private const int TotalCeilingBytes = 176_182;
+ // #4452 (merge): re-measured on the tree combining dev's #4442 get_read_latency addition with this
+ // branch's scheduler-issues tool description growth (+95). Constant set to the value
+ // McpToolsListBudgetTests itself measured on the merged tree, not the two deltas added by hand.
+ private const int TotalCeilingBytes = 176_277;
diff --git a/Darling/Darling.Tests/SystemHealthParserTests.cs b/Darling/Darling.Tests/SystemHealthParserTests.cs
index a120c137c9..44b31edb86 100644
--- a/Darling/Darling.Tests/SystemHealthParserTests.cs
+++ b/Darling/Darling.Tests/SystemHealthParserTests.cs
@@ -27,7 +27,7 @@ public class SystemHealthParserTests
private static string LoadFixture(string name) =>
File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "Fixtures", "SystemHealth", name));
- private static readonly DateTime SchedulerTime = new(2026, 7, 5, 11, 58, 0, 123, DateTimeKind.Utc);
+ private static readonly DateTime SchedulerHighCpuTime = new(2026, 9, 26, 22, 8, 48, 939, DateTimeKind.Utc);
private static readonly DateTime ErrorTime = new(2026, 7, 5, 12, 0, 5, 500, DateTimeKind.Utc);
private static readonly DateTime ResourceTime = new(2026, 7, 5, 12, 1, 0, 0, DateTimeKind.Utc);
private static readonly DateTime BrokerTime = new(2026, 7, 5, 12, 2, 10, 250, DateTimeKind.Utc);
@@ -41,20 +41,75 @@ private static string LoadFixture(string name) =>
// ── SchedulerIssues (scheduler_monitor_system_health_ring_buffer_recorded) ──
[Fact]
- public void SchedulerIssue_ShredsEveryColumn()
+ public void SchedulerIssue_ShredsEveryColumn_HighSqlCpu()
{
- var r = SystemHealthParser.ParseSchedulerIssue(LoadFixture("scheduler_monitor.xml"));
+ // Real field capture: process_utilization=94, system_idle=2 -> other = 100-94-2=4;
+ // working_set_delta 4096 bytes -> 4096/1048576 = 0.00390625 MB, rounded to 0.00.
+ var r = SystemHealthParser.ParseSchedulerIssue(LoadFixture("scheduler_monitor_high_sql_cpu.xml"));
Assert.NotNull(r);
- Assert.Equal(SchedulerTime, r!.EventTime);
- Assert.Equal(10, r.SchedulerId);
- Assert.Equal(3, r.CpuId);
- Assert.Equal("WARNING", r.Status); // data[@name="status"]/text, NOT the numeric value
- Assert.True(r.IsOnline);
- Assert.False(r.IsRunnable);
- Assert.True(r.IsRunning);
- Assert.Equal(4500L, r.NonYieldingTimeMs); // data[@name="non_yielding_time"] (no _ms suffix in XML)
- Assert.Equal(4L, r.ThreadQuantumMs); // data[@name="thread_quantum"]
+ Assert.Equal(SchedulerHighCpuTime, r!.EventTime);
+ Assert.Equal(94, r.SqlCpuUtilization);
+ Assert.Equal(4, r.OtherProcessCpu);
+ Assert.Equal(2, r.SystemIdle);
+ Assert.Equal(100, r.MemoryUtilization);
+ Assert.Equal(505L, r.PageFaults);
+ Assert.Equal(0.00m, r.WorkingSetDeltaMb);
+ Assert.True(SystemHealthSignificance.IsSignificant(r)); // SQL CPU 94 >= 90
+ }
+
+ [Fact]
+ public void SchedulerIssue_ShredsEveryColumn_Normal()
+ {
+ // Real field capture: process_utilization=3, system_idle=95 -> other = 100-3-95=2;
+ // working_set_delta 77824 bytes -> 77824/1048576 = 0.074218... MB, rounded to 0.07.
+ var r = SystemHealthParser.ParseSchedulerIssue(LoadFixture("scheduler_monitor_normal.xml"));
+
+ Assert.NotNull(r);
+ Assert.Equal(3, r!.SqlCpuUtilization);
+ Assert.Equal(2, r.OtherProcessCpu);
+ Assert.Equal(95, r.SystemIdle);
+ Assert.Equal(100, r.MemoryUtilization);
+ Assert.Equal(99L, r.PageFaults);
+ Assert.Equal(0.07m, r.WorkingSetDeltaMb);
+ Assert.False(SystemHealthSignificance.IsSignificant(r));
+ }
+
+ [Fact]
+ public void SchedulerIssue_OtherProcessCpu_Significant()
+ {
+ // Derived from the real "normal" capture (system_idle changed 95 -> 40) to exercise the
+ // other-process-CPU arm: other = 100-3-40=57 >= 50.
+ var r = SystemHealthParser.ParseSchedulerIssue(LoadFixture("scheduler_monitor_other_process_cpu.xml"));
+ Assert.NotNull(r);
+ Assert.Equal(57, r!.OtherProcessCpu);
+ Assert.True(SystemHealthSignificance.IsSignificant(r));
+ }
+
+ [Fact]
+ public void SchedulerIssue_LowMemory_Significant()
+ {
+ // Derived from the real "normal" capture (memory_utilization changed 100 -> 50) to exercise
+ // the low-memory arm: 50 <= 50.
+ var r = SystemHealthParser.ParseSchedulerIssue(LoadFixture("scheduler_monitor_low_memory.xml"));
+ Assert.NotNull(r);
+ Assert.Equal(50, r!.MemoryUtilization);
+ Assert.True(SystemHealthSignificance.IsSignificant(r));
+ }
+
+ [Theory]
+ [InlineData(90, 0, 100, true)] // sql cpu boundary: 90 is significant
+ [InlineData(89, 0, 100, false)] // one under, not significant
+ [InlineData(0, 50, 100, true)] // other-process boundary: 50 is significant (sql=0,idle=50->other=50)
+ [InlineData(0, 51, 100, false)] // other-process 49, not significant (idle=51->other=49)
+ [InlineData(0, 100, 50, true)] // memory boundary: 50 is significant
+ [InlineData(0, 100, 51, false)] // memory 51, not significant
+ [InlineData(null, null, null, false)] // all null -> not significant (SQL null comparison is false)
+ public void SchedulerIssue_SignificanceBoundaries(int? sqlCpu, int? idle, int? mem, bool expected)
+ {
+ var other = sqlCpu is { } sc && idle is { } id ? 100 - sc - id : (int?)null;
+ var r = new SchedulerIssueRecord { SqlCpuUtilization = sqlCpu, OtherProcessCpu = other, SystemIdle = idle, MemoryUtilization = mem };
+ Assert.Equal(expected, SystemHealthSignificance.IsSignificant(r));
}
// ── SevereErrors (error_reported) ──
@@ -385,7 +440,7 @@ public void ParseEvents_DispatchesEachEventToItsCategory()
{
var events = new (string?, string?)[]
{
- (SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor.xml")),
+ (SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor_high_sql_cpu.xml")),
(SystemHealthParser.ErrorReportedEvent, LoadFixture("error_reported.xml")),
(SystemHealthParser.SpServerDiagnosticsEvent, LoadFixture("sp_server_diagnostics_system.xml")),
(SystemHealthParser.SpServerDiagnosticsEvent, LoadFixture("sp_server_diagnostics_resource.xml")),
@@ -410,7 +465,7 @@ public void ParseEvents_DispatchesEachEventToItsCategory()
Assert.Single(result.MemoryConditions);
Assert.Single(result.CpuTasks);
Assert.Equal(2, result.IoIssues.Count);
- Assert.Equal(10, result.SchedulerIssues[0].SchedulerId);
+ Assert.Equal(94, result.SchedulerIssues[0].SqlCpuUtilization);
Assert.Equal(823, result.SevereErrors[0].ErrorNumber);
Assert.Equal(6, result.SystemHealth[0].BadPagesDetected);
}
diff --git a/Darling/Darling.Tests/TsqlConventionGuardTests.cs b/Darling/Darling.Tests/TsqlConventionGuardTests.cs
index 6c7f324f0c..b5c76dc121 100644
--- a/Darling/Darling.Tests/TsqlConventionGuardTests.cs
+++ b/Darling/Darling.Tests/TsqlConventionGuardTests.cs
@@ -1431,6 +1431,10 @@ where the bound has nothing to compare against. */
"PerformanceMonitor.Collectors/StallWaitProbe.cs FitsUnderBudget",
"PerformanceMonitor.Common/SystemHealthParser.cs GbFromBytes",
"PerformanceMonitor.Common/SystemHealthParser.cs GbFromKb",
+ /* #4452: the same expression-bodied one-liner shape as GbFromBytes/GbFromKb just above — it strands
+ only its own numeric literals (1048576, the rounding precision), not T-SQL or a tempdb label, so
+ no census reads a site of that kind here. */
+ "PerformanceMonitor.Common/SystemHealthParser.cs MbFromBytes",
"PerformanceMonitor.Notifications/WebhookAlertService.cs DeriveResourceDatabase",
"Darling/PerformanceMonitor.Darling.Analysis/PgBaselineProvider.cs IsCommandTimeout",
/* #4276: the same expression-bodied classifier shape as IsCommandTimeout just above — it strands
diff --git a/Darling/Darling.Tests/ViewerSystemEventsTests.cs b/Darling/Darling.Tests/ViewerSystemEventsTests.cs
index 8aa4f05879..8ccbd5f612 100644
--- a/Darling/Darling.Tests/ViewerSystemEventsTests.cs
+++ b/Darling/Darling.Tests/ViewerSystemEventsTests.cs
@@ -98,21 +98,24 @@ public void DatabaseNameMapRead_ReferencesColumnsThatExistInTheGeneratedTable()
}
}
- // ── Significance: Scheduler Issues (status = WARNING) ──
+ // ── Significance: Scheduler Issues (SQL CPU >= 90, other-process CPU >= 50, or memory <= 50) ──
[Theory]
- [InlineData("WARNING", true)]
- [InlineData("OK", false)]
- [InlineData("", false)]
- [InlineData(null, false)]
- public void SchedulerIssue_SignificantOnlyWhenWarning(string? status, bool expected) =>
- Assert.Equal(expected, SystemEventSignificance.IsSignificant(new SchedulerIssueRecord { Status = status }));
+ [InlineData(90, 0, 100, true)]
+ [InlineData(89, 0, 100, false)]
+ [InlineData(0, 50, 100, true)]
+ [InlineData(0, 49, 100, false)]
+ [InlineData(0, 0, 50, true)]
+ [InlineData(0, 0, 51, false)]
+ [InlineData(null, null, null, false)]
+ public void SchedulerIssue_SignificantOnThresholds(int? sqlCpu, int? other, int? mem, bool expected) =>
+ Assert.Equal(expected, SystemEventSignificance.IsSignificant(new SchedulerIssueRecord { SqlCpuUtilization = sqlCpu, OtherProcessCpu = other, MemoryUtilization = mem }));
[Fact]
public void SchedulerIssue_Fixture_IsSignificant()
{
- var record = SystemHealthParser.ParseSchedulerIssue(LoadFixture("scheduler_monitor.xml"))!;
- Assert.Equal("WARNING", record.Status);
+ var record = SystemHealthParser.ParseSchedulerIssue(LoadFixture("scheduler_monitor_high_sql_cpu.xml"))!;
+ Assert.Equal(94, record.SqlCpuUtilization);
Assert.True(SystemEventSignificance.IsSignificant(record));
}
@@ -350,7 +353,7 @@ public void ShredThenFilter_KeepsOnlySignificantRows_AcrossAllCategories()
{
var events = new (string?, string?)[]
{
- (SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor.xml")),
+ (SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor_high_sql_cpu.xml")),
(SystemHealthParser.ErrorReportedEvent, LoadFixture("error_reported.xml")),
(SystemHealthParser.SpServerDiagnosticsEvent, LoadFixture("sp_server_diagnostics_system.xml")),
(SystemHealthParser.SpServerDiagnosticsEvent, LoadFixture("sp_server_diagnostics_resource.xml")),
diff --git a/Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpHealthParserTools.cs b/Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpHealthParserTools.cs
index 18e2664591..be1fdb812c 100644
--- a/Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpHealthParserTools.cs
+++ b/Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpHealthParserTools.cs
@@ -221,7 +221,7 @@ public static async Task GetIOIssues(
catch (Exception ex) when (ex is not OperationCanceledException) { return McpHelpers.FormatError("get_health_parser_io_issues", ex); }
}
- [McpServerTool(Name = "get_health_parser_scheduler_issues"), Description("Gets scheduler issues from system_health (non-yielding schedulers, scheduler-monitor warnings) over an event_time window ending at as_of, newest first. Gated: WARNING-state results only. An empty answer with status empty is a real result: nothing in this window passed the gate, and events_in_window counts what was captured and filtered out. status unavailable with source_observed false is no evidence either way. <> Gets scheduler issues from system_health: non-yielding schedulers and scheduler-monitor warnings, with the scheduler/cpu ids, online/runnable/running state, and non-yielding time. " + McpToolGuideTopics.SystemHealthEmptyWindows)]
+ [McpServerTool(Name = "get_health_parser_scheduler_issues"), Description("Gets scheduler-monitor utilization samples from system_health over an event_time window ending at as_of, newest first. Gated: only samples where SQL Server's own CPU is pinned, other processes are using half the box or more, or memory utilization has dropped to half or below. An empty answer with status empty is a real result: nothing in this window passed the gate, and events_in_window counts what was captured and filtered out. status unavailable with source_observed false is no evidence either way. <> Gets scheduler-monitor utilization samples from system_health: SQL Server CPU percent, other-process CPU percent, idle CPU percent, memory utilization percent, page faults, and working-set change in MB. Significant when SQL CPU is pinned (>= 90), other-process CPU is high (>= 50), or memory utilization is low (<= 50). " + McpToolGuideTopics.SystemHealthEmptyWindows)]
public static async Task GetSchedulerIssues(
NpgsqlDataSource postgres,
[Description("Server name or display name.")] string? server_name = null,
@@ -239,7 +239,7 @@ public static async Task GetSchedulerIssues(
if (c.EarlyReturn != null) return c.EarlyReturn;
if (c.Rows.Count == 0)
return await EmptyAsync(postgres, c, hours_back, SystemHealthParser.SchedulerMonitorEvent,
- "none was a scheduler-monitor record in the WARNING state", cancellationToken);
+ "none was a significant scheduler-monitor sample (SQL CPU, other-process CPU, or memory utilization outside the warning thresholds)", cancellationToken);
return JsonSerializer.Serialize(new
{
@@ -252,14 +252,12 @@ public static async Task GetSchedulerIssues(
issues = c.Rows.Take(limit).Select(r => new
{
event_time = r.EventTime?.ToString("o"),
- scheduler_id = r.SchedulerId,
- cpu_id = r.CpuId,
- status = r.Status,
- is_online = r.IsOnline,
- is_runnable = r.IsRunnable,
- is_running = r.IsRunning,
- non_yielding_time_ms = r.NonYieldingTimeMs,
- thread_quantum_ms = r.ThreadQuantumMs
+ sql_cpu_utilization = r.SqlCpuUtilization,
+ other_process_cpu = r.OtherProcessCpu,
+ system_idle = r.SystemIdle,
+ memory_utilization = r.MemoryUtilization,
+ page_faults = r.PageFaults,
+ working_set_delta_mb = r.WorkingSetDeltaMb
})
}, McpHelpers.JsonOptions);
}
diff --git a/Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.SystemEvents.cs b/Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.SystemEvents.cs
index 70f348dc33..0f4ffed435 100644
--- a/Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.SystemEvents.cs
+++ b/Darling/PerformanceMonitor.Darling.Viewer/ViewerDataService.SystemEvents.cs
@@ -40,18 +40,16 @@ public static string Local(DateTime? utc) =>
utc is { } t ? ViewerTimeHelper.ForDisplay(t).ToString("yyyy-MM-dd HH:mm:ss") : "";
}
-/// One scheduler-monitor WARNING row (Scheduler Issues sub-tab). Mirrors *_SchedulerIssues.
+/// One scheduler-monitor utilization sample (Scheduler Issues sub-tab), flagged the way sp_HealthParser flags this section: SQL CPU pinned, other-process CPU high, or memory utilization low.
public sealed class SchedulerIssueRow(SchedulerIssueRecord record)
{
public string EventTimeLocal => SystemEventRowFormat.Local(record.EventTime);
- public int? SchedulerId => record.SchedulerId;
- public int? CpuId => record.CpuId;
- public string? Status => record.Status;
- public bool? IsOnline => record.IsOnline;
- public bool? IsRunnable => record.IsRunnable;
- public bool? IsRunning => record.IsRunning;
- public long? NonYieldingTimeMs => record.NonYieldingTimeMs;
- public long? ThreadQuantumMs => record.ThreadQuantumMs;
+ public int? SqlCpuUtilization => record.SqlCpuUtilization;
+ public int? OtherProcessCpu => record.OtherProcessCpu;
+ public int? SystemIdle => record.SystemIdle;
+ public int? MemoryUtilization => record.MemoryUtilization;
+ public long? PageFaults => record.PageFaults;
+ public decimal? WorkingSetDeltaMb => record.WorkingSetDeltaMb;
}
///
diff --git a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.xaml b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.xaml
index a14f9c8217..4e944b11b8 100644
--- a/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.xaml
+++ b/Darling/PerformanceMonitor.Darling.Viewer/ViewerServerTab.xaml
@@ -3219,17 +3219,15 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
diff --git a/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor.xml b/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor.xml
deleted file mode 100644
index bae3a49deb..0000000000
--- a/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
- 10
- 3
- 2WARNING
- 1true
- 0false
- 1true
- 4500
- 4
-
diff --git a/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_high_sql_cpu.xml b/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_high_sql_cpu.xml
new file mode 100644
index 0000000000..798be8e0df
--- /dev/null
+++ b/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_high_sql_cpu.xml
@@ -0,0 +1,16 @@
+
+
+ 0
+ 0
+ 94
+ 2
+ 2233593750
+ 30312500
+ 505
+ 4096
+ 100
+
diff --git a/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_low_memory.xml b/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_low_memory.xml
new file mode 100644
index 0000000000..0bd959d45d
--- /dev/null
+++ b/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_low_memory.xml
@@ -0,0 +1,16 @@
+
+
+ 0
+ 0
+ 3
+ 95
+ 176250000
+ 7187500
+ 99
+ 77824
+ 50
+
diff --git a/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_normal.xml b/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_normal.xml
new file mode 100644
index 0000000000..7f0f5c74be
--- /dev/null
+++ b/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_normal.xml
@@ -0,0 +1,16 @@
+
+
+ 0
+ 0
+ 3
+ 95
+ 176250000
+ 7187500
+ 99
+ 77824
+ 100
+
diff --git a/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_other_process_cpu.xml b/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_other_process_cpu.xml
new file mode 100644
index 0000000000..13dad6fca4
--- /dev/null
+++ b/Lite.Tests/Fixtures/SystemHealth/scheduler_monitor_other_process_cpu.xml
@@ -0,0 +1,16 @@
+
+
+ 0
+ 0
+ 3
+ 40
+ 176250000
+ 7187500
+ 99
+ 77824
+ 100
+
diff --git a/Lite.Tests/McpToolGuideHeads.HealthParser.cs b/Lite.Tests/McpToolGuideHeads.HealthParser.cs
index ed084ecda6..07bd0d658e 100644
--- a/Lite.Tests/McpToolGuideHeads.HealthParser.cs
+++ b/Lite.Tests/McpToolGuideHeads.HealthParser.cs
@@ -42,7 +42,7 @@ private static readonly (string Tool, string Fact)[] GateFacts =
("get_health_parser_memory_node_oom", "Ungated: every recorded OOM is returned."),
("get_health_parser_severe_errors", "Gated: severity 19 or higher only, benign connection-reset error numbers excluded"),
("get_health_parser_io_issues", "Gated: WARNING-state results only."),
- ("get_health_parser_scheduler_issues", "Gated: WARNING-state results only."),
+ ("get_health_parser_scheduler_issues", "Gated: only samples where SQL Server's own CPU is pinned, other processes are using half the box or more, or memory utilization has dropped to half or below."),
("get_health_parser_memory_conditions", "Gated: only snapshots whose last notification is RESOURCE_MEMPHYSICAL_LOW."),
("get_health_parser_cpu_tasks", "Gated: WARNING-state results with at least 10 pending tasks only."),
("get_health_parser_memory_broker", "Gated: RESOURCE_MEMPHYSICAL_LOW notifications only."),
diff --git a/Lite.Tests/McpToolsListBudget/McpHealthParserTools.txt b/Lite.Tests/McpToolsListBudget/McpHealthParserTools.txt
index e784049bf8..23c6a5eb63 100644
--- a/Lite.Tests/McpToolsListBudget/McpHealthParserTools.txt
+++ b/Lite.Tests/McpToolsListBudget/McpHealthParserTools.txt
@@ -29,7 +29,7 @@ param get_health_parser_memory_node_oom.hours_back 41
param get_health_parser_memory_node_oom.limit 38
param get_health_parser_memory_node_oom.server_name 28
-tool get_health_parser_scheduler_issues 446
+tool get_health_parser_scheduler_issues 536
param get_health_parser_scheduler_issues.as_of 167
param get_health_parser_scheduler_issues.hours_back 41
param get_health_parser_scheduler_issues.limit 38
diff --git a/Lite.Tests/McpToolsListBudgetTests.cs b/Lite.Tests/McpToolsListBudgetTests.cs
index 650417c1d1..e2f7d7f978 100644
--- a/Lite.Tests/McpToolsListBudgetTests.cs
+++ b/Lite.Tests/McpToolsListBudgetTests.cs
@@ -137,7 +137,9 @@ combined total with blocking (#4267) changes on top. */
window-floor sentence (422 -> 351, banking 71 bytes); get_top_queries_by_cpu and get_top_procedures_by_cpu
each gain that same sentence (474 -> 600 and 406 -> 532, +126 bytes apiece), making all three heads
byte-identical to Darling's. Net +181, matching Darling's twin change exactly. */
- private const int TotalCeilingBytes = 92_222;
+ // #4452: get_scheduler_issues' description grew to stay byte-identical with Darling's twin
+ // (+95). Constant set to the value McpToolsListBudgetTests itself measured on this tree.
+ private const int TotalCeilingBytes = 92_317;
private const int ConvertedHeadCap = 1_000;
private const int ConvertedParameterCap = 200;
diff --git a/Lite.Tests/SystemEventsReaderTests.cs b/Lite.Tests/SystemEventsReaderTests.cs
index bba79c35c0..3d6389885e 100644
--- a/Lite.Tests/SystemEventsReaderTests.cs
+++ b/Lite.Tests/SystemEventsReaderTests.cs
@@ -84,7 +84,7 @@ public async Task SystemHealthCategories_ReadThroughVView_KeepOnlyTheSignificant
stored event_time column; the ROW's own EventTimeLocal comes from the XML @timestamp (a Stage-2a
parser concern, tested there). */
var eventTime = Truncate(DateTime.UtcNow.AddHours(-1));
- await SeedHealthEventAsync(SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor.xml"), eventTime);
+ await SeedHealthEventAsync(SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor_high_sql_cpu.xml"), eventTime);
await SeedHealthEventAsync(SystemHealthParser.ErrorReportedEvent, LoadFixture("error_reported.xml"), eventTime);
await SeedHealthEventAsync(SystemHealthParser.SpServerDiagnosticsEvent, LoadFixture("sp_server_diagnostics_system.xml"), eventTime);
await SeedHealthEventAsync(SystemHealthParser.SpServerDiagnosticsEvent, LoadFixture("sp_server_diagnostics_resource.xml"), eventTime);
@@ -177,8 +177,8 @@ public async Task SystemHealthWindow_ExcludesEventsOutsideTheEventTimeBounds()
var service = new LocalDataService(_duckDb);
// One in-window scheduler warning, one stored 48h ago — a 24h read must return only the recent one.
- await SeedHealthEventAsync(SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor.xml"), Truncate(DateTime.UtcNow.AddHours(-1)));
- await SeedHealthEventAsync(SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor.xml"), Truncate(DateTime.UtcNow.AddHours(-48)));
+ await SeedHealthEventAsync(SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor_high_sql_cpu.xml"), Truncate(DateTime.UtcNow.AddHours(-1)));
+ await SeedHealthEventAsync(SystemHealthParser.SchedulerMonitorEvent, LoadFixture("scheduler_monitor_high_sql_cpu.xml"), Truncate(DateTime.UtcNow.AddHours(-48)));
Assert.Single(await service.GetSchedulerIssuesAsync(ServerId, hoursBack: 24));
}
diff --git a/Lite/Controls/ServerTab.xaml b/Lite/Controls/ServerTab.xaml
index 4308528f66..568adb00f2 100644
--- a/Lite/Controls/ServerTab.xaml
+++ b/Lite/Controls/ServerTab.xaml
@@ -2686,17 +2686,15 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
diff --git a/Lite/Mcp/McpHealthParserTools.cs b/Lite/Mcp/McpHealthParserTools.cs
index 55d3c8538b..55221c11af 100644
--- a/Lite/Mcp/McpHealthParserTools.cs
+++ b/Lite/Mcp/McpHealthParserTools.cs
@@ -183,7 +183,7 @@ public static async Task GetIOIssues(
catch (Exception ex) { return McpHelpers.FormatError("get_health_parser_io_issues", ex); }
}
- [McpServerTool(Name = "get_health_parser_scheduler_issues"), Description("Gets scheduler issues from system_health (non-yielding schedulers, scheduler-monitor warnings) over an event_time window ending at as_of, newest first. Gated: WARNING-state results only. An empty answer with status empty is a real result: nothing in this window passed the gate, and events_in_window counts what was captured and filtered out. status unavailable with source_observed false is no evidence either way. <> Gets scheduler issues from system_health: non-yielding schedulers and scheduler-monitor warnings, with the scheduler/cpu ids, online/runnable/running state, and non-yielding time. " + McpToolGuideTopics.SystemHealthEmptyWindows)]
+ [McpServerTool(Name = "get_health_parser_scheduler_issues"), Description("Gets scheduler-monitor utilization samples from system_health over an event_time window ending at as_of, newest first. Gated: only samples where SQL Server's own CPU is pinned, other processes are using half the box or more, or memory utilization has dropped to half or below. An empty answer with status empty is a real result: nothing in this window passed the gate, and events_in_window counts what was captured and filtered out. status unavailable with source_observed false is no evidence either way. <> Gets scheduler-monitor utilization samples from system_health: SQL Server CPU percent, other-process CPU percent, idle CPU percent, memory utilization percent, page faults, and working-set change in MB. " + McpToolGuideTopics.SystemHealthEmptyWindows)]
public static async Task GetSchedulerIssues(
LocalDataService dataService,
ServerManager serverManager,
@@ -204,7 +204,7 @@ public static async Task GetSchedulerIssues(
var lastCapturedAt = await dataService.GetLastSystemHealthCaptureAsync(resolved.ServerId);
if (rows.Count == 0)
return await EmptyAsync(dataService, resolved.ServerId, resolved.ServerName, hours_back, windowEnd, SystemHealthParser.SchedulerMonitorEvent,
- "none was a scheduler-monitor record in the WARNING state", capturedInWindow: null, lastCapturedAt);
+ "none was a significant scheduler-monitor sample (SQL CPU, other-process CPU, or memory utilization outside the warning thresholds)", capturedInWindow: null, lastCapturedAt);
return JsonSerializer.Serialize(new
{
@@ -217,14 +217,12 @@ public static async Task GetSchedulerIssues(
issues = rows.Take(limit).Select(r => new
{
event_time = r.EventTime?.ToString("o"),
- scheduler_id = r.SchedulerId,
- cpu_id = r.CpuId,
- status = r.Status,
- is_online = r.IsOnline,
- is_runnable = r.IsRunnable,
- is_running = r.IsRunning,
- non_yielding_time_ms = r.NonYieldingTimeMs,
- thread_quantum_ms = r.ThreadQuantumMs
+ sql_cpu_utilization = r.SqlCpuUtilization,
+ other_process_cpu = r.OtherProcessCpu,
+ system_idle = r.SystemIdle,
+ memory_utilization = r.MemoryUtilization,
+ page_faults = r.PageFaults,
+ working_set_delta_mb = r.WorkingSetDeltaMb
})
}, McpHelpers.JsonOptions);
}
diff --git a/Lite/Services/LocalDataService.SystemEvents.cs b/Lite/Services/LocalDataService.SystemEvents.cs
index bb28b5e70e..854698bc37 100644
--- a/Lite/Services/LocalDataService.SystemEvents.cs
+++ b/Lite/Services/LocalDataService.SystemEvents.cs
@@ -47,20 +47,18 @@ internal static class SystemEventRowFormat
public static string Local(DateTime? utc) => ServerTimeHelper.FormatServerTime(utc, "yyyy-MM-dd HH:mm:ss");
}
-/// One scheduler-monitor WARNING row (Scheduler Issues sub-tab). Mirrors sp_HealthParser's *_SchedulerIssues.
+/// One scheduler-monitor utilization sample (Scheduler Issues sub-tab), flagged the way sp_HealthParser flags this section: SQL CPU pinned, other-process CPU high, or memory utilization low.
public sealed class SchedulerIssueRow(SchedulerIssueRecord record)
{
public string EventTimeLocal => SystemEventRowFormat.Local(record.EventTime);
/// Raw naive-UTC event time (the XE @timestamp) for the MCP layer's ISO output.
public DateTime? EventTime => record.EventTime;
- public int? SchedulerId => record.SchedulerId;
- public int? CpuId => record.CpuId;
- public string? Status => record.Status;
- public bool? IsOnline => record.IsOnline;
- public bool? IsRunnable => record.IsRunnable;
- public bool? IsRunning => record.IsRunning;
- public long? NonYieldingTimeMs => record.NonYieldingTimeMs;
- public long? ThreadQuantumMs => record.ThreadQuantumMs;
+ public int? SqlCpuUtilization => record.SqlCpuUtilization;
+ public int? OtherProcessCpu => record.OtherProcessCpu;
+ public int? SystemIdle => record.SystemIdle;
+ public int? MemoryUtilization => record.MemoryUtilization;
+ public long? PageFaults => record.PageFaults;
+ public decimal? WorkingSetDeltaMb => record.WorkingSetDeltaMb;
}
///
diff --git a/PerformanceMonitor.Common/SystemHealthModels.cs b/PerformanceMonitor.Common/SystemHealthModels.cs
index b15a11c20c..70e792fb80 100644
--- a/PerformanceMonitor.Common/SystemHealthModels.cs
+++ b/PerformanceMonitor.Common/SystemHealthModels.cs
@@ -28,38 +28,35 @@ namespace PerformanceMonitor.Common
*/
///
- /// One scheduler-monitor warning row, shredded from a
+ /// One scheduler-monitor utilization sample, shredded from a
/// scheduler_monitor_system_health_ring_buffer_recorded event. Mirrors sp_HealthParser's
- /// *_SchedulerIssues table (non-yielding schedulers, deadlocked schedulers).
+ /// current "Parsing scheduler monitor data" section: this event is a periodic CPU/memory
+ /// utilization sample, not a per-scheduler status record — it carries no scheduler_id, status,
+ /// online/runnable/running flags, or non-yielding/thread-quantum timers (an earlier version of
+ /// this record read those fields, which this event has never carried; every row came back null).
///
public sealed record SchedulerIssueRecord
{
/// Event @timestamp, parsed as UTC (sp_HealthParser converts to server-local at render; kept UTC here).
public DateTime? EventTime { get; init; }
- /// xpath data[@name="scheduler_id"]/value.
- public int? SchedulerId { get; init; }
+ /// xpath data[@name="process_utilization"]/value — SQL Server's own CPU percent.
+ public int? SqlCpuUtilization { get; init; }
- /// xpath data[@name="cpu_id"]/value.
- public int? CpuId { get; init; }
+ /// Computed: 100 - SqlCpuUtilization - SystemIdle — the CPU percent used by everything else on the box. Null if either input is null.
+ public int? OtherProcessCpu { get; init; }
- /// xpath data[@name="status"]/text (the friendly enum text, e.g. WARNING).
- public string? Status { get; init; }
+ /// xpath data[@name="system_idle"]/value — the box's idle CPU percent.
+ public int? SystemIdle { get; init; }
- /// xpath data[@name="is_online"]/value (bit).
- public bool? IsOnline { get; init; }
+ /// xpath data[@name="memory_utilization"]/value — percent of committed memory in the working set (100 is healthy; low means the working set was trimmed).
+ public int? MemoryUtilization { get; init; }
- /// xpath data[@name="is_runnable"]/value (bit).
- public bool? IsRunnable { get; init; }
-
- /// xpath data[@name="is_running"]/value (bit).
- public bool? IsRunning { get; init; }
-
- /// xpath data[@name="non_yielding_time"]/value (bigint; the table column is named *_ms).
- public long? NonYieldingTimeMs { get; init; }
+ /// xpath data[@name="page_faults"]/value.
+ public long? PageFaults { get; init; }
- /// xpath data[@name="thread_quantum"]/value (bigint; the table column is named *_ms).
- public long? ThreadQuantumMs { get; init; }
+ /// xpath data[@name="working_set_delta"]/value, bytes converted to MB (÷1,048,576), rounded to 2 decimal places.
+ public decimal? WorkingSetDeltaMb { get; init; }
}
///
diff --git a/PerformanceMonitor.Common/SystemHealthParser.cs b/PerformanceMonitor.Common/SystemHealthParser.cs
index 2f46474a2a..843fc3ec67 100644
--- a/PerformanceMonitor.Common/SystemHealthParser.cs
+++ b/PerformanceMonitor.Common/SystemHealthParser.cs
@@ -135,17 +135,18 @@ private static void Add(List list, T? record) where T : class
if (ev == null)
return null;
+ var sqlCpu = ParseInt(DataValue(ev, "process_utilization"));
+ var idle = ParseInt(DataValue(ev, "system_idle"));
+
return new SchedulerIssueRecord
{
EventTime = ParseTimestamp(ev),
- SchedulerId = ParseInt(DataValue(ev, "scheduler_id")),
- CpuId = ParseInt(DataValue(ev, "cpu_id")),
- Status = DataText(ev, "status"),
- IsOnline = ParseBool(DataValue(ev, "is_online")),
- IsRunnable = ParseBool(DataValue(ev, "is_runnable")),
- IsRunning = ParseBool(DataValue(ev, "is_running")),
- NonYieldingTimeMs = ParseLong(DataValue(ev, "non_yielding_time")),
- ThreadQuantumMs = ParseLong(DataValue(ev, "thread_quantum")),
+ SqlCpuUtilization = sqlCpu,
+ OtherProcessCpu = sqlCpu is { } sc && idle is { } id ? 100 - sc - id : (int?)null,
+ SystemIdle = idle,
+ MemoryUtilization = ParseInt(DataValue(ev, "memory_utilization")),
+ PageFaults = ParseLong(DataValue(ev, "page_faults")),
+ WorkingSetDeltaMb = MbFromBytes(DataValue(ev, "working_set_delta")),
};
}
@@ -548,6 +549,9 @@ public static List ParseIoIssues(string? eventXml)
/// KB -> GB by integer division (÷1024²), matching sp_HealthParser's bigint arithmetic (truncating).
private static long? GbFromKb(string? s) => ParseLong(s) is { } v ? v / 1024 / 1024 : (long?)null;
+ /// Bytes -> MB (÷1,048,576) as decimal(19,2), matching sp_HealthParser's CONVERT(decimal(19,2), bigint / 1048576.) — real division, then rounded to 2 places (banker's rounding via decimal.Round, matching CONVERT's rounding behavior).
+ private static decimal? MbFromBytes(string? s) => ParseLong(s) is { } v ? Math.Round(v / 1048576m, 2, MidpointRounding.AwayFromZero) : (decimal?)null;
+
///
/// Replaces control characters — except tab (9), line feed (10) and carriage return (13) — with '?',
/// the exact set sp_HealthParser strips when building its query_text column. Null in, null out.
diff --git a/PerformanceMonitor.Common/SystemHealthSignificance.cs b/PerformanceMonitor.Common/SystemHealthSignificance.cs
index 2c4a2d5c65..c4d6ab0167 100644
--- a/PerformanceMonitor.Common/SystemHealthSignificance.cs
+++ b/PerformanceMonitor.Common/SystemHealthSignificance.cs
@@ -105,12 +105,16 @@ public static class SystemHealthSignificance
};
///
- /// A scheduler-monitor row is significant when its status is WARNING (a non-yielding or offline
- /// scheduler), per sp_HealthParser.sql line 4540 under @warnings_only = 1. Routine heartbeat
- /// rows (any other status) are dropped.
+ /// A scheduler-monitor sample is significant per sp_HealthParser's @warnings_only predicate for
+ /// this section: SQL Server's own CPU is pinned (>= 90), other processes are using half the box or
+ /// more (>= 50), or memory utilization has dropped to half or below (<= 50, the classic external
+ /// memory-pressure signal). A null comparison is false, so a sample with everything null is not
+ /// significant.
///
public static bool IsSignificant(SchedulerIssueRecord record) =>
- string.Equals(record.Status, WarningStatus, StringComparison.Ordinal);
+ record.SqlCpuUtilization >= 90 ||
+ record.OtherProcessCpu >= 50 ||
+ record.MemoryUtilization <= 50;
///
/// An error_reported row is significant when severity >= 19 and the error number is not one of the