View Javadoc
1   /*
2    * Copyright 2016-2024 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.hardware.platform.unix.solaris;
6   
7   import static oshi.software.os.unix.solaris.SolarisOperatingSystem.HAS_KSTAT2;
8   
9   import java.util.ArrayList;
10  import java.util.Arrays;
11  import java.util.Collections;
12  import java.util.HashMap;
13  import java.util.List;
14  import java.util.Locale;
15  import java.util.Map;
16  import java.util.regex.Matcher;
17  import java.util.regex.Pattern;
18  
19  import com.sun.jna.platform.unix.solaris.LibKstat.Kstat;
20  
21  import oshi.annotation.concurrent.ThreadSafe;
22  import oshi.hardware.common.AbstractCentralProcessor;
23  import oshi.jna.platform.unix.SolarisLibc;
24  import oshi.util.ExecutingCommand;
25  import oshi.util.ParseUtil;
26  import oshi.util.platform.unix.solaris.KstatUtil;
27  import oshi.util.platform.unix.solaris.KstatUtil.KstatChain;
28  import oshi.util.tuples.Quartet;
29  
30  /**
31   * A CPU
32   */
33  @ThreadSafe
34  final class SolarisCentralProcessor extends AbstractCentralProcessor {
35  
36      private static final String KSTAT_SYSTEM_CPU = "kstat:/system/cpu/";
37      private static final String INFO = "/info";
38      private static final String SYS = "/sys";
39  
40      private static final String KSTAT_PM_CPU = "kstat:/pm/cpu/";
41      private static final String PSTATE = "/pstate";
42  
43      private static final String CPU_INFO = "cpu_info";
44  
45      @Override
46      protected ProcessorIdentifier queryProcessorId() {
47          boolean cpu64bit = "64".equals(ExecutingCommand.getFirstAnswer("isainfo -b").trim());
48          if (HAS_KSTAT2) {
49              // Use Kstat2 implementation
50              return queryProcessorId2(cpu64bit);
51          }
52          String cpuVendor = "";
53          String cpuName = "";
54          String cpuFamily = "";
55          String cpuModel = "";
56          String cpuStepping = "";
57          long cpuFreq = 0L;
58  
59          // Get first result
60          try (KstatChain kc = KstatUtil.openChain()) {
61              Kstat ksp = kc.lookup(CPU_INFO, -1, null);
62              // Set values
63              if (ksp != null && kc.read(ksp)) {
64                  cpuVendor = KstatUtil.dataLookupString(ksp, "vendor_id");
65                  cpuName = KstatUtil.dataLookupString(ksp, "brand");
66                  cpuFamily = KstatUtil.dataLookupString(ksp, "family");
67                  cpuModel = KstatUtil.dataLookupString(ksp, "model");
68                  cpuStepping = KstatUtil.dataLookupString(ksp, "stepping");
69                  cpuFreq = KstatUtil.dataLookupLong(ksp, "clock_MHz") * 1_000_000L;
70              }
71          }
72          String processorID = getProcessorID(cpuStepping, cpuModel, cpuFamily);
73  
74          return new ProcessorIdentifier(cpuVendor, cpuName, cpuFamily, cpuModel, cpuStepping, processorID, cpu64bit,
75                  cpuFreq);
76      }
77  
78      private static ProcessorIdentifier queryProcessorId2(boolean cpu64bit) {
79          Object[] results = KstatUtil.queryKstat2(KSTAT_SYSTEM_CPU + "0" + INFO, "vendor_id", "brand", "family", "model",
80                  "stepping", "clock_MHz");
81  
82          String cpuVendor = results[0] == null ? "" : (String) results[0];
83          String cpuName = results[1] == null ? "" : (String) results[1];
84          String cpuFamily = results[2] == null ? "" : results[2].toString();
85          String cpuModel = results[3] == null ? "" : results[3].toString();
86          String cpuStepping = results[4] == null ? "" : results[4].toString();
87          long cpuFreq = results[5] == null ? 0L : (long) results[5];
88  
89          String processorID = getProcessorID(cpuStepping, cpuModel, cpuFamily);
90          return new ProcessorIdentifier(cpuVendor, cpuName, cpuFamily, cpuModel, cpuStepping, processorID, cpu64bit,
91                  cpuFreq);
92      }
93  
94      @Override
95      protected Quartet<List<LogicalProcessor>, List<PhysicalProcessor>, List<ProcessorCache>, List<String>> initProcessorCounts() {
96          List<LogicalProcessor> logProcs;
97          Map<Integer, Integer> numaNodeMap = mapNumaNodes();
98          if (HAS_KSTAT2) {
99              // Use Kstat2 implementation
100             logProcs = initProcessorCounts2(numaNodeMap);
101         } else {
102             logProcs = new ArrayList<>();
103             try (KstatChain kc = KstatUtil.openChain()) {
104                 List<Kstat> kstats = kc.lookupAll(CPU_INFO, -1, null);
105 
106                 for (Kstat ksp : kstats) {
107                     if (ksp != null && kc.read(ksp)) {
108                         int procId = logProcs.size(); // 0-indexed
109                         String chipId = KstatUtil.dataLookupString(ksp, "chip_id");
110                         String coreId = KstatUtil.dataLookupString(ksp, "core_id");
111                         LogicalProcessor logProc = new LogicalProcessor(procId, ParseUtil.parseIntOrDefault(coreId, 0),
112                                 ParseUtil.parseIntOrDefault(chipId, 0), numaNodeMap.getOrDefault(procId, 0));
113                         logProcs.add(logProc);
114                     }
115                 }
116             }
117         }
118         if (logProcs.isEmpty()) {
119             logProcs.add(new LogicalProcessor(0, 0, 0));
120         }
121         Map<Integer, String> dmesg = new HashMap<>();
122         // Jan 9 14:04:28 solaris unix: [ID 950921 kern.info] cpu0: Intel(r) Celeron(r)
123         // CPU J3455 @ 1.50GHz
124         // but NOT: Jan 9 14:04:28 solaris unix: [ID 950921 kern.info] cpu0: x86 (chipid
125         // 0x0 GenuineIntel 506C9 family 6 model 92 step 9 clock 1500 MHz)
126         Pattern p = Pattern.compile(".* cpu(\\\\d+): ((ARM|AMD|Intel).+)");
127         for (String s : ExecutingCommand.runNative("dmesg")) {
128             Matcher m = p.matcher(s);
129             if (m.matches()) {
130                 int coreId = ParseUtil.parseIntOrDefault(m.group(1), 0);
131                 dmesg.put(coreId, m.group(2).trim());
132             }
133         }
134         if (dmesg.isEmpty()) {
135             return new Quartet<>(logProcs, null, null, Collections.emptyList());
136         }
137         List<String> featureFlags = ExecutingCommand.runNative("isainfo -x");
138         return new Quartet<>(logProcs, createProcListFromDmesg(logProcs, dmesg), null, featureFlags);
139     }
140 
141     private static List<LogicalProcessor> initProcessorCounts2(Map<Integer, Integer> numaNodeMap) {
142         List<LogicalProcessor> logProcs = new ArrayList<>();
143 
144         List<Object[]> results = KstatUtil.queryKstat2List(KSTAT_SYSTEM_CPU, INFO, "chip_id", "core_id");
145         for (Object[] result : results) {
146             int procId = logProcs.size();
147             long chipId = result[0] == null ? 0L : (long) result[0];
148             long coreId = result[1] == null ? 0L : (long) result[1];
149             LogicalProcessor logProc = new LogicalProcessor(procId, (int) coreId, (int) chipId,
150                     numaNodeMap.getOrDefault(procId, 0));
151             logProcs.add(logProc);
152         }
153         if (logProcs.isEmpty()) {
154             logProcs.add(new LogicalProcessor(0, 0, 0));
155         }
156         return logProcs;
157     }
158 
159     private static Map<Integer, Integer> mapNumaNodes() {
160         // Get numa node info from lgrpinfo
161         Map<Integer, Integer> numaNodeMap = new HashMap<>();
162         int lgroup = 0;
163         for (String line : ExecutingCommand.runNative("lgrpinfo -c leaves")) {
164             // Format:
165             // lgroup 0 (root):
166             // CPUs: 0 1
167             // CPUs: 0-7
168             // CPUs: 0-3 6 7 12 13
169             // CPU: 0
170             // CPU: 1
171             if (line.startsWith("lgroup")) {
172                 lgroup = ParseUtil.getFirstIntValue(line);
173             } else if (line.contains("CPUs:") || line.contains("CPU:")) {
174                 for (Integer cpu : ParseUtil.parseHyphenatedIntList(line.split(":")[1])) {
175                     numaNodeMap.put(cpu, lgroup);
176                 }
177             }
178         }
179         return numaNodeMap;
180     }
181 
182     @Override
183     public long[] querySystemCpuLoadTicks() {
184         long[] ticks = new long[TickType.values().length];
185         // Average processor ticks
186         long[][] procTicks = getProcessorCpuLoadTicks();
187         for (int i = 0; i < ticks.length; i++) {
188             for (long[] procTick : procTicks) {
189                 ticks[i] += procTick[i];
190             }
191             ticks[i] /= procTicks.length;
192         }
193         return ticks;
194     }
195 
196     @Override
197     public long[] queryCurrentFreq() {
198         if (HAS_KSTAT2) {
199             // Use Kstat2 implementation
200             return queryCurrentFreq2(getLogicalProcessorCount());
201         }
202         long[] freqs = new long[getLogicalProcessorCount()];
203         Arrays.fill(freqs, -1);
204         try (KstatChain kc = KstatUtil.openChain()) {
205             for (int i = 0; i < freqs.length; i++) {
206                 for (Kstat ksp : kc.lookupAll(CPU_INFO, i, null)) {
207                     if (ksp != null && kc.read(ksp)) {
208                         freqs[i] = KstatUtil.dataLookupLong(ksp, "current_clock_Hz");
209                     }
210                 }
211             }
212         }
213         return freqs;
214     }
215 
216     private static long[] queryCurrentFreq2(int processorCount) {
217         long[] freqs = new long[processorCount];
218         Arrays.fill(freqs, -1);
219 
220         List<Object[]> results = KstatUtil.queryKstat2List(KSTAT_SYSTEM_CPU, INFO, "current_clock_Hz");
221         int cpu = -1;
222         for (Object[] result : results) {
223             if (++cpu >= freqs.length) {
224                 break;
225             }
226             freqs[cpu] = result[0] == null ? -1L : (long) result[0];
227         }
228         return freqs;
229     }
230 
231     @Override
232     public long queryMaxFreq() {
233         if (HAS_KSTAT2) {
234             // Use Kstat2 implementation
235             return queryMaxFreq2();
236         }
237         long max = -1L;
238         try (KstatChain kc = KstatUtil.openChain()) {
239             for (Kstat ksp : kc.lookupAll(CPU_INFO, 0, null)) {
240                 if (kc.read(ksp)) {
241                     String suppFreq = KstatUtil.dataLookupString(ksp, "supported_frequencies_Hz");
242                     if (!suppFreq.isEmpty()) {
243                         for (String s : suppFreq.split(":")) {
244                             long freq = ParseUtil.parseLongOrDefault(s, -1L);
245                             if (max < freq) {
246                                 max = freq;
247                             }
248                         }
249                     }
250                 }
251             }
252         }
253         return max;
254     }
255 
256     private static long queryMaxFreq2() {
257         long max = -1L;
258         List<Object[]> results = KstatUtil.queryKstat2List(KSTAT_PM_CPU, PSTATE, "supported_frequencies");
259         for (Object[] result : results) {
260             for (long freq : result[0] == null ? new long[0] : (long[]) result[0]) {
261                 if (freq > max) {
262                     max = freq;
263                 }
264             }
265         }
266         return max;
267     }
268 
269     @Override
270     public double[] getSystemLoadAverage(int nelem) {
271         if (nelem < 1 || nelem > 3) {
272             throw new IllegalArgumentException("Must include from one to three elements.");
273         }
274         double[] average = new double[nelem];
275         int retval = SolarisLibc.INSTANCE.getloadavg(average, nelem);
276         if (retval < nelem) {
277             for (int i = Math.max(retval, 0); i < average.length; i++) {
278                 average[i] = -1d;
279             }
280         }
281         return average;
282     }
283 
284     @Override
285     public long[][] queryProcessorCpuLoadTicks() {
286         if (HAS_KSTAT2) {
287             // Use Kstat2 implementation
288             return queryProcessorCpuLoadTicks2(getLogicalProcessorCount());
289         }
290         long[][] ticks = new long[getLogicalProcessorCount()][TickType.values().length];
291         int cpu = -1;
292         try (KstatChain kc = KstatUtil.openChain()) {
293             for (Kstat ksp : kc.lookupAll("cpu", -1, "sys")) {
294                 // This is a new CPU
295                 if (++cpu >= ticks.length) {
296                     // Shouldn't happen
297                     break;
298                 }
299                 if (kc.read(ksp)) {
300                     ticks[cpu][TickType.IDLE.getIndex()] = KstatUtil.dataLookupLong(ksp, "cpu_ticks_idle");
301                     ticks[cpu][TickType.SYSTEM.getIndex()] = KstatUtil.dataLookupLong(ksp, "cpu_ticks_kernel");
302                     ticks[cpu][TickType.USER.getIndex()] = KstatUtil.dataLookupLong(ksp, "cpu_ticks_user");
303                 }
304             }
305         }
306         return ticks;
307     }
308 
309     private static long[][] queryProcessorCpuLoadTicks2(int processorCount) {
310         long[][] ticks = new long[processorCount][TickType.values().length];
311         List<Object[]> results = KstatUtil.queryKstat2List(KSTAT_SYSTEM_CPU, SYS, "cpu_ticks_idle", "cpu_ticks_kernel",
312                 "cpu_ticks_user");
313         int cpu = -1;
314         for (Object[] result : results) {
315             if (++cpu >= ticks.length) {
316                 break;
317             }
318             ticks[cpu][TickType.IDLE.getIndex()] = result[0] == null ? 0L : (long) result[0];
319             ticks[cpu][TickType.SYSTEM.getIndex()] = result[1] == null ? 0L : (long) result[1];
320             ticks[cpu][TickType.USER.getIndex()] = result[2] == null ? 0L : (long) result[2];
321         }
322         return ticks;
323     }
324 
325     /**
326      * Fetches the ProcessorID by encoding the stepping, model, family, and feature flags.
327      *
328      * @param stepping The stepping
329      * @param model    The model
330      * @param family   The family
331      * @return The Processor ID string
332      */
333     private static String getProcessorID(String stepping, String model, String family) {
334         List<String> isainfo = ExecutingCommand.runNative("isainfo -v");
335         StringBuilder flags = new StringBuilder();
336         for (String line : isainfo) {
337             if (line.startsWith("32-bit")) {
338                 break;
339             } else if (!line.startsWith("64-bit")) {
340                 flags.append(' ').append(line.trim());
341             }
342         }
343         return createProcessorID(stepping, model, family,
344                 ParseUtil.whitespaces.split(flags.toString().toLowerCase(Locale.ROOT)));
345     }
346 
347     @Override
348     public long queryContextSwitches() {
349         if (HAS_KSTAT2) {
350             // Use Kstat2 implementation
351             return queryContextSwitches2();
352         }
353         long swtch = 0;
354         List<String> kstat = ExecutingCommand.runNative("kstat -p cpu_stat:::/pswitch\\\\|inv_swtch/");
355         for (String s : kstat) {
356             swtch += ParseUtil.parseLastLong(s, 0L);
357         }
358         return swtch;
359     }
360 
361     private static long queryContextSwitches2() {
362         long swtch = 0;
363         List<Object[]> results = KstatUtil.queryKstat2List(KSTAT_SYSTEM_CPU, SYS, "pswitch", "inv_swtch");
364         for (Object[] result : results) {
365             swtch += result[0] == null ? 0L : (long) result[0];
366             swtch += result[1] == null ? 0L : (long) result[1];
367         }
368         return swtch;
369     }
370 
371     @Override
372     public long queryInterrupts() {
373         if (HAS_KSTAT2) {
374             // Use Kstat2 implementation
375             return queryInterrupts2();
376         }
377         long intr = 0;
378         List<String> kstat = ExecutingCommand.runNative("kstat -p cpu_stat:::/intr/");
379         for (String s : kstat) {
380             intr += ParseUtil.parseLastLong(s, 0L);
381         }
382         return intr;
383     }
384 
385     private static long queryInterrupts2() {
386         long intr = 0;
387         List<Object[]> results = KstatUtil.queryKstat2List(KSTAT_SYSTEM_CPU, SYS, "intr");
388         for (Object[] result : results) {
389             intr += result[0] == null ? 0L : (long) result[0];
390         }
391         return intr;
392     }
393 }