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