QrRapido/Services/Monitoring/ResourceMonitoringService.cs
2025-07-28 18:22:47 -03:00

251 lines
11 KiB
C#

using Microsoft.Extensions.Options;
using System.Diagnostics;
using System.Runtime;
namespace QRRapidoApp.Services.Monitoring
{
public class ResourceMonitoringService : BackgroundService
{
private readonly ILogger<ResourceMonitoringService> _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<int, long> _previousGcCounts = new();
private DateTime _lastMeasurement = DateTime.UtcNow;
private double _previousCpuTime = 0;
public ResourceMonitoringService(
ILogger<ResourceMonitoringService> logger,
IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
_applicationName = configuration["ApplicationName"] ?? "QRRapido";
// Load configuration
_intervalSeconds = configuration.GetValue<int>("ResourceMonitoring:IntervalSeconds", 30);
_cpuThresholdPercent = configuration.GetValue<double>("ResourceMonitoring:CpuThresholdPercent", 80.0);
_memoryThresholdMB = configuration.GetValue<long>("ResourceMonitoring:MemoryThresholdMB", 512);
_consecutiveAlertsBeforeError = configuration.GetValue<int>("ResourceMonitoring:ConsecutiveAlertsBeforeError", 4);
_gcCollectionThreshold = configuration.GetValue<int>("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<string, object>
{
["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);
}
}
}
}