View Javadoc
1   /*
2    * Copyright 2016-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.windows;
6   
7   import static oshi.util.Memoizer.memoize;
8   
9   import java.util.Arrays;
10  import java.util.HashMap;
11  import java.util.List;
12  import java.util.Locale;
13  import java.util.Map;
14  import java.util.concurrent.TimeUnit;
15  import java.util.function.Supplier;
16  import java.util.stream.Collectors;
17  
18  import oshi.jna.platform.windows.Kernel32.ProcessorFeature;
19  import org.slf4j.Logger;
20  import org.slf4j.LoggerFactory;
21  
22  import com.sun.jna.platform.win32.Advapi32Util;
23  import com.sun.jna.platform.win32.PowrProf.POWER_INFORMATION_LEVEL;
24  import com.sun.jna.platform.win32.VersionHelpers;
25  import com.sun.jna.platform.win32.Win32Exception;
26  import com.sun.jna.platform.win32.WinReg;
27  import com.sun.jna.platform.win32.COM.WbemcliUtil.WmiResult;
28  
29  import oshi.annotation.concurrent.ThreadSafe;
30  import oshi.driver.windows.LogicalProcessorInformation;
31  import oshi.driver.windows.perfmon.LoadAverage;
32  import oshi.driver.windows.perfmon.ProcessorInformation;
33  import oshi.driver.windows.perfmon.ProcessorInformation.InterruptsProperty;
34  import oshi.driver.windows.perfmon.ProcessorInformation.ProcessorFrequencyProperty;
35  import oshi.driver.windows.perfmon.ProcessorInformation.ProcessorTickCountProperty;
36  import oshi.driver.windows.perfmon.ProcessorInformation.ProcessorUtilityTickCountProperty;
37  import oshi.driver.windows.perfmon.SystemInformation;
38  import oshi.driver.windows.perfmon.SystemInformation.ContextSwitchProperty;
39  import oshi.driver.windows.wmi.Win32Processor;
40  import oshi.driver.windows.wmi.Win32Processor.ProcessorIdProperty;
41  import oshi.hardware.common.AbstractCentralProcessor;
42  import oshi.jna.Struct.CloseableSystemInfo;
43  import oshi.jna.platform.windows.Kernel32;
44  import oshi.jna.platform.windows.PowrProf;
45  import oshi.jna.platform.windows.PowrProf.ProcessorPowerInformation;
46  import oshi.util.GlobalConfig;
47  import oshi.util.ParseUtil;
48  import oshi.util.platform.windows.WmiUtil;
49  import oshi.util.tuples.Pair;
50  import oshi.util.tuples.Quartet;
51  import oshi.util.tuples.Triplet;
52  
53  /**
54   * A CPU, representing all of a system's processors. It may contain multiple individual Physical and Logical processors.
55   */
56  @ThreadSafe
57  final class WindowsCentralProcessor extends AbstractCentralProcessor {
58  
59      private static final Logger LOG = LoggerFactory.getLogger(WindowsCentralProcessor.class);
60  
61      // populated by initProcessorCounts called by the parent constructor
62      private Map<String, Integer> numaNodeProcToLogicalProcMap;
63  
64      // Whether to start a daemon thread ot calculate load average
65      private static final boolean USE_LOAD_AVERAGE = GlobalConfig.get(GlobalConfig.OSHI_OS_WINDOWS_LOADAVERAGE, false);
66      static {
67          if (USE_LOAD_AVERAGE) {
68              LoadAverage.startDaemon();
69          }
70      }
71  
72      // Whether to match task manager using Processor Utility ticks
73      private static final boolean USE_CPU_UTILITY = VersionHelpers.IsWindows8OrGreater()
74              && GlobalConfig.get(GlobalConfig.OSHI_OS_WINDOWS_CPU_UTILITY, false);
75  
76      // This tick query is memoized to enforce a minimum elapsed time for determining
77      // the capacity base multiplier
78      private final Supplier<Pair<List<String>, Map<ProcessorUtilityTickCountProperty, List<Long>>>> processorUtilityCounters = USE_CPU_UTILITY
79              ? memoize(WindowsCentralProcessor::queryProcessorUtilityCounters, TimeUnit.MILLISECONDS.toNanos(300L))
80              : null;
81      // Store the initial query and start the memoizer expiration
82      private Map<ProcessorUtilityTickCountProperty, List<Long>> initialUtilityCounters = USE_CPU_UTILITY
83              ? processorUtilityCounters.get().getB()
84              : null;
85      // Lazily initialized
86      private Long utilityBaseMultiplier = null;
87  
88      /**
89       * Initializes Class variables
90       */
91      @Override
92      protected ProcessorIdentifier queryProcessorId() {
93          String cpuVendor = "";
94          String cpuName = "";
95          String cpuIdentifier = "";
96          String cpuFamily = "";
97          String cpuModel = "";
98          String cpuStepping = "";
99          long cpuVendorFreq = 0L;
100         String processorID;
101         boolean cpu64bit = false;
102 
103         final String cpuRegistryRoot = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\";
104         String[] processorIds = Advapi32Util.registryGetKeys(WinReg.HKEY_LOCAL_MACHINE, cpuRegistryRoot);
105         if (processorIds.length > 0) {
106             String cpuRegistryPath = cpuRegistryRoot + processorIds[0];
107             cpuVendor = Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, cpuRegistryPath,
108                     "VendorIdentifier");
109             cpuName = Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, cpuRegistryPath,
110                     "ProcessorNameString");
111             cpuIdentifier = Advapi32Util.registryGetStringValue(WinReg.HKEY_LOCAL_MACHINE, cpuRegistryPath,
112                     "Identifier");
113             try {
114                 cpuVendorFreq = Advapi32Util.registryGetIntValue(WinReg.HKEY_LOCAL_MACHINE, cpuRegistryPath, "~MHz")
115                         * 1_000_000L;
116             } catch (Win32Exception e) {
117                 // Leave as 0, parse the identifier as backup
118             }
119         }
120         if (!cpuIdentifier.isEmpty()) {
121             cpuFamily = parseIdentifier(cpuIdentifier, "Family");
122             cpuModel = parseIdentifier(cpuIdentifier, "Model");
123             cpuStepping = parseIdentifier(cpuIdentifier, "Stepping");
124         }
125         try (CloseableSystemInfo sysinfo = new CloseableSystemInfo()) {
126             Kernel32.INSTANCE.GetNativeSystemInfo(sysinfo);
127             int processorArchitecture = sysinfo.processorArchitecture.pi.wProcessorArchitecture.intValue();
128             if (processorArchitecture == 9 // PROCESSOR_ARCHITECTURE_AMD64
129                     || processorArchitecture == 12 // PROCESSOR_ARCHITECTURE_ARM64
130                     || processorArchitecture == 6) { // PROCESSOR_ARCHITECTURE_IA64
131                 cpu64bit = true;
132             }
133         }
134         WmiResult<ProcessorIdProperty> processorId = Win32Processor.queryProcessorId();
135         if (processorId.getResultCount() > 0) {
136             processorID = WmiUtil.getString(processorId, ProcessorIdProperty.PROCESSORID, 0);
137         } else {
138             processorID = createProcessorID(cpuStepping, cpuModel, cpuFamily,
139                     cpu64bit ? new String[] { "ia64" } : new String[0]);
140         }
141         return new ProcessorIdentifier(cpuVendor, cpuName, cpuFamily, cpuModel, cpuStepping, processorID, cpu64bit,
142                 cpuVendorFreq);
143     }
144 
145     /**
146      * Parses identifier string
147      *
148      * @param identifier the full identifier string
149      * @param key        the key to retrieve
150      * @return the string following id
151      */
152     private static String parseIdentifier(String identifier, String key) {
153         String[] idSplit = ParseUtil.whitespaces.split(identifier);
154         boolean found = false;
155         for (String s : idSplit) {
156             // If key string found, return next value
157             if (found) {
158                 return s;
159             }
160             found = s.equals(key);
161         }
162         // If key string not found, return empty string
163         return "";
164     }
165 
166     @Override
167     protected Quartet<List<LogicalProcessor>, List<PhysicalProcessor>, List<ProcessorCache>, List<String>> initProcessorCounts() {
168         Triplet<List<LogicalProcessor>, List<PhysicalProcessor>, List<ProcessorCache>> lpi;
169         if (VersionHelpers.IsWindows7OrGreater()) {
170             lpi = LogicalProcessorInformation.getLogicalProcessorInformationEx();
171             // Save numaNode,Processor lookup for future PerfCounter instance lookup
172             // The processor number is based on the Processor Group, so we keep a separate
173             // index by NUMA node.
174             int curNode = -1;
175             int procNum = 0;
176             // 0-indexed list of all lps for array lookup
177             int lp = 0;
178             this.numaNodeProcToLogicalProcMap = new HashMap<>();
179             for (LogicalProcessor logProc : lpi.getA()) {
180                 int node = logProc.getNumaNode();
181                 // This list is grouped by NUMA node so a change in node will reset this counter
182                 if (node != curNode) {
183                     curNode = node;
184                     procNum = 0;
185                 }
186                 numaNodeProcToLogicalProcMap.put(String.format(Locale.ROOT, "%d,%d", logProc.getNumaNode(), procNum++),
187                         lp++);
188             }
189         } else {
190             lpi = LogicalProcessorInformation.getLogicalProcessorInformation();
191         }
192         List<String> featureFlags = Arrays.stream(ProcessorFeature.values())
193                 .filter(f -> Kernel32.INSTANCE.IsProcessorFeaturePresent(f.value())).map(ProcessorFeature::name)
194                 .collect(Collectors.toList());
195         return new Quartet<>(lpi.getA(), lpi.getB(), lpi.getC(), featureFlags);
196     }
197 
198     @Override
199     public long[] querySystemCpuLoadTicks() {
200         // To get load in processor group scenario, we need perfmon counters, but the
201         // _Total instance is an average rather than total (scaled) number of ticks
202         // which matches GetSystemTimes() results. We can just query the per-processor
203         // ticks and add them up. Calling the get() method gains the benefit of
204         // synchronizing this output with the memoized result of per-processor ticks as
205         // well.
206         long[] ticks = new long[TickType.values().length];
207         // Sum processor ticks
208         long[][] procTicks = getProcessorCpuLoadTicks();
209         for (int i = 0; i < ticks.length; i++) {
210             for (long[] procTick : procTicks) {
211                 ticks[i] += procTick[i];
212             }
213         }
214         return ticks;
215     }
216 
217     @Override
218     public long[] queryCurrentFreq() {
219         if (VersionHelpers.IsWindows7OrGreater()) {
220             Pair<List<String>, Map<ProcessorFrequencyProperty, List<Long>>> instanceValuePair = ProcessorInformation
221                     .queryFrequencyCounters();
222             List<String> instances = instanceValuePair.getA();
223             Map<ProcessorFrequencyProperty, List<Long>> valueMap = instanceValuePair.getB();
224             List<Long> percentMaxList = valueMap.get(ProcessorFrequencyProperty.PERCENTOFMAXIMUMFREQUENCY);
225             if (!instances.isEmpty()) {
226                 long maxFreq = this.getMaxFreq();
227                 long[] freqs = new long[getLogicalProcessorCount()];
228                 for (String instance : instances) {
229                     int cpu = instance.contains(",") ? numaNodeProcToLogicalProcMap.getOrDefault(instance, 0)
230                             : ParseUtil.parseIntOrDefault(instance, 0);
231                     if (cpu >= getLogicalProcessorCount()) {
232                         continue;
233                     }
234                     freqs[cpu] = percentMaxList.get(cpu) * maxFreq / 100L;
235                 }
236                 return freqs;
237             }
238         }
239         // If <Win7 or anything failed in PDH/WMI, use the native call
240         return queryNTPower(2); // Current is field index 2
241     }
242 
243     @Override
244     public long queryMaxFreq() {
245         long[] freqs = queryNTPower(1); // Max is field index 1
246         return Arrays.stream(freqs).max().orElse(-1L);
247     }
248 
249     /**
250      * Call CallNTPowerInformation for Processor information and return an array of the specified index
251      *
252      * @param fieldIndex The field, in order as defined in the {@link PowrProf#PROCESSOR_INFORMATION} structure.
253      * @return The array of values.
254      */
255     private long[] queryNTPower(int fieldIndex) {
256         ProcessorPowerInformation ppi = new ProcessorPowerInformation();
257         ProcessorPowerInformation[] ppiArray = (ProcessorPowerInformation[]) ppi.toArray(getLogicalProcessorCount());
258         long[] freqs = new long[getLogicalProcessorCount()];
259         if (0 != PowrProf.INSTANCE.CallNtPowerInformation(POWER_INFORMATION_LEVEL.ProcessorInformation, null, 0,
260                 ppiArray[0].getPointer(), ppi.size() * ppiArray.length)) {
261             LOG.error("Unable to get Processor Information");
262             Arrays.fill(freqs, -1L);
263             return freqs;
264         }
265         for (int i = 0; i < freqs.length; i++) {
266             if (fieldIndex == 1) { // Max
267                 freqs[i] = ppiArray[i].maxMhz * 1_000_000L;
268             } else if (fieldIndex == 2) { // Current
269                 freqs[i] = ppiArray[i].currentMhz * 1_000_000L;
270             } else {
271                 freqs[i] = -1L;
272             }
273             // In Win11 23H2 CallNtPowerInformation returns all 0's so use vendor freq
274             if (freqs[i] == 0) {
275                 freqs[i] = getProcessorIdentifier().getVendorFreq();
276             }
277         }
278         return freqs;
279     }
280 
281     @Override
282     public double[] getSystemLoadAverage(int nelem) {
283         if (nelem < 1 || nelem > 3) {
284             throw new IllegalArgumentException("Must include from one to three elements.");
285         }
286         return LoadAverage.queryLoadAverage(nelem);
287     }
288 
289     @Override
290     public long[][] queryProcessorCpuLoadTicks() {
291         // These are used in all cases
292         List<String> instances;
293         List<Long> systemList;
294         List<Long> userList;
295         List<Long> irqList;
296         List<Long> softIrqList;
297         List<Long> idleList;
298         // These are only used with USE_CPU_UTILITY
299         List<Long> baseList = null;
300         List<Long> systemUtility = null;
301         List<Long> processorUtility = null;
302         List<Long> processorUtilityBase = null;
303         List<Long> initSystemList = null;
304         List<Long> initUserList = null;
305         List<Long> initBase = null;
306         List<Long> initSystemUtility = null;
307         List<Long> initProcessorUtility = null;
308         List<Long> initProcessorUtilityBase = null;
309         if (USE_CPU_UTILITY) {
310             Pair<List<String>, Map<ProcessorUtilityTickCountProperty, List<Long>>> instanceValuePair = processorUtilityCounters
311                     .get();
312             instances = instanceValuePair.getA();
313             Map<ProcessorUtilityTickCountProperty, List<Long>> valueMap = instanceValuePair.getB();
314             systemList = valueMap.get(ProcessorUtilityTickCountProperty.PERCENTPRIVILEGEDTIME);
315             userList = valueMap.get(ProcessorUtilityTickCountProperty.PERCENTUSERTIME);
316             irqList = valueMap.get(ProcessorUtilityTickCountProperty.PERCENTINTERRUPTTIME);
317             softIrqList = valueMap.get(ProcessorUtilityTickCountProperty.PERCENTDPCTIME);
318             // % Processor Time is actually Idle time
319             idleList = valueMap.get(ProcessorUtilityTickCountProperty.PERCENTPROCESSORTIME);
320             baseList = valueMap.get(ProcessorUtilityTickCountProperty.TIMESTAMP_SYS100NS);
321             // Utility ticks, if configured
322             systemUtility = valueMap.get(ProcessorUtilityTickCountProperty.PERCENTPRIVILEGEDUTILITY);
323             processorUtility = valueMap.get(ProcessorUtilityTickCountProperty.PERCENTPROCESSORUTILITY);
324             processorUtilityBase = valueMap.get(ProcessorUtilityTickCountProperty.PERCENTPROCESSORUTILITY_BASE);
325 
326             initSystemList = initialUtilityCounters.get(ProcessorUtilityTickCountProperty.PERCENTPRIVILEGEDTIME);
327             initUserList = initialUtilityCounters.get(ProcessorUtilityTickCountProperty.PERCENTUSERTIME);
328             initBase = initialUtilityCounters.get(ProcessorUtilityTickCountProperty.TIMESTAMP_SYS100NS);
329             // Utility ticks, if configured
330             initSystemUtility = initialUtilityCounters.get(ProcessorUtilityTickCountProperty.PERCENTPRIVILEGEDUTILITY);
331             initProcessorUtility = initialUtilityCounters
332                     .get(ProcessorUtilityTickCountProperty.PERCENTPROCESSORUTILITY);
333             initProcessorUtilityBase = initialUtilityCounters
334                     .get(ProcessorUtilityTickCountProperty.PERCENTPROCESSORUTILITY_BASE);
335         } else {
336             Pair<List<String>, Map<ProcessorTickCountProperty, List<Long>>> instanceValuePair = ProcessorInformation
337                     .queryProcessorCounters();
338             instances = instanceValuePair.getA();
339             Map<ProcessorTickCountProperty, List<Long>> valueMap = instanceValuePair.getB();
340             systemList = valueMap.get(ProcessorTickCountProperty.PERCENTPRIVILEGEDTIME);
341             userList = valueMap.get(ProcessorTickCountProperty.PERCENTUSERTIME);
342             irqList = valueMap.get(ProcessorTickCountProperty.PERCENTINTERRUPTTIME);
343             softIrqList = valueMap.get(ProcessorTickCountProperty.PERCENTDPCTIME);
344             // % Processor Time is actually Idle time
345             idleList = valueMap.get(ProcessorTickCountProperty.PERCENTPROCESSORTIME);
346         }
347 
348         int ncpu = getLogicalProcessorCount();
349         long[][] ticks = new long[ncpu][TickType.values().length];
350         if (instances.isEmpty() || systemList == null || userList == null || irqList == null || softIrqList == null
351                 || idleList == null
352                 || (USE_CPU_UTILITY && (baseList == null || systemUtility == null || processorUtility == null
353                         || processorUtilityBase == null || initSystemList == null || initUserList == null
354                         || initBase == null || initSystemUtility == null || initProcessorUtility == null
355                         || initProcessorUtilityBase == null))) {
356             return ticks;
357         }
358         for (String instance : instances) {
359             int cpu = instance.contains(",") ? numaNodeProcToLogicalProcMap.getOrDefault(instance, 0)
360                     : ParseUtil.parseIntOrDefault(instance, 0);
361             if (cpu >= ncpu) {
362                 continue;
363             }
364             ticks[cpu][TickType.SYSTEM.getIndex()] = systemList.get(cpu);
365             ticks[cpu][TickType.USER.getIndex()] = userList.get(cpu);
366             ticks[cpu][TickType.IRQ.getIndex()] = irqList.get(cpu);
367             ticks[cpu][TickType.SOFTIRQ.getIndex()] = softIrqList.get(cpu);
368             ticks[cpu][TickType.IDLE.getIndex()] = idleList.get(cpu);
369 
370             // If users want Task Manager output we have to do some math to get there
371             if (USE_CPU_UTILITY) {
372                 // We have two new capacity numbers, processor (all but idle) and system
373                 // (included in processor). To further complicate matters, these are in percent
374                 // units so must be divided by 100.
375 
376                 // The 100NS elapsed time counter is a constant multiple of the capacity base
377                 // counter. By enforcing a memoized pause we'll either have zero elapsed time or
378                 // sufficient delay to determine this offset reliably once and not have to
379                 // recalculate it
380 
381                 // Get elapsed time in 100NS
382                 long deltaT = baseList.get(cpu) - initBase.get(cpu);
383                 if (deltaT > 0) {
384                     // Get elapsed utility base
385                     long deltaBase = processorUtilityBase.get(cpu) - initProcessorUtilityBase.get(cpu);
386                     // The ratio of elapsed clock to elapsed utility base is an integer constant.
387                     // We can calculate a conversion factor to ensure a consistent application of
388                     // the correction. Since Utility is in percent, this is actually 100x the true
389                     // multiplier but is at the level where the integer calculation is precise
390                     long multiplier = lazilyCalculateMultiplier(deltaBase, deltaT);
391 
392                     // 0 multiplier means we just re-initialized ticks
393                     if (multiplier > 0) {
394                         // Get utility delta
395                         long deltaProc = processorUtility.get(cpu) - initProcessorUtility.get(cpu);
396                         long deltaSys = systemUtility.get(cpu) - initSystemUtility.get(cpu);
397 
398                         // Calculate new target ticks
399                         // Correct for the 100x multiplier at the end
400                         long newUser = initUserList.get(cpu) + multiplier * (deltaProc - deltaSys) / 100;
401                         long newSystem = initSystemList.get(cpu) + multiplier * deltaSys / 100;
402 
403                         // Adjust user to new, saving the delta
404                         long delta = newUser - ticks[cpu][TickType.USER.getIndex()];
405                         ticks[cpu][TickType.USER.getIndex()] = newUser;
406                         // Do the same for system
407                         delta += newSystem - ticks[cpu][TickType.SYSTEM.getIndex()];
408                         ticks[cpu][TickType.SYSTEM.getIndex()] = newSystem;
409                         // Subtract delta from idle
410                         ticks[cpu][TickType.IDLE.getIndex()] -= delta;
411                     }
412                 }
413             }
414 
415             // Decrement IRQ from system to avoid double counting in the total array
416             ticks[cpu][TickType.SYSTEM.getIndex()] -= ticks[cpu][TickType.IRQ.getIndex()]
417                     + ticks[cpu][TickType.SOFTIRQ.getIndex()];
418 
419             // Raw value is cumulative 100NS-ticks
420             // Divide by 10_000 to get milliseconds
421             ticks[cpu][TickType.SYSTEM.getIndex()] /= 10_000L;
422             ticks[cpu][TickType.USER.getIndex()] /= 10_000L;
423             ticks[cpu][TickType.IRQ.getIndex()] /= 10_000L;
424             ticks[cpu][TickType.SOFTIRQ.getIndex()] /= 10_000L;
425             ticks[cpu][TickType.IDLE.getIndex()] /= 10_000L;
426         }
427         // Skipping nice and IOWait, they'll stay 0
428         return ticks;
429     }
430 
431     /**
432      * Lazily calculate the capacity tick multiplier once.
433      *
434      * @param deltaBase The difference in base ticks.
435      * @param deltaT    The difference in elapsed 100NS time
436      * @return The ratio of elapsed time to base ticks
437      */
438     private synchronized long lazilyCalculateMultiplier(long deltaBase, long deltaT) {
439         if (utilityBaseMultiplier == null) {
440             // If too much time has elapsed from class instantiation, re-initialize the
441             // ticks and return without calculating. Approx 7 minutes for 100NS counter to
442             // exceed max unsigned int.
443             if (deltaT >> 32 > 0) {
444                 initialUtilityCounters = processorUtilityCounters.get().getB();
445                 return 0L;
446             }
447             // Base counter wraps approximately every 115 minutes
448             // If deltaBase is nonpositive assume it has wrapped
449             if (deltaBase <= 0) {
450                 deltaBase += 1L << 32;
451             }
452             long multiplier = Math.round((double) deltaT / deltaBase);
453             // If not enough time has elapsed, return the value this one time but don't
454             // persist. 5000 ms = 50 million 100NS ticks
455             if (deltaT < 50_000_000L) {
456                 return multiplier;
457             }
458             utilityBaseMultiplier = multiplier;
459         }
460         return utilityBaseMultiplier;
461     }
462 
463     private static Pair<List<String>, Map<ProcessorUtilityTickCountProperty, List<Long>>> queryProcessorUtilityCounters() {
464         return ProcessorInformation.queryProcessorCapacityCounters();
465     }
466 
467     @Override
468     public long queryContextSwitches() {
469         return SystemInformation.queryContextSwitchCounters().getOrDefault(ContextSwitchProperty.CONTEXTSWITCHESPERSEC,
470                 0L);
471     }
472 
473     @Override
474     public long queryInterrupts() {
475         return ProcessorInformation.queryInterruptCounters().getOrDefault(InterruptsProperty.INTERRUPTSPERSEC, 0L);
476     }
477 }