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
12 changes: 12 additions & 0 deletions Lite/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@

namespace PerformanceMonitorLite;

public enum CpuAlertMode
{
/// <summary>sql_server_cpu + other_process_cpu — matches OS user+system, "is the box in trouble".</summary>
Total,
/// <summary>SQL Server scheduler ProcessUtilization only.</summary>
SqlOnly
}

public partial class App : Application
{
[DllImport("shell32.dll", SetLastError = true)]
Expand Down Expand Up @@ -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;
/// <summary>Which CPU metric the alert evaluates against. Total = sql_server_cpu + other_process_cpu (matches OS user+system). SqlOnly = SQL Server scheduler %.</summary>
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;
Expand Down Expand Up @@ -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<CpuAlertMode>(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();
Expand Down
16 changes: 9 additions & 7 deletions Lite/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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,
Expand All @@ -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);
}

Expand Down
44 changes: 38 additions & 6 deletions Lite/Services/LocalDataService.Overview.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -112,6 +115,7 @@ FROM v_collection_log
DisplayName = displayName,
ServerId = serverId,
CpuPercent = cpuPercent,
OtherProcessCpuPercent = otherProcessCpuPercent,
MemoryMb = memoryMb,
BlockingCount = blockingCount,
DeadlockCount = deadlockCount,
Expand All @@ -128,13 +132,34 @@ public class ServerSummaryItem
public bool? IsOnline { get; set; }
/// <summary>True when the server is reachable but one or more collectors have consecutive errors.</summary>
public bool HasCollectorErrors { get; set; }
/// <summary>SQL Server scheduler ProcessUtilization from sys.dm_os_ring_buffers. NULL on Azure SQL DB.</summary>
public double? CpuPercent { get; set; }
/// <summary>Non-SQL-Server CPU on the host (computed as 100 - SystemIdle - ProcessUtilization). NULL on Azure SQL DB.</summary>
public double? OtherProcessCpuPercent { get; set; }
/// <summary>Total non-idle CPU on the host = sql_server + other_process. Tracks closer to OS user+system counters.</summary>
public double? TotalCpuPercent =>
CpuPercent.HasValue ? CpuPercent.Value + (OtherProcessCpuPercent ?? 0) : null;
/// <summary>The CPU value the alert evaluator and headline display use. Driven by App.AlertCpuMode.</summary>
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}%" : "--";
/// <summary>
/// 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.
/// </summary>
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";
Expand All @@ -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");

Expand Down
9 changes: 6 additions & 3 deletions Lite/Windows/SettingsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,12 @@
<TextBox x:Name="AlertCpuThresholdBox" Width="45" Text="80" Margin="6,0,0,0" VerticalAlignment="Center"/>
<TextBlock Text="%" VerticalAlignment="Center" Margin="2,0,0,0"
Foreground="{DynamicResource ForegroundBrush}"/>
<TextBlock Text="(default 80% — Lite collects directly from DMVs so catches spikes faster than Dashboard)"
FontSize="10" FontStyle="Italic" Foreground="{DynamicResource ForegroundMutedBrush}"
VerticalAlignment="Center" Margin="8,0,0,0"/>
<TextBlock Text="measured as" VerticalAlignment="Center" Margin="8,0,4,0"
Foreground="{DynamicResource ForegroundBrush}"/>
<ComboBox x:Name="AlertCpuModeBox" Width="220" VerticalAlignment="Center">
<ComboBoxItem Content="Total non-idle CPU (SQL + other processes)" Tag="Total"/>
<ComboBoxItem Content="SQL Server only (scheduler %)" Tag="SqlOnly"/>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="20,6,0,0">
<CheckBox x:Name="AlertBlockingCheckBox" Content="Blocking sessions &#x2265;" VerticalAlignment="Center"
Expand Down
10 changes: 9 additions & 1 deletion Lite/Windows/SettingsWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ private void LoadAlertSettings()
NotifyConnectionCheckBox.IsChecked = App.NotifyConnectionChanges;
AlertCpuCheckBox.IsChecked = App.AlertCpuEnabled;
AlertCpuThresholdBox.Text = App.AlertCpuThreshold.ToString();
AlertCpuModeBox.SelectedIndex = App.AlertCpuMode == CpuAlertMode.SqlOnly ? 1 : 0;
AlertBlockingCheckBox.IsChecked = App.AlertBlockingEnabled;
AlertBlockingThresholdBox.Text = App.AlertBlockingThreshold.ToString();
AlertDeadlockCheckBox.IsChecked = App.AlertDeadlockEnabled;
Expand Down Expand Up @@ -615,6 +616,7 @@ private bool SaveAlertSettings()
App.AlertCpuEnabled = AlertCpuCheckBox.IsChecked == true;
if (int.TryParse(AlertCpuThresholdBox.Text, out var cpu) && cpu > 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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand All @@ -753,7 +757,10 @@ private void UpdateAlertPreviewText()
var parts = new System.Collections.Generic.List<string>();

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)
Expand All @@ -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;
Expand Down
Loading