251 lines
11 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
} |