From bb3efae5dc42f2a2cfc26fa3bd6e43ba90671d0b Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Tue, 14 Apr 2026 01:00:24 +0200 Subject: [PATCH] refactor CollectorCoordinator (method logic extraction) --- .../Monitoring/Core/AnalyzerManager.cs | 124 +++++++ .../Monitoring/Core/CollectorCoordinator.cs | 304 ++--------------- .../Core/PeriodicCollectorManager.cs | 306 ++++++++++++++++++ .../AutopilotMonitor.Agent/buildcounter.txt | 2 +- 4 files changed, 450 insertions(+), 286 deletions(-) create mode 100644 src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/AnalyzerManager.cs create mode 100644 src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/PeriodicCollectorManager.cs diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/AnalyzerManager.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/AnalyzerManager.cs new file mode 100644 index 00000000..953cafb1 --- /dev/null +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/AnalyzerManager.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using AutopilotMonitor.Agent.Core.Configuration; +using AutopilotMonitor.Agent.Core.Logging; +using AutopilotMonitor.Agent.Core.Monitoring.Analyzers; +using AutopilotMonitor.Shared.Models; + +namespace AutopilotMonitor.Agent.Core.Monitoring.Core +{ + /// + /// Manages agent analyzers: initialization from remote config, startup execution + /// on a background thread, and shutdown execution with optional WhiteGlove part. + /// + public class AnalyzerManager + { + private readonly AgentConfiguration _configuration; + private readonly AgentLogger _logger; + private readonly Action _emitEvent; + private readonly RemoteConfigService _remoteConfigService; + + private readonly List _analyzers = new List(); + + public AnalyzerManager( + AgentConfiguration configuration, + AgentLogger logger, + Action emitEvent, + RemoteConfigService remoteConfigService) + { + _configuration = configuration; + _logger = logger; + _emitEvent = emitEvent; + _remoteConfigService = remoteConfigService; + } + + public void Initialize() + { + _analyzers.Clear(); + + var analyzerConfig = _remoteConfigService?.CurrentConfig?.Analyzers ?? new AnalyzerConfiguration(); + + if (analyzerConfig.EnableLocalAdminAnalyzer) + { + _analyzers.Add(new LocalAdminAnalyzer( + _configuration.SessionId, + _configuration.TenantId, + _emitEvent, + _logger, + analyzerConfig.LocalAdminAllowedAccounts + )); + _logger.Info("LocalAdminAnalyzer registered"); + } + else + { + _logger.Info("LocalAdminAnalyzer disabled by remote config"); + } + + if (analyzerConfig.EnableSoftwareInventoryAnalyzer) + { + _analyzers.Add(new SoftwareInventoryAnalyzer( + _configuration.SessionId, + _configuration.TenantId, + _emitEvent, + _logger + )); + _logger.Info("SoftwareInventoryAnalyzer registered"); + } + else + { + _logger.Info("SoftwareInventoryAnalyzer disabled by remote config"); + } + + _logger.Info($"Analyzers initialized: {_analyzers.Count} active"); + } + + public void RunStartup() + { + if (_analyzers.Count == 0) + return; + + var analyzers = new List(_analyzers); + + _logger.Info($"Scheduling {analyzers.Count} startup analyzer(s) on background thread"); + + Task.Run(() => + { + foreach (var analyzer in analyzers) + { + try + { + analyzer.AnalyzeAtStartup(); + } + catch (Exception ex) + { + _logger.Error($"Analyzer {analyzer.Name} threw during startup", ex); + } + } + }); + } + + public void RunShutdown(int? whiteGlovePart = null) + { + if (_analyzers.Count == 0) + return; + + _logger.Info($"Running {_analyzers.Count} shutdown analyzer(s) (whiteGlovePart={whiteGlovePart?.ToString() ?? "none"})"); + + foreach (var analyzer in _analyzers) + { + try + { + if (analyzer is SoftwareInventoryAnalyzer softwareAnalyzer) + softwareAnalyzer.AnalyzeAtShutdown(whiteGlovePart); + else + analyzer.AnalyzeAtShutdown(); + } + catch (Exception ex) + { + _logger.Error($"Analyzer {analyzer.Name} threw during shutdown", ex); + } + } + } + } +} diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/CollectorCoordinator.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/CollectorCoordinator.cs index af39005f..5127d232 100644 --- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/CollectorCoordinator.cs +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/CollectorCoordinator.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using AutopilotMonitor.Agent.Core.Configuration; using AutopilotMonitor.Agent.Core.Logging; -using AutopilotMonitor.Agent.Core.Monitoring.Analyzers; using AutopilotMonitor.Agent.Core.Monitoring.Collectors; using AutopilotMonitor.Agent.Core.Monitoring.Network; using AutopilotMonitor.Agent.Core.Monitoring.Replay; @@ -39,11 +37,12 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core private LogReplayService _logReplay; // Optional collectors (toggled via remote config) - private PerformanceCollector _performanceCollector; - private AgentSelfMetricsCollector _agentSelfMetricsCollector; private DeliveryOptimizationCollector _deliveryOptimizationCollector; private StallProbeCollector _stallProbeCollector; + // Periodic collectors + idle management + private PeriodicCollectorManager _periodicManager; + // Smart enrollment tracking private EnrollmentTracker _enrollmentTracker; @@ -56,13 +55,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core private NetworkChangeDetector _networkChangeDetector; // Analyzers - private readonly List _analyzers = new List(); - - // Idle detection - private DateTime _lastRealEventTime = DateTime.UtcNow; - private bool _collectorsIdleStopped; - private int _collectorIdleTimeoutMinutes = 15; - private Timer _idleCheckTimer; + private AnalyzerManager _analyzerManager; // Max lifetime safety net private Timer _maxLifetimeTimer; @@ -98,7 +91,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core /// /// Whether periodic collectors are currently paused due to idle timeout. /// - public bool CollectorsIdleStopped => _collectorsIdleStopped; + public bool CollectorsIdleStopped => _periodicManager?.CollectorsIdleStopped ?? false; /// /// Called from EmitEvent when a non-periodic event is received. @@ -106,15 +99,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core /// public void OnRealEventReceived() { - _lastRealEventTime = DateTime.UtcNow; - - try { _stallProbeCollector?.ResetProbes(); } - catch (Exception ex) { _logger.Verbose($"StallProbeCollector.ResetProbes failed: {ex.Message}"); } - - if (_collectorsIdleStopped) - { - RestartPeriodicCollectors(); - } + _periodicManager?.OnRealEventReceived(); } /// @@ -148,9 +133,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core /// public void StopPerformanceCollectorOnly() { - _performanceCollector?.Stop(); - _performanceCollector?.Dispose(); - _performanceCollector = null; + _periodicManager?.StopPerformanceCollectorOnly(); } /// @@ -166,13 +149,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core /// public static bool IsPeriodicEvent(string eventType) { - return eventType == "performance_snapshot" || - eventType == "agent_metrics_snapshot" || - eventType == "performance_collector_stopped" || - eventType == "agent_metrics_collector_stopped" || - eventType == Constants.EventTypes.StallProbeCheck || - eventType == Constants.EventTypes.StallProbeResult || - eventType == Constants.EventTypes.SessionStalled; + return PeriodicCollectorManager.IsPeriodicEvent(eventType); } public void StartEventCollectors() @@ -235,37 +212,10 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core try { - var perfInterval = collectors?.PerformanceIntervalSeconds ?? 60; - _performanceCollector = new PerformanceCollector( - _configuration.SessionId, - _configuration.TenantId, - _emitEvent, - _logger, - perfInterval - ); - _performanceCollector.Start(); - _logger.Info($"PerformanceCollector started (interval={perfInterval}s)"); - - if (collectors?.EnableAgentSelfMetrics != false) - { - var selfMetricsInterval = collectors?.AgentSelfMetricsIntervalSeconds ?? 60; - _agentSelfMetricsCollector = new AgentSelfMetricsCollector( - _configuration.SessionId, - _configuration.TenantId, - _emitEvent, - _apiClient.NetworkMetrics, - _spool, - _logger, - _agentVersion, - selfMetricsInterval - ); - _agentSelfMetricsCollector.Start(); - _logger.Info($"AgentSelfMetricsCollector started (interval={selfMetricsInterval}s)"); - } - - _collectorIdleTimeoutMinutes = collectors?.CollectorIdleTimeoutMinutes ?? 15; - _lastRealEventTime = DateTime.UtcNow; - _collectorsIdleStopped = false; + _periodicManager = new PeriodicCollectorManager( + _configuration, _logger, _agentVersion, + _emitEvent, _apiClient, _spool, _remoteConfigService); + _periodicManager.Start(); if (collectors?.StallProbeEnabled != false) { @@ -292,16 +242,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core _logger.Info("StallProbeCollector disabled via config"); } - _idleCheckTimer = new Timer( - IdleCheckCallback, - null, - TimeSpan.FromSeconds(60), - TimeSpan.FromSeconds(60) - ); - if (_collectorIdleTimeoutMinutes > 0) - _logger.Info($"Collector idle timeout enabled: {_collectorIdleTimeoutMinutes} min"); - else - _logger.Info("Collector idle timeout disabled (0)"); + _periodicManager.StallProbeCollector = _stallProbeCollector; if (string.IsNullOrEmpty(_configuration.ReplayLogDir)) { @@ -421,8 +362,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core public void StopOptionalCollectors() { - _idleCheckTimer?.Dispose(); - _idleCheckTimer = null; + _periodicManager?.Stop(); _maxLifetimeTimer?.Dispose(); _maxLifetimeTimer = null; @@ -440,14 +380,6 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core _networkChangeDetector?.Dispose(); _networkChangeDetector = null; - _performanceCollector?.Stop(); - _performanceCollector?.Dispose(); - _performanceCollector = null; - - _agentSelfMetricsCollector?.Stop(); - _agentSelfMetricsCollector?.Dispose(); - _agentSelfMetricsCollector = null; - _deliveryOptimizationCollector?.Stop(); _deliveryOptimizationCollector?.Dispose(); _deliveryOptimizationCollector = null; @@ -512,90 +444,18 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core public void InitializeAnalyzers() { - _analyzers.Clear(); - - var analyzerConfig = _remoteConfigService?.CurrentConfig?.Analyzers ?? new AnalyzerConfiguration(); - - if (analyzerConfig.EnableLocalAdminAnalyzer) - { - _analyzers.Add(new LocalAdminAnalyzer( - _configuration.SessionId, - _configuration.TenantId, - _emitEvent, - _logger, - analyzerConfig.LocalAdminAllowedAccounts - )); - _logger.Info("LocalAdminAnalyzer registered"); - } - else - { - _logger.Info("LocalAdminAnalyzer disabled by remote config"); - } - - if (analyzerConfig.EnableSoftwareInventoryAnalyzer) - { - _analyzers.Add(new SoftwareInventoryAnalyzer( - _configuration.SessionId, - _configuration.TenantId, - _emitEvent, - _logger - )); - _logger.Info("SoftwareInventoryAnalyzer registered"); - } - else - { - _logger.Info("SoftwareInventoryAnalyzer disabled by remote config"); - } - - _logger.Info($"Analyzers initialized: {_analyzers.Count} active"); + _analyzerManager = new AnalyzerManager(_configuration, _logger, _emitEvent, _remoteConfigService); + _analyzerManager.Initialize(); } public void RunStartupAnalyzers() { - if (_analyzers.Count == 0) - return; - - var analyzers = new List(_analyzers); - - _logger.Info($"Scheduling {analyzers.Count} startup analyzer(s) on background thread"); - - Task.Run(() => - { - foreach (var analyzer in analyzers) - { - try - { - analyzer.AnalyzeAtStartup(); - } - catch (Exception ex) - { - _logger.Error($"Analyzer {analyzer.Name} threw during startup", ex); - } - } - }); + _analyzerManager?.RunStartup(); } public void RunShutdownAnalyzers(int? whiteGlovePart = null) { - if (_analyzers.Count == 0) - return; - - _logger.Info($"Running {_analyzers.Count} shutdown analyzer(s) (whiteGlovePart={whiteGlovePart?.ToString() ?? "none"})"); - - foreach (var analyzer in _analyzers) - { - try - { - if (analyzer is SoftwareInventoryAnalyzer softwareAnalyzer) - softwareAnalyzer.AnalyzeAtShutdown(whiteGlovePart); - else - analyzer.AnalyzeAtShutdown(); - } - catch (Exception ex) - { - _logger.Error($"Analyzer {analyzer.Name} threw during shutdown", ex); - } - } + _analyzerManager?.RunShutdown(whiteGlovePart); } /// @@ -659,138 +519,12 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core _enrollmentTracker?.NotifyDesktopArrived(); } - private void IdleCheckCallback(object state) - { - var idleMinutes = (DateTime.UtcNow - _lastRealEventTime).TotalMinutes; - - try - { - _stallProbeCollector?.CheckAndRunProbes(idleMinutes); - } - catch (Exception ex) - { - _logger.Error("StallProbeCollector.CheckAndRunProbes threw", ex); - } - - if (_collectorsIdleStopped) - return; - - if (idleMinutes < _collectorIdleTimeoutMinutes) - return; - - _logger.Info($"Collector idle timeout reached ({_collectorIdleTimeoutMinutes} min, idle for {idleMinutes:F0} min) — stopping periodic collectors"); - - if (_performanceCollector != null) - { - _performanceCollector.Stop(); - _performanceCollector.Dispose(); - _performanceCollector = null; - - _emitEvent(new EnrollmentEvent - { - SessionId = _configuration.SessionId, - TenantId = _configuration.TenantId, - Timestamp = DateTime.UtcNow, - EventType = "performance_collector_stopped", - Severity = EventSeverity.Info, - Source = "MonitoringService", - Phase = EnrollmentPhase.Unknown, - Message = $"Performance collector stopped after {_collectorIdleTimeoutMinutes} min idle (no real enrollment activity)", - Data = new Dictionary - { - { "reason", "idle_timeout" }, - { "idleTimeoutMinutes", _collectorIdleTimeoutMinutes }, - { "idleMinutes", Math.Round(idleMinutes, 1) } - } - }); - } - - if (_agentSelfMetricsCollector != null) - { - _agentSelfMetricsCollector.Stop(); - _agentSelfMetricsCollector.Dispose(); - _agentSelfMetricsCollector = null; - - _emitEvent(new EnrollmentEvent - { - SessionId = _configuration.SessionId, - TenantId = _configuration.TenantId, - Timestamp = DateTime.UtcNow, - EventType = "agent_metrics_collector_stopped", - Severity = EventSeverity.Info, - Source = "MonitoringService", - Phase = EnrollmentPhase.Unknown, - Message = $"AgentSelfMetrics collector stopped after {_collectorIdleTimeoutMinutes} min idle (no real enrollment activity)", - Data = new Dictionary - { - { "reason", "idle_timeout" }, - { "idleTimeoutMinutes", _collectorIdleTimeoutMinutes }, - { "idleMinutes", Math.Round(idleMinutes, 1) } - } - }); - } - - _collectorsIdleStopped = true; - } - - private void RestartPeriodicCollectors() - { - var config = _remoteConfigService?.CurrentConfig; - var collectors = config?.Collectors; - - _logger.Info("Restarting periodic collectors — new enrollment activity detected after idle stop"); - - try - { - var perfInterval = collectors?.PerformanceIntervalSeconds ?? 60; - _performanceCollector = new PerformanceCollector( - _configuration.SessionId, - _configuration.TenantId, - _emitEvent, - _logger, - perfInterval - ); - _performanceCollector.Start(); - - if (collectors?.EnableAgentSelfMetrics != false) - { - var selfMetricsInterval = collectors?.AgentSelfMetricsIntervalSeconds ?? 60; - _agentSelfMetricsCollector = new AgentSelfMetricsCollector( - _configuration.SessionId, - _configuration.TenantId, - _emitEvent, - _apiClient.NetworkMetrics, - _spool, - _logger, - _agentVersion, - selfMetricsInterval - ); - _agentSelfMetricsCollector.Start(); - } - - _lastRealEventTime = DateTime.UtcNow; - _collectorsIdleStopped = false; - if (_collectorIdleTimeoutMinutes > 0) - { - _idleCheckTimer?.Change(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60)); - } - - _logger.Info("Periodic collectors restarted successfully"); - } - catch (Exception ex) - { - _logger.Error("Error restarting periodic collectors", ex); - } - } - public void Dispose() { - _idleCheckTimer?.Dispose(); + _periodicManager?.Dispose(); _maxLifetimeTimer?.Dispose(); _espAndHelloTracker?.Dispose(); _logReplay?.Dispose(); - _performanceCollector?.Dispose(); - _agentSelfMetricsCollector?.Dispose(); _deliveryOptimizationCollector?.Dispose(); _enrollmentTracker?.Dispose(); _networkChangeDetector?.Dispose(); diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/PeriodicCollectorManager.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/PeriodicCollectorManager.cs new file mode 100644 index 00000000..b2537182 --- /dev/null +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/PeriodicCollectorManager.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using AutopilotMonitor.Agent.Core.Configuration; +using AutopilotMonitor.Agent.Core.Logging; +using AutopilotMonitor.Agent.Core.Monitoring.Collectors; +using AutopilotMonitor.Agent.Core.Monitoring.Network; +using AutopilotMonitor.Shared; +using AutopilotMonitor.Shared.Models; + +namespace AutopilotMonitor.Agent.Core.Monitoring.Core +{ + /// + /// Manages periodic collectors (PerformanceCollector, AgentSelfMetricsCollector) and their + /// idle lifecycle: stops them after inactivity, restarts when real enrollment activity resumes. + /// + public class PeriodicCollectorManager : IDisposable + { + private readonly AgentConfiguration _configuration; + private readonly AgentLogger _logger; + private readonly string _agentVersion; + private readonly Action _emitEvent; + private readonly BackendApiClient _apiClient; + private readonly EventSpool _spool; + private readonly RemoteConfigService _remoteConfigService; + + private PerformanceCollector _performanceCollector; + private AgentSelfMetricsCollector _agentSelfMetricsCollector; + + private DateTime _lastRealEventTime = DateTime.UtcNow; + private bool _collectorsIdleStopped; + private int _collectorIdleTimeoutMinutes = 15; + private Timer _idleCheckTimer; + + /// + /// Optional reference to the StallProbeCollector for resetting/running probes + /// during idle checks. Set after creation in StartOptionalCollectors. + /// + public StallProbeCollector StallProbeCollector { get; set; } + + public PeriodicCollectorManager( + AgentConfiguration configuration, + AgentLogger logger, + string agentVersion, + Action emitEvent, + BackendApiClient apiClient, + EventSpool spool, + RemoteConfigService remoteConfigService) + { + _configuration = configuration; + _logger = logger; + _agentVersion = agentVersion; + _emitEvent = emitEvent; + _apiClient = apiClient; + _spool = spool; + _remoteConfigService = remoteConfigService; + } + + /// + /// Whether periodic collectors are currently paused due to idle timeout. + /// + public bool CollectorsIdleStopped => _collectorsIdleStopped; + + /// + /// Starts the performance and metrics collectors and arms the idle check timer. + /// Called from CollectorCoordinator.StartOptionalCollectors. + /// + public void Start() + { + var config = _remoteConfigService?.CurrentConfig; + var collectors = config?.Collectors; + + var perfInterval = collectors?.PerformanceIntervalSeconds ?? 60; + _performanceCollector = new PerformanceCollector( + _configuration.SessionId, + _configuration.TenantId, + _emitEvent, + _logger, + perfInterval + ); + _performanceCollector.Start(); + _logger.Info($"PerformanceCollector started (interval={perfInterval}s)"); + + if (collectors?.EnableAgentSelfMetrics != false) + { + var selfMetricsInterval = collectors?.AgentSelfMetricsIntervalSeconds ?? 60; + _agentSelfMetricsCollector = new AgentSelfMetricsCollector( + _configuration.SessionId, + _configuration.TenantId, + _emitEvent, + _apiClient.NetworkMetrics, + _spool, + _logger, + _agentVersion, + selfMetricsInterval + ); + _agentSelfMetricsCollector.Start(); + _logger.Info($"AgentSelfMetricsCollector started (interval={selfMetricsInterval}s)"); + } + + _collectorIdleTimeoutMinutes = collectors?.CollectorIdleTimeoutMinutes ?? 15; + _lastRealEventTime = DateTime.UtcNow; + _collectorsIdleStopped = false; + + _idleCheckTimer = new Timer( + IdleCheckCallback, + null, + TimeSpan.FromSeconds(60), + TimeSpan.FromSeconds(60) + ); + if (_collectorIdleTimeoutMinutes > 0) + _logger.Info($"Collector idle timeout enabled: {_collectorIdleTimeoutMinutes} min"); + else + _logger.Info("Collector idle timeout disabled (0)"); + } + + /// + /// Called when a non-periodic event is received. Updates idle tracking, + /// resets stall probes, and restarts paused collectors. + /// + public void OnRealEventReceived() + { + _lastRealEventTime = DateTime.UtcNow; + + try { StallProbeCollector?.ResetProbes(); } + catch (Exception ex) { _logger.Verbose($"StallProbeCollector.ResetProbes failed: {ex.Message}"); } + + if (_collectorsIdleStopped) + { + RestartPeriodicCollectors(); + } + } + + /// + /// Stops just the performance collector (used during WhiteGlove completion). + /// + public void StopPerformanceCollectorOnly() + { + _performanceCollector?.Stop(); + _performanceCollector?.Dispose(); + _performanceCollector = null; + } + + /// + /// Stops all periodic collectors and the idle check timer. + /// + public void Stop() + { + _idleCheckTimer?.Dispose(); + _idleCheckTimer = null; + + _performanceCollector?.Stop(); + _performanceCollector?.Dispose(); + _performanceCollector = null; + + _agentSelfMetricsCollector?.Stop(); + _agentSelfMetricsCollector?.Dispose(); + _agentSelfMetricsCollector = null; + } + + /// + /// Returns true for events generated by periodic timers (not real enrollment activity). + /// + public static bool IsPeriodicEvent(string eventType) + { + return eventType == "performance_snapshot" || + eventType == "agent_metrics_snapshot" || + eventType == "performance_collector_stopped" || + eventType == "agent_metrics_collector_stopped" || + eventType == Constants.EventTypes.StallProbeCheck || + eventType == Constants.EventTypes.StallProbeResult || + eventType == Constants.EventTypes.SessionStalled; + } + + private void IdleCheckCallback(object state) + { + var idleMinutes = (DateTime.UtcNow - _lastRealEventTime).TotalMinutes; + + try + { + StallProbeCollector?.CheckAndRunProbes(idleMinutes); + } + catch (Exception ex) + { + _logger.Error("StallProbeCollector.CheckAndRunProbes threw", ex); + } + + if (_collectorsIdleStopped) + return; + + if (idleMinutes < _collectorIdleTimeoutMinutes) + return; + + _logger.Info($"Collector idle timeout reached ({_collectorIdleTimeoutMinutes} min, idle for {idleMinutes:F0} min) — stopping periodic collectors"); + + if (_performanceCollector != null) + { + _performanceCollector.Stop(); + _performanceCollector.Dispose(); + _performanceCollector = null; + + _emitEvent(new EnrollmentEvent + { + SessionId = _configuration.SessionId, + TenantId = _configuration.TenantId, + Timestamp = DateTime.UtcNow, + EventType = "performance_collector_stopped", + Severity = EventSeverity.Info, + Source = "MonitoringService", + Phase = EnrollmentPhase.Unknown, + Message = $"Performance collector stopped after {_collectorIdleTimeoutMinutes} min idle (no real enrollment activity)", + Data = new Dictionary + { + { "reason", "idle_timeout" }, + { "idleTimeoutMinutes", _collectorIdleTimeoutMinutes }, + { "idleMinutes", Math.Round(idleMinutes, 1) } + } + }); + } + + if (_agentSelfMetricsCollector != null) + { + _agentSelfMetricsCollector.Stop(); + _agentSelfMetricsCollector.Dispose(); + _agentSelfMetricsCollector = null; + + _emitEvent(new EnrollmentEvent + { + SessionId = _configuration.SessionId, + TenantId = _configuration.TenantId, + Timestamp = DateTime.UtcNow, + EventType = "agent_metrics_collector_stopped", + Severity = EventSeverity.Info, + Source = "MonitoringService", + Phase = EnrollmentPhase.Unknown, + Message = $"AgentSelfMetrics collector stopped after {_collectorIdleTimeoutMinutes} min idle (no real enrollment activity)", + Data = new Dictionary + { + { "reason", "idle_timeout" }, + { "idleTimeoutMinutes", _collectorIdleTimeoutMinutes }, + { "idleMinutes", Math.Round(idleMinutes, 1) } + } + }); + } + + _collectorsIdleStopped = true; + } + + private void RestartPeriodicCollectors() + { + var config = _remoteConfigService?.CurrentConfig; + var collectors = config?.Collectors; + + _logger.Info("Restarting periodic collectors — new enrollment activity detected after idle stop"); + + try + { + var perfInterval = collectors?.PerformanceIntervalSeconds ?? 60; + _performanceCollector = new PerformanceCollector( + _configuration.SessionId, + _configuration.TenantId, + _emitEvent, + _logger, + perfInterval + ); + _performanceCollector.Start(); + + if (collectors?.EnableAgentSelfMetrics != false) + { + var selfMetricsInterval = collectors?.AgentSelfMetricsIntervalSeconds ?? 60; + _agentSelfMetricsCollector = new AgentSelfMetricsCollector( + _configuration.SessionId, + _configuration.TenantId, + _emitEvent, + _apiClient.NetworkMetrics, + _spool, + _logger, + _agentVersion, + selfMetricsInterval + ); + _agentSelfMetricsCollector.Start(); + } + + _lastRealEventTime = DateTime.UtcNow; + _collectorsIdleStopped = false; + if (_collectorIdleTimeoutMinutes > 0) + { + _idleCheckTimer?.Change(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(60)); + } + + _logger.Info("Periodic collectors restarted successfully"); + } + catch (Exception ex) + { + _logger.Error("Error restarting periodic collectors", ex); + } + } + + public void Dispose() + { + _idleCheckTimer?.Dispose(); + _performanceCollector?.Dispose(); + _agentSelfMetricsCollector?.Dispose(); + } + } +} diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index 3b2be154..2ebd3a0c 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -941 +945