From a5cab0464401dc3688192e5e1ec426d3c5244a04 Mon Sep 17 00:00:00 2001 From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com> Date: Thu, 12 Feb 2026 11:05:46 -0500 Subject: [PATCH] Lite: show per-server collector health in status bar (#5) Status bar now filters collector health by the selected server tab instead of showing global aggregate. Overview tab still shows all-server health. Co-Authored-By: Claude Opus 4.6 --- Lite/Controls/ServerTab.xaml.cs | 1 + Lite/MainWindow.xaml.cs | 10 +++++++++- Lite/Services/RemoteCollectorService.cs | 26 +++++++++++++++---------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Lite/Controls/ServerTab.xaml.cs b/Lite/Controls/ServerTab.xaml.cs index b09a3661..c7001e88 100644 --- a/Lite/Controls/ServerTab.xaml.cs +++ b/Lite/Controls/ServerTab.xaml.cs @@ -30,6 +30,7 @@ public partial class ServerTab : UserControl private readonly ServerConnection _server; private readonly LocalDataService _dataService; private readonly int _serverId; + public int ServerId => _serverId; private readonly CredentialService _credentialService; private readonly DispatcherTimer _refreshTimer; private readonly Dictionary _legendPanels = new(); diff --git a/Lite/MainWindow.xaml.cs b/Lite/MainWindow.xaml.cs index fb231638..69e63df0 100644 --- a/Lite/MainWindow.xaml.cs +++ b/Lite/MainWindow.xaml.cs @@ -219,6 +219,8 @@ private void ServerTabControl_SelectionChanged(object sender, SelectionChangedEv { ServerTimeHelper.UtcOffsetMinutes = serverTab.UtcOffsetMinutes; } + + UpdateCollectorHealth(); } private void RefreshServerList() @@ -290,7 +292,13 @@ private void UpdateCollectorHealth() return; } - var health = _collectorService.GetHealthSummary(); + int? selectedServerId = null; + if (ServerTabControl.SelectedItem is TabItem { Content: ServerTab serverTab }) + { + selectedServerId = serverTab.ServerId; + } + + var health = _collectorService.GetHealthSummary(selectedServerId); if (health.TotalCollectors == 0) { diff --git a/Lite/Services/RemoteCollectorService.cs b/Lite/Services/RemoteCollectorService.cs index 6dfb5353..0973c20d 100644 --- a/Lite/Services/RemoteCollectorService.cs +++ b/Lite/Services/RemoteCollectorService.cs @@ -30,6 +30,7 @@ namespace PerformanceMonitorLite.Services; /// public class CollectorHealthEntry { + public int ServerId { get; set; } public string CollectorName { get; set; } = ""; public DateTime? LastSuccessTime { get; set; } public DateTime? LastErrorTime { get; set; } @@ -82,9 +83,9 @@ public partial class RemoteCollectorService private long _lastDuckDbMs; /// - /// Tracks health state per collector (keyed by collector name). + /// Tracks health state per collector per server. /// - private readonly Dictionary _collectorHealth = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary<(int ServerId, string CollectorName), CollectorHealthEntry> _collectorHealth = new(); private readonly object _healthLock = new(); /// @@ -113,20 +114,24 @@ public RemoteCollectorService( public Task SeedDeltaCacheAsync() => _deltaCalculator.SeedFromDatabaseAsync(_duckDb); /// - /// Gets a summary of collector health across all tracked collectors. + /// Gets a summary of collector health. When serverId is provided, filters to that server only. /// - public CollectorHealthSummary GetHealthSummary() + public CollectorHealthSummary GetHealthSummary(int? serverId = null) { lock (_healthLock) { var summary = new CollectorHealthSummary { - TotalCollectors = _collectorHealth.Count, LoggingFailures = _logInsertFailures }; foreach (var entry in _collectorHealth.Values) { + if (serverId.HasValue && entry.ServerId != serverId.Value) + continue; + + summary.TotalCollectors++; + if (entry.ConsecutiveErrors > 0) { summary.ErroringCollectors++; @@ -141,14 +146,15 @@ public CollectorHealthSummary GetHealthSummary() /// /// Records a collector execution result for health tracking. /// - private void RecordCollectorResult(string collectorName, bool success, string? errorMessage = null) + private void RecordCollectorResult(int serverId, string collectorName, bool success, string? errorMessage = null) { lock (_healthLock) { - if (!_collectorHealth.TryGetValue(collectorName, out var entry)) + var key = (serverId, collectorName); + if (!_collectorHealth.TryGetValue(key, out var entry)) { - entry = new CollectorHealthEntry { CollectorName = collectorName }; - _collectorHealth[collectorName] = entry; + entry = new CollectorHealthEntry { ServerId = serverId, CollectorName = collectorName }; + _collectorHealth[key] = entry; } if (success) @@ -330,7 +336,7 @@ public async Task RunCollectorAsync(ServerConnection server, string collectorNam } // Track collector health - RecordCollectorResult(collectorName, status == "SUCCESS", errorMessage); + RecordCollectorResult(GetServerId(server), collectorName, status == "SUCCESS", errorMessage); // Log the collection attempt await LogCollectionAsync(GetServerId(server), collectorName, startTime, status, errorMessage, rowsCollected, _lastSqlMs, _lastDuckDbMs);