More precise system utilization (#1562)

* More precise system utilization
* The current method usually indicates a load that is too low
* SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION idle time error in Windows11 22H2 is fixed
* consistent with Process Explorer, Hwinfo, SIV

* Add fix for old Windows 11 Versions 22H2 and 23H2
Source: https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/pull/1571
Additional error checks for load calculation

* Update CpuLoad.cs

---------

Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com>
This commit is contained in:
sebastian-dev
2025-02-04 16:45:48 +01:00
committed by GitHub
co-authored by PhyxionNL
parent 342c4f5f52
commit 24b5e04315
2 changed files with 79 additions and 29 deletions
+77 -27
View File
@@ -14,15 +14,16 @@ namespace LibreHardwareMonitor.Hardware.Cpu;
internal class CpuLoad
{
private readonly float[] _threadLoads;
private static readonly bool _queryIdleTimeSeparated = QueryIdleTimeSeparated();
private readonly double[] _threadLoads;
private double _totalLoad;
private long[] _idleTimes;
private float _totalLoad;
private long[] _totalTimes;
public CpuLoad(CpuId[][] cpuid)
{
_threadLoads = new float[cpuid.Sum(x => x.Length)];
_threadLoads = new double[cpuid.Sum(x => x.Length)];
_totalLoad = 0;
try
@@ -51,30 +52,62 @@ internal class CpuLoad
idle = null;
total = null;
//Query processor idle information
Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION[] idleInformation = new Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION[64];
int idleSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION));
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorIdleInformation, idleInformation, idleInformation.Length * idleSize, out int idleReturn) != 0)
return false;
//use the idle time routine only with a few specific windows 11 versions
if (_queryIdleTimeSeparated)
return GetWindowsTimesFromIdleTimes(out idle, out total);
//Query processor performance information
Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[] perfInformation = new Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[64];
int perfSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION));
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorPerformanceInformation,
perfInformation,
perfInformation.Length * perfSize,
out int perfReturn) != 0)
{
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorPerformanceInformation, perfInformation, perfInformation.Length * perfSize, out int perfReturn) != 0)
return false;
}
idle = new long[idleReturn / idleSize];
for (int i = 0; i < idle.Length; i++)
idle[i] = idleInformation[i].IdleTime;
idle = new long[perfReturn / perfSize];
total = new long[perfReturn / perfSize];
for (int i = 0; i < total.Length; i++)
{
idle[i] = perfInformation[i].IdleTime;
total[i] = perfInformation[i].KernelTime + perfInformation[i].UserTime;
}
return true;
}
private static bool GetWindowsTimesFromIdleTimes(out long[] idle, out long[] total)
{
idle = null;
total = null;
Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[] perfInformation = new Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[64];
int perfSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION));
Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION[] idleInformation = new Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION[64];
int idleSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION));
//Query processor performance and idle information
//these 2 methods must be called as directly as possible one after the other
//Query processor idle information
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorIdleInformation, idleInformation, idleInformation.Length * idleSize, out int idleReturn) != 0)
return false;
//Query processor performance information
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorPerformanceInformation, perfInformation, perfInformation.Length * perfSize, out int perfReturn) != 0)
return false;
int perfItemsCount = perfReturn / perfSize;
int idleItemsCount = idleReturn / idleSize;
if (perfItemsCount != idleItemsCount)
return false;
idle = new long[perfItemsCount];
total = new long[perfItemsCount];
for (int i = 0; i < perfItemsCount; i++)
{
idle[i] = idleInformation[i].IdleTime;
total[i] = perfInformation[i].KernelTime + perfInformation[i].UserTime;
}
return true;
}
@@ -132,12 +165,12 @@ internal class CpuLoad
return true;
}
public float GetTotalLoad()
public double GetTotalLoad()
{
return _totalLoad;
}
public float GetThreadLoad(int thread)
public double GetThreadLoad(int thread)
{
return _threadLoads[thread];
}
@@ -157,28 +190,45 @@ internal class CpuLoad
if (newIdleTimes == null)
return;
float total = 0;
double total = 0;
int count = 0;
for (int i = 0; i < _threadLoads.Length && i < _idleTimes.Length && i < newIdleTimes.Length; i++)
{
float idle = (newIdleTimes[i] - _idleTimes[i]) / (float)(newTotalTimes[i] - _totalTimes[i]);
_threadLoads[i] = 100f * (1.0f - Math.Min(idle, 1.0f));
double idle = (newIdleTimes[i] - _idleTimes[i]) / (double)(newTotalTimes[i] - _totalTimes[i]);
idle = idle < 0.0 ? 0.0 : idle;
idle = idle > 1.0 ? 1.0 : idle;
double load = 100.0 * (1.0 - Math.Min(idle, 1.0));
_threadLoads[i] = Math.Round(load, 2);
total += idle;
count++;
}
if (count > 0)
{
total = 1.0f - (total / count);
total = total < 0 ? 0 : total;
total = 1.0 - (total / count);
total = total < 0.0 ? 0.0 : total;
total = total > 1.0 ? 1.0 : total;
}
else
{
total = 0;
}
_totalLoad = total * 100;
_totalLoad = Math.Round(total * 100.0, 2);
_totalTimes = newTotalTimes;
_idleTimes = newIdleTimes;
}
private static bool QueryIdleTimeSeparated()
{
if (Software.OperatingSystem.IsUnix)
return false;
// From Windows 11 22H2 the CPU idle time returned by SystemProcessorPerformanceInformation is invalid, this issue has been fixed with 24H2.
OperatingSystem os = Environment.OSVersion;
Version win1122H2 = new Version(10, 0, 22621, 0);
Version win1124H2 = new Version(10, 0, 26100, 0);
return os.Version >= win1122H2 && os.Version < win1124H2;
}
}
@@ -305,14 +305,14 @@ public class GenericCpu : Hardware
{
if (_threadLoads[i] != null)
{
_threadLoads[i].Value = _cpuLoad.GetThreadLoad(i);
_threadLoads[i].Value = (float)_cpuLoad.GetThreadLoad(i);
maxLoad = Math.Max(maxLoad, _threadLoads[i].Value ?? 0);
}
}
}
if (_totalLoad != null)
_totalLoad.Value = _cpuLoad.GetTotalLoad();
_totalLoad.Value = (float)_cpuLoad.GetTotalLoad();
if (_maxLoad != null)
_maxLoad.Value = maxLoad;