using Microsoft.Extensions.Options; using System.Diagnostics; using System.Runtime; namespace QRRapidoApp.Services.Monitoring { public class ResourceMonitoringService : BackgroundService { private readonly ILogger _logger; private readonly IConfiguration _configuration; private readonly string _applicationName; // Configuration values private readonly int _intervalSeconds; private readonly double _cpuThresholdPercent; private readonly long _memoryThresholdMB; private readonly int _consecutiveAlertsBeforeError; private readonly int _gcCollectionThreshold; // Monitoring state private int _consecutiveHighCpuAlerts = 0; private int _consecutiveHighMemoryAlerts = 0; private readonly Dictionary _previousGcCounts = new(); private DateTime _lastMeasurement = DateTime.UtcNow; private double _previousCpuTime = 0; public ResourceMonitoringService( ILogger logger, IConfiguration configuration) { _logger = logger; _configuration = configuration; _applicationName = configuration["ApplicationName"] ?? "QRRapido"; // Load configuration _intervalSeconds = configuration.GetValue("ResourceMonitoring:IntervalSeconds", 30); _cpuThresholdPercent = configuration.GetValue("ResourceMonitoring:CpuThresholdPercent", 80.0); _memoryThresholdMB = configuration.GetValue("ResourceMonitoring:MemoryThresholdMB", 512); _consecutiveAlertsBeforeError = configuration.GetValue("ResourceMonitoring:ConsecutiveAlertsBeforeError", 4); _gcCollectionThreshold = configuration.GetValue("ResourceMonitoring:GcCollectionThreshold", 10); _logger.LogInformation("ResourceMonitoringService initialized for {ApplicationName} - Interval: {IntervalSeconds}s, CPU Threshold: {CpuThreshold}%, Memory Threshold: {MemoryThreshold}MB", _applicationName, _intervalSeconds, _cpuThresholdPercent, _memoryThresholdMB); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("ResourceMonitoringService started for {ApplicationName}", _applicationName); while (!stoppingToken.IsCancellationRequested) { try { await MonitorResourcesAsync(); } catch (Exception ex) { _logger.LogError(ex, "Error in ResourceMonitoringService for {ApplicationName}", _applicationName); } await Task.Delay(TimeSpan.FromSeconds(_intervalSeconds), stoppingToken); } _logger.LogInformation("ResourceMonitoringService stopped for {ApplicationName}", _applicationName); } private async Task MonitorResourcesAsync() { var currentProcess = Process.GetCurrentProcess(); var now = DateTime.UtcNow; // CPU Usage calculation var currentCpuTime = currentProcess.TotalProcessorTime.TotalMilliseconds; var elapsedTime = (now - _lastMeasurement).TotalMilliseconds; var cpuUsagePercent = 0.0; if (_previousCpuTime > 0 && elapsedTime > 0) { var cpuTimeDelta = currentCpuTime - _previousCpuTime; var coreCount = Environment.ProcessorCount; cpuUsagePercent = (cpuTimeDelta / (elapsedTime * coreCount)) * 100; } _previousCpuTime = currentCpuTime; _lastMeasurement = now; // Memory Usage var workingSetMB = currentProcess.WorkingSet64 / (1024 * 1024); var privateMemoryMB = currentProcess.PrivateMemorySize64 / (1024 * 1024); var virtualMemoryMB = currentProcess.VirtualMemorySize64 / (1024 * 1024); // GC Statistics var gen0Collections = GC.CollectionCount(0); var gen1Collections = GC.CollectionCount(1); var gen2Collections = GC.CollectionCount(2); var totalMemoryMB = GC.GetTotalMemory(false) / (1024 * 1024); // Calculate GC pressure (collections since last measurement) var gen0Pressure = CalculateGcPressure(0, gen0Collections); var gen1Pressure = CalculateGcPressure(1, gen1Collections); var gen2Pressure = CalculateGcPressure(2, gen2Collections); var totalGcPressure = gen0Pressure + gen1Pressure + gen2Pressure; // Thread and Handle counts var threadCount = currentProcess.Threads.Count; var handleCount = currentProcess.HandleCount; // Determine status and log level var status = DetermineResourceStatus(cpuUsagePercent, workingSetMB, totalGcPressure); // Log structured resource metrics using (_logger.BeginScope(new Dictionary { ["ApplicationName"] = _applicationName, ["ResourceMonitoring"] = true, ["CpuUsagePercent"] = Math.Round(cpuUsagePercent, 2), ["WorkingSetMB"] = workingSetMB, ["PrivateMemoryMB"] = privateMemoryMB, ["VirtualMemoryMB"] = virtualMemoryMB, ["GcTotalMemoryMB"] = totalMemoryMB, ["Gen0Collections"] = gen0Collections, ["Gen1Collections"] = gen1Collections, ["Gen2Collections"] = gen2Collections, ["GcPressure"] = totalGcPressure, ["ThreadCount"] = threadCount, ["HandleCount"] = handleCount, ["ProcessId"] = currentProcess.Id, ["Uptime"] = (DateTime.UtcNow - Process.GetCurrentProcess().StartTime).ToString(@"dd\.hh\:mm\:ss"), ["Status"] = status })) { var logLevel = status switch { "Critical" => LogLevel.Error, "Warning" => LogLevel.Warning, _ => LogLevel.Information }; _logger.Log(logLevel, "Resource monitoring - CPU: {CpuUsage:F1}%, Memory: {Memory}MB, GC Pressure: {GcPressure}, Threads: {Threads}, Status: {Status}", cpuUsagePercent, workingSetMB, totalGcPressure, threadCount, status); } // Check for alerts await CheckResourceAlertsAsync(cpuUsagePercent, workingSetMB, totalGcPressure); } private long CalculateGcPressure(int generation, long currentCount) { if (!_previousGcCounts.ContainsKey(generation)) { _previousGcCounts[generation] = currentCount; return 0; } var pressure = currentCount - _previousGcCounts[generation]; _previousGcCounts[generation] = currentCount; return pressure; } private string DetermineResourceStatus(double cpuUsage, long memoryMB, long gcPressure) { if (cpuUsage > _cpuThresholdPercent * 1.2 || memoryMB > _memoryThresholdMB * 1.5 || gcPressure > _gcCollectionThreshold * 2) { return "Critical"; } if (cpuUsage > _cpuThresholdPercent || memoryMB > _memoryThresholdMB || gcPressure > _gcCollectionThreshold) { return "Warning"; } return "Healthy"; } private async Task CheckResourceAlertsAsync(double cpuUsage, long memoryMB, long gcPressure) { // CPU Alert Logic if (cpuUsage > _cpuThresholdPercent) { _consecutiveHighCpuAlerts++; if (_consecutiveHighCpuAlerts >= _consecutiveAlertsBeforeError) { _logger.LogError("ALERT: High CPU usage detected for {ApplicationName} - {CpuUsage:F1}% for {ConsecutiveAlerts} consecutive measurements (threshold: {Threshold}%)", _applicationName, cpuUsage, _consecutiveHighCpuAlerts, _cpuThresholdPercent); } else { _logger.LogWarning("High CPU usage detected for {ApplicationName} - {CpuUsage:F1}% (threshold: {Threshold}%) - Alert {Current}/{Required}", _applicationName, cpuUsage, _cpuThresholdPercent, _consecutiveHighCpuAlerts, _consecutiveAlertsBeforeError); } } else { if (_consecutiveHighCpuAlerts > 0) { _logger.LogInformation("CPU usage normalized for {ApplicationName} - {CpuUsage:F1}% (was high for {PreviousAlerts} measurements)", _applicationName, cpuUsage, _consecutiveHighCpuAlerts); } _consecutiveHighCpuAlerts = 0; } // Memory Alert Logic if (memoryMB > _memoryThresholdMB) { _consecutiveHighMemoryAlerts++; if (_consecutiveHighMemoryAlerts >= _consecutiveAlertsBeforeError) { _logger.LogError("ALERT: High memory usage detected for {ApplicationName} - {MemoryMB}MB for {ConsecutiveAlerts} consecutive measurements (threshold: {Threshold}MB)", _applicationName, memoryMB, _consecutiveHighMemoryAlerts, _memoryThresholdMB); // Suggest GC collection on persistent high memory if (_consecutiveHighMemoryAlerts > _consecutiveAlertsBeforeError * 2) { _logger.LogWarning("Forcing garbage collection due to persistent high memory usage for {ApplicationName}", _applicationName); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } } else { _logger.LogWarning("High memory usage detected for {ApplicationName} - {MemoryMB}MB (threshold: {Threshold}MB) - Alert {Current}/{Required}", _applicationName, memoryMB, _memoryThresholdMB, _consecutiveHighMemoryAlerts, _consecutiveAlertsBeforeError); } } else { if (_consecutiveHighMemoryAlerts > 0) { _logger.LogInformation("Memory usage normalized for {ApplicationName} - {MemoryMB}MB (was high for {PreviousAlerts} measurements)", _applicationName, memoryMB, _consecutiveHighMemoryAlerts); } _consecutiveHighMemoryAlerts = 0; } // GC Pressure Alert if (gcPressure > _gcCollectionThreshold) { _logger.LogWarning("High GC pressure detected for {ApplicationName} - {GcPressure} collections in last interval (threshold: {Threshold})", _applicationName, gcPressure, _gcCollectionThreshold); } } } }