1
2
3
4
5 package oshi.software.os.mac;
6
7 import static oshi.software.os.OSProcess.State.INVALID;
8 import static oshi.software.os.OSProcess.State.NEW;
9 import static oshi.software.os.OSProcess.State.OTHER;
10 import static oshi.software.os.OSProcess.State.RUNNING;
11 import static oshi.software.os.OSProcess.State.SLEEPING;
12 import static oshi.software.os.OSProcess.State.STOPPED;
13 import static oshi.software.os.OSProcess.State.WAITING;
14 import static oshi.software.os.OSProcess.State.ZOMBIE;
15 import static oshi.util.Memoizer.memoize;
16
17 import java.nio.charset.StandardCharsets;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.LinkedHashMap;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.Map;
24 import java.util.function.Supplier;
25 import java.util.stream.Collectors;
26
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 import com.sun.jna.Memory;
31 import com.sun.jna.Native;
32 import com.sun.jna.platform.mac.IOKit.IOIterator;
33 import com.sun.jna.platform.mac.IOKit.IORegistryEntry;
34 import com.sun.jna.platform.mac.IOKitUtil;
35 import com.sun.jna.platform.mac.SystemB;
36 import com.sun.jna.platform.mac.SystemB.Group;
37 import com.sun.jna.platform.mac.SystemB.Passwd;
38 import com.sun.jna.platform.unix.LibCAPI.size_t;
39 import com.sun.jna.platform.unix.Resource;
40
41 import oshi.annotation.concurrent.ThreadSafe;
42 import oshi.driver.mac.ThreadInfo;
43 import oshi.jna.Struct.CloseableProcTaskAllInfo;
44 import oshi.jna.Struct.CloseableRUsageInfoV2;
45 import oshi.jna.Struct.CloseableVnodePathInfo;
46 import oshi.software.common.AbstractOSProcess;
47 import oshi.software.os.OSThread;
48 import oshi.util.GlobalConfig;
49 import oshi.util.ParseUtil;
50 import oshi.util.platform.mac.SysctlUtil;
51 import oshi.util.tuples.Pair;
52
53
54
55
56 @ThreadSafe
57 public class MacOSProcess extends AbstractOSProcess {
58
59 private static final Logger LOG = LoggerFactory.getLogger(MacOSProcess.class);
60
61 private static final int ARGMAX = SysctlUtil.sysctl("kern.argmax", 0);
62 private static final long TICKS_PER_MS;
63 static {
64
65 long ticksPerSec = 1_000_000_000L;
66 IOIterator iter = IOKitUtil.getMatchingServices("IOPlatformDevice");
67 if (iter != null) {
68 IORegistryEntry cpu = iter.next();
69 while (cpu != null) {
70 try {
71 String s = cpu.getName().toLowerCase(Locale.ROOT);
72 if (s.startsWith("cpu") && s.length() > 3) {
73 byte[] data = cpu.getByteArrayProperty("timebase-frequency");
74 if (data != null) {
75 ticksPerSec = ParseUtil.byteArrayToLong(data, 4, false);
76 break;
77 }
78 }
79 } finally {
80 cpu.release();
81 }
82 cpu = iter.next();
83 }
84 iter.release();
85 }
86
87 TICKS_PER_MS = ticksPerSec / 1000L;
88 }
89
90 private static final boolean LOG_MAC_SYSCTL_WARNING = GlobalConfig.get(GlobalConfig.OSHI_OS_MAC_SYSCTL_LOGWARNING,
91 false);
92
93 private static final int MAC_RLIMIT_NOFILE = 8;
94
95
96 private static final int P_LP64 = 0x4;
97
98
99
100 private static final int SSLEEP = 1;
101 private static final int SWAIT = 2;
102 private static final int SRUN = 3;
103 private static final int SIDL = 4;
104 private static final int SZOMB = 5;
105 private static final int SSTOP = 6;
106
107 private int majorVersion;
108 private int minorVersion;
109 private final MacOperatingSystem os;
110
111 private Supplier<String> commandLine = memoize(this::queryCommandLine);
112 private Supplier<Pair<List<String>, Map<String, String>>> argsEnviron = memoize(this::queryArgsAndEnvironment);
113
114 private String name = "";
115 private String path = "";
116 private String currentWorkingDirectory;
117 private String user;
118 private String userID;
119 private String group;
120 private String groupID;
121 private State state = INVALID;
122 private int parentProcessID;
123 private int threadCount;
124 private int priority;
125 private long virtualSize;
126 private long residentSetSize;
127 private long kernelTime;
128 private long userTime;
129 private long startTime;
130 private long upTime;
131 private long bytesRead;
132 private long bytesWritten;
133 private long openFiles;
134 private int bitness;
135 private long minorFaults;
136 private long majorFaults;
137 private long contextSwitches;
138
139 public MacOSProcess(int pid, int major, int minor, MacOperatingSystem os) {
140 super(pid);
141 this.majorVersion = major;
142 this.minorVersion = minor;
143 this.os = os;
144 updateAttributes();
145 }
146
147 @Override
148 public String getName() {
149 return this.name;
150 }
151
152 @Override
153 public String getPath() {
154 return this.path;
155 }
156
157 @Override
158 public String getCommandLine() {
159 return this.commandLine.get();
160 }
161
162 private String queryCommandLine() {
163 return String.join(" ", getArguments());
164 }
165
166 @Override
167 public List<String> getArguments() {
168 return argsEnviron.get().getA();
169 }
170
171 @Override
172 public Map<String, String> getEnvironmentVariables() {
173 return argsEnviron.get().getB();
174 }
175
176 private Pair<List<String>, Map<String, String>> queryArgsAndEnvironment() {
177 int pid = getProcessID();
178
179 List<String> args = new ArrayList<>();
180
181
182 Map<String, String> env = new LinkedHashMap<>();
183
184
185 int[] mib = new int[3];
186 mib[0] = 1;
187 mib[1] = 49;
188 mib[2] = pid;
189
190 try (Memory procargs = new Memory(ARGMAX)) {
191 procargs.clear();
192 size_t.ByReference size = new size_t.ByReference(ARGMAX);
193
194 if (0 == SystemB.INSTANCE.sysctl(mib, mib.length, procargs, size, null, size_t.ZERO)) {
195
196
197
198
199
200
201 int nargs = procargs.getInt(0);
202
203 if (nargs > 0 && nargs <= 1024) {
204
205 long offset = SystemB.INT_SIZE;
206
207 offset += procargs.getString(offset).length();
208
209
210 while (offset < size.longValue()) {
211
212 while (procargs.getByte(offset) == 0) {
213 if (++offset >= size.longValue()) {
214 break;
215 }
216 }
217
218 String arg = procargs.getString(offset);
219 if (nargs-- > 0) {
220
221 args.add(arg);
222 } else {
223
224 int idx = arg.indexOf('=');
225 if (idx > 0) {
226 env.put(arg.substring(0, idx), arg.substring(idx + 1));
227 }
228 }
229
230 offset += arg.length();
231 }
232 }
233 } else {
234
235 if (pid > 0 && LOG_MAC_SYSCTL_WARNING) {
236 LOG.warn(
237 "Failed sysctl call for process arguments (kern.procargs2), process {} may not exist. Error code: {}",
238 pid, Native.getLastError());
239 }
240 }
241 }
242 return new Pair<>(Collections.unmodifiableList(args), Collections.unmodifiableMap(env));
243 }
244
245 @Override
246 public String getCurrentWorkingDirectory() {
247 return this.currentWorkingDirectory;
248 }
249
250 @Override
251 public String getUser() {
252 return this.user;
253 }
254
255 @Override
256 public String getUserID() {
257 return this.userID;
258 }
259
260 @Override
261 public String getGroup() {
262 return this.group;
263 }
264
265 @Override
266 public String getGroupID() {
267 return this.groupID;
268 }
269
270 @Override
271 public State getState() {
272 return this.state;
273 }
274
275 @Override
276 public int getParentProcessID() {
277 return this.parentProcessID;
278 }
279
280 @Override
281 public int getThreadCount() {
282 return this.threadCount;
283 }
284
285 @Override
286 public List<OSThread> getThreadDetails() {
287 long now = System.currentTimeMillis();
288 return ThreadInfo.queryTaskThreads(getProcessID()).stream().parallel().map(stat -> {
289
290 long start = Math.max(now - stat.getUpTime(), getStartTime());
291 return new MacOSThread(getProcessID(), stat.getThreadId(), stat.getState(), stat.getSystemTime(),
292 stat.getUserTime(), start, now - start, stat.getPriority());
293 }).collect(Collectors.toList());
294 }
295
296 @Override
297 public int getPriority() {
298 return this.priority;
299 }
300
301 @Override
302 public long getVirtualSize() {
303 return this.virtualSize;
304 }
305
306 @Override
307 public long getResidentSetSize() {
308 return this.residentSetSize;
309 }
310
311 @Override
312 public long getKernelTime() {
313 return this.kernelTime;
314 }
315
316 @Override
317 public long getUserTime() {
318 return this.userTime;
319 }
320
321 @Override
322 public long getUpTime() {
323 return this.upTime;
324 }
325
326 @Override
327 public long getStartTime() {
328 return this.startTime;
329 }
330
331 @Override
332 public long getBytesRead() {
333 return this.bytesRead;
334 }
335
336 @Override
337 public long getBytesWritten() {
338 return this.bytesWritten;
339 }
340
341 @Override
342 public long getOpenFiles() {
343 return this.openFiles;
344 }
345
346 @Override
347 public long getSoftOpenFileLimit() {
348 if (getProcessID() == this.os.getProcessId()) {
349 final Resource.Rlimit rlimit = new Resource.Rlimit();
350 SystemB.INSTANCE.getrlimit(MAC_RLIMIT_NOFILE, rlimit);
351 return rlimit.rlim_cur;
352 } else {
353 return -1L;
354 }
355 }
356
357 @Override
358 public long getHardOpenFileLimit() {
359 if (getProcessID() == this.os.getProcessId()) {
360 final Resource.Rlimit rlimit = new Resource.Rlimit();
361 SystemB.INSTANCE.getrlimit(MAC_RLIMIT_NOFILE, rlimit);
362 return rlimit.rlim_max;
363 } else {
364 return -1L;
365 }
366 }
367
368 @Override
369 public int getBitness() {
370 return this.bitness;
371 }
372
373 @Override
374 public long getAffinityMask() {
375
376 int logicalProcessorCount = SysctlUtil.sysctl("hw.logicalcpu", 1);
377 return logicalProcessorCount < 64 ? (1L << logicalProcessorCount) - 1 : -1L;
378 }
379
380 @Override
381 public long getMinorFaults() {
382 return this.minorFaults;
383 }
384
385 @Override
386 public long getMajorFaults() {
387 return this.majorFaults;
388 }
389
390 @Override
391 public long getContextSwitches() {
392 return this.contextSwitches;
393 }
394
395 @Override
396 public boolean updateAttributes() {
397 long now = System.currentTimeMillis();
398 try (CloseableProcTaskAllInfo taskAllInfo = new CloseableProcTaskAllInfo()) {
399 if (0 > SystemB.INSTANCE.proc_pidinfo(getProcessID(), SystemB.PROC_PIDTASKALLINFO, 0, taskAllInfo,
400 taskAllInfo.size()) || taskAllInfo.ptinfo.pti_threadnum < 1) {
401 this.state = INVALID;
402 return false;
403 }
404 try (Memory buf = new Memory(SystemB.PROC_PIDPATHINFO_MAXSIZE)) {
405 if (0 < SystemB.INSTANCE.proc_pidpath(getProcessID(), buf, SystemB.PROC_PIDPATHINFO_MAXSIZE)) {
406 this.path = buf.getString(0).trim();
407
408 String[] pathSplit = this.path.split("/");
409 if (pathSplit.length > 0) {
410 this.name = pathSplit[pathSplit.length - 1];
411 }
412 }
413 }
414 if (this.name.isEmpty()) {
415
416 this.name = Native.toString(taskAllInfo.pbsd.pbi_comm, StandardCharsets.UTF_8);
417 }
418
419 switch (taskAllInfo.pbsd.pbi_status) {
420 case SSLEEP:
421 this.state = SLEEPING;
422 break;
423 case SWAIT:
424 this.state = WAITING;
425 break;
426 case SRUN:
427 this.state = RUNNING;
428 break;
429 case SIDL:
430 this.state = NEW;
431 break;
432 case SZOMB:
433 this.state = ZOMBIE;
434 break;
435 case SSTOP:
436 this.state = STOPPED;
437 break;
438 default:
439 this.state = OTHER;
440 break;
441 }
442 this.parentProcessID = taskAllInfo.pbsd.pbi_ppid;
443 this.userID = Integer.toString(taskAllInfo.pbsd.pbi_uid);
444 Passwd pwuid = SystemB.INSTANCE.getpwuid(taskAllInfo.pbsd.pbi_uid);
445 this.user = pwuid == null ? Integer.toString(taskAllInfo.pbsd.pbi_uid) : pwuid.pw_name;
446 this.groupID = Integer.toString(taskAllInfo.pbsd.pbi_gid);
447 Group grgid = SystemB.INSTANCE.getgrgid(taskAllInfo.pbsd.pbi_gid);
448 this.group = grgid == null ? Integer.toString(taskAllInfo.pbsd.pbi_gid) : grgid.gr_name;
449 this.threadCount = taskAllInfo.ptinfo.pti_threadnum;
450 this.priority = taskAllInfo.ptinfo.pti_priority;
451 this.virtualSize = taskAllInfo.ptinfo.pti_virtual_size;
452 this.residentSetSize = taskAllInfo.ptinfo.pti_resident_size;
453 this.kernelTime = taskAllInfo.ptinfo.pti_total_system / TICKS_PER_MS;
454 this.userTime = taskAllInfo.ptinfo.pti_total_user / TICKS_PER_MS;
455 this.startTime = taskAllInfo.pbsd.pbi_start_tvsec * 1000L + taskAllInfo.pbsd.pbi_start_tvusec / 1000L;
456 this.upTime = now - this.startTime;
457 this.openFiles = taskAllInfo.pbsd.pbi_nfiles;
458 this.bitness = (taskAllInfo.pbsd.pbi_flags & P_LP64) == 0 ? 32 : 64;
459 this.majorFaults = taskAllInfo.ptinfo.pti_pageins;
460
461 this.minorFaults = taskAllInfo.ptinfo.pti_faults - taskAllInfo.ptinfo.pti_pageins;
462 this.contextSwitches = taskAllInfo.ptinfo.pti_csw;
463 }
464 if (this.majorVersion > 10 || this.minorVersion >= 9) {
465 try (CloseableRUsageInfoV2 rUsageInfoV2 = new CloseableRUsageInfoV2()) {
466 if (0 == SystemB.INSTANCE.proc_pid_rusage(getProcessID(), SystemB.RUSAGE_INFO_V2, rUsageInfoV2)) {
467 this.bytesRead = rUsageInfoV2.ri_diskio_bytesread;
468 this.bytesWritten = rUsageInfoV2.ri_diskio_byteswritten;
469 }
470 }
471 }
472 try (CloseableVnodePathInfo vpi = new CloseableVnodePathInfo()) {
473 if (0 < SystemB.INSTANCE.proc_pidinfo(getProcessID(), SystemB.PROC_PIDVNODEPATHINFO, 0, vpi, vpi.size())) {
474 this.currentWorkingDirectory = Native.toString(vpi.pvi_cdir.vip_path, StandardCharsets.US_ASCII);
475 }
476 }
477 return true;
478 }
479 }