1
2
3
4
5 package oshi.software.os.unix.openbsd;
6
7 import static oshi.software.os.OSProcess.State.INVALID;
8 import static oshi.software.os.OSProcess.State.OTHER;
9 import static oshi.software.os.OSProcess.State.RUNNING;
10 import static oshi.software.os.OSProcess.State.SLEEPING;
11 import static oshi.software.os.OSProcess.State.STOPPED;
12 import static oshi.software.os.OSProcess.State.WAITING;
13 import static oshi.software.os.OSProcess.State.ZOMBIE;
14 import static oshi.software.os.OSThread.ThreadFiltering.VALID_THREAD;
15 import static oshi.util.Memoizer.memoize;
16
17 import java.util.ArrayList;
18 import java.util.Arrays;
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.Predicate;
25 import java.util.function.Supplier;
26 import java.util.stream.Collectors;
27
28 import com.sun.jna.platform.unix.Resource;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import com.sun.jna.Memory;
33 import com.sun.jna.Native;
34 import com.sun.jna.Pointer;
35 import com.sun.jna.platform.unix.LibCAPI.size_t;
36
37 import oshi.annotation.concurrent.ThreadSafe;
38 import oshi.jna.ByRef.CloseableSizeTByReference;
39 import oshi.jna.platform.unix.OpenBsdLibc;
40 import oshi.software.common.AbstractOSProcess;
41 import oshi.software.os.OSThread;
42 import oshi.software.os.unix.openbsd.OpenBsdOperatingSystem.PsKeywords;
43 import oshi.util.ExecutingCommand;
44 import oshi.util.ParseUtil;
45 import oshi.util.platform.unix.openbsd.FstatUtil;
46
47
48
49
50 @ThreadSafe
51 public class OpenBsdOSProcess extends AbstractOSProcess {
52
53 private static final Logger LOG = LoggerFactory.getLogger(OpenBsdOSProcess.class);
54
55
56
57
58 enum PsThreadColumns {
59 TID, STATE, ETIME, CPUTIME, NIVCSW, NVCSW, MAJFLT, MINFLT, PRI, ARGS;
60 }
61
62 static final String PS_THREAD_COLUMNS = Arrays.stream(PsThreadColumns.values()).map(Enum::name)
63 .map(name -> name.toLowerCase(Locale.ROOT)).collect(Collectors.joining(","));
64
65 private static final int ARGMAX;
66
67 static {
68 int[] mib = new int[2];
69 mib[0] = 1;
70 mib[1] = 8;
71 try (Memory m = new Memory(Integer.BYTES);
72 CloseableSizeTByReference size = new CloseableSizeTByReference(Integer.BYTES)) {
73 if (OpenBsdLibc.INSTANCE.sysctl(mib, mib.length, m, size, null, size_t.ZERO) == 0) {
74 ARGMAX = m.getInt(0);
75 } else {
76 LOG.warn("Failed sysctl call for process arguments max size (kern.argmax). Error code: {}",
77 Native.getLastError());
78 ARGMAX = 0;
79 }
80 }
81 }
82
83 private final OpenBsdOperatingSystem os;
84
85 private Supplier<String> commandLine = memoize(this::queryCommandLine);
86 private Supplier<List<String>> arguments = memoize(this::queryArguments);
87 private Supplier<Map<String, String>> environmentVariables = memoize(this::queryEnvironmentVariables);
88
89 private String name;
90 private String path = "";
91 private String user;
92 private String userID;
93 private String group;
94 private String groupID;
95 private State state = INVALID;
96 private int parentProcessID;
97 private int threadCount;
98 private int priority;
99 private long virtualSize;
100 private long residentSetSize;
101 private long kernelTime;
102 private long userTime;
103 private long startTime;
104 private long upTime;
105 private long bytesRead;
106 private long bytesWritten;
107 private long minorFaults;
108 private long majorFaults;
109 private long contextSwitches;
110 private int bitness;
111 private String commandLineBackup;
112
113 public OpenBsdOSProcess(int pid, Map<PsKeywords, String> psMap, OpenBsdOperatingSystem os) {
114 super(pid);
115 this.os = os;
116
117
118 this.bitness = Native.LONG_SIZE * 8;
119 updateThreadCount();
120 updateAttributes(psMap);
121 }
122
123 @Override
124 public String getName() {
125 return this.name;
126 }
127
128 @Override
129 public String getPath() {
130 return this.path;
131 }
132
133 @Override
134 public String getCommandLine() {
135 return this.commandLine.get();
136 }
137
138 private String queryCommandLine() {
139 String cl = String.join(" ", getArguments());
140 return cl.isEmpty() ? this.commandLineBackup : cl;
141 }
142
143 @Override
144 public List<String> getArguments() {
145 return arguments.get();
146 }
147
148 private List<String> queryArguments() {
149 if (ARGMAX > 0) {
150
151 int[] mib = new int[4];
152 mib[0] = 1;
153 mib[1] = 55;
154 mib[2] = getProcessID();
155 mib[3] = 1;
156
157 try (Memory m = new Memory(ARGMAX);
158 CloseableSizeTByReference size = new CloseableSizeTByReference(ARGMAX)) {
159
160 if (OpenBsdLibc.INSTANCE.sysctl(mib, mib.length, m, size, null, size_t.ZERO) == 0) {
161
162 List<String> args = new ArrayList<>();
163
164 long offset = 0;
165
166 long baseAddr = Pointer.nativeValue(m);
167 long maxAddr = baseAddr + size.getValue().longValue();
168
169 long argAddr = Pointer.nativeValue(m.getPointer(offset));
170 while (argAddr > baseAddr && argAddr < maxAddr) {
171 args.add(m.getString(argAddr - baseAddr));
172 offset += Native.POINTER_SIZE;
173 argAddr = Pointer.nativeValue(m.getPointer(offset));
174 }
175 return Collections.unmodifiableList(args);
176 }
177 }
178 }
179 return Collections.emptyList();
180 }
181
182 @Override
183 public Map<String, String> getEnvironmentVariables() {
184 return environmentVariables.get();
185 }
186
187 private Map<String, String> queryEnvironmentVariables() {
188
189 int[] mib = new int[4];
190 mib[0] = 1;
191 mib[1] = 55;
192 mib[2] = getProcessID();
193 mib[3] = 3;
194
195 try (Memory m = new Memory(ARGMAX); CloseableSizeTByReference size = new CloseableSizeTByReference(ARGMAX)) {
196
197 if (OpenBsdLibc.INSTANCE.sysctl(mib, mib.length, m, size, null, size_t.ZERO) == 0) {
198
199 Map<String, String> env = new LinkedHashMap<>();
200
201 long offset = 0;
202
203 long baseAddr = Pointer.nativeValue(m);
204 long maxAddr = baseAddr + size.longValue();
205
206 long argAddr = Pointer.nativeValue(m.getPointer(offset));
207 while (argAddr > baseAddr && argAddr < maxAddr) {
208 String envStr = m.getString(argAddr - baseAddr);
209 int idx = envStr.indexOf('=');
210 if (idx > 0) {
211 env.put(envStr.substring(0, idx), envStr.substring(idx + 1));
212 }
213 offset += Native.POINTER_SIZE;
214 argAddr = Pointer.nativeValue(m.getPointer(offset));
215 }
216 return Collections.unmodifiableMap(env);
217 }
218 }
219 return Collections.emptyMap();
220 }
221
222 @Override
223 public String getCurrentWorkingDirectory() {
224 return FstatUtil.getCwd(getProcessID());
225 }
226
227 @Override
228 public String getUser() {
229 return this.user;
230 }
231
232 @Override
233 public String getUserID() {
234 return this.userID;
235 }
236
237 @Override
238 public String getGroup() {
239 return this.group;
240 }
241
242 @Override
243 public String getGroupID() {
244 return this.groupID;
245 }
246
247 @Override
248 public State getState() {
249 return this.state;
250 }
251
252 @Override
253 public int getParentProcessID() {
254 return this.parentProcessID;
255 }
256
257 @Override
258 public int getThreadCount() {
259 return this.threadCount;
260 }
261
262 @Override
263 public int getPriority() {
264 return this.priority;
265 }
266
267 @Override
268 public long getVirtualSize() {
269 return this.virtualSize;
270 }
271
272 @Override
273 public long getResidentSetSize() {
274 return this.residentSetSize;
275 }
276
277 @Override
278 public long getKernelTime() {
279 return this.kernelTime;
280 }
281
282 @Override
283 public long getUserTime() {
284 return this.userTime;
285 }
286
287 @Override
288 public long getUpTime() {
289 return this.upTime;
290 }
291
292 @Override
293 public long getStartTime() {
294 return this.startTime;
295 }
296
297 @Override
298 public long getBytesRead() {
299 return this.bytesRead;
300 }
301
302 @Override
303 public long getBytesWritten() {
304 return this.bytesWritten;
305 }
306
307 @Override
308 public long getOpenFiles() {
309 return FstatUtil.getOpenFiles(getProcessID());
310 }
311
312 @Override
313 public long getSoftOpenFileLimit() {
314 if (getProcessID() == this.os.getProcessId()) {
315 final Resource.Rlimit rlimit = new Resource.Rlimit();
316 OpenBsdLibc.INSTANCE.getrlimit(OpenBsdLibc.RLIMIT_NOFILE, rlimit);
317 return rlimit.rlim_cur;
318 } else {
319 return -1L;
320 }
321 }
322
323 @Override
324 public long getHardOpenFileLimit() {
325 if (getProcessID() == this.os.getProcessId()) {
326 final Resource.Rlimit rlimit = new Resource.Rlimit();
327 OpenBsdLibc.INSTANCE.getrlimit(OpenBsdLibc.RLIMIT_NOFILE, rlimit);
328 return rlimit.rlim_max;
329 } else {
330 return -1L;
331 }
332 }
333
334 @Override
335 public int getBitness() {
336 return this.bitness;
337 }
338
339 @Override
340 public long getAffinityMask() {
341 long bitMask = 0L;
342
343
344 String cpuset = ExecutingCommand.getFirstAnswer("cpuset -gp " + getProcessID());
345
346
347
348 String[] split = cpuset.split(":");
349 if (split.length > 1) {
350 String[] bits = split[1].split(",");
351 for (String bit : bits) {
352 int bitToSet = ParseUtil.parseIntOrDefault(bit.trim(), -1);
353 if (bitToSet >= 0) {
354 bitMask |= 1L << bitToSet;
355 }
356 }
357 }
358 return bitMask;
359 }
360
361 @Override
362 public List<OSThread> getThreadDetails() {
363 String psCommand = "ps -aHwwxo " + PS_THREAD_COLUMNS;
364 if (getProcessID() >= 0) {
365 psCommand += " -p " + getProcessID();
366 }
367 Predicate<Map<PsThreadColumns, String>> hasColumnsArgs = threadMap -> threadMap
368 .containsKey(PsThreadColumns.ARGS);
369 return ExecutingCommand.runNative(psCommand).stream().skip(1)
370 .map(thread -> ParseUtil.stringToEnumMap(PsThreadColumns.class, thread.trim(), ' '))
371 .filter(hasColumnsArgs).map(threadMap -> new OpenBsdOSThread(getProcessID(), threadMap))
372 .filter(VALID_THREAD).collect(Collectors.toList());
373 }
374
375 @Override
376 public long getMinorFaults() {
377 return this.minorFaults;
378 }
379
380 @Override
381 public long getMajorFaults() {
382 return this.majorFaults;
383 }
384
385 @Override
386 public long getContextSwitches() {
387 return this.contextSwitches;
388 }
389
390 @Override
391 public boolean updateAttributes() {
392
393 String psCommand = "ps -awwxo " + OpenBsdOperatingSystem.PS_COMMAND_ARGS + " -p " + getProcessID();
394 List<String> procList = ExecutingCommand.runNative(psCommand);
395 if (procList.size() > 1) {
396
397 Map<PsKeywords, String> psMap = ParseUtil.stringToEnumMap(PsKeywords.class, procList.get(1).trim(), ' ');
398
399 if (psMap.containsKey(PsKeywords.ARGS)) {
400 updateThreadCount();
401 return updateAttributes(psMap);
402 }
403 }
404 this.state = INVALID;
405 return false;
406 }
407
408 private boolean updateAttributes(Map<PsKeywords, String> psMap) {
409 long now = System.currentTimeMillis();
410 switch (psMap.get(PsKeywords.STATE).charAt(0)) {
411 case 'R':
412 this.state = RUNNING;
413 break;
414 case 'I':
415 case 'S':
416 this.state = SLEEPING;
417 break;
418 case 'D':
419 case 'L':
420 case 'U':
421 this.state = WAITING;
422 break;
423 case 'Z':
424 this.state = ZOMBIE;
425 break;
426 case 'T':
427 this.state = STOPPED;
428 break;
429 default:
430 this.state = OTHER;
431 break;
432 }
433 this.parentProcessID = ParseUtil.parseIntOrDefault(psMap.get(PsKeywords.PPID), 0);
434 this.user = psMap.get(PsKeywords.USER);
435 this.userID = psMap.get(PsKeywords.UID);
436 this.group = psMap.get(PsKeywords.GROUP);
437 this.groupID = psMap.get(PsKeywords.GID);
438 this.priority = ParseUtil.parseIntOrDefault(psMap.get(PsKeywords.PRI), 0);
439
440 this.virtualSize = ParseUtil.parseLongOrDefault(psMap.get(PsKeywords.VSZ), 0) * 1024;
441 this.residentSetSize = ParseUtil.parseLongOrDefault(psMap.get(PsKeywords.RSS), 0) * 1024;
442
443 long elapsedTime = ParseUtil.parseDHMSOrDefault(psMap.get(PsKeywords.ETIME), 0L);
444 this.upTime = elapsedTime < 1L ? 1L : elapsedTime;
445 this.startTime = now - this.upTime;
446 this.userTime = ParseUtil.parseDHMSOrDefault(psMap.get(PsKeywords.CPUTIME), 0L);
447
448 this.kernelTime = 0L;
449 this.path = psMap.get(PsKeywords.COMM);
450 this.name = this.path.substring(this.path.lastIndexOf('/') + 1);
451 this.minorFaults = ParseUtil.parseLongOrDefault(psMap.get(PsKeywords.MINFLT), 0L);
452 this.majorFaults = ParseUtil.parseLongOrDefault(psMap.get(PsKeywords.MAJFLT), 0L);
453 long nonVoluntaryContextSwitches = ParseUtil.parseLongOrDefault(psMap.get(PsKeywords.NIVCSW), 0L);
454 long voluntaryContextSwitches = ParseUtil.parseLongOrDefault(psMap.get(PsKeywords.NVCSW), 0L);
455 this.contextSwitches = voluntaryContextSwitches + nonVoluntaryContextSwitches;
456 this.commandLineBackup = psMap.get(PsKeywords.ARGS);
457 return true;
458 }
459
460 private void updateThreadCount() {
461 List<String> threadList = ExecutingCommand.runNative("ps -axHo tid -p " + getProcessID());
462 if (!threadList.isEmpty()) {
463
464 this.threadCount = threadList.size() - 1;
465 }
466 this.threadCount = 1;
467 }
468 }