1
2
3
4
5 package oshi.software.os.linux;
6
7 import static oshi.software.os.OSProcess.State.INVALID;
8 import static oshi.software.os.OSThread.ThreadFiltering.VALID_THREAD;
9 import static oshi.util.Memoizer.memoize;
10
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.math.BigInteger;
16 import java.nio.file.Files;
17 import java.nio.file.InvalidPathException;
18 import java.nio.file.Path;
19 import java.nio.file.Paths;
20 import java.util.Arrays;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.Locale;
24 import java.util.Map;
25 import java.util.Optional;
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 oshi.annotation.concurrent.ThreadSafe;
34 import oshi.driver.linux.proc.ProcessStat;
35 import oshi.jna.platform.linux.LinuxLibc;
36 import oshi.software.common.AbstractOSProcess;
37 import oshi.software.os.OSThread;
38 import oshi.util.ExecutingCommand;
39 import oshi.util.FileUtil;
40 import oshi.util.GlobalConfig;
41 import oshi.util.ParseUtil;
42 import oshi.util.UserGroupInfo;
43 import oshi.util.Util;
44 import oshi.util.platform.linux.ProcPath;
45
46
47
48
49 @ThreadSafe
50 public class LinuxOSProcess extends AbstractOSProcess {
51
52 private static final Logger LOG = LoggerFactory.getLogger(LinuxOSProcess.class);
53
54 private static final boolean LOG_PROCFS_WARNING = GlobalConfig.get(GlobalConfig.OSHI_OS_LINUX_PROCFS_LOGWARNING,
55 false);
56
57
58 private static final int[] PROC_PID_STAT_ORDERS = new int[ProcPidStat.values().length];
59
60 static {
61 for (ProcPidStat stat : ProcPidStat.values()) {
62
63
64 PROC_PID_STAT_ORDERS[stat.ordinal()] = stat.getOrder() - 1;
65 }
66 }
67
68 private final LinuxOperatingSystem os;
69
70 private Supplier<Integer> bitness = memoize(this::queryBitness);
71 private Supplier<String> commandLine = memoize(this::queryCommandLine);
72 private Supplier<List<String>> arguments = memoize(this::queryArguments);
73 private Supplier<Map<String, String>> environmentVariables = memoize(this::queryEnvironmentVariables);
74 private Supplier<String> user = memoize(this::queryUser);
75 private Supplier<String> group = memoize(this::queryGroup);
76
77 private String name;
78 private String path = "";
79 private String userID;
80 private String groupID;
81 private State state = INVALID;
82 private int parentProcessID;
83 private int threadCount;
84 private int priority;
85 private long virtualSize;
86 private long residentSetSize;
87 private long kernelTime;
88 private long userTime;
89 private long startTime;
90 private long upTime;
91 private long bytesRead;
92 private long bytesWritten;
93 private long minorFaults;
94 private long majorFaults;
95 private long contextSwitches;
96
97 public LinuxOSProcess(int pid, LinuxOperatingSystem os) {
98 super(pid);
99 this.os = os;
100 updateAttributes();
101 }
102
103 @Override
104 public String getName() {
105 return this.name;
106 }
107
108 @Override
109 public String getPath() {
110 return this.path;
111 }
112
113 @Override
114 public String getCommandLine() {
115 return commandLine.get();
116 }
117
118 private String queryCommandLine() {
119 return Arrays.stream(FileUtil
120 .getStringFromFile(String.format(Locale.ROOT, ProcPath.PID_CMDLINE, getProcessID())).split("\0"))
121 .collect(Collectors.joining(" "));
122 }
123
124 @Override
125 public List<String> getArguments() {
126 return arguments.get();
127 }
128
129 private List<String> queryArguments() {
130 return Collections.unmodifiableList(ParseUtil.parseByteArrayToStrings(FileUtil
131 .readAllBytes(String.format(Locale.ROOT, ProcPath.PID_CMDLINE, getProcessID()), LOG_PROCFS_WARNING)));
132 }
133
134 @Override
135 public Map<String, String> getEnvironmentVariables() {
136 return environmentVariables.get();
137 }
138
139 private Map<String, String> queryEnvironmentVariables() {
140 return Collections.unmodifiableMap(ParseUtil.parseByteArrayToStringMap(FileUtil
141 .readAllBytes(String.format(Locale.ROOT, ProcPath.PID_ENVIRON, getProcessID()), LOG_PROCFS_WARNING)));
142 }
143
144 @Override
145 public String getCurrentWorkingDirectory() {
146 try {
147 String cwdLink = String.format(Locale.ROOT, ProcPath.PID_CWD, getProcessID());
148 String cwd = new File(cwdLink).getCanonicalPath();
149 if (!cwd.equals(cwdLink)) {
150 return cwd;
151 }
152 } catch (IOException e) {
153 LOG.trace("Couldn't find cwd for pid {}: {}", getProcessID(), e.getMessage());
154 }
155 return "";
156 }
157
158 @Override
159 public String getUser() {
160 return user.get();
161 }
162
163 private String queryUser() {
164 return UserGroupInfo.getUser(userID);
165 }
166
167 @Override
168 public String getUserID() {
169 return this.userID;
170 }
171
172 @Override
173 public String getGroup() {
174 return group.get();
175 }
176
177 private String queryGroup() {
178 return UserGroupInfo.getGroupName(groupID);
179 }
180
181 @Override
182 public String getGroupID() {
183 return this.groupID;
184 }
185
186 @Override
187 public State getState() {
188 return this.state;
189 }
190
191 @Override
192 public int getParentProcessID() {
193 return this.parentProcessID;
194 }
195
196 @Override
197 public int getThreadCount() {
198 return this.threadCount;
199 }
200
201 @Override
202 public int getPriority() {
203 return this.priority;
204 }
205
206 @Override
207 public long getVirtualSize() {
208 return this.virtualSize;
209 }
210
211 @Override
212 public long getResidentSetSize() {
213 return this.residentSetSize;
214 }
215
216 @Override
217 public long getKernelTime() {
218 return this.kernelTime;
219 }
220
221 @Override
222 public long getUserTime() {
223 return this.userTime;
224 }
225
226 @Override
227 public long getUpTime() {
228 return this.upTime;
229 }
230
231 @Override
232 public long getStartTime() {
233 return this.startTime;
234 }
235
236 @Override
237 public long getBytesRead() {
238 return this.bytesRead;
239 }
240
241 @Override
242 public long getBytesWritten() {
243 return this.bytesWritten;
244 }
245
246 @Override
247 public List<OSThread> getThreadDetails() {
248 return ProcessStat.getThreadIds(getProcessID()).stream().parallel()
249 .map(id -> new LinuxOSThread(getProcessID(), id)).filter(VALID_THREAD).collect(Collectors.toList());
250 }
251
252 @Override
253 public long getMinorFaults() {
254 return this.minorFaults;
255 }
256
257 @Override
258 public long getMajorFaults() {
259 return this.majorFaults;
260 }
261
262 @Override
263 public long getContextSwitches() {
264 return this.contextSwitches;
265 }
266
267 @Override
268 public long getOpenFiles() {
269 return ProcessStat.getFileDescriptorFiles(getProcessID()).length;
270 }
271
272 @Override
273 public long getSoftOpenFileLimit() {
274 if (getProcessID() == this.os.getProcessId()) {
275 final Resource.Rlimit rlimit = new Resource.Rlimit();
276 LinuxLibc.INSTANCE.getrlimit(LinuxLibc.RLIMIT_NOFILE, rlimit);
277 return rlimit.rlim_cur;
278 } else {
279 return getProcessOpenFileLimit(getProcessID(), 1);
280 }
281 }
282
283 @Override
284 public long getHardOpenFileLimit() {
285 if (getProcessID() == this.os.getProcessId()) {
286 final Resource.Rlimit rlimit = new Resource.Rlimit();
287 LinuxLibc.INSTANCE.getrlimit(LinuxLibc.RLIMIT_NOFILE, rlimit);
288 return rlimit.rlim_max;
289 } else {
290 return getProcessOpenFileLimit(getProcessID(), 2);
291 }
292 }
293
294 @Override
295 public int getBitness() {
296 return this.bitness.get();
297 }
298
299 private int queryBitness() {
300
301
302 byte[] buffer = new byte[5];
303 if (!path.isEmpty()) {
304 try (InputStream is = new FileInputStream(path)) {
305 if (is.read(buffer) == buffer.length) {
306 return buffer[4] == 1 ? 32 : 64;
307 }
308 } catch (IOException e) {
309 LOG.warn("Failed to read process file: {}", path);
310 }
311 }
312 return 0;
313 }
314
315 @Override
316 public long getAffinityMask() {
317
318
319 String mask = ExecutingCommand.getFirstAnswer("taskset -p " + getProcessID());
320
321
322
323 String[] split = ParseUtil.whitespaces.split(mask);
324 try {
325 return new BigInteger(split[split.length - 1], 16).longValue();
326 } catch (NumberFormatException e) {
327 return 0;
328 }
329 }
330
331 @Override
332 public boolean updateAttributes() {
333 String procPidExe = String.format(Locale.ROOT, ProcPath.PID_EXE, getProcessID());
334 try {
335 Path link = Paths.get(procPidExe);
336 this.path = Files.readSymbolicLink(link).toString();
337
338 int index = path.indexOf(" (deleted)");
339 if (index != -1) {
340 path = path.substring(0, index);
341 }
342 } catch (InvalidPathException | IOException | UnsupportedOperationException | SecurityException e) {
343 LOG.debug("Unable to open symbolic link {}", procPidExe);
344 }
345
346
347 Map<String, String> io = FileUtil
348 .getKeyValueMapFromFile(String.format(Locale.ROOT, ProcPath.PID_IO, getProcessID()), ":");
349 Map<String, String> status = FileUtil
350 .getKeyValueMapFromFile(String.format(Locale.ROOT, ProcPath.PID_STATUS, getProcessID()), ":");
351 String stat = FileUtil.getStringFromFile(String.format(Locale.ROOT, ProcPath.PID_STAT, getProcessID()));
352 if (stat.isEmpty()) {
353 this.state = INVALID;
354 return false;
355 }
356
357
358 getMissingDetails(status, stat);
359
360 long now = System.currentTimeMillis();
361
362
363
364
365 long[] statArray = ParseUtil.parseStringToLongArray(stat, PROC_PID_STAT_ORDERS,
366 ProcessStat.PROC_PID_STAT_LENGTH, ' ');
367
368
369
370
371 this.startTime = (LinuxOperatingSystem.BOOTTIME * LinuxOperatingSystem.getHz()
372 + statArray[ProcPidStat.START_TIME.ordinal()]) * 1000L / LinuxOperatingSystem.getHz();
373
374
375
376 if (startTime >= now) {
377 startTime = now - 1;
378 }
379 this.parentProcessID = (int) statArray[ProcPidStat.PPID.ordinal()];
380 this.threadCount = (int) statArray[ProcPidStat.THREAD_COUNT.ordinal()];
381 this.priority = (int) statArray[ProcPidStat.PRIORITY.ordinal()];
382 this.virtualSize = statArray[ProcPidStat.VSZ.ordinal()];
383 this.residentSetSize = statArray[ProcPidStat.RSS.ordinal()] * LinuxOperatingSystem.getPageSize();
384 this.kernelTime = statArray[ProcPidStat.KERNEL_TIME.ordinal()] * 1000L / LinuxOperatingSystem.getHz();
385 this.userTime = statArray[ProcPidStat.USER_TIME.ordinal()] * 1000L / LinuxOperatingSystem.getHz();
386 this.minorFaults = statArray[ProcPidStat.MINOR_FAULTS.ordinal()];
387 this.majorFaults = statArray[ProcPidStat.MAJOR_FAULTS.ordinal()];
388 long nonVoluntaryContextSwitches = ParseUtil.parseLongOrDefault(status.get("nonvoluntary_ctxt_switches"), 0L);
389 long voluntaryContextSwitches = ParseUtil.parseLongOrDefault(status.get("voluntary_ctxt_switches"), 0L);
390 this.contextSwitches = voluntaryContextSwitches + nonVoluntaryContextSwitches;
391
392 this.upTime = now - startTime;
393
394
395 this.bytesRead = ParseUtil.parseLongOrDefault(io.getOrDefault("read_bytes", ""), 0L);
396 this.bytesWritten = ParseUtil.parseLongOrDefault(io.getOrDefault("write_bytes", ""), 0L);
397
398
399
400 this.userID = ParseUtil.whitespaces.split(status.getOrDefault("Uid", ""))[0];
401
402 this.groupID = ParseUtil.whitespaces.split(status.getOrDefault("Gid", ""))[0];
403
404 this.name = status.getOrDefault("Name", "");
405 this.state = ProcessStat.getState(status.getOrDefault("State", "U").charAt(0));
406 return true;
407 }
408
409
410
411
412
413
414
415 private static void getMissingDetails(Map<String, String> status, String stat) {
416 if (status == null || stat == null) {
417 return;
418 }
419
420 int nameStart = stat.indexOf('(');
421 int nameEnd = stat.indexOf(')');
422 if (Util.isBlank(status.get("Name")) && nameStart > 0 && nameStart < nameEnd) {
423
424 String statName = stat.substring(nameStart + 1, nameEnd);
425 status.put("Name", statName);
426 }
427
428
429 if (Util.isBlank(status.get("State")) && nameEnd > 0 && stat.length() > nameEnd + 2) {
430 String statState = String.valueOf(stat.charAt(nameEnd + 2));
431 status.put("State", statState);
432 }
433 }
434
435
436
437
438
439 private enum ProcPidStat {
440
441
442 PPID(4), MINOR_FAULTS(10), MAJOR_FAULTS(12), USER_TIME(14), KERNEL_TIME(15), PRIORITY(18), THREAD_COUNT(20),
443 START_TIME(22), VSZ(23), RSS(24);
444
445 private final int order;
446
447 public int getOrder() {
448 return this.order;
449 }
450
451 ProcPidStat(int order) {
452 this.order = order;
453 }
454 }
455
456 private long getProcessOpenFileLimit(long processId, int index) {
457 final String limitsPath = String.format(Locale.ROOT, "/proc/%d/limits", processId);
458 if (!Files.exists(Paths.get(limitsPath))) {
459 return -1;
460 }
461 final List<String> lines = FileUtil.readFile(limitsPath);
462 final Optional<String> maxOpenFilesLine = lines.stream().filter(line -> line.startsWith("Max open files"))
463 .findFirst();
464 if (!maxOpenFilesLine.isPresent()) {
465 return -1;
466 }
467
468
469 final String[] split = maxOpenFilesLine.get().split("\\D+");
470 return ParseUtil.parseLongOrDefault(split[index], -1);
471 }
472 }