mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
refactor CollectorCoordinator (method logic extraction)
This commit is contained in:
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages agent analyzers: initialization from remote config, startup execution
|
||||
/// on a background thread, and shutdown execution with optional WhiteGlove part.
|
||||
/// </summary>
|
||||
public class AnalyzerManager
|
||||
{
|
||||
private readonly AgentConfiguration _configuration;
|
||||
private readonly AgentLogger _logger;
|
||||
private readonly Action<EnrollmentEvent> _emitEvent;
|
||||
private readonly RemoteConfigService _remoteConfigService;
|
||||
|
||||
private readonly List<IAgentAnalyzer> _analyzers = new List<IAgentAnalyzer>();
|
||||
|
||||
public AnalyzerManager(
|
||||
AgentConfiguration configuration,
|
||||
AgentLogger logger,
|
||||
Action<EnrollmentEvent> 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<IAgentAnalyzer>(_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<IAgentAnalyzer> _analyzers = new List<IAgentAnalyzer>();
|
||||
|
||||
// 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
|
||||
/// <summary>
|
||||
/// Whether periodic collectors are currently paused due to idle timeout.
|
||||
/// </summary>
|
||||
public bool CollectorsIdleStopped => _collectorsIdleStopped;
|
||||
public bool CollectorsIdleStopped => _periodicManager?.CollectorsIdleStopped ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Called from EmitEvent when a non-periodic event is received.
|
||||
@@ -106,15 +99,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
/// </summary>
|
||||
public void OnRealEventReceived()
|
||||
{
|
||||
_lastRealEventTime = DateTime.UtcNow;
|
||||
|
||||
try { _stallProbeCollector?.ResetProbes(); }
|
||||
catch (Exception ex) { _logger.Verbose($"StallProbeCollector.ResetProbes failed: {ex.Message}"); }
|
||||
|
||||
if (_collectorsIdleStopped)
|
||||
{
|
||||
RestartPeriodicCollectors();
|
||||
}
|
||||
_periodicManager?.OnRealEventReceived();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -148,9 +133,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
/// </summary>
|
||||
public void StopPerformanceCollectorOnly()
|
||||
{
|
||||
_performanceCollector?.Stop();
|
||||
_performanceCollector?.Dispose();
|
||||
_performanceCollector = null;
|
||||
_periodicManager?.StopPerformanceCollectorOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -166,13 +149,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
/// </summary>
|
||||
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<IAgentAnalyzer>(_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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<string, object>
|
||||
{
|
||||
{ "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<string, object>
|
||||
{
|
||||
{ "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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages periodic collectors (PerformanceCollector, AgentSelfMetricsCollector) and their
|
||||
/// idle lifecycle: stops them after inactivity, restarts when real enrollment activity resumes.
|
||||
/// </summary>
|
||||
public class PeriodicCollectorManager : IDisposable
|
||||
{
|
||||
private readonly AgentConfiguration _configuration;
|
||||
private readonly AgentLogger _logger;
|
||||
private readonly string _agentVersion;
|
||||
private readonly Action<EnrollmentEvent> _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;
|
||||
|
||||
/// <summary>
|
||||
/// Optional reference to the StallProbeCollector for resetting/running probes
|
||||
/// during idle checks. Set after creation in StartOptionalCollectors.
|
||||
/// </summary>
|
||||
public StallProbeCollector StallProbeCollector { get; set; }
|
||||
|
||||
public PeriodicCollectorManager(
|
||||
AgentConfiguration configuration,
|
||||
AgentLogger logger,
|
||||
string agentVersion,
|
||||
Action<EnrollmentEvent> emitEvent,
|
||||
BackendApiClient apiClient,
|
||||
EventSpool spool,
|
||||
RemoteConfigService remoteConfigService)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
_agentVersion = agentVersion;
|
||||
_emitEvent = emitEvent;
|
||||
_apiClient = apiClient;
|
||||
_spool = spool;
|
||||
_remoteConfigService = remoteConfigService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether periodic collectors are currently paused due to idle timeout.
|
||||
/// </summary>
|
||||
public bool CollectorsIdleStopped => _collectorsIdleStopped;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the performance and metrics collectors and arms the idle check timer.
|
||||
/// Called from CollectorCoordinator.StartOptionalCollectors.
|
||||
/// </summary>
|
||||
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)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a non-periodic event is received. Updates idle tracking,
|
||||
/// resets stall probes, and restarts paused collectors.
|
||||
/// </summary>
|
||||
public void OnRealEventReceived()
|
||||
{
|
||||
_lastRealEventTime = DateTime.UtcNow;
|
||||
|
||||
try { StallProbeCollector?.ResetProbes(); }
|
||||
catch (Exception ex) { _logger.Verbose($"StallProbeCollector.ResetProbes failed: {ex.Message}"); }
|
||||
|
||||
if (_collectorsIdleStopped)
|
||||
{
|
||||
RestartPeriodicCollectors();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops just the performance collector (used during WhiteGlove completion).
|
||||
/// </summary>
|
||||
public void StopPerformanceCollectorOnly()
|
||||
{
|
||||
_performanceCollector?.Stop();
|
||||
_performanceCollector?.Dispose();
|
||||
_performanceCollector = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops all periodic collectors and the idle check timer.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
_idleCheckTimer?.Dispose();
|
||||
_idleCheckTimer = null;
|
||||
|
||||
_performanceCollector?.Stop();
|
||||
_performanceCollector?.Dispose();
|
||||
_performanceCollector = null;
|
||||
|
||||
_agentSelfMetricsCollector?.Stop();
|
||||
_agentSelfMetricsCollector?.Dispose();
|
||||
_agentSelfMetricsCollector = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true for events generated by periodic timers (not real enrollment activity).
|
||||
/// </summary>
|
||||
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<string, object>
|
||||
{
|
||||
{ "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<string, object>
|
||||
{
|
||||
{ "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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
941
|
||||
945
|
||||
|
||||
Reference in New Issue
Block a user