View Javadoc
1   /*
2    * Copyright 2021-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.unix.openbsd;
6   
7   import static oshi.jna.platform.unix.OpenBsdLibc.CP_IDLE;
8   import static oshi.jna.platform.unix.OpenBsdLibc.CP_INTR;
9   import static oshi.jna.platform.unix.OpenBsdLibc.CP_NICE;
10  import static oshi.jna.platform.unix.OpenBsdLibc.CP_SYS;
11  import static oshi.jna.platform.unix.OpenBsdLibc.CP_USER;
12  import static oshi.jna.platform.unix.OpenBsdLibc.CTL_HW;
13  import static oshi.jna.platform.unix.OpenBsdLibc.CTL_KERN;
14  import static oshi.jna.platform.unix.OpenBsdLibc.HW_CPUSPEED;
15  import static oshi.jna.platform.unix.OpenBsdLibc.HW_MACHINE;
16  import static oshi.jna.platform.unix.OpenBsdLibc.HW_MODEL;
17  import static oshi.jna.platform.unix.OpenBsdLibc.KERN_CPTIME;
18  import static oshi.jna.platform.unix.OpenBsdLibc.KERN_CPTIME2;
19  import static oshi.util.Memoizer.defaultExpiration;
20  import static oshi.util.Memoizer.memoize;
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.function.Supplier;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  import com.sun.jna.Memory;
36  import com.sun.jna.Native;
37  
38  import oshi.annotation.concurrent.ThreadSafe;
39  import oshi.hardware.CentralProcessor.ProcessorCache.Type;
40  import oshi.hardware.common.AbstractCentralProcessor;
41  import oshi.jna.platform.unix.OpenBsdLibc;
42  import oshi.util.ExecutingCommand;
43  import oshi.util.ParseUtil;
44  import oshi.util.platform.unix.openbsd.OpenBsdSysctlUtil;
45  import oshi.util.tuples.Pair;
46  import oshi.util.tuples.Quartet;
47  import oshi.util.tuples.Triplet;
48  
49  /**
50   * OpenBSD Central Processor implementation
51   */
52  @ThreadSafe
53  public class OpenBsdCentralProcessor extends AbstractCentralProcessor {
54      private final Supplier<Pair<Long, Long>> vmStats = memoize(OpenBsdCentralProcessor::queryVmStats,
55              defaultExpiration());
56      private static final Pattern DMESG_CPU = Pattern.compile("cpu(\\d+): smt (\\d+), core (\\d+), package (\\d+)");
57  
58      @Override
59      protected ProcessorIdentifier queryProcessorId() {
60          String cpuVendor = OpenBsdSysctlUtil.sysctl("machdep.cpuvendor", "");
61          int[] mib = new int[2];
62          mib[0] = CTL_HW;
63          mib[1] = HW_MODEL;
64          String cpuName = OpenBsdSysctlUtil.sysctl(mib, "");
65          // CPUID: first 32 bits is cpufeature, last 32 bits is cpuid
66          int cpuid = ParseUtil.hexStringToInt(OpenBsdSysctlUtil.sysctl("machdep.cpuid", ""), 0);
67          int cpufeature = ParseUtil.hexStringToInt(OpenBsdSysctlUtil.sysctl("machdep.cpufeature", ""), 0);
68          Triplet<Integer, Integer, Integer> cpu = cpuidToFamilyModelStepping(cpuid);
69          String cpuFamily = cpu.getA().toString();
70          String cpuModel = cpu.getB().toString();
71          String cpuStepping = cpu.getC().toString();
72          long cpuFreq = ParseUtil.parseHertz(cpuName);
73          if (cpuFreq < 0) {
74              cpuFreq = queryMaxFreq();
75          }
76          mib[1] = HW_MACHINE;
77          String machine = OpenBsdSysctlUtil.sysctl(mib, "");
78          boolean cpu64bit = machine != null && machine.contains("64")
79                  || ExecutingCommand.getFirstAnswer("uname -m").trim().contains("64");
80          String processorID = String.format(Locale.ROOT, "%08x%08x", cpufeature, cpuid);
81  
82          return new ProcessorIdentifier(cpuVendor, cpuName, cpuFamily, cpuModel, cpuStepping, processorID, cpu64bit,
83                  cpuFreq);
84      }
85  
86      private static Triplet<Integer, Integer, Integer> cpuidToFamilyModelStepping(int cpuid) {
87          // family is bits 27:20 | 11:8
88          int family = cpuid >> 16 & 0xff0 | cpuid >> 8 & 0xf;
89          // model is bits 19:16 | 7:4
90          int model = cpuid >> 12 & 0xf0 | cpuid >> 4 & 0xf;
91          // stepping is bits 3:0
92          int stepping = cpuid & 0xf;
93          return new Triplet<>(family, model, stepping);
94      }
95  
96      @Override
97      protected long[] queryCurrentFreq() {
98          long[] freq = new long[1];
99          int[] mib = new int[2];
100         mib[0] = CTL_HW;
101         mib[1] = HW_CPUSPEED;
102         freq[0] = OpenBsdSysctlUtil.sysctl(mib, 0L) * 1_000_000L;
103         return freq;
104     }
105 
106     @Override
107     protected Quartet<List<LogicalProcessor>, List<PhysicalProcessor>, List<ProcessorCache>, List<String>> initProcessorCounts() {
108         // Iterate dmesg, look for lines:
109         // cpu0: smt 0, core 0, package 0
110         // cpu1: smt 0, core 1, package 0
111         Map<Integer, Integer> coreMap = new HashMap<>();
112         Map<Integer, Integer> packageMap = new HashMap<>();
113         for (String line : ExecutingCommand.runNative("dmesg")) {
114             Matcher m = DMESG_CPU.matcher(line);
115             if (m.matches()) {
116                 int cpu = ParseUtil.parseIntOrDefault(m.group(1), 0);
117                 coreMap.put(cpu, ParseUtil.parseIntOrDefault(m.group(3), 0));
118                 packageMap.put(cpu, ParseUtil.parseIntOrDefault(m.group(4), 0));
119             }
120         }
121         // native call seems to fail here, use fallback
122         int logicalProcessorCount = OpenBsdSysctlUtil.sysctl("hw.ncpuonline", 1);
123         // If we found more procs in dmesg, update
124         if (logicalProcessorCount < coreMap.keySet().size()) {
125             logicalProcessorCount = coreMap.keySet().size();
126         }
127         List<LogicalProcessor> logProcs = new ArrayList<>(logicalProcessorCount);
128         for (int i = 0; i < logicalProcessorCount; i++) {
129             logProcs.add(new LogicalProcessor(i, coreMap.getOrDefault(i, 0), packageMap.getOrDefault(i, 0)));
130         }
131         Map<Integer, String> cpuMap = new HashMap<>();
132         // cpu0 at mainbus0 mpidr 0: ARM Cortex-A7 r0p5
133         // but NOT: cpu0 at mainbus0: apid 0 (boot processor)
134         // cpu0: AMD GX-412TC SOC, 998.28 MHz, 16-30-01
135         // cpu0: AMD EPYC 7313P 16-Core Processor, 2994.74 MHz, 19-01-01
136         // cpu0: Intel(R) Celeron(R) N4000 CPU @ 1.10GHz, 2491.67 MHz, 06-7a-01
137         Pattern p = Pattern.compile("cpu(\\\\d+).*: ((ARM|AMD|Intel|Apple).+)");
138 
139         Set<ProcessorCache> caches = new HashSet<>();
140         // cpu0: 48KB 64b/line 12-way D-cache, 32KB 64b/line 8-way I-cache,
141         // ... 1MB 64b/line 10-way L2 cache, 24MB 64b/line 12-way L3 cache
142         // cpu0: 32KB 64b/line 8-way D-cache, 64KB 64b/line 4-way I-cache,
143         // ... 512KB 64b/line 8-way L2 cache, 4MB 64b/line 16-way L3 cache
144         // cpu0: 32KB 64b/line 2-way I-cache, 32KB 64b/line 8-way D-cache,
145         // ... 2MB 64b/line 16-way L2 cache
146         // cpu0: 32KB 64b/line 8-way I-cache, 32KB 64b/line 8-way D-cache,
147         // ... 512KB 64b/line 8-way L2 cache
148         // cpu0: 256KB 64b/line disabled L2 cache
149         // cpu0: 1MB 64b/line 16-way L2 cache
150         // --- Mac M1 example ---
151         // shows different for icestorm/firestrorm and multiple lines for L1 vs. L2
152         // cpu0 at mainbus0 mpidr 0: Apple Icestorm Pro r2p0
153         // cpu0: 128KB 64b/line 8-way L1 VIPT I-cache, 64KB 64b/line 8-way L1 D-cache
154         // cpu0: 4096KB 128b/line 16-way L2 cache
155         // cpu2 at mainbus0 mpidr 10100: Apple Firestorm Pro r2p0
156         // cpu2: 192KB 64b/line 6-way L1 VIPT I-cache, 128KB 64b/line 8-way L1 D-cache
157         // cpu2: 12288KB 128b/line 12-way L2 cache
158         // --- BIG:little example ---
159         // shows different for A53/A72 and multiple lines for L1 vs. L2
160         // cpu0 at mainbus0 mpidr 0: ARM Cortex-A53 r0p4
161         // cpu0: 32KB 64b/line 2-way L1 VIPT I-cache, 32KB 64b/line 4-way L1 D-cache
162         // cpu0: 512KB 64b/line 16-way L2 cache
163         // cpu4 at mainbus0 mpidr 100: ARM Cortex-A72 r0p2
164         // cpu4: 48KB 64b/line 3-way L1 PIPT I-cache, 32KB 64b/line 2-way L1 D-cache
165         // cpu4: 1024KB 64b/line 16-way L2 cache
166         Pattern q = Pattern.compile("cpu(\\\\d+).*: (.+(I-|D-|L\\d+\\s)cache)");
167         Set<String> featureFlags = new LinkedHashSet<>();
168         for (String s : ExecutingCommand.runNative("dmesg")) {
169             Matcher m = p.matcher(s);
170             if (m.matches()) {
171                 int coreId = ParseUtil.parseIntOrDefault(m.group(1), 0);
172                 cpuMap.put(coreId, m.group(2).trim());
173             } else {
174                 Matcher n = q.matcher(s);
175                 if (n.matches()) {
176                     for (String cacheStr : n.group(1).split(",")) {
177                         ProcessorCache cache = parseCacheStr(cacheStr);
178                         if (cache != null) {
179                             caches.add(cache);
180                         }
181                     }
182                 }
183             }
184             if (s.startsWith("cpu")) {
185                 String[] ss = s.trim().split(": ");
186                 if (ss.length == 2 && ss[1].split(",").length > 3) {
187                     featureFlags.add(ss[1]);
188                 }
189             }
190         }
191         List<PhysicalProcessor> physProcs = cpuMap.isEmpty() ? null : createProcListFromDmesg(logProcs, cpuMap);
192         return new Quartet<>(logProcs, physProcs, orderedProcCaches(caches), new ArrayList<>(featureFlags));
193     }
194 
195     private ProcessorCache parseCacheStr(String cacheStr) {
196         String[] split = ParseUtil.whitespaces.split(cacheStr);
197         if (split.length > 3) {
198             switch (split[split.length - 1]) {
199             case "I-cache":
200                 return new ProcessorCache(1, ParseUtil.getFirstIntValue(split[2]), ParseUtil.getFirstIntValue(split[1]),
201                         ParseUtil.parseDecimalMemorySizeToBinary(split[0]), Type.INSTRUCTION);
202             case "D-cache":
203                 return new ProcessorCache(1, ParseUtil.getFirstIntValue(split[2]), ParseUtil.getFirstIntValue(split[1]),
204                         ParseUtil.parseDecimalMemorySizeToBinary(split[0]), Type.DATA);
205             default:
206                 return new ProcessorCache(ParseUtil.getFirstIntValue(split[3]), ParseUtil.getFirstIntValue(split[2]),
207                         ParseUtil.getFirstIntValue(split[1]), ParseUtil.parseDecimalMemorySizeToBinary(split[0]),
208                         Type.UNIFIED);
209             }
210         }
211         return null;
212     }
213 
214     /**
215      * Get number of context switches
216      *
217      * @return The context switches
218      */
219     @Override
220     protected long queryContextSwitches() {
221         return vmStats.get().getA();
222     }
223 
224     /**
225      * Get number of interrupts
226      *
227      * @return The interrupts
228      */
229     @Override
230     protected long queryInterrupts() {
231         return vmStats.get().getB();
232     }
233 
234     private static Pair<Long, Long> queryVmStats() {
235         long contextSwitches = 0L;
236         long interrupts = 0L;
237         List<String> vmstat = ExecutingCommand.runNative("vmstat -s");
238         for (String line : vmstat) {
239             if (line.endsWith("cpu context switches")) {
240                 contextSwitches = ParseUtil.getFirstIntValue(line);
241             } else if (line.endsWith("interrupts")) {
242                 interrupts = ParseUtil.getFirstIntValue(line);
243             }
244         }
245         return new Pair<>(contextSwitches, interrupts);
246     }
247 
248     /**
249      * Get the system CPU load ticks
250      *
251      * @return The system CPU load ticks
252      */
253     @Override
254     protected long[] querySystemCpuLoadTicks() {
255         long[] ticks = new long[TickType.values().length];
256         int[] mib = new int[2];
257         mib[0] = CTL_KERN;
258         mib[1] = KERN_CPTIME;
259         try (Memory m = OpenBsdSysctlUtil.sysctl(mib)) {
260             // array of 5 or 6 native longs
261             long[] cpuTicks = cpTimeToTicks(m, false);
262             if (cpuTicks.length >= 5) {
263                 ticks[TickType.USER.getIndex()] = cpuTicks[CP_USER];
264                 ticks[TickType.NICE.getIndex()] = cpuTicks[CP_NICE];
265                 ticks[TickType.SYSTEM.getIndex()] = cpuTicks[CP_SYS];
266                 int offset = cpuTicks.length > 5 ? 1 : 0;
267                 ticks[TickType.IRQ.getIndex()] = cpuTicks[CP_INTR + offset];
268                 ticks[TickType.IDLE.getIndex()] = cpuTicks[CP_IDLE + offset];
269             }
270         }
271         return ticks;
272     }
273 
274     /**
275      * Get the processor CPU load ticks
276      *
277      * @return The processor CPU load ticks
278      */
279     @Override
280     protected long[][] queryProcessorCpuLoadTicks() {
281         long[][] ticks = new long[getLogicalProcessorCount()][TickType.values().length];
282         int[] mib = new int[3];
283         mib[0] = CTL_KERN;
284         mib[1] = KERN_CPTIME2;
285         for (int cpu = 0; cpu < getLogicalProcessorCount(); cpu++) {
286             mib[2] = cpu;
287             try (Memory m = OpenBsdSysctlUtil.sysctl(mib)) {
288                 // array of 5 or 6 longs
289                 long[] cpuTicks = cpTimeToTicks(m, true);
290                 if (cpuTicks.length >= 5) {
291                     ticks[cpu][TickType.USER.getIndex()] = cpuTicks[CP_USER];
292                     ticks[cpu][TickType.NICE.getIndex()] = cpuTicks[CP_NICE];
293                     ticks[cpu][TickType.SYSTEM.getIndex()] = cpuTicks[CP_SYS];
294                     int offset = cpuTicks.length > 5 ? 1 : 0;
295                     ticks[cpu][TickType.IRQ.getIndex()] = cpuTicks[CP_INTR + offset];
296                     ticks[cpu][TickType.IDLE.getIndex()] = cpuTicks[CP_IDLE + offset];
297                 }
298             }
299         }
300         return ticks;
301     }
302 
303     /**
304      * Parse memory buffer returned from sysctl kern.cptime or kern.cptime2 to an array of 5 or 6 longs depending on
305      * version.
306      * <p>
307      * Versions 6.4 and later have a 6-element array while earlier versions have only 5 elements. Additionally
308      * kern.cptime uses a native-sized long (32- or 64-bit) value while kern.cptime2 is always a 64-bit value.
309      *
310      * @param m          A buffer containing the array.
311      * @param force64bit True if the buffer is filled with 64-bit longs, false if native long sized values
312      * @return The array
313      */
314     private static long[] cpTimeToTicks(Memory m, boolean force64bit) {
315         long longBytes = force64bit ? 8L : Native.LONG_SIZE;
316         int arraySize = m == null ? 0 : (int) (m.size() / longBytes);
317         if (force64bit && m != null) {
318             return m.getLongArray(0, arraySize);
319         }
320         long[] ticks = new long[arraySize];
321         for (int i = 0; i < arraySize; i++) {
322             ticks[i] = m.getNativeLong(i * longBytes).longValue();
323         }
324         return ticks;
325     }
326 
327     /**
328      * Returns the system load average for the number of elements specified, up to 3, representing 1, 5, and 15 minutes.
329      * The system load average is the sum of the number of runnable entities queued to the available processors and the
330      * number of runnable entities running on the available processors averaged over a period of time. The way in which
331      * the load average is calculated is operating system specific but is typically a damped time-dependent average. If
332      * the load average is not available, a negative value is returned. This method is designed to provide a hint about
333      * the system load and may be queried frequently.
334      * <p>
335      * The load average may be unavailable on some platforms (e.g., Windows) where it is expensive to implement this
336      * method.
337      *
338      * @param nelem Number of elements to return.
339      * @return an array of the system load averages for 1, 5, and 15 minutes with the size of the array specified by
340      *         nelem; or negative values if not available.
341      */
342     @Override
343     public double[] getSystemLoadAverage(int nelem) {
344         if (nelem < 1 || nelem > 3) {
345             throw new IllegalArgumentException("Must include from one to three elements.");
346         }
347         double[] average = new double[nelem];
348         int retval = OpenBsdLibc.INSTANCE.getloadavg(average, nelem);
349         if (retval < nelem) {
350             Arrays.fill(average, -1d);
351         }
352         return average;
353     }
354 }