Update project files and configurations for improved structure and maintainability

This commit is contained in:
Phoenix
2025-08-07 16:53:10 +08:00
parent 3b3bdf3d46
commit 3f64ace8a7
11 changed files with 1062 additions and 837 deletions
+527 -87
View File
@@ -25,6 +25,10 @@ namespace ResourceMonitorService.Services
private readonly Dictionary<string, PerformanceCounter> _counters;
private readonly Dictionary<string, long> _previousNetworkBytes;
private readonly Dictionary<string, DateTime> _previousNetworkTime;
private readonly Dictionary<string, long> _previousDiskBytes;
private readonly Dictionary<string, DateTime> _previousDiskTime;
private readonly Dictionary<string, int> _errorCounts;
private readonly Dictionary<int, (TimeSpan ProcessorTime, DateTime Timestamp)> _previousProcessorTimes;
public ResourceMonitorService(ILogger<ResourceMonitorService> logger, IOptions<MonitoringSettings> settings)
{
@@ -33,6 +37,10 @@ namespace ResourceMonitorService.Services
_counters = new Dictionary<string, PerformanceCounter>();
_previousNetworkBytes = new Dictionary<string, long>();
_previousNetworkTime = new Dictionary<string, DateTime>();
_previousDiskBytes = new Dictionary<string, long>();
_previousDiskTime = new Dictionary<string, DateTime>();
_errorCounts = new Dictionary<string, int>();
_previousProcessorTimes = new Dictionary<int, (TimeSpan ProcessorTime, DateTime Timestamp)>();
InitializeCounters();
}
@@ -59,6 +67,7 @@ namespace ResourceMonitorService.Services
#pragma warning restore CA1416 // Validate platform compatibility
// Initialize counters with first reading
#pragma warning disable CA1416 // Validate platform compatibility
foreach (var counter in _counters.Values)
{
try
@@ -67,9 +76,10 @@ namespace ResourceMonitorService.Services
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to initialize performance counter: {CounterName}", counter.CounterName);
LogSuppressedWarning("counter_init", ex, $"Failed to initialize performance counter: {counter.CounterName}");
}
}
#pragma warning restore CA1416 // Validate platform compatibility
}
catch (Exception ex)
{
@@ -77,6 +87,22 @@ namespace ResourceMonitorService.Services
}
}
private void LogSuppressedWarning(string errorKey, Exception ex, string message)
{
if (!_errorCounts.ContainsKey(errorKey))
{
_errorCounts[errorKey] = 0;
}
_errorCounts[errorKey]++;
// Only log every 10th occurrence and the first 3 occurrences
if (_errorCounts[errorKey] <= 3 || _errorCounts[errorKey] % 10 == 0)
{
_logger.LogDebug(ex, "{Message} (occurrence #{Count})", message, _errorCounts[errorKey]);
}
}
public async Task<ResourceUsage> GetResourceUsageAsync()
{
var timestamp = DateTime.Now;
@@ -274,27 +300,46 @@ namespace ResourceMonitorService.Services
uint temperature;
NvmlWrapper.NvmlDeviceGetTemperature(device, 0, out temperature);
uint fanSpeed;
NvmlWrapper.NvmlDeviceGetFanSpeed(device, out fanSpeed);
uint fanSpeed = 0;
var fanResult = NvmlWrapper.NvmlDeviceGetFanSpeed(device, out fanSpeed);
if (fanResult != 0)
{
fanSpeed = 0; // Reset to 0 if call failed
LogSuppressedWarning("gpu_fan", new Exception($"NVML fan speed call failed with code: {fanResult}"), "Could not get GPU fan speed");
}
// Try to get additional information
uint powerUsage = 0;
var powerResult = NvmlWrapper.NvmlDeviceGetPowerUsage(device, out powerUsage);
if (powerResult != 0) powerUsage = 0; // Reset to 0 if call failed, power is in milliwatts
// Get memory information
NvmlWrapper.NvmlMemory memory;
var memoryResult = NvmlWrapper.NvmlDeviceGetMemoryInfo(device, out memory);
var memoryTotal = memoryResult == 0 ? memory.Total : 0UL;
var memoryUsed = memoryResult == 0 ? memory.Used : 0UL;
// Get GPU name via NVML
var name = "NVIDIA GPU";
var nameBuffer = new byte[256];
var nameResult = NvmlWrapper.NvmlDeviceGetName(device, nameBuffer, 256);
if (nameResult == 0)
{
name = System.Text.Encoding.ASCII.GetString(nameBuffer).TrimEnd('\0');
}
var driverVersion = "Unknown";
var memoryTotal = 0UL;
var memoryUsed = 0UL;
var powerUsage = 0U;
try
{
// Get GPU name and memory info via WMI as fallback
// Get driver version via WMI as fallback
#pragma warning disable CA1416 // Validate platform compatibility
using var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_VideoController WHERE Name LIKE '%NVIDIA%'");
using var collection = searcher.Get();
foreach (ManagementObject obj in collection)
{
name = obj["Name"]?.ToString() ?? name;
driverVersion = obj["DriverVersion"]?.ToString() ?? driverVersion;
if (obj["AdapterRAM"] != null)
// If NVML memory call failed, try WMI as fallback
if (memoryTotal == 0 && obj["AdapterRAM"] != null)
{
memoryTotal = (ulong)obj["AdapterRAM"];
}
@@ -304,7 +349,7 @@ namespace ResourceMonitorService.Services
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not get additional GPU information via WMI");
LogSuppressedWarning("gpu_wmi", ex, "Could not get additional GPU information via WMI");
}
NvmlWrapper.NvmlShutdown();
@@ -315,7 +360,7 @@ namespace ResourceMonitorService.Services
MemoryUsage = utilization.Memory,
Temperature = temperature,
FanSpeed = fanSpeed,
PowerUsage = powerUsage,
PowerUsage = powerUsage, // Power in milliwatts
MemoryTotal = memoryTotal,
MemoryUsed = memoryUsed,
IsAvailable = true,
@@ -357,6 +402,7 @@ namespace ResourceMonitorService.Services
return await Task.Run(() =>
{
var diskUsages = new List<DiskUsage>();
var timestamp = DateTime.Now;
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
@@ -374,50 +420,14 @@ namespace ResourceMonitorService.Services
UsagePercentage = (float)(drive.TotalSize - drive.AvailableFreeSpace) / drive.TotalSize * 100
};
// Get disk performance data
try
{
var diskName = drive.Name.Replace("\\", "").Replace(":", "");
#pragma warning disable CA1416 // Validate platform compatibility
using var readCounter = new PerformanceCounter("LogicalDisk", "Disk Read Bytes/sec", diskName);
using var writeCounter = new PerformanceCounter("LogicalDisk", "Disk Write Bytes/sec", diskName);
using var timeCounter = new PerformanceCounter("LogicalDisk", "% Disk Time", diskName);
// Get disk performance data with proper timing
GetDiskPerformanceData(drive, diskUsage, timestamp);
readCounter.NextValue();
writeCounter.NextValue();
timeCounter.NextValue();
Thread.Sleep(1000);
// Get disk temperature using SMART data
GetDiskTemperature(drive, diskUsage);
diskUsage.ReadSpeed = readCounter.NextValue() / (1024 * 1024); // Convert to MB/s
diskUsage.WriteSpeed = writeCounter.NextValue() / (1024 * 1024); // Convert to MB/s
diskUsage.DiskTime = timeCounter.NextValue();
#pragma warning restore CA1416 // Validate platform compatibility
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not get disk performance data for drive {Drive}", drive.Name);
}
// Try to determine if it's an SSD
try
{
#pragma warning disable CA1416 // Validate platform compatibility
using var searcher = new ManagementObjectSearcher($"SELECT * FROM Win32_LogicalDisk WHERE DeviceID='{drive.Name.TrimEnd('\\')}'");
using var collection = searcher.Get();
foreach (ManagementObject obj in collection)
{
// This is a simplified check; more sophisticated detection would be needed
var mediaType = obj["MediaType"]?.ToString();
diskUsage.IsSSD = mediaType?.Contains("SSD") == true || mediaType?.Contains("Solid") == true;
break;
}
#pragma warning restore CA1416 // Validate platform compatibility
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not determine disk type for drive {Drive}", drive.Name);
}
// Get additional disk information
GetDiskInfo(drive, diskUsage);
diskUsages.Add(diskUsage);
}
@@ -433,6 +443,217 @@ namespace ResourceMonitorService.Services
}
}
private void GetDiskPerformanceData(DriveInfo drive, DiskUsage diskUsage, DateTime timestamp)
{
try
{
var diskName = drive.Name.Replace("\\", "").Replace(":", "");
var diskKey = $"disk_{diskName}";
#pragma warning disable CA1416 // Validate platform compatibility
// Try different counter instance names that Windows might use
var possibleNames = new[] { diskName, $"{diskName}:", drive.Name.TrimEnd('\\'), "_Total" };
foreach (var name in possibleNames)
{
try
{
using var readCounter = new PerformanceCounter("LogicalDisk", "Disk Read Bytes/sec", name);
using var writeCounter = new PerformanceCounter("LogicalDisk", "Disk Write Bytes/sec", name);
using var timeCounter = new PerformanceCounter("LogicalDisk", "% Disk Time", name);
using var readOpsCounter = new PerformanceCounter("LogicalDisk", "Disk Reads/sec", name);
using var writeOpsCounter = new PerformanceCounter("LogicalDisk", "Disk Writes/sec", name);
var readBytes = (long)readCounter.NextValue();
var writeBytes = (long)writeCounter.NextValue();
var readOps = (long)readOpsCounter.NextValue();
var writeOps = (long)writeOpsCounter.NextValue();
// Calculate speeds if we have previous data
var readKey = $"{diskKey}_read";
var writeKey = $"{diskKey}_write";
var readOpsKey = $"{diskKey}_read_ops";
var writeOpsKey = $"{diskKey}_write_ops";
if (_previousDiskBytes.ContainsKey(readKey) && _previousDiskTime.ContainsKey(readKey))
{
var timeDiff = (timestamp - _previousDiskTime[readKey]).TotalSeconds;
if (timeDiff > 0)
{
var readBytesDiff = readBytes - _previousDiskBytes[readKey];
var writeBytesDiff = writeBytes - _previousDiskBytes[writeKey];
var readOpsDiff = readOps - _previousDiskBytes[readOpsKey];
var writeOpsDiff = writeOps - _previousDiskBytes[writeOpsKey];
diskUsage.ReadSpeed = (float)(readBytesDiff / timeDiff / (1024 * 1024)); // MB/s
diskUsage.WriteSpeed = (float)(writeBytesDiff / timeDiff / (1024 * 1024)); // MB/s
diskUsage.ReadOperations = (long)(readOpsDiff / timeDiff);
diskUsage.WriteOperations = (long)(writeOpsDiff / timeDiff);
}
}
// Store current values for next calculation
_previousDiskBytes[readKey] = readBytes;
_previousDiskBytes[writeKey] = writeBytes;
_previousDiskBytes[readOpsKey] = readOps;
_previousDiskBytes[writeOpsKey] = writeOps;
_previousDiskTime[readKey] = timestamp;
_previousDiskTime[writeKey] = timestamp;
// Get current disk time percentage
diskUsage.DiskTime = timeCounter.NextValue();
break; // Successfully got data, exit the loop
}
catch (Exception ex)
{
LogSuppressedWarning($"disk_perf_{name}", ex, $"Could not get disk performance data for instance {name}");
}
}
#pragma warning restore CA1416 // Validate platform compatibility
}
catch (Exception ex)
{
LogSuppressedWarning($"disk_perf_{drive.Name}", ex, $"Could not get disk performance data for drive {drive.Name}");
}
}
private void GetDiskTemperature(DriveInfo drive, DiskUsage diskUsage)
{
try
{
#pragma warning disable CA1416 // Validate platform compatibility
// Try to get SMART data for temperature
var physicalDriveQuery = $@"\\.\{drive.Name.Replace("\\", "").Replace(":", "")}";
// Method 1: Try WMI Win32_DiskDrive
using var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive");
using var collection = searcher.Get();
foreach (ManagementObject disk in collection)
{
var model = disk["Model"]?.ToString() ?? "";
var serialNumber = disk["SerialNumber"]?.ToString() ?? "";
// Try to get SMART data using MSStorageDriver_ATAPISmartData
try
{
using var smartSearcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM MSStorageDriver_ATAPISmartData");
using var smartCollection = smartSearcher.Get();
foreach (ManagementObject smartData in smartCollection)
{
var vendorSpecific = smartData["VendorSpecific"] as byte[];
if (vendorSpecific != null && vendorSpecific.Length >= 362)
{
// SMART attribute 194 (0xC2) is typically temperature
// This is a simplified extraction - real implementation would need proper SMART parsing
for (int i = 2; i < 362; i += 12)
{
if (i + 11 < vendorSpecific.Length && vendorSpecific[i] == 194) // Temperature attribute
{
diskUsage.Temperature = vendorSpecific[i + 5]; // Raw value is typically temperature
break;
}
}
}
}
}
catch (Exception ex)
{
LogSuppressedWarning($"smart_temp_{drive.Name}", ex, $"Could not get SMART temperature for {drive.Name}");
}
}
// Method 2: Try thermal zone if SMART fails
if (diskUsage.Temperature == 0)
{
using var thermalSearcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM MSStorageDriver_FailurePredictStatus");
using var thermalCollection = thermalSearcher.Get();
foreach (ManagementObject obj in thermalCollection)
{
// This is a fallback method - may not provide temperature but can indicate drive health
var active = obj["Active"]?.ToString() ?? "";
var reason = obj["Reason"]?.ToString() ?? "";
if (active == "True")
{
// Drive is reporting potential failure - this doesn't give us temperature but is useful
LogSuppressedWarning($"disk_health_{drive.Name}", new Exception($"Drive health warning: {reason}"), $"Drive {drive.Name} health warning");
}
}
}
#pragma warning restore CA1416 // Validate platform compatibility
}
catch (Exception ex)
{
LogSuppressedWarning($"disk_temp_{drive.Name}", ex, $"Could not get disk temperature for drive {drive.Name}");
}
}
private void GetDiskInfo(DriveInfo drive, DiskUsage diskUsage)
{
try
{
#pragma warning disable CA1416 // Validate platform compatibility
// Get more detailed disk information
using var searcher = new ManagementObjectSearcher($"SELECT * FROM Win32_LogicalDisk WHERE DeviceID='{drive.Name.TrimEnd('\\')}'");
using var collection = searcher.Get();
foreach (ManagementObject obj in collection)
{
// Try to determine if it's an SSD by checking the physical disk
var deviceId = obj["DeviceID"]?.ToString();
if (!string.IsNullOrEmpty(deviceId))
{
// Get the associated physical disk
using var partitionSearcher = new ManagementObjectSearcher($"ASSOCIATORS OF {{Win32_LogicalDisk.DeviceID='{deviceId}'}} WHERE AssocClass=Win32_LogicalDiskToPartition");
using var partitionCollection = partitionSearcher.Get();
foreach (ManagementObject partition in partitionCollection)
{
using var diskSearcher = new ManagementObjectSearcher($"ASSOCIATORS OF {{Win32_DiskPartition.DeviceID='{partition["DeviceID"]}'}} WHERE AssocClass=Win32_DiskDriveToDiskPartition");
using var diskCollection = diskSearcher.Get();
foreach (ManagementObject physicalDisk in diskCollection)
{
var mediaType = physicalDisk["MediaType"]?.ToString() ?? "";
var model = physicalDisk["Model"]?.ToString() ?? "";
// More sophisticated SSD detection
diskUsage.IsSSD = mediaType.Contains("SSD") ||
model.ToLower().Contains("ssd") ||
model.ToLower().Contains("solid") ||
model.ToLower().Contains("nvme") ||
CheckIfSSDByRotationRate(physicalDisk);
break;
}
break;
}
}
break;
}
#pragma warning restore CA1416 // Validate platform compatibility
}
catch (Exception ex)
{
LogSuppressedWarning($"disk_info_{drive.Name}", ex, $"Could not get detailed disk information for drive {drive.Name}");
}
}
private bool CheckIfSSDByRotationRate(ManagementObject physicalDisk)
{
try
{
// Try to get rotation rate - SSDs typically report 0 or 1
var nominalMediaRotationRate = physicalDisk["NominalMediaRotationRate"];
if (nominalMediaRotationRate != null)
{
var rotationRate = Convert.ToUInt32(nominalMediaRotationRate);
return rotationRate == 0 || rotationRate == 1; // SSD indicators
}
}
catch
{
// Ignore errors in rotation rate detection
}
return false;
}
public async Task<NetworkUsage> GetNetworkUsageAsync()
{
try
@@ -524,35 +745,89 @@ namespace ResourceMonitorService.Services
{
return await Task.Run(() =>
{
var processes = Process.GetProcesses()
.Where(p => !p.HasExited)
.Select(p =>
var allProcesses = Process.GetProcesses();
_logger.LogDebug("Found {ProcessCount} total processes", allProcesses.Length);
var validProcesses = new List<ProcessInfo>();
int skippedCount = 0;
var currentTime = DateTime.Now;
// Get total system memory for percentage calculations
var totalSystemMemory = GetTotalSystemMemory();
foreach (var p in allProcesses)
{
try
{
if (p.HasExited)
{
skippedCount++;
continue;
}
var memoryUsage = (ulong)p.WorkingSet64;
var cpuUsage = CalculateProcessCpuUsage(p.Id, p.TotalProcessorTime, currentTime);
var processInfo = new ProcessInfo
{
Id = p.Id,
Name = p.ProcessName,
MemoryUsage = memoryUsage,
MemoryUsagePercentage = totalSystemMemory > 0 ? (float)(memoryUsage * 100.0 / totalSystemMemory) : 0f,
ProcessorTime = p.TotalProcessorTime,
CpuUsage = cpuUsage
};
// Try to get additional info, but don't fail if we can't
try
{
return new ProcessInfo
{
Id = p.Id,
Name = p.ProcessName,
MemoryUsage = (ulong)p.WorkingSet64,
ProcessorTime = p.TotalProcessorTime,
StartTime = p.StartTime,
ExecutablePath = p.MainModule?.FileName ?? "",
CommandLine = GetProcessCommandLine(p.Id)
};
processInfo.StartTime = p.StartTime;
}
catch
{
return null; // Skip processes that throw exceptions
processInfo.StartTime = DateTime.MinValue;
}
})
.Where(p => p != null)
.OrderByDescending(p => p!.MemoryUsage)
try
{
processInfo.ExecutablePath = p.MainModule?.FileName ?? "";
}
catch
{
processInfo.ExecutablePath = "";
}
try
{
processInfo.CommandLine = GetProcessCommandLine(p.Id);
}
catch
{
processInfo.CommandLine = "";
}
validProcesses.Add(processInfo);
}
catch (Exception ex)
{
skippedCount++;
_logger.LogTrace(ex, "Skipped process {ProcessId} due to access error", p.Id);
}
}
_logger.LogDebug("Processed {ValidCount} valid processes, skipped {SkippedCount}", validProcesses.Count, skippedCount);
// Clean up old process entries to prevent memory leaks
CleanupOldProcessEntries(validProcesses.Select(p => p.Id).ToHashSet());
var topProcesses = validProcesses
.OrderByDescending(p => p.CpuUsage)
.ThenByDescending(p => p.MemoryUsage)
.Take(count)
.Cast<ProcessInfo>()
.ToList();
return processes;
_logger.LogDebug("Returning {TopCount} top processes", topProcesses.Count);
return topProcesses;
});
}
catch (Exception ex)
@@ -583,7 +858,7 @@ namespace ResourceMonitorService.Services
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not get CPU temperature");
LogSuppressedWarning("cpu_temperature", ex, "Could not get CPU temperature");
return 0f;
}
}
@@ -600,28 +875,95 @@ namespace ResourceMonitorService.Services
HardDrives = new List<HardDriveTemp>()
};
// Try to get hard drive temperatures
// Get hard drive temperatures using improved method
try
{
#pragma warning disable CA1416 // Validate platform compatibility
using var searcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM MSStorageDriver_ATAPISmartData");
using var collection = searcher.Get();
foreach (ManagementObject obj in collection)
var drives = DriveInfo.GetDrives();
foreach (var drive in drives.Where(d => d.IsReady && d.DriveType == DriveType.Fixed))
{
var instanceName = obj["InstanceName"]?.ToString() ?? "";
// This would need more sophisticated parsing for actual SMART data
temperatureInfo.HardDrives.Add(new HardDriveTemp
var hardDriveTemp = new HardDriveTemp
{
Drive = instanceName,
Temperature = 0f, // Would need SMART data parsing
Drive = drive.Name,
Temperature = 0f,
Health = "Unknown"
});
}
};
// Use the same SMART data access as in GetDiskTemperature
try
{
#pragma warning disable CA1416 // Validate platform compatibility
using var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive");
using var collection = searcher.Get();
foreach (ManagementObject disk in collection)
{
var model = disk["Model"]?.ToString() ?? "";
// Try to get SMART data using MSStorageDriver_ATAPISmartData
try
{
using var smartSearcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM MSStorageDriver_ATAPISmartData");
using var smartCollection = smartSearcher.Get();
foreach (ManagementObject smartData in smartCollection)
{
var instanceName = smartData["InstanceName"]?.ToString() ?? "";
if (instanceName.Contains(drive.Name.Replace("\\", "").Replace(":", "")))
{
var vendorSpecific = smartData["VendorSpecific"] as byte[];
if (vendorSpecific != null && vendorSpecific.Length >= 362)
{
// Parse SMART attributes for temperature (attribute 194)
for (int i = 2; i < 362; i += 12)
{
if (i + 11 < vendorSpecific.Length && vendorSpecific[i] == 194)
{
hardDriveTemp.Temperature = vendorSpecific[i + 5];
break;
}
}
// Parse SMART attributes for health indicators
bool hasWarnings = false;
for (int i = 2; i < 362; i += 12)
{
if (i + 11 < vendorSpecific.Length)
{
var attributeId = vendorSpecific[i];
var threshold = vendorSpecific[i + 2];
var value = vendorSpecific[i + 3];
// Check critical attributes
if ((attributeId == 5 || attributeId == 196 || attributeId == 197 || attributeId == 198) && value <= threshold)
{
hasWarnings = true;
break;
}
}
}
hardDriveTemp.Health = hasWarnings ? "Warning" : "Good";
}
break;
}
}
}
catch (Exception ex)
{
LogSuppressedWarning($"smart_temp_info_{drive.Name}", ex, $"Could not get SMART temperature info for {drive.Name}");
}
}
#pragma warning restore CA1416 // Validate platform compatibility
}
catch (Exception ex)
{
LogSuppressedWarning($"hdd_temp_info_{drive.Name}", ex, $"Could not get temperature info for drive {drive.Name}");
}
temperatureInfo.HardDrives.Add(hardDriveTemp);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not get hard drive temperatures");
LogSuppressedWarning("hdd_temperature", ex, "Could not get hard drive temperatures");
}
return temperatureInfo;
@@ -654,6 +996,104 @@ namespace ResourceMonitorService.Services
}
}
private float CalculateProcessCpuUsage(int processId, TimeSpan currentProcessorTime, DateTime currentTime)
{
try
{
// Check if we have previous data for this process
if (_previousProcessorTimes.TryGetValue(processId, out var previousData))
{
var timeDifference = currentTime - previousData.Timestamp;
var processorTimeDifference = currentProcessorTime - previousData.ProcessorTime;
// Avoid division by zero and ensure meaningful time has passed
if (timeDifference.TotalMilliseconds > 100 && processorTimeDifference.TotalMilliseconds >= 0)
{
// Calculate CPU usage percentage
// ProcessorTime is the total time the process has used the CPU
// We need to calculate how much of the elapsed time was spent using CPU
var cpuUsagePercent = (processorTimeDifference.TotalMilliseconds / timeDifference.TotalMilliseconds) * 100.0;
// Account for multiple cores - divide by number of cores to get a percentage relative to total system
cpuUsagePercent = cpuUsagePercent / Environment.ProcessorCount;
// Store current data for next calculation
_previousProcessorTimes[processId] = (currentProcessorTime, currentTime);
return Math.Min((float)cpuUsagePercent, 100.0f); // Cap at 100%
}
}
// Store current data for next calculation (first time seeing this process or insufficient time passed)
_previousProcessorTimes[processId] = (currentProcessorTime, currentTime);
return 0f; // Can't calculate on first measurement
}
catch (Exception ex)
{
LogSuppressedWarning($"cpu_calc_{processId}", ex, $"Error calculating CPU usage for process {processId}");
return 0f;
}
}
private void CleanupOldProcessEntries(HashSet<int> currentProcessIds)
{
try
{
// Remove entries for processes that no longer exist
var keysToRemove = _previousProcessorTimes.Keys
.Where(pid => !currentProcessIds.Contains(pid))
.ToList();
foreach (var key in keysToRemove)
{
_previousProcessorTimes.Remove(key);
}
// Also remove very old entries (older than 5 minutes) to prevent indefinite growth
var cutoffTime = DateTime.Now.AddMinutes(-5);
var oldKeys = _previousProcessorTimes
.Where(kvp => kvp.Value.Timestamp < cutoffTime)
.Select(kvp => kvp.Key)
.ToList();
foreach (var key in oldKeys)
{
_previousProcessorTimes.Remove(key);
}
if (keysToRemove.Count > 0 || oldKeys.Count > 0)
{
_logger.LogTrace("Cleaned up {RemovedCount} old process entries, {OldCount} expired entries",
keysToRemove.Count, oldKeys.Count);
}
}
catch (Exception ex)
{
LogSuppressedWarning("cleanup_process", ex, "Error cleaning up old process entries");
}
}
private ulong GetTotalSystemMemory()
{
try
{
#pragma warning disable CA1416 // Validate platform compatibility
using var searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem");
using var collection = searcher.Get();
foreach (ManagementObject obj in collection)
{
return (ulong)obj["TotalPhysicalMemory"];
}
#pragma warning restore CA1416 // Validate platform compatibility
return 0UL;
}
catch (Exception ex)
{
LogSuppressedWarning("total_memory", ex, "Could not get total system memory");
return 0UL;
}
}
public void Dispose()
{
#pragma warning disable CA1416 // Validate platform compatibility