diff --git a/Lite/Controls/ServerTab.xaml b/Lite/Controls/ServerTab.xaml
index 7cb808dc..192b3a8a 100644
--- a/Lite/Controls/ServerTab.xaml
+++ b/Lite/Controls/ServerTab.xaml
@@ -1008,6 +1008,9 @@
+
+
+
diff --git a/Lite/Services/LocalDataService.CollectionHealth.cs b/Lite/Services/LocalDataService.CollectionHealth.cs
index 254710fd..c30557cf 100644
--- a/Lite/Services/LocalDataService.CollectionHealth.cs
+++ b/Lite/Services/LocalDataService.CollectionHealth.cs
@@ -31,7 +31,8 @@ public async Task> GetCollectionHealthAsync(int serverI
AVG(duration_ms) AS avg_duration_ms,
MAX(CASE WHEN status = 'SUCCESS' THEN collection_time END) AS last_success_time,
MAX(collection_time) AS last_run_time,
- MAX(CASE WHEN status = 'ERROR' THEN error_message END) AS last_error
+ MAX(CASE WHEN status = 'ERROR' THEN error_message END) AS last_error,
+ MAX(CASE WHEN status = 'ERROR' THEN collection_time END) AS last_error_time
FROM collection_log
WHERE server_id = $1
AND collection_time >= $2
@@ -54,7 +55,8 @@ GROUP BY collector_name
AvgDurationMs = reader.IsDBNull(4) ? 0 : ToDouble(reader.GetValue(4)),
LastSuccessTime = reader.IsDBNull(5) ? null : reader.GetDateTime(5),
LastRunTime = reader.IsDBNull(6) ? null : reader.GetDateTime(6),
- LastError = reader.IsDBNull(7) ? null : reader.GetString(7)
+ LastError = reader.IsDBNull(7) ? null : reader.GetString(7),
+ LastErrorTime = reader.IsDBNull(8) ? null : reader.GetDateTime(8)
});
}
@@ -141,6 +143,7 @@ public class CollectorHealthRow
public DateTime? LastSuccessTime { get; set; }
public DateTime? LastRunTime { get; set; }
public string? LastError { get; set; }
+ public DateTime? LastErrorTime { get; set; }
public double FailureRatePercent => TotalRuns > 0 ? (double)ErrorCount / TotalRuns * 100 : 0;
public double HoursSinceLastSuccess => LastSuccessTime.HasValue
@@ -170,5 +173,9 @@ public string HealthStatus
public string LastRunFormatted => LastRunTime.HasValue
? LastRunTime.Value.ToLocalTime().ToString("MM/dd HH:mm:ss")
: "Never";
+
+ public string LastErrorFormatted => LastErrorTime.HasValue
+ ? LastErrorTime.Value.ToLocalTime().ToString("MM/dd HH:mm:ss")
+ : "";
}
diff --git a/Lite/Services/RemoteCollectorService.MemoryGrants.cs b/Lite/Services/RemoteCollectorService.MemoryGrants.cs
index a9ad8a89..fd455f18 100644
--- a/Lite/Services/RemoteCollectorService.MemoryGrants.cs
+++ b/Lite/Services/RemoteCollectorService.MemoryGrants.cs
@@ -79,7 +79,7 @@ WHERE mg.session_id <> @@SPID
reader.IsDBNull(9) ? 0L : Convert.ToInt64(reader.GetValue(9)),
reader.IsDBNull(10) ? false : Convert.ToBoolean(reader.GetValue(10)),
reader.IsDBNull(11) ? 0 : Convert.ToInt32(reader.GetValue(11)),
- reader.IsDBNull(12) ? 0m : Convert.ToDecimal(reader.GetValue(12))));
+ reader.IsDBNull(12) ? 0m : SafeToDecimal(reader.GetValue(12))));
}
sqlSw.Stop();
diff --git a/Lite/Services/RemoteCollectorService.cs b/Lite/Services/RemoteCollectorService.cs
index 9f1c5d07..9e2c3886 100644
--- a/Lite/Services/RemoteCollectorService.cs
+++ b/Lite/Services/RemoteCollectorService.cs
@@ -517,6 +517,32 @@ protected static int GetServerId(ServerConnection server)
return GetDeterministicHashCode(server.ServerName);
}
+ ///
+ /// Safely converts a SQL Server float/real value to decimal.
+ /// Returns 0 for Infinity, NaN, or values outside decimal range.
+ ///
+ protected static decimal SafeToDecimal(object value)
+ {
+ try
+ {
+ if (value is double d)
+ {
+ if (double.IsInfinity(d) || double.IsNaN(d))
+ return 0m;
+ }
+ else if (value is float f)
+ {
+ if (float.IsInfinity(f) || float.IsNaN(f))
+ return 0m;
+ }
+ return Convert.ToDecimal(value);
+ }
+ catch (OverflowException)
+ {
+ return 0m;
+ }
+ }
+
///
/// Deterministic hash code for a string. .NET Core randomizes string.GetHashCode()
/// per process, so we use a simple FNV-1a hash to get a stable value across restarts.