Skip to content

Commit 24b5e04

Browse files
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: LibreHardwareMonitor/LibreHardwareMonitor#1571 Additional error checks for load calculation * Update CpuLoad.cs --------- Co-authored-by: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com>
1 parent 342c4f5 commit 24b5e04

2 files changed

Lines changed: 78 additions & 28 deletions

File tree

LibreHardwareMonitorLib/Hardware/Cpu/CpuLoad.cs

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ namespace LibreHardwareMonitor.Hardware.Cpu;
1414

1515
internal class CpuLoad
1616
{
17-
private readonly float[] _threadLoads;
17+
private static readonly bool _queryIdleTimeSeparated = QueryIdleTimeSeparated();
1818

19+
private readonly double[] _threadLoads;
20+
private double _totalLoad;
1921
private long[] _idleTimes;
20-
private float _totalLoad;
2122
private long[] _totalTimes;
2223

2324
public CpuLoad(CpuId[][] cpuid)
2425
{
25-
_threadLoads = new float[cpuid.Sum(x => x.Length)];
26+
_threadLoads = new double[cpuid.Sum(x => x.Length)];
2627
_totalLoad = 0;
2728

2829
try
@@ -51,30 +52,62 @@ private static bool GetWindowsTimes(out long[] idle, out long[] total)
5152
idle = null;
5253
total = null;
5354

54-
//Query processor idle information
55+
//use the idle time routine only with a few specific windows 11 versions
56+
if (_queryIdleTimeSeparated)
57+
return GetWindowsTimesFromIdleTimes(out idle, out total);
58+
59+
//Query processor performance information
60+
Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[] perfInformation = new Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[64];
61+
int perfSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION));
62+
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorPerformanceInformation, perfInformation, perfInformation.Length * perfSize, out int perfReturn) != 0)
63+
return false;
64+
65+
idle = new long[perfReturn / perfSize];
66+
total = new long[perfReturn / perfSize];
67+
for (int i = 0; i < total.Length; i++)
68+
{
69+
idle[i] = perfInformation[i].IdleTime;
70+
total[i] = perfInformation[i].KernelTime + perfInformation[i].UserTime;
71+
}
72+
73+
return true;
74+
}
75+
76+
private static bool GetWindowsTimesFromIdleTimes(out long[] idle, out long[] total)
77+
{
78+
idle = null;
79+
total = null;
80+
81+
Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[] perfInformation = new Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[64];
82+
int perfSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION));
5583
Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION[] idleInformation = new Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION[64];
5684
int idleSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_IDLE_INFORMATION));
85+
86+
//Query processor performance and idle information
87+
//these 2 methods must be called as directly as possible one after the other
88+
89+
//Query processor idle information
5790
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorIdleInformation, idleInformation, idleInformation.Length * idleSize, out int idleReturn) != 0)
5891
return false;
5992

6093
//Query processor performance information
61-
Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[] perfInformation = new Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[64];
62-
int perfSize = Marshal.SizeOf(typeof(Interop.NtDll.SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION));
63-
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorPerformanceInformation,
64-
perfInformation,
65-
perfInformation.Length * perfSize,
66-
out int perfReturn) != 0)
67-
{
94+
if (Interop.NtDll.NtQuerySystemInformation(Interop.NtDll.SYSTEM_INFORMATION_CLASS.SystemProcessorPerformanceInformation, perfInformation, perfInformation.Length * perfSize, out int perfReturn) != 0)
6895
return false;
69-
}
7096

71-
idle = new long[idleReturn / idleSize];
72-
for (int i = 0; i < idle.Length; i++)
73-
idle[i] = idleInformation[i].IdleTime;
97+
int perfItemsCount = perfReturn / perfSize;
98+
int idleItemsCount = idleReturn / idleSize;
7499

