using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ResourceMonitorService.Configuration; using ResourceMonitorService.Models; using System.Diagnostics; using System.Management; namespace ResourceMonitorService.Services { public interface IResourceMonitorService { Task GetResourceUsageAsync(); Task GetCpuUsageAsync(); Task GetMemoryUsageAsync(); Task GetGpuUsageAsync(); Task> GetDiskUsageAsync(); Task GetNetworkUsageAsync(); Task> GetTopProcessesAsync(int count = 10); } public class ResourceMonitorService : IResourceMonitorService { private readonly ILogger _logger; private readonly MonitoringSettings _settings; private readonly Dictionary _counters; private readonly Dictionary _previousNetworkBytes; private readonly Dictionary _previousNetworkTime; private readonly Dictionary _previousDiskBytes; private readonly Dictionary _previousDiskTime; private readonly Dictionary _errorCounts; private readonly Dictionary _previousProcessorTimes; public ResourceMonitorService(ILogger logger, IOptions settings) { _logger = logger; _settings = settings.Value; _counters = new Dictionary(); _previousNetworkBytes = new Dictionary(); _previousNetworkTime = new Dictionary(); _previousDiskBytes = new Dictionary(); _previousDiskTime = new Dictionary(); _errorCounts = new Dictionary(); _previousProcessorTimes = new Dictionary(); InitializeCounters(); } private void InitializeCounters() { try { #pragma warning disable CA1416 // Validate platform compatibility _counters["cpu"] = new PerformanceCounter("Processor", "% Processor Time", "_Total"); _counters["memory_available"] = new PerformanceCounter("Memory", "Available MBytes"); if (_settings.EnableNetworkMonitoring) { _counters["network_bytes_sent"] = new PerformanceCounter("Network Interface", "Bytes Sent/sec", "*"); _counters["network_bytes_received"] = new PerformanceCounter("Network Interface", "Bytes Received/sec", "*"); } if (_settings.EnableDiskMonitoring) { _counters["disk_read"] = new PerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", "_Total"); _counters["disk_write"] = new PerformanceCounter("PhysicalDisk", "Disk Write Bytes/sec", "_Total"); _counters["disk_time"] = new PerformanceCounter("PhysicalDisk", "% Disk Time", "_Total"); } #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 { counter.NextValue(); } catch (Exception ex) { LogSuppressedWarning("counter_init", ex, $"Failed to initialize performance counter: {counter.CounterName}"); } } #pragma warning restore CA1416 // Validate platform compatibility } catch (Exception ex) { _logger.LogError(ex, "Failed to initialize performance counters"); } } 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 GetResourceUsageAsync() { var timestamp = DateTime.Now; var tasks = new List { Task.Run(async () => await GetCpuUsageAsync()), Task.Run(async () => await GetMemoryUsageAsync()) }; if (_settings.EnableGpuMonitoring) tasks.Add(Task.Run(async () => await GetGpuUsageAsync())); if (_settings.EnableDiskMonitoring) tasks.Add(Task.Run(async () => await GetDiskUsageAsync())); if (_settings.EnableNetworkMonitoring) tasks.Add(Task.Run(async () => await GetNetworkUsageAsync())); if (_settings.EnableProcessMonitoring) tasks.Add(Task.Run(async () => await GetTopProcessesAsync(_settings.MaxProcessesToTrack))); await Task.WhenAll(tasks); return new ResourceUsage { Timestamp = timestamp, CPU = await GetCpuUsageAsync(), Memory = await GetMemoryUsageAsync(), GPU = _settings.EnableGpuMonitoring ? await GetGpuUsageAsync() : new GpuUsage(), Disks = _settings.EnableDiskMonitoring ? await GetDiskUsageAsync() : new List(), Network = _settings.EnableNetworkMonitoring ? await GetNetworkUsageAsync() : new NetworkUsage(), TopProcesses = _settings.EnableProcessMonitoring ? await GetTopProcessesAsync(_settings.MaxProcessesToTrack) : new List(), Temperature = _settings.EnableTemperatureMonitoring ? await GetTemperatureInfoAsync() : new TemperatureInfo() }; } public async Task GetCpuUsageAsync() { try { var temperature = await GetCpuTemperatureAsync(); return await Task.Run(() => { #pragma warning disable CA1416 // Validate platform compatibility var usage = _counters.TryGetValue("cpu", out var cpuCounter) ? cpuCounter.NextValue() : 0f; // Get per-core usage var coreUsages = new List(); for (int i = 0; i < Environment.ProcessorCount; i++) { try { using var coreCounter = new PerformanceCounter("Processor", "% Processor Time", i.ToString()); coreCounter.NextValue(); Thread.Sleep(100); // Small delay for accurate reading coreUsages.Add(coreCounter.NextValue()); } catch { coreUsages.Add(0f); } } // Get CPU frequency var maxFrequency = 0f; var currentFrequency = 0f; try { using var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Processor"); using var collection = searcher.Get(); foreach (ManagementObject obj in collection) { maxFrequency = Convert.ToSingle(obj["MaxClockSpeed"]); currentFrequency = Convert.ToSingle(obj["CurrentClockSpeed"]); break; } } catch (Exception ex) { _logger.LogWarning(ex, "Could not get CPU frequency information"); } #pragma warning restore CA1416 // Validate platform compatibility return new CpuUsage { Usage = usage, CoreUsages = coreUsages.ToArray(), MaxFrequency = maxFrequency, CurrentFrequency = currentFrequency, IsThrottling = currentFrequency < maxFrequency * 0.9f, // Consider throttling if running below 90% max frequency Temperature = temperature }; }); } catch (Exception ex) { _logger.LogError(ex, "Error getting CPU usage"); return new CpuUsage(); } } public async Task GetMemoryUsageAsync() { try { return await Task.Run(() => { #pragma warning disable CA1416 // Validate platform compatibility var availableMemoryMB = _counters.TryGetValue("memory_available", out var memCounter) ? memCounter.NextValue() : 0f; var availableMemory = (ulong)(availableMemoryMB * 1024 * 1024); // Get total memory var totalMemory = 0UL; using (var searcher = new ManagementObjectSearcher("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem")) using (var collection = searcher.Get()) { foreach (ManagementObject obj in collection) { totalMemory = (ulong)obj["TotalPhysicalMemory"]; break; } } var usedMemory = totalMemory - availableMemory; var usagePercentage = totalMemory > 0 ? (float)(usedMemory) / totalMemory * 100 : 0f; // Get additional memory info var committedMemory = 0UL; var pagedMemory = 0UL; var nonPagedMemory = 0UL; try { using var osSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem"); using var osCollection = osSearcher.Get(); foreach (ManagementObject obj in osCollection) { var totalVirtualMemory = (ulong)obj["TotalVirtualMemorySize"] * 1024; var freeVirtualMemory = (ulong)obj["FreeVirtualMemory"] * 1024; committedMemory = totalVirtualMemory - freeVirtualMemory; break; } using var perfSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_PerfRawData_PerfOS_Memory"); using var perfCollection = perfSearcher.Get(); foreach (ManagementObject obj in perfCollection) { pagedMemory = (ulong)obj["PoolPagedBytes"]; nonPagedMemory = (ulong)obj["PoolNonpagedBytes"]; break; } } catch (Exception ex) { _logger.LogWarning(ex, "Could not get extended memory information"); } #pragma warning restore CA1416 // Validate platform compatibility return new MemoryUsage { UsagePercentage = usagePercentage, UsedMemory = usedMemory, AvailableMemory = availableMemory, TotalMemory = totalMemory, CommittedMemory = committedMemory, PagedMemory = pagedMemory, NonPagedMemory = nonPagedMemory }; }); } catch (Exception ex) { _logger.LogError(ex, "Error getting memory usage"); return new MemoryUsage(); } } public async Task GetGpuUsageAsync() { try { return await Task.Run(() => { try { NvmlWrapper.NvmlInit(); IntPtr device; NvmlWrapper.NvmlDeviceGetHandleByIndex(0, out device); NvmlWrapper.NvmlUtilization utilization; NvmlWrapper.NvmlDeviceGetUtilizationRates(device, out utilization); uint temperature; NvmlWrapper.NvmlDeviceGetTemperature(device, 0, out temperature); 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"); } 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"; try { // 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) { driverVersion = obj["DriverVersion"]?.ToString() ?? driverVersion; // If NVML memory call failed, try WMI as fallback if (memoryTotal == 0 && obj["AdapterRAM"] != null) { memoryTotal = (ulong)obj["AdapterRAM"]; } break; } #pragma warning restore CA1416 // Validate platform compatibility } catch (Exception ex) { LogSuppressedWarning("gpu_wmi", ex, "Could not get additional GPU information via WMI"); } NvmlWrapper.NvmlShutdown(); return new GpuUsage { Usage = utilization.Gpu, MemoryUsage = utilization.Memory, Temperature = temperature, FanSpeed = fanSpeed, PowerUsage = powerUsage, // Power in milliwatts MemoryTotal = memoryTotal, MemoryUsed = memoryUsed, IsAvailable = true, Name = name, DriverVersion = driverVersion, Error = string.Empty }; } catch (Exception ex) { return new GpuUsage { Usage = 0, MemoryUsage = 0, Temperature = 0, FanSpeed = 0, PowerUsage = 0, MemoryTotal = 0, MemoryUsed = 0, IsAvailable = false, Name = "Unknown", DriverVersion = "Unknown", Error = ex.Message }; } }); } catch (Exception ex) { _logger.LogError(ex, "Error getting GPU usage"); return new GpuUsage { Error = ex.Message }; } } public async Task> GetDiskUsageAsync() { try { return await Task.Run(() => { var diskUsages = new List(); var timestamp = DateTime.Now; var drives = DriveInfo.GetDrives(); foreach (var drive in drives) { if (drive.IsReady) { var diskUsage = new DiskUsage { DriveLetter = drive.Name, Label = drive.VolumeLabel, FileSystem = drive.DriveFormat, TotalSize = (ulong)drive.TotalSize, FreeSpace = (ulong)drive.AvailableFreeSpace, UsedSpace = (ulong)(drive.TotalSize - drive.AvailableFreeSpace), UsagePercentage = (float)(drive.TotalSize - drive.AvailableFreeSpace) / drive.TotalSize * 100 }; // Get disk performance data with proper timing GetDiskPerformanceData(drive, diskUsage, timestamp); // Get disk temperature using SMART data GetDiskTemperature(drive, diskUsage); // Get additional disk information GetDiskInfo(drive, diskUsage); diskUsages.Add(diskUsage); } } return diskUsages; }); } catch (Exception ex) { _logger.LogError(ex, "Error getting disk usage"); return new List(); } } 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 GetNetworkUsageAsync() { try { return await Task.Run(() => { var networkUsage = new NetworkUsage(); var adapters = new List(); try { #pragma warning disable CA1416 // Validate platform compatibility using var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PerfRawData_Tcpip_NetworkInterface WHERE Name != 'Loopback'"); using var collection = searcher.Get(); foreach (ManagementObject obj in collection) { var name = obj["Name"]?.ToString() ?? ""; if (name.Contains("Loopback") || name.Contains("Isatap") || name.Contains("Teredo")) continue; var bytesSent = Convert.ToInt64(obj["BytesSentPerSec"] ?? 0); var bytesReceived = Convert.ToInt64(obj["BytesReceivedPerSec"] ?? 0); var timestamp = DateTime.Now; var adapter = new NetworkAdapter { Name = name, IsOperational = true }; // Calculate speeds if we have previous data var key = $"{name}_sent"; if (_previousNetworkBytes.ContainsKey(key) && _previousNetworkTime.ContainsKey(key)) { var timeDiff = (timestamp - _previousNetworkTime[key]).TotalSeconds; if (timeDiff > 0) { var bytesDiff = bytesSent - _previousNetworkBytes[key]; adapter.UploadSpeed = (float)(bytesDiff / timeDiff / (1024 * 1024)); // MB/s networkUsage.UploadSpeed += adapter.UploadSpeed; } } key = $"{name}_received"; if (_previousNetworkBytes.ContainsKey(key) && _previousNetworkTime.ContainsKey(key)) { var timeDiff = (timestamp - _previousNetworkTime[key]).TotalSeconds; if (timeDiff > 0) { var bytesDiff = bytesReceived - _previousNetworkBytes[key]; adapter.DownloadSpeed = (float)(bytesDiff / timeDiff / (1024 * 1024)); // MB/s networkUsage.DownloadSpeed += adapter.DownloadSpeed; } } // Store current values for next calculation _previousNetworkBytes[$"{name}_sent"] = bytesSent; _previousNetworkBytes[$"{name}_received"] = bytesReceived; _previousNetworkTime[$"{name}_sent"] = timestamp; _previousNetworkTime[$"{name}_received"] = timestamp; networkUsage.BytesSent += (ulong)bytesSent; networkUsage.BytesReceived += (ulong)bytesReceived; adapters.Add(adapter); } #pragma warning restore CA1416 // Validate platform compatibility } catch (Exception ex) { _logger.LogWarning(ex, "Could not get network performance data"); } networkUsage.Adapters = adapters; return networkUsage; }); } catch (Exception ex) { _logger.LogError(ex, "Error getting network usage"); return new NetworkUsage(); } } public async Task> GetTopProcessesAsync(int count = 10) { try { return await Task.Run(() => { var allProcesses = Process.GetProcesses(); _logger.LogDebug("Found {ProcessCount} total processes", allProcesses.Length); var validProcesses = new List(); 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 { processInfo.StartTime = p.StartTime; } catch { processInfo.StartTime = DateTime.MinValue; } 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) .ToList(); _logger.LogDebug("Returning {TopCount} top processes", topProcesses.Count); return topProcesses; }); } catch (Exception ex) { _logger.LogError(ex, "Error getting top processes"); return new List(); } } private async Task GetCpuTemperatureAsync() { try { return await Task.Run(() => { // Try to get CPU temperature from WMI #pragma warning disable CA1416 // Validate platform compatibility using var searcher = new ManagementObjectSearcher(@"root\WMI", "SELECT * FROM MSAcpi_ThermalZoneTemperature"); using var collection = searcher.Get(); foreach (ManagementObject obj in collection) { var temp = Convert.ToDouble(obj["CurrentTemperature"]); return (float)((temp - 2732) / 10.0); // Convert from tenths of Kelvin to Celsius } #pragma warning restore CA1416 // Validate platform compatibility return 0f; }); } catch (Exception ex) { LogSuppressedWarning("cpu_temperature", ex, "Could not get CPU temperature"); return 0f; } } private async Task GetTemperatureInfoAsync() { try { return await Task.Run(() => { var temperatureInfo = new TemperatureInfo { CPU = GetCpuTemperatureAsync().Result, HardDrives = new List() }; // Get hard drive temperatures using improved method try { var drives = DriveInfo.GetDrives(); foreach (var drive in drives.Where(d => d.IsReady && d.DriveType == DriveType.Fixed)) { var hardDriveTemp = new HardDriveTemp { 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) { LogSuppressedWarning("hdd_temperature", ex, "Could not get hard drive temperatures"); } return temperatureInfo; }); } catch (Exception ex) { _logger.LogError(ex, "Error getting temperature information"); return new TemperatureInfo(); } } private string GetProcessCommandLine(int processId) { try { #pragma warning disable CA1416 // Validate platform compatibility using var searcher = new ManagementObjectSearcher($"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {processId}"); using var collection = searcher.Get(); foreach (ManagementObject obj in collection) { return obj["CommandLine"]?.ToString() ?? ""; } #pragma warning restore CA1416 // Validate platform compatibility return ""; } catch { return ""; } } 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 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 foreach (var counter in _counters.Values) { counter?.Dispose(); } _counters.Clear(); #pragma warning restore CA1416 // Validate platform compatibility } } }