diff --git a/Lite/App.xaml.cs b/Lite/App.xaml.cs
index 8583a08..0164df1 100644
--- a/Lite/App.xaml.cs
+++ b/Lite/App.xaml.cs
@@ -17,6 +17,14 @@
namespace PerformanceMonitorLite;
+public enum CpuAlertMode
+{
+ /// sql_server_cpu + other_process_cpu — matches OS user+system, "is the box in trouble".
+ Total,
+ /// SQL Server scheduler ProcessUtilization only.
+ SqlOnly
+}
+
public partial class App : Application
{
[DllImport("shell32.dll", SetLastError = true)]
@@ -71,6 +79,8 @@ public partial class App : Application
public static bool NotifyConnectionChanges { get; set; } = true;
public static bool AlertCpuEnabled { get; set; } = true;
public static int AlertCpuThreshold { get; set; } = 80;
+ /// Which CPU metric the alert evaluates against. Total = sql_server_cpu + other_process_cpu (matches OS user+system). SqlOnly = SQL Server scheduler %.
+ public static CpuAlertMode AlertCpuMode { get; set; } = CpuAlertMode.Total;
public static bool AlertBlockingEnabled { get; set; } = true;
public static int AlertBlockingThreshold { get; set; } = 1;
public static bool AlertDeadlockEnabled { get; set; } = true;
@@ -323,6 +333,8 @@ public static void LoadAlertSettings()
if (root.TryGetProperty("notify_connection_changes", out v)) NotifyConnectionChanges = v.GetBoolean();
if (root.TryGetProperty("alert_cpu_enabled", out v)) AlertCpuEnabled = v.GetBoolean();
if (root.TryGetProperty("alert_cpu_threshold", out v)) AlertCpuThreshold = v.GetInt32();
+ if (root.TryGetProperty("alert_cpu_mode", out v) && Enum.TryParse(v.GetString(), out var mode))
+ AlertCpuMode = mode;
if (root.TryGetProperty("alert_blocking_enabled", out v)) AlertBlockingEnabled = v.GetBoolean();
if (root.TryGetProperty("alert_blocking_threshold", out v)) AlertBlockingThreshold = v.GetInt32();
if (root.TryGetProperty("alert_deadlock_enabled", out v)) AlertDeadlockEnabled = v.GetBoolean();
diff --git a/Lite/MainWindow.xaml.cs b/Lite/MainWindow.xaml.cs
index 59819af..2825462 100644
--- a/Lite/MainWindow.xaml.cs
+++ b/Lite/MainWindow.xaml.cs
@@ -1288,10 +1288,12 @@ private async void CheckPerformanceAlerts(ServerSummaryItem summary)
/* Skip popup/email alerts if user has acknowledged or silenced this server */
bool suppressPopups = !_alertStateService.ShouldShowAlerts(key);
- /* CPU alerts */
+ /* CPU alerts — uses the metric the user selected (Total non-idle CPU by default, or SQL Server only). */
+ var alertCpuValue = summary.CpuPercentForAlert;
+ string cpuMetricLabel = App.AlertCpuMode == CpuAlertMode.Total ? "Total CPU" : "SQL CPU";
bool cpuExceeded = App.AlertCpuEnabled
- && summary.CpuPercent.HasValue
- && summary.CpuPercent.Value >= App.AlertCpuThreshold;
+ && alertCpuValue.HasValue
+ && alertCpuValue.Value >= App.AlertCpuThreshold;
if (cpuExceeded)
{
@@ -1306,16 +1308,16 @@ private async void CheckPerformanceAlerts(ServerSummaryItem summary)
{
_trayService.ShowNotification(
"High CPU",
- $"{summary.DisplayName}: CPU at {summary.CpuPercent:F0}% (threshold: {App.AlertCpuThreshold}%)",
+ $"{summary.DisplayName}: {cpuMetricLabel} at {alertCpuValue:F0}% (threshold: {App.AlertCpuThreshold}%)",
Hardcodet.Wpf.TaskbarNotification.BalloonIcon.Warning);
}
- var cpuDetailText = $" CPU: {summary.CpuPercent:F0}%\n Threshold: {App.AlertCpuThreshold}%";
+ var cpuDetailText = $" {cpuMetricLabel}: {alertCpuValue:F0}%\n Threshold: {App.AlertCpuThreshold}%";
await _emailAlertService.TrySendAlertEmailAsync(
"High CPU",
summary.DisplayName,
- $"{summary.CpuPercent:F0}%",
+ $"{alertCpuValue:F0}%",
$"{App.AlertCpuThreshold}%",
summary.ServerId,
muted: isMuted,
@@ -1327,7 +1329,7 @@ await _emailAlertService.TrySendAlertEmailAsync(
_activeCpuAlert[key] = false;
_trayService.ShowNotification(
"CPU Resolved",
- $"{summary.DisplayName}: CPU back to {summary.CpuPercent:F0}%",
+ $"{summary.DisplayName}: {cpuMetricLabel} back to {alertCpuValue:F0}%",
Hardcodet.Wpf.TaskbarNotification.BalloonIcon.Info);
}
diff --git a/Lite/Services/LocalDataService.Overview.cs b/Lite/Services/LocalDataService.Overview.cs
index eee8a89..104b9e2 100644
--- a/Lite/Services/LocalDataService.Overview.cs
+++ b/Lite/Services/LocalDataService.Overview.cs
@@ -24,16 +24,18 @@ public partial class LocalDataService
using var connection = await OpenConnectionAsync();
double? cpuPercent = null;
+ double? otherProcessCpuPercent = null;
double? memoryMb = null;
int blockingCount = 0;
int deadlockCount = 0;
DateTime? lastCollection = null;
- /* Latest CPU */
+ /* Latest CPU — read both SQL Server CPU and other-process CPU so the UI can surface
+ total non-idle CPU alongside the SQL-only number. */
using (var cmd = connection.CreateCommand())
{
cmd.CommandText = @"
-SELECT sqlserver_cpu_utilization, sample_time
+SELECT sqlserver_cpu_utilization, other_process_cpu_utilization, sample_time
FROM v_cpu_utilization_stats
WHERE server_id = $1
ORDER BY sample_time DESC
@@ -43,7 +45,8 @@ ORDER BY sample_time DESC
if (await reader.ReadAsync())
{
cpuPercent = reader.IsDBNull(0) ? null : ToDouble(reader.GetValue(0));
- lastCollection = reader.IsDBNull(1) ? null : reader.GetDateTime(1);
+ otherProcessCpuPercent = reader.IsDBNull(1) ? null : ToDouble(reader.GetValue(1));
+ lastCollection = reader.IsDBNull(2) ? null : reader.GetDateTime(2);
}
}
@@ -112,6 +115,7 @@ FROM v_collection_log
DisplayName = displayName,
ServerId = serverId,
CpuPercent = cpuPercent,
+ OtherProcessCpuPercent = otherProcessCpuPercent,
MemoryMb = memoryMb,
BlockingCount = blockingCount,
DeadlockCount = deadlockCount,
@@ -128,13 +132,34 @@ public class ServerSummaryItem
public bool? IsOnline { get; set; }
/// True when the server is reachable but one or more collectors have consecutive errors.
public bool HasCollectorErrors { get; set; }
+ /// SQL Server scheduler ProcessUtilization from sys.dm_os_ring_buffers. NULL on Azure SQL DB.
public double? CpuPercent { get; set; }
+ /// Non-SQL-Server CPU on the host (computed as 100 - SystemIdle - ProcessUtilization). NULL on Azure SQL DB.
+ public double? OtherProcessCpuPercent { get; set; }
+ /// Total non-idle CPU on the host = sql_server + other_process. Tracks closer to OS user+system counters.
+ public double? TotalCpuPercent =>
+ CpuPercent.HasValue ? CpuPercent.Value + (OtherProcessCpuPercent ?? 0) : null;
+ /// The CPU value the alert evaluator and headline display use. Driven by App.AlertCpuMode.
+ public double? CpuPercentForAlert =>
+ App.AlertCpuMode == CpuAlertMode.Total ? (TotalCpuPercent ?? CpuPercent) : CpuPercent;
public double? MemoryMb { get; set; }
public int BlockingCount { get; set; }
public int DeadlockCount { get; set; }
public DateTime? LastCollectionTime { get; set; }
- public string CpuDisplay => CpuPercent.HasValue ? $"{CpuPercent:F0}%" : "--";
+ ///
+ /// Headline CPU display. Shows total non-idle CPU prominently with the SQL-only number alongside,
+ /// e.g. "64% (SQL 60%)". Falls back to a single number when only one value is available.
+ ///
+ public string CpuDisplay
+ {
+ get
+ {
+ if (!CpuPercent.HasValue) return "--";
+ if (!OtherProcessCpuPercent.HasValue) return $"{CpuPercent:F0}%";
+ return $"{TotalCpuPercent:F0}% (SQL {CpuPercent:F0}%)";
+ }
+ }
public string MemoryDisplay => MemoryMb.HasValue ? $"{MemoryMb / 1024.0:F1} GB" : "--";
public string BlockingDisplay => BlockingCount > 0 ? BlockingCount.ToString() : "0";
public string DeadlockDisplay => DeadlockCount > 0 ? DeadlockCount.ToString() : "0";
@@ -158,14 +183,21 @@ public class ServerSummaryItem
public bool IsOffline => IsOnline == false;
/* Color coding */
- public SolidColorBrush CpuBrush => MakeBrush(CpuPercent >= 80 ? "#E57373" : CpuPercent >= 50 ? "#FFB74D" : "#81C784");
+ public SolidColorBrush CpuBrush
+ {
+ get
+ {
+ var v = CpuPercentForAlert;
+ return MakeBrush(v >= 80 ? "#E57373" : v >= 50 ? "#FFB74D" : "#81C784");
+ }
+ }
public SolidColorBrush BlockingBrush => MakeBrush(BlockingCount > 0 ? "#FFB74D" : "#81C784");
public SolidColorBrush DeadlockBrush => MakeBrush(DeadlockCount > 0 ? "#E57373" : "#81C784");
public SolidColorBrush CardBorderBrush => MakeBrush(
IsOnline == false ? "#E57373" :
DeadlockCount > 0 ? "#E57373" :
BlockingCount > 0 ? "#FFB74D" :
- CpuPercent >= 80 ? "#FFB74D" :
+ CpuPercentForAlert >= 80 ? "#FFB74D" :
HasCollectorErrors ? "#FFD54F" : // amber border when collectors are failing
"#2a2d35");
diff --git a/Lite/Windows/SettingsWindow.xaml b/Lite/Windows/SettingsWindow.xaml
index f10c76e..47fe448 100644
--- a/Lite/Windows/SettingsWindow.xaml
+++ b/Lite/Windows/SettingsWindow.xaml
@@ -161,9 +161,12 @@
-
+
+
+
+
+
0 && cpu <= 100)
App.AlertCpuThreshold = cpu;
+ App.AlertCpuMode = AlertCpuModeBox.SelectedIndex == 1 ? CpuAlertMode.SqlOnly : CpuAlertMode.Total;
App.AlertBlockingEnabled = AlertBlockingCheckBox.IsChecked == true;
if (int.TryParse(AlertBlockingThresholdBox.Text, out var blocking) && blocking > 0)
App.AlertBlockingThreshold = blocking;
@@ -675,6 +677,7 @@ private bool SaveAlertSettings()
root["notify_connection_changes"] = App.NotifyConnectionChanges;
root["alert_cpu_enabled"] = App.AlertCpuEnabled;
root["alert_cpu_threshold"] = App.AlertCpuThreshold;
+ root["alert_cpu_mode"] = App.AlertCpuMode.ToString();
root["alert_blocking_enabled"] = App.AlertBlockingEnabled;
root["alert_blocking_threshold"] = App.AlertBlockingThreshold;
root["alert_deadlock_enabled"] = App.AlertDeadlockEnabled;
@@ -728,6 +731,7 @@ private void AlertsEnabledCheckBox_Changed(object sender, RoutedEventArgs e)
private void RestoreAlertDefaultsButton_Click(object sender, RoutedEventArgs e)
{
AlertCpuThresholdBox.Text = "80";
+ AlertCpuModeBox.SelectedIndex = 0; // Total
AlertBlockingThresholdBox.Text = "1";
AlertDeadlockThresholdBox.Text = "1";
AlertPoisonWaitThresholdBox.Text = "500";
@@ -753,7 +757,10 @@ private void UpdateAlertPreviewText()
var parts = new System.Collections.Generic.List();
if (AlertCpuCheckBox.IsChecked == true)
- parts.Add($"CPU > {AlertCpuThresholdBox.Text}%");
+ {
+ string cpuLabel = AlertCpuModeBox.SelectedIndex == 1 ? "SQL CPU" : "Total CPU";
+ parts.Add($"{cpuLabel} > {AlertCpuThresholdBox.Text}%");
+ }
if (AlertBlockingCheckBox.IsChecked == true)
parts.Add($"blocking >= {AlertBlockingThresholdBox.Text}");
if (AlertDeadlockCheckBox.IsChecked == true)
@@ -778,6 +785,7 @@ private void UpdateAlertControlStates()
NotifyConnectionCheckBox.IsEnabled = enabled;
AlertCpuCheckBox.IsEnabled = enabled;
AlertCpuThresholdBox.IsEnabled = enabled;
+ AlertCpuModeBox.IsEnabled = enabled;
AlertBlockingCheckBox.IsEnabled = enabled;
AlertBlockingThresholdBox.IsEnabled = enabled;
AlertDeadlockCheckBox.IsEnabled = enabled;