75-
total = new long[perfReturn / perfSize];
76-
for (int i = 0; i < total.Length; i++)
100+
if (perfItemsCount != idleItemsCount)
101+
return false;
102+
103+
idle = new long[perfItemsCount];
104+
total = new long[perfItemsCount];
105+
106+
for (int i = 0; i < perfItemsCount; i++)
107+
{
108+
idle[i] = idleInformation[i].IdleTime;
77109
total[i] = perfInformation[i].KernelTime + perfInformation[i].UserTime;
110+
}
78111

79112
return true;
80113
}
@@ -132,12 +165,12 @@ private static bool GetUnixTimes(out long[] idle, out long[] total)
132165
return true;
133166
}
134167

135-
public float GetTotalLoad()
168+
public double GetTotalLoad()
136169
{
137170
return _totalLoad;
138171
}
139172

140-
public float GetThreadLoad(int thread)
173+
public double GetThreadLoad(int thread)
141174
{
142175
return _threadLoads[thread];
143176
}
@@ -157,28 +190,45 @@ public void Update()
157190
if (newIdleTimes == null)
158191
return;
159192

160-
float total = 0;
193+
double total = 0;
161194
int count = 0;
162195
for (int i = 0; i < _threadLoads.Length && i < _idleTimes.Length && i < newIdleTimes.Length; i++)
163196
{
164-
float idle = (newIdleTimes[i] - _idleTimes[i]) / (float)(newTotalTimes[i] - _totalTimes[i]);
165-
_threadLoads[i] = 100f * (1.0f - Math.Min(idle, 1.0f));
197+
double idle = (newIdleTimes[i] - _idleTimes[i]) / (double)(newTotalTimes[i] - _totalTimes[i]);
198+
idle = idle < 0.0 ? 0.0 : idle;
199+
idle = idle > 1.0 ? 1.0 : idle;
200+
201+
double load = 100.0 * (1.0 - Math.Min(idle, 1.0));
202+
_threadLoads[i] = Math.Round(load, 2);
166203
total += idle;
167204
count++;
168205
}
169206

170207
if (count > 0)
171208
{
172-
total = 1.0f - (total / count);
173-
total = total < 0 ? 0 : total;
209+
total = 1.0 - (total / count);
210+
total = total < 0.0 ? 0.0 : total;
211+
total = total > 1.0 ? 1.0 : total;
174212
}
175213
else
176214
{
177215
total = 0;
178216
}
179-
180-
_totalLoad = total * 100;
217+
218+
_totalLoad = Math.Round(total * 100.0, 2);
181219
_totalTimes = newTotalTimes;
182220
_idleTimes = newIdleTimes;
183221
}
222+
223+
private static bool QueryIdleTimeSeparated()
224+
{
225+
if (Software.OperatingSystem.IsUnix)
226+
return false;
227+
228+
// From Windows 11 22H2 the CPU idle time returned by SystemProcessorPerformanceInformation is invalid, this issue has been fixed with 24H2.
229+
OperatingSystem os = Environment.OSVersion;
230+
Version win1122H2 = new Version(10, 0, 22621, 0);
231+
Version win1124H2 = new Version(10, 0, 26100, 0);
232+
return os.Version >= win1122H2 && os.Version < win1124H2;
233+
}
184234
}

LibreHardwareMonitorLib/Hardware/Cpu/GenericCpu.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,14 @@ public override void Update()
305305
{
306306
if (_threadLoads[i] != null)
307307
{
308-
_threadLoads[i].Value = _cpuLoad.GetThreadLoad(i);
308+
_threadLoads[i].Value = (float)_cpuLoad.GetThreadLoad(i);
309309
maxLoad = Math.Max(maxLoad, _threadLoads[i].Value ?? 0);
310310
}
311311
}
312312
}
313313

314314
if (_totalLoad != null)
315-
_totalLoad.Value = _cpuLoad.GetTotalLoad();
315+
_totalLoad.Value = (float)_cpuLoad.GetTotalLoad();
316316

317317
if (_maxLoad != null)
318318
_maxLoad.Value = maxLoad;

0 commit comments

Comments
 (0)