View Javadoc
1   /*
2    * Copyright 2016-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.linux;
6   
7   import static oshi.software.os.linux.LinuxOperatingSystem.HAS_UDEV;
8   import static oshi.util.platform.linux.ProcPath.CPUINFO;
9   import static oshi.util.platform.linux.ProcPath.MODEL;
10  
11  import java.io.File;
12  import java.io.IOException;
13  import java.nio.file.Files;
14  import java.nio.file.Path;
15  import java.nio.file.Paths;
16  import java.util.ArrayList;
17  import java.util.Arrays;
18  import java.util.Collections;
19  import java.util.Comparator;
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Locale;
24  import java.util.Map;
25  import java.util.Optional;
26  import java.util.Set;
27  import java.util.stream.Collectors;
28  import java.util.stream.LongStream;
29  import java.util.stream.Stream;
30  
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import com.sun.jna.platform.linux.Udev;
35  import com.sun.jna.platform.linux.Udev.UdevContext;
36  import com.sun.jna.platform.linux.Udev.UdevDevice;
37  import com.sun.jna.platform.linux.Udev.UdevEnumerate;
38  import com.sun.jna.platform.linux.Udev.UdevListEntry;
39  
40  import oshi.annotation.concurrent.ThreadSafe;
41  import oshi.driver.linux.Lshw;
42  import oshi.driver.linux.proc.CpuInfo;
43  import oshi.driver.linux.proc.CpuStat;
44  import oshi.hardware.CentralProcessor.ProcessorCache.Type;
45  import oshi.hardware.common.AbstractCentralProcessor;
46  import oshi.jna.platform.linux.LinuxLibc;
47  import oshi.software.os.linux.LinuxOperatingSystem;
48  import oshi.util.ExecutingCommand;
49  import oshi.util.FileUtil;
50  import oshi.util.ParseUtil;
51  import oshi.util.Util;
52  import oshi.util.platform.linux.SysPath;
53  import oshi.util.tuples.Quartet;
54  
55  /**
56   * A CPU as defined in Linux /proc.
57   */
58  @ThreadSafe
59  final class LinuxCentralProcessor extends AbstractCentralProcessor {
60  
61      private static final Logger LOG = LoggerFactory.getLogger(LinuxCentralProcessor.class);
62  
63      @Override
64      protected ProcessorIdentifier queryProcessorId() {
65          String cpuVendor = "";
66          String cpuName = "";
67          String cpuFamily = "";
68          String cpuModel = "";
69          String cpuStepping = "";
70          String processorID;
71          long cpuFreq = 0L;
72          boolean cpu64bit = false;
73  
74          StringBuilder armStepping = new StringBuilder(); // For ARM equivalent
75          String[] flags = new String[0];
76          List<String> cpuInfo = FileUtil.readFile(CPUINFO);
77          for (String line : cpuInfo) {
78              String[] splitLine = ParseUtil.whitespacesColonWhitespace.split(line);
79              if (splitLine.length < 2) {
80                  // special case
81                  if (line.startsWith("CPU architecture: ")) {
82                      cpuFamily = line.replace("CPU architecture: ", "").trim();
83                  }
84                  continue;
85              }
86              switch (splitLine[0].toLowerCase(Locale.ROOT)) {
87              case "vendor_id":
88              case "cpu implementer":
89                  cpuVendor = splitLine[1];
90                  break;
91              case "model name":
92              case "processor": // some ARM chips
93                  // Ignore processor number
94                  if (!splitLine[1].matches("[0-9]+")) {
95                      cpuName = splitLine[1];
96                  }
97                  break;
98              case "flags":
99                  flags = splitLine[1].toLowerCase(Locale.ROOT).split(" ");
100                 for (String flag : flags) {
101                     if ("lm".equals(flag)) {
102                         cpu64bit = true;
103                         break;
104                     }
105                 }
106                 break;
107             case "stepping":
108                 cpuStepping = splitLine[1];
109                 break;
110             case "cpu variant":
111                 if (!armStepping.toString().startsWith("r")) {
112                     // CPU variant format always starts with 0x
113                     int rev = ParseUtil.parseLastInt(splitLine[1], 0);
114                     armStepping.insert(0, "r" + rev);
115                 }
116                 break;
117             case "cpu revision":
118                 if (!armStepping.toString().contains("p")) {
119                     armStepping.append('p').append(splitLine[1]);
120                 }
121                 break;
122             case "model":
123             case "cpu part":
124                 cpuModel = splitLine[1];
125                 break;
126             case "cpu family":
127                 cpuFamily = splitLine[1];
128                 break;
129             case "cpu mhz":
130                 cpuFreq = ParseUtil.parseHertz(splitLine[1]);
131                 break;
132             default:
133                 // Do nothing
134             }
135         }
136         if (cpuName.isEmpty()) {
137             cpuName = FileUtil.getStringFromFile(MODEL);
138         }
139         if (cpuName.contains("Hz")) {
140             // if Name contains CPU vendor frequency, ignore cpuinfo and use it
141             cpuFreq = -1L;
142         } else {
143             // Try lshw and use it in preference to cpuinfo
144             long cpuCapacity = Lshw.queryCpuCapacity();
145             if (cpuCapacity > cpuFreq) {
146                 cpuFreq = cpuCapacity;
147             }
148         }
149         if (cpuStepping.isEmpty()) {
150             cpuStepping = armStepping.toString();
151         }
152         processorID = getProcessorID(cpuVendor, cpuStepping, cpuModel, cpuFamily, flags);
153         if (cpuVendor.startsWith("0x") || cpuModel.isEmpty() || cpuName.isEmpty()) {
154             List<String> lscpu = ExecutingCommand.runNative("lscpu");
155             for (String line : lscpu) {
156                 if (line.startsWith("Architecture:") && cpuVendor.startsWith("0x")) {
157                     cpuVendor = line.replace("Architecture:", "").trim();
158                 } else if (line.startsWith("Vendor ID:")) {
159                     cpuVendor = line.replace("Vendor ID:", "").trim();
160                 } else if (line.startsWith("Model name:")) {
161                     String modelName = line.replace("Model name:", "").trim();
162                     cpuModel = cpuModel.isEmpty() ? modelName : cpuModel;
163                     cpuName = cpuName.isEmpty() ? modelName : cpuName;
164                 }
165             }
166         }
167         return new ProcessorIdentifier(cpuVendor, cpuName, cpuFamily, cpuModel, cpuStepping, processorID, cpu64bit,
168                 cpuFreq);
169     }
170 
171     @Override
172     protected Quartet<List<LogicalProcessor>, List<PhysicalProcessor>, List<ProcessorCache>, List<String>> initProcessorCounts() {
173         // Attempt to read from sysfs
174         Quartet<List<LogicalProcessor>, List<ProcessorCache>, Map<Integer, Integer>, Map<Integer, String>> topology = HAS_UDEV
175                 ? readTopologyFromUdev()
176                 : readTopologyFromSysfs();
177         // This sometimes fails so fall back to CPUID
178         if (topology.getA().isEmpty()) {
179             topology = readTopologyFromCpuinfo();
180         }
181         List<LogicalProcessor> logProcs = topology.getA();
182         List<ProcessorCache> caches = topology.getB();
183         Map<Integer, Integer> coreEfficiencyMap = topology.getC();
184         Map<Integer, String> modAliasMap = topology.getD();
185         // Failsafe
186         if (logProcs.isEmpty()) {
187             logProcs.add(new LogicalProcessor(0, 0, 0));
188         }
189         if (coreEfficiencyMap.isEmpty()) {
190             coreEfficiencyMap.put(0, 0);
191         }
192         // Sort
193         logProcs.sort(Comparator.comparingInt(LogicalProcessor::getProcessorNumber));
194 
195         List<PhysicalProcessor> physProcs = coreEfficiencyMap.entrySet().stream().sorted(Map.Entry.comparingByKey())
196                 .map(e -> {
197                     int pkgId = e.getKey() >> 16;
198                     int coreId = e.getKey() & 0xffff;
199                     return new PhysicalProcessor(pkgId, coreId, e.getValue(), modAliasMap.getOrDefault(e.getKey(), ""));
200                 }).collect(Collectors.toList());
201         List<String> featureFlags = CpuInfo.queryFeatureFlags();
202         return new Quartet<>(logProcs, physProcs, caches, featureFlags);
203     }
204 
205     private static Quartet<List<LogicalProcessor>, List<ProcessorCache>, Map<Integer, Integer>, Map<Integer, String>> readTopologyFromUdev() {
206         List<LogicalProcessor> logProcs = new ArrayList<>();
207         Set<ProcessorCache> caches = new HashSet<>();
208         Map<Integer, Integer> coreEfficiencyMap = new HashMap<>();
209         Map<Integer, String> modAliasMap = new HashMap<>();
210         // Enumerate CPU topology from sysfs via udev
211         UdevContext udev = Udev.INSTANCE.udev_new();
212         try {
213             UdevEnumerate enumerate = udev.enumerateNew();
214             try {
215                 enumerate.addMatchSubsystem("cpu");
216                 enumerate.scanDevices();
217                 for (UdevListEntry entry = enumerate.getListEntry(); entry != null; entry = entry.getNext()) {
218                     String syspath = entry.getName(); // /sys/devices/system/cpu/cpuX
219                     UdevDevice device = udev.deviceNewFromSyspath(syspath);
220                     String modAlias = null;
221                     if (device != null) {
222                         try {
223                             modAlias = device.getPropertyValue("MODALIAS");
224                         } finally {
225                             device.unref();
226                         }
227                     }
228                     logProcs.add(
229                             getLogicalProcessorFromSyspath(syspath, caches, modAlias, coreEfficiencyMap, modAliasMap));
230                 }
231             } finally {
232                 enumerate.unref();
233             }
234         } finally {
235             udev.unref();
236         }
237         return new Quartet<>(logProcs, orderedProcCaches(caches), coreEfficiencyMap, modAliasMap);
238     }
239 
240     private static Quartet<List<LogicalProcessor>, List<ProcessorCache>, Map<Integer, Integer>, Map<Integer, String>> readTopologyFromSysfs() {
241         List<LogicalProcessor> logProcs = new ArrayList<>();
242         Set<ProcessorCache> caches = new HashSet<>();
243         Map<Integer, Integer> coreEfficiencyMap = new HashMap<>();
244         Map<Integer, String> modAliasMap = new HashMap<>();
245         try {
246             try (Stream<Path> cpuFiles = Files.find(Paths.get(SysPath.CPU), Integer.MAX_VALUE,
247                     (path, basicFileAttributes) -> path.toFile().getName().matches("cpu\\d+"))) {
248                 cpuFiles.forEach(cpu -> {
249                     String syspath = cpu.toString(); // /sys/devices/system/cpu/cpuX
250                     Map<String, String> uevent = FileUtil.getKeyValueMapFromFile(syspath + "/uevent", "=");
251                     String modAlias = uevent.get("MODALIAS");
252                     // updates caches as a side-effect
253                     logProcs.add(
254                             getLogicalProcessorFromSyspath(syspath, caches, modAlias, coreEfficiencyMap, modAliasMap));
255                 });
256             }
257         } catch (IOException e) {
258             // No udev and no cpu info in sysfs? Bad.
259             LOG.warn("Unable to find CPU information in sysfs at path {}", SysPath.CPU);
260         }
261         return new Quartet<>(logProcs, orderedProcCaches(caches), coreEfficiencyMap, modAliasMap);
262     }
263 
264     private static LogicalProcessor getLogicalProcessorFromSyspath(String syspath, Set<ProcessorCache> caches,
265             String modAlias, Map<Integer, Integer> coreEfficiencyMap, Map<Integer, String> modAliasMap) {
266         int processor = ParseUtil.getFirstIntValue(syspath);
267         int coreId = FileUtil.getIntFromFile(syspath + "/topology/core_id");
268         int pkgId = FileUtil.getIntFromFile(syspath + "/topology/physical_package_id");
269         int pkgCoreKey = (pkgId << 16) + coreId;
270         // The cpu_capacity value may not exist, this will just store 0
271         coreEfficiencyMap.put(pkgCoreKey, FileUtil.getIntFromFile(syspath + "/cpu_capacity"));
272         if (!Util.isBlank(modAlias)) {
273             modAliasMap.put(pkgCoreKey, modAlias);
274         }
275         int nodeId = 0;
276         final String nodePrefix = syspath + "/node";
277         try (Stream<Path> path = Files.list(Paths.get(syspath))) {
278             Optional<Path> first = path.filter(p -> p.toString().startsWith(nodePrefix)).findFirst();
279             if (first.isPresent()) {
280                 nodeId = ParseUtil.getFirstIntValue(first.get().getFileName().toString());
281             }
282         } catch (IOException e) {
283             // ignore
284         }
285         final String cachePath = syspath + "/cache";
286         final String indexPrefix = cachePath + "/index";
287         try (Stream<Path> path = Files.list(Paths.get(cachePath))) {
288             path.filter(p -> p.toString().startsWith(indexPrefix)).forEach(c -> {
289                 int level = FileUtil.getIntFromFile(c + "/level"); // 1
290                 Type type = parseCacheType(FileUtil.getStringFromFile(c + "/type")); // Data
291                 int associativity = FileUtil.getIntFromFile(c + "/ways_of_associativity"); // 8
292                 int lineSize = FileUtil.getIntFromFile(c + "/coherency_line_size"); // 64
293                 long size = ParseUtil.parseDecimalMemorySizeToBinary(FileUtil.getStringFromFile(c + "/size")); // 32K
294                 caches.add(new ProcessorCache(level, associativity, lineSize, size, type));
295             });
296         } catch (IOException e) {
297             // ignore
298         }
299         return new LogicalProcessor(processor, coreId, pkgId, nodeId);
300     }
301 
302     private static ProcessorCache.Type parseCacheType(String type) {
303         try {
304             return ProcessorCache.Type.valueOf(type.toUpperCase(Locale.ROOT));
305         } catch (IllegalArgumentException e) {
306             return ProcessorCache.Type.UNIFIED;
307         }
308     }
309 
310     private static Quartet<List<LogicalProcessor>, List<ProcessorCache>, Map<Integer, Integer>, Map<Integer, String>> readTopologyFromCpuinfo() {
311         List<LogicalProcessor> logProcs = new ArrayList<>();
312         Set<ProcessorCache> caches = mapCachesFromLscpu();
313         Map<Integer, Integer> numaNodeMap = mapNumaNodesFromLscpu();
314         Map<Integer, Integer> coreEfficiencyMap = new HashMap<>();
315 
316         List<String> procCpu = FileUtil.readFile(CPUINFO);
317         int currentProcessor = 0;
318         int currentCore = 0;
319         int currentPackage = 0;
320 
321         boolean first = true;
322         for (String cpu : procCpu) {
323             // Count logical processors
324             if (cpu.startsWith("processor")) {
325                 if (first) {
326                     first = false;
327                 } else {
328                     // add from the previous iteration
329                     logProcs.add(new LogicalProcessor(currentProcessor, currentCore, currentPackage,
330                             numaNodeMap.getOrDefault(currentProcessor, 0)));
331                     // Count unique combinations of core id and physical id.
332                     coreEfficiencyMap.put((currentPackage << 16) + currentCore, 0);
333                 }
334                 // start creating for this iteration
335                 currentProcessor = ParseUtil.parseLastInt(cpu, 0);
336             } else if (cpu.startsWith("core id") || cpu.startsWith("cpu number")) {
337                 currentCore = ParseUtil.parseLastInt(cpu, 0);
338             } else if (cpu.startsWith("physical id")) {
339                 currentPackage = ParseUtil.parseLastInt(cpu, 0);
340             }
341         }
342         logProcs.add(new LogicalProcessor(currentProcessor, currentCore, currentPackage,
343                 numaNodeMap.getOrDefault(currentProcessor, 0)));
344         coreEfficiencyMap.put((currentPackage << 16) + currentCore, 0);
345         return new Quartet<>(logProcs, orderedProcCaches(caches), coreEfficiencyMap, Collections.emptyMap());
346     }
347 
348     private static Map<Integer, Integer> mapNumaNodesFromLscpu() {
349         Map<Integer, Integer> numaNodeMap = new HashMap<>();
350         // Get numa node info from lscpu
351         List<String> lscpu = ExecutingCommand.runNative("lscpu -p=cpu,node");
352         // Format:
353         // # comment lines starting with #
354         // # then comma-delimited cpu,node
355         // 0,0
356         // 1,0
357         for (String line : lscpu) {
358             if (!line.startsWith("#")) {
359                 int pos = line.indexOf(',');
360                 if (pos > 0 && pos < line.length()) {
361                     numaNodeMap.put(ParseUtil.parseIntOrDefault(line.substring(0, pos), 0),
362                             ParseUtil.parseIntOrDefault(line.substring(pos + 1), 0));
363                 }
364             }
365         }
366         return numaNodeMap;
367     }
368 
369     private static Set<ProcessorCache> mapCachesFromLscpu() {
370         Set<ProcessorCache> caches = new HashSet<>();
371         int level = 0;
372         Type type = null;
373         int associativity = 0;
374         int lineSize = 0;
375         long size = 0L;
376         // Get numa node info from lscpu
377         List<String> lscpu = ExecutingCommand.runNative("lscpu -B -C --json");
378         for (String line : lscpu) {
379             String s = line.trim();
380             if (s.startsWith("}")) {
381                 // done with this entry, save it
382                 if (level > 0 && type != null) {
383                     caches.add(new ProcessorCache(level, associativity, lineSize, size, type));
384                 }
385                 level = 0;
386                 type = null;
387                 associativity = 0;
388                 lineSize = 0;
389                 size = 0L;
390             } else if (s.contains("one-size")) {
391                 // "one-size": "65536",
392                 String[] split = ParseUtil.notDigits.split(s);
393                 if (split.length > 1) {
394                     size = ParseUtil.parseLongOrDefault(split[1], 0L);
395                 }
396             } else if (s.contains("ways")) {
397                 // "ways": null,
398                 // "ways": 4,
399                 String[] split = ParseUtil.notDigits.split(s);
400                 if (split.length > 1) {
401                     associativity = ParseUtil.parseIntOrDefault(split[1], 0);
402                 }
403             } else if (s.contains("type")) {
404                 // "type": "Unified",
405                 String[] split = s.split("\"");
406                 if (split.length > 2) {
407                     type = parseCacheType(split[split.length - 2]);
408                 }
409             } else if (s.contains("level")) {
410                 // "level": 3,
411                 String[] split = ParseUtil.notDigits.split(s);
412                 if (split.length > 1) {
413                     level = ParseUtil.parseIntOrDefault(split[1], 0);
414                 }
415             } else if (s.contains("coherency-size")) {
416                 // "coherency-size": 64
417                 String[] split = ParseUtil.notDigits.split(s);
418                 if (split.length > 1) {
419                     lineSize = ParseUtil.parseIntOrDefault(split[1], 0);
420                 }
421             }
422         }
423         return caches;
424     }
425 
426     @Override
427     public long[] querySystemCpuLoadTicks() {
428         // convert the Linux Jiffies to Milliseconds.
429         long[] ticks = CpuStat.getSystemCpuLoadTicks();
430         // In rare cases, /proc/stat reading fails. If so, try again.
431         if (LongStream.of(ticks).sum() == 0) {
432             ticks = CpuStat.getSystemCpuLoadTicks();
433         }
434         long hz = LinuxOperatingSystem.getHz();
435         for (int i = 0; i < ticks.length; i++) {
436             ticks[i] = ticks[i] * 1000L / hz;
437         }
438         return ticks;
439     }
440 
441     @Override
442     public long[] queryCurrentFreq() {
443         long[] freqs = new long[getLogicalProcessorCount()];
444         // Attempt to fill array from cpu-freq source
445         long max = 0L;
446         UdevContext udev = Udev.INSTANCE.udev_new();
447         try {
448             UdevEnumerate enumerate = udev.enumerateNew();
449             try {
450                 enumerate.addMatchSubsystem("cpu");
451                 enumerate.scanDevices();
452                 for (UdevListEntry entry = enumerate.getListEntry(); entry != null; entry = entry.getNext()) {
453                     String syspath = entry.getName(); // /sys/devices/system/cpu/cpuX
454                     int cpu = ParseUtil.getFirstIntValue(syspath);
455                     if (cpu >= 0 && cpu < freqs.length) {
456                         freqs[cpu] = FileUtil.getLongFromFile(syspath + "/cpufreq/scaling_cur_freq");
457                         if (freqs[cpu] == 0) {
458                             freqs[cpu] = FileUtil.getLongFromFile(syspath + "/cpufreq/cpuinfo_cur_freq");
459                         }
460                     }
461                     if (max < freqs[cpu]) {
462                         max = freqs[cpu];
463                     }
464                 }
465                 if (max > 0L) {
466                     // If successful, array is filled with values in KHz.
467                     for (int i = 0; i < freqs.length; i++) {
468                         freqs[i] *= 1000L;
469                     }
470                     return freqs;
471                 }
472             } finally {
473                 enumerate.unref();
474             }
475         } finally {
476             udev.unref();
477         }
478         // If unsuccessful, try from /proc/cpuinfo
479         Arrays.fill(freqs, -1);
480         List<String> cpuInfo = FileUtil.readFile(CPUINFO);
481         int proc = 0;
482         for (String s : cpuInfo) {
483             if (s.toLowerCase(Locale.ROOT).contains("cpu mhz")) {
484                 freqs[proc] = Math.round(ParseUtil.parseLastDouble(s, 0d) * 1_000_000d);
485                 if (++proc >= freqs.length) {
486                     break;
487                 }
488             }
489         }
490         return freqs;
491     }
492 
493     @Override
494     public long queryMaxFreq() {
495         long policyMax = -1L;
496         // Iterate the policy directories to find the system-wide policy max
497         UdevContext udev = Udev.INSTANCE.udev_new();
498         try {
499             UdevEnumerate enumerate = udev.enumerateNew();
500             try {
501                 enumerate.addMatchSubsystem("cpu");
502                 enumerate.scanDevices();
503                 // Find the parent directory of cpuX paths
504                 // We only need the first one of the iteration
505                 UdevListEntry entry = enumerate.getListEntry();
506                 if (entry != null) {
507                     String syspath = entry.getName(); // /sys/devices/system/cpu/cpu0
508                     String cpuFreqPath = syspath.substring(0, syspath.lastIndexOf(File.separatorChar)) + "/cpufreq";
509                     String policyPrefix = cpuFreqPath + "/policy";
510                     try (Stream<Path> path = Files.list(Paths.get(cpuFreqPath))) {
511                         Optional<Long> maxPolicy = path.filter(p -> p.toString().startsWith(policyPrefix)).map(p -> {
512                             long freq = FileUtil.getLongFromFile(p.toString() + "/scaling_max_freq");
513                             if (freq == 0) {
514                                 freq = FileUtil.getLongFromFile(p.toString() + "/cpuinfo_max_freq");
515                             }
516                             return freq;
517                         }).max(Long::compare);
518                         if (maxPolicy.isPresent()) {
519                             // Value is in kHz
520                             policyMax = maxPolicy.get() * 1000L;
521                         }
522                     } catch (IOException e) {
523                         // ignore
524                     }
525                 }
526             } finally {
527                 enumerate.unref();
528             }
529         } finally {
530             udev.unref();
531         }
532         // Check lshw as a backup
533         long lshwMax = Lshw.queryCpuCapacity();
534         // And get the highest of existing current frequencies
535         return LongStream.concat(LongStream.of(policyMax, lshwMax), Arrays.stream(this.getCurrentFreq())).max()
536                 .orElse(-1L);
537     }
538 
539     @Override
540     public double[] getSystemLoadAverage(int nelem) {
541         if (nelem < 1 || nelem > 3) {
542             throw new IllegalArgumentException("Must include from one to three elements.");
543         }
544         double[] average = new double[nelem];
545         int retval = LinuxLibc.INSTANCE.getloadavg(average, nelem);
546         if (retval < nelem) {
547             for (int i = Math.max(retval, 0); i < average.length; i++) {
548                 average[i] = -1d;
549             }
550         }
551         return average;
552     }
553 
554     @Override
555     public long[][] queryProcessorCpuLoadTicks() {
556         long[][] ticks = CpuStat.getProcessorCpuLoadTicks(getLogicalProcessorCount());
557         // In rare cases, /proc/stat reading fails. If so, try again.
558         // In theory we should check all of them, but on failure we can expect all 0's
559         // so we only need to check for processor 0
560         if (LongStream.of(ticks[0]).sum() == 0) {
561             ticks = CpuStat.getProcessorCpuLoadTicks(getLogicalProcessorCount());
562         }
563         // convert the Linux Jiffies to Milliseconds.
564         long hz = LinuxOperatingSystem.getHz();
565         for (int i = 0; i < ticks.length; i++) {
566             for (int j = 0; j < ticks[i].length; j++) {
567                 ticks[i][j] = ticks[i][j] * 1000L / hz;
568             }
569         }
570         return ticks;
571     }
572 
573     /**
574      * Fetches the ProcessorID from dmidecode (if possible with root permissions), the cpuid command (if installed) or
575      * by encoding the stepping, model, family, and feature flags.
576      *
577      * @param vendor   The vendor
578      * @param stepping The stepping
579      * @param model    The model
580      * @param family   The family
581      * @param flags    The flags
582      * @return The Processor ID string
583      */
584     private static String getProcessorID(String vendor, String stepping, String model, String family, String[] flags) {
585         boolean procInfo = false;
586         String marker = "Processor Information";
587         for (String checkLine : ExecutingCommand.runNative("dmidecode -t 4")) {
588             if (!procInfo && checkLine.contains(marker)) {
589                 marker = "ID:";
590                 procInfo = true;
591             } else if (procInfo && checkLine.contains(marker)) {
592                 return checkLine.split(marker)[1].trim();
593             }
594         }
595         // If we've gotten this far, dmidecode failed. Try cpuid.
596         marker = "eax=";
597         for (String checkLine : ExecutingCommand.runNative("cpuid -1r")) {
598             if (checkLine.contains(marker) && checkLine.trim().startsWith("0x00000001")) {
599                 String eax = "";
600                 String edx = "";
601                 for (String register : ParseUtil.whitespaces.split(checkLine)) {
602                     if (register.startsWith("eax=")) {
603                         eax = ParseUtil.removeMatchingString(register, "eax=0x");
604                     } else if (register.startsWith("edx=")) {
605                         edx = ParseUtil.removeMatchingString(register, "edx=0x");
606                     }
607                 }
608                 return edx + eax;
609             }
610         }
611         // If we've gotten this far, dmidecode failed. Encode arguments
612         if (vendor.startsWith("0x")) {
613             return createMIDR(vendor, stepping, model, family) + "00000000";
614         }
615         return createProcessorID(stepping, model, family, flags);
616     }
617 
618     /**
619      * Creates the MIDR, the ARM equivalent of CPUID ProcessorID
620      *
621      * @param vendor   the CPU implementer
622      * @param stepping the "rnpn" variant and revision
623      * @param model    the partnum
624      * @param family   the architecture
625      * @return A 32-bit hex string for the MIDR
626      */
627     private static String createMIDR(String vendor, String stepping, String model, String family) {
628         int midrBytes = 0;
629         // Build 32-bit MIDR
630         if (stepping.startsWith("r") && stepping.contains("p")) {
631             String[] rev = stepping.substring(1).split("p");
632             // 3:0 – Revision: last n in rnpn
633             midrBytes |= ParseUtil.parseLastInt(rev[1], 0);
634             // 23:20 - Variant: first n in rnpn
635             midrBytes |= ParseUtil.parseLastInt(rev[0], 0) << 20;
636         }
637         // 15:4 - PartNum = model
638         midrBytes |= ParseUtil.parseLastInt(model, 0) << 4;
639         // 19:16 - Architecture = family
640         midrBytes |= ParseUtil.parseLastInt(family, 0) << 16;
641         // 31:24 - Implementer = vendor
642         midrBytes |= ParseUtil.parseLastInt(vendor, 0) << 24;
643 
644         return String.format(Locale.ROOT, "%08X", midrBytes);
645     }
646 
647     @Override
648     public long queryContextSwitches() {
649         return CpuStat.getContextSwitches();
650     }
651 
652     @Override
653     public long queryInterrupts() {
654         return CpuStat.getInterrupts();
655     }
656 }