1
2
3
4
5 package oshi.hardware.platform.mac;
6
7 import static oshi.util.Memoizer.memoize;
8
9 import java.nio.charset.StandardCharsets;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.List;
15 import java.util.Locale;
16 import java.util.Map;
17 import java.util.Objects;
18 import java.util.Set;
19 import java.util.function.Supplier;
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22 import java.util.stream.Collectors;
23 import java.util.stream.Stream;
24
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import com.sun.jna.Native;
29 import com.sun.jna.platform.mac.IOKit.IOIterator;
30 import com.sun.jna.platform.mac.IOKit.IORegistryEntry;
31 import com.sun.jna.platform.mac.IOKitUtil;
32 import com.sun.jna.platform.mac.SystemB;
33
34 import oshi.annotation.concurrent.ThreadSafe;
35 import oshi.hardware.CentralProcessor.ProcessorCache.Type;
36 import oshi.hardware.common.AbstractCentralProcessor;
37 import oshi.jna.ByRef.CloseableIntByReference;
38 import oshi.jna.ByRef.CloseablePointerByReference;
39 import oshi.jna.Struct.CloseableHostCpuLoadInfo;
40 import oshi.util.ExecutingCommand;
41 import oshi.util.FormatUtil;
42 import oshi.util.ParseUtil;
43 import oshi.util.Util;
44 import oshi.util.platform.mac.SysctlUtil;
45 import oshi.util.tuples.Quartet;
46
47
48
49
50 @ThreadSafe
51 final class MacCentralProcessor extends AbstractCentralProcessor {
52
53 private static final Logger LOG = LoggerFactory.getLogger(MacCentralProcessor.class);
54
55 private static final Set<String> ARM_P_CORES = Stream
56 .of("apple,firestorm arm,v8", "apple,avalanche arm,v8", "apple,everest arm,v8").collect(Collectors.toSet());
57
58 private static final int ARM_CPUTYPE = 0x0100000C;
59 private static final int M1_CPUFAMILY = 0x1b588bb3;
60 private static final int M2_CPUFAMILY = 0xda33d83d;
61 private static final int M3_CPUFAMILY = 0x8765edea;
62 private static final long DEFAULT_FREQUENCY = 2_400_000_000L;
63 private static final Pattern CPU_N = Pattern.compile("^cpu(\\d+)");
64
65 private final Supplier<String> vendor = memoize(MacCentralProcessor::platformExpert);
66 private final boolean isArmCpu = isArmCpu();
67
68
69
70 private long performanceCoreFrequency = DEFAULT_FREQUENCY;
71 private long efficiencyCoreFrequency = DEFAULT_FREQUENCY;
72
73 @Override
74 protected ProcessorIdentifier queryProcessorId() {
75 String cpuName = SysctlUtil.sysctl("machdep.cpu.brand_string", "");
76 String cpuVendor;
77 String cpuStepping;
78 String cpuModel;
79 String cpuFamily;
80 String processorID;
81
82
83 if (cpuName.startsWith("Apple")) {
84
85 cpuVendor = vendor.get();
86 cpuStepping = "0";
87 cpuModel = "0";
88 int type;
89 int family;
90 if (isArmCpu) {
91 type = ARM_CPUTYPE;
92 int mSeries = ParseUtil.getFirstIntValue(cpuName);
93 switch (mSeries) {
94 case 2:
95 family = M2_CPUFAMILY;
96 break;
97 case 3:
98 family = M3_CPUFAMILY;
99 break;
100 default:
101
102 family = M1_CPUFAMILY;
103 }
104 } else {
105 type = SysctlUtil.sysctl("hw.cputype", 0);
106 family = SysctlUtil.sysctl("hw.cpufamily", 0);
107 }
108
109 cpuFamily = String.format(Locale.ROOT, "0x%08x", family);
110
111 processorID = String.format(Locale.ROOT, "%08x%08x", type, family);
112 } else {
113
114 cpuVendor = SysctlUtil.sysctl("machdep.cpu.vendor", "");
115 int i = SysctlUtil.sysctl("machdep.cpu.stepping", -1);
116 cpuStepping = i < 0 ? "" : Integer.toString(i);
117 i = SysctlUtil.sysctl("machdep.cpu.model", -1);
118 cpuModel = i < 0 ? "" : Integer.toString(i);
119 i = SysctlUtil.sysctl("machdep.cpu.family", -1);
120 cpuFamily = i < 0 ? "" : Integer.toString(i);
121 long processorIdBits = 0L;
122 processorIdBits |= SysctlUtil.sysctl("machdep.cpu.signature", 0);
123 processorIdBits |= (SysctlUtil.sysctl("machdep.cpu.feature_bits", 0L) & 0xffffffff) << 32;
124 processorID = String.format(Locale.ROOT, "%016x", processorIdBits);
125 }
126 if (isArmCpu) {
127 calculateNominalFrequencies();
128 }
129 long cpuFreq = isArmCpu ? performanceCoreFrequency : SysctlUtil.sysctl("hw.cpufrequency", 0L);
130 boolean cpu64bit = SysctlUtil.sysctl("hw.cpu64bit_capable", 0) != 0;
131
132 return new ProcessorIdentifier(cpuVendor, cpuName, cpuFamily, cpuModel, cpuStepping, processorID, cpu64bit,
133 cpuFreq);
134 }
135
136 @Override
137 protected Quartet<List<LogicalProcessor>, List<PhysicalProcessor>, List<ProcessorCache>, List<String>> initProcessorCounts() {
138 int logicalProcessorCount = SysctlUtil.sysctl("hw.logicalcpu", 1);
139 int physicalProcessorCount = SysctlUtil.sysctl("hw.physicalcpu", 1);
140 int physicalPackageCount = SysctlUtil.sysctl("hw.packages", 1);
141 List<LogicalProcessor> logProcs = new ArrayList<>(logicalProcessorCount);
142 Set<Integer> pkgCoreKeys = new HashSet<>();
143 for (int i = 0; i < logicalProcessorCount; i++) {
144 int coreId = i * physicalProcessorCount / logicalProcessorCount;
145 int pkgId = i * physicalPackageCount / logicalProcessorCount;
146 logProcs.add(new LogicalProcessor(i, coreId, pkgId));
147 pkgCoreKeys.add((pkgId << 16) + coreId);
148 }
149 Map<Integer, String> compatMap = queryCompatibleStrings();
150 int perflevels = SysctlUtil.sysctl("hw.nperflevels", 1, false);
151 List<PhysicalProcessor> physProcs = pkgCoreKeys.stream().sorted().map(k -> {
152 String compat = compatMap.getOrDefault(k, "").toLowerCase(Locale.ROOT);
153
154
155
156
157 int efficiency = ARM_P_CORES.contains(compat) ? 1 : 0;
158 return new PhysicalProcessor(k >> 16, k & 0xffff, efficiency, compat);
159 }).collect(Collectors.toList());
160 List<ProcessorCache> caches = orderedProcCaches(getCacheValues(perflevels));
161 List<String> featureFlags = getFeatureFlagsFromSysctl();
162 return new Quartet<>(logProcs, physProcs, caches, featureFlags);
163 }
164
165 private Set<ProcessorCache> getCacheValues(int perflevels) {
166 int linesize = (int) SysctlUtil.sysctl("hw.cachelinesize", 0L);
167 int l1associativity = SysctlUtil.sysctl("machdep.cpu.cache.L1_associativity", 0, false);
168 int l2associativity = SysctlUtil.sysctl("machdep.cpu.cache.L2_associativity", 0, false);
169 Set<ProcessorCache> caches = new HashSet<>();
170 for (int i = 0; i < perflevels; i++) {
171 int size = SysctlUtil.sysctl("hw.perflevel" + i + ".l1icachesize", 0, false);
172 if (size > 0) {
173 caches.add(new ProcessorCache(1, l1associativity, linesize, size, Type.INSTRUCTION));
174 }
175 size = SysctlUtil.sysctl("hw.perflevel" + i + ".l1dcachesize", 0, false);
176 if (size > 0) {
177 caches.add(new ProcessorCache(1, l1associativity, linesize, size, Type.DATA));
178 }
179 size = SysctlUtil.sysctl("hw.perflevel" + i + ".l2cachesize", 0, false);
180 if (size > 0) {
181 caches.add(new ProcessorCache(2, l2associativity, linesize, size, Type.UNIFIED));
182 }
183 size = SysctlUtil.sysctl("hw.perflevel" + i + ".l3cachesize", 0, false);
184 if (size > 0) {
185 caches.add(new ProcessorCache(3, 0, linesize, size, Type.UNIFIED));
186 }
187 }
188 return caches;
189 }
190
191 private List<String> getFeatureFlagsFromSysctl() {
192 List<String> x86Features = Arrays.asList("features", "extfeatures", "leaf7_features").stream().map(f -> {
193 String key = "machdep.cpu." + f;
194 String features = SysctlUtil.sysctl(key, "", false);
195 return Util.isBlank(features) ? null : (key + ": " + features);
196 }).filter(Objects::nonNull).collect(Collectors.toList());
197 return x86Features.isEmpty() ? ExecutingCommand.runNative("sysctl -a hw.optional") : x86Features;
198 }
199
200 @Override
201 public long[] querySystemCpuLoadTicks() {
202 long[] ticks = new long[TickType.values().length];
203 int machPort = SystemB.INSTANCE.mach_host_self();
204 try (CloseableHostCpuLoadInfo cpuLoadInfo = new CloseableHostCpuLoadInfo();
205 CloseableIntByReference size = new CloseableIntByReference(cpuLoadInfo.size())) {
206 if (0 != SystemB.INSTANCE.host_statistics(machPort, SystemB.HOST_CPU_LOAD_INFO, cpuLoadInfo, size)) {
207 LOG.error("Failed to get System CPU ticks. Error code: {} ", Native.getLastError());
208 return ticks;
209 }
210
211 ticks[TickType.USER.getIndex()] = cpuLoadInfo.cpu_ticks[SystemB.CPU_STATE_USER];
212 ticks[TickType.NICE.getIndex()] = cpuLoadInfo.cpu_ticks[SystemB.CPU_STATE_NICE];
213 ticks[TickType.SYSTEM.getIndex()] = cpuLoadInfo.cpu_ticks[SystemB.CPU_STATE_SYSTEM];
214 ticks[TickType.IDLE.getIndex()] = cpuLoadInfo.cpu_ticks[SystemB.CPU_STATE_IDLE];
215 }
216
217 return ticks;
218 }
219
220 @Override
221 public long[] queryCurrentFreq() {
222 if (isArmCpu) {
223 Map<Integer, Long> physFreqMap = new HashMap<>();
224 getPhysicalProcessors().stream().forEach(p -> physFreqMap.put(p.getPhysicalProcessorNumber(),
225 p.getEfficiency() > 0 ? performanceCoreFrequency : efficiencyCoreFrequency));
226 return getLogicalProcessors().stream().map(LogicalProcessor::getPhysicalProcessorNumber)
227 .map(p -> physFreqMap.getOrDefault(p, performanceCoreFrequency)).mapToLong(f -> f).toArray();
228 }
229 return new long[] { getProcessorIdentifier().getVendorFreq() };
230 }
231
232 @Override
233 public long queryMaxFreq() {
234 if (isArmCpu) {
235 return performanceCoreFrequency;
236 }
237 return SysctlUtil.sysctl("hw.cpufrequency_max", getProcessorIdentifier().getVendorFreq());
238 }
239
240 @Override
241 public double[] getSystemLoadAverage(int nelem) {
242 if (nelem < 1 || nelem > 3) {
243 throw new IllegalArgumentException("Must include from one to three elements.");
244 }
245 double[] average = new double[nelem];
246 int retval = SystemB.INSTANCE.getloadavg(average, nelem);
247 if (retval < nelem) {
248 Arrays.fill(average, -1d);
249 }
250 return average;
251 }
252
253 @Override
254 public long[][] queryProcessorCpuLoadTicks() {
255 long[][] ticks = new long[getLogicalProcessorCount()][TickType.values().length];
256
257 int machPort = SystemB.INSTANCE.mach_host_self();
258 try (CloseableIntByReference procCount = new CloseableIntByReference();
259 CloseablePointerByReference procCpuLoadInfo = new CloseablePointerByReference();
260 CloseableIntByReference procInfoCount = new CloseableIntByReference()) {
261 if (0 != SystemB.INSTANCE.host_processor_info(machPort, SystemB.PROCESSOR_CPU_LOAD_INFO, procCount,
262 procCpuLoadInfo, procInfoCount)) {
263 LOG.error("Failed to update CPU Load. Error code: {}", Native.getLastError());
264 return ticks;
265 }
266
267 int[] cpuTicks = procCpuLoadInfo.getValue().getIntArray(0, procInfoCount.getValue());
268 for (int cpu = 0; cpu < procCount.getValue(); cpu++) {
269 int offset = cpu * SystemB.CPU_STATE_MAX;
270 ticks[cpu][TickType.USER.getIndex()] = FormatUtil
271 .getUnsignedInt(cpuTicks[offset + SystemB.CPU_STATE_USER]);
272 ticks[cpu][TickType.NICE.getIndex()] = FormatUtil
273 .getUnsignedInt(cpuTicks[offset + SystemB.CPU_STATE_NICE]);
274 ticks[cpu][TickType.SYSTEM.getIndex()] = FormatUtil
275 .getUnsignedInt(cpuTicks[offset + SystemB.CPU_STATE_SYSTEM]);
276 ticks[cpu][TickType.IDLE.getIndex()] = FormatUtil
277 .getUnsignedInt(cpuTicks[offset + SystemB.CPU_STATE_IDLE]);
278 }
279 }
280 return ticks;
281 }
282
283 @Override
284 public long queryContextSwitches() {
285
286
287
288 return 0L;
289 }
290
291 @Override
292 public long queryInterrupts() {
293
294
295
296 return 0L;
297 }
298
299 private static String platformExpert() {
300 String manufacturer = null;
301 IORegistryEntry platformExpert = IOKitUtil.getMatchingService("IOPlatformExpertDevice");
302 if (platformExpert != null) {
303
304 byte[] data = platformExpert.getByteArrayProperty("manufacturer");
305 if (data != null) {
306 manufacturer = Native.toString(data, StandardCharsets.UTF_8);
307 }
308 platformExpert.release();
309 }
310 return Util.isBlank(manufacturer) ? "Apple Inc." : manufacturer;
311 }
312
313
314
315 private static Map<Integer, String> queryCompatibleStrings() {
316 Map<Integer, String> compatibleStrMap = new HashMap<>();
317
318
319 IOIterator iter = IOKitUtil.getMatchingServices("IOPlatformDevice");
320 if (iter != null) {
321 IORegistryEntry cpu = iter.next();
322 while (cpu != null) {
323 Matcher m = CPU_N.matcher(cpu.getName().toLowerCase(Locale.ROOT));
324 if (m.matches()) {
325 int procId = ParseUtil.parseIntOrDefault(m.group(1), 0);
326
327 byte[] data = cpu.getByteArrayProperty("compatible");
328 if (data != null) {
329
330
331 compatibleStrMap.put(procId,
332 new String(data, StandardCharsets.UTF_8).replace('\0', ' ').trim());
333 }
334 }
335 cpu.release();
336 cpu = iter.next();
337 }
338 iter.release();
339 }
340 return compatibleStrMap;
341 }
342
343
344
345 private boolean isArmCpu() {
346 return getPhysicalProcessors().stream().map(PhysicalProcessor::getIdString).anyMatch(id -> id.contains("arm"));
347 }
348
349 private void calculateNominalFrequencies() {
350 IOIterator iter = IOKitUtil.getMatchingServices("AppleARMIODevice");
351 if (iter != null) {
352 try {
353 IORegistryEntry device = iter.next();
354 try {
355 while (device != null) {
356 if (device.getName().equalsIgnoreCase("pmgr")) {
357 performanceCoreFrequency = getMaxFreqFromByteArray(
358 device.getByteArrayProperty("voltage-states5-sram"));
359 efficiencyCoreFrequency = getMaxFreqFromByteArray(
360 device.getByteArrayProperty("voltage-states1-sram"));
361 return;
362 }
363 device.release();
364 device = iter.next();
365 }
366 } finally {
367 if (device != null) {
368 device.release();
369 }
370 }
371 } finally {
372 iter.release();
373 }
374 }
375 }
376
377 private long getMaxFreqFromByteArray(byte[] data) {
378
379 if (data != null && data.length >= 8) {
380 byte[] freqData = Arrays.copyOfRange(data, data.length - 8, data.length - 4);
381 return ParseUtil.byteArrayToLong(freqData, 4, false);
382 }
383 return DEFAULT_FREQUENCY;
384 }
385 }