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