View Javadoc
1   /*
2    * Copyright 2020-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.unix.solaris;
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.defaultExpiration;
16  import static oshi.util.Memoizer.memoize;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.nio.file.Files;
21  import java.nio.file.Path;
22  import java.nio.file.Paths;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.Map;
28  import java.util.Optional;
29  import java.util.function.Supplier;
30  import java.util.stream.Collectors;
31  import java.util.stream.Stream;
32  
33  import com.sun.jna.platform.unix.Resource;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import com.sun.jna.Native;
38  
39  import oshi.annotation.concurrent.ThreadSafe;
40  import oshi.driver.unix.solaris.PsInfo;
41  import oshi.jna.platform.unix.SolarisLibc;
42  import oshi.jna.platform.unix.SolarisLibc.SolarisPrUsage;
43  import oshi.jna.platform.unix.SolarisLibc.SolarisPsInfo;
44  import oshi.software.common.AbstractOSProcess;
45  import oshi.software.os.OSThread;
46  import oshi.util.Constants;
47  import oshi.util.ExecutingCommand;
48  import oshi.util.ParseUtil;
49  import oshi.util.UserGroupInfo;
50  import oshi.util.tuples.Pair;
51  
52  /**
53   * OSProcess implementation
54   */
55  @ThreadSafe
56  public class SolarisOSProcess extends AbstractOSProcess {
57  
58      private static final Logger LOG = LoggerFactory.getLogger(SolarisOSProcess.class);
59  
60      private final SolarisOperatingSystem os;
61  
62      private Supplier<Integer> bitness = memoize(this::queryBitness);
63      private Supplier<SolarisPsInfo> psinfo = memoize(this::queryPsInfo, defaultExpiration());
64      private Supplier<String> commandLine = memoize(this::queryCommandLine);
65      private Supplier<Pair<List<String>, Map<String, String>>> cmdEnv = memoize(this::queryCommandlineEnvironment);
66      private Supplier<SolarisPrUsage> prusage = memoize(this::queryPrUsage, defaultExpiration());
67  
68      private String name;
69      private String path = "";
70      private String commandLineBackup;
71      private String user;
72      private String userID;
73      private String group;
74      private String groupID;
75      private State state = State.INVALID;
76      private int parentProcessID;
77      private int threadCount;
78      private int priority;
79      private long virtualSize;
80      private long residentSetSize;
81      private long kernelTime;
82      private long userTime;
83      private long startTime;
84      private long upTime;
85      private long bytesRead;
86      private long bytesWritten;
87      private long minorFaults;
88      private long majorFaults;
89      private long contextSwitches = 0; // default
90  
91      public SolarisOSProcess(int pid, SolarisOperatingSystem os) {
92          super(pid);
93          this.os = os;
94          updateAttributes();
95      }
96  
97      private SolarisPsInfo queryPsInfo() {
98          return PsInfo.queryPsInfo(this.getProcessID());
99      }
100 
101     private SolarisPrUsage queryPrUsage() {
102         return PsInfo.queryPrUsage(this.getProcessID());
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 cmdEnv.get().getA();
128     }
129 
130     @Override
131     public Map<String, String> getEnvironmentVariables() {
132         return cmdEnv.get().getB();
133     }
134 
135     private Pair<List<String>, Map<String, String>> queryCommandlineEnvironment() {
136         return PsInfo.queryArgsEnv(getProcessID(), psinfo.get());
137     }
138 
139     @Override
140     public String getCurrentWorkingDirectory() {
141         try {
142             String cwdLink = "/proc" + getProcessID() + "/cwd";
143             String cwd = new File(cwdLink).getCanonicalPath();
144             if (!cwd.equals(cwdLink)) {
145                 return cwd;
146             }
147         } catch (IOException e) {
148             LOG.trace("Couldn't find cwd for pid {}: {}", getProcessID(), e.getMessage());
149         }
150         return "";
151     }
152 
153     @Override
154     public String getUser() {
155         return this.user;
156     }
157 
158     @Override
159     public String getUserID() {
160         return this.userID;
161     }
162 
163     @Override
164     public String getGroup() {
165         return this.group;
166     }
167 
168     @Override
169     public String getGroupID() {
170         return this.groupID;
171     }
172 
173     @Override
174     public State getState() {
175         return this.state;
176     }
177 
178     @Override
179     public int getParentProcessID() {
180         return this.parentProcessID;
181     }
182 
183     @Override
184     public int getThreadCount() {
185         return this.threadCount;
186     }
187 
188     @Override
189     public int getPriority() {
190         return this.priority;
191     }
192 
193     @Override
194     public long getVirtualSize() {
195         return this.virtualSize;
196     }
197 
198     @Override
199     public long getResidentSetSize() {
200         return this.residentSetSize;
201     }
202 
203     @Override
204     public long getKernelTime() {
205         return this.kernelTime;
206     }
207 
208     @Override
209     public long getUserTime() {
210         return this.userTime;
211     }
212 
213     @Override
214     public long getUpTime() {
215         return this.upTime;
216     }
217 
218     @Override
219     public long getStartTime() {
220         return this.startTime;
221     }
222 
223     @Override
224     public long getBytesRead() {
225         return this.bytesRead;
226     }
227 
228     @Override
229     public long getBytesWritten() {
230         return this.bytesWritten;
231     }
232 
233     @Override
234     public long getMinorFaults() {
235         return this.minorFaults;
236     }
237 
238     @Override
239     public long getMajorFaults() {
240         return this.majorFaults;
241     }
242 
243     @Override
244     public long getContextSwitches() {
245         return this.contextSwitches;
246     }
247 
248     @Override
249     public long getOpenFiles() {
250         try (Stream<Path> fd = Files.list(Paths.get("/proc/" + getProcessID() + "/fd"))) {
251             return fd.count();
252         } catch (IOException e) {
253             return 0L;
254         }
255     }
256 
257     @Override
258     public long getSoftOpenFileLimit() {
259         if (getProcessID() == this.os.getProcessId()) {
260             final Resource.Rlimit rlimit = new Resource.Rlimit();
261             SolarisLibc.INSTANCE.getrlimit(SolarisLibc.RLIMIT_NOFILE, rlimit);
262             return rlimit.rlim_cur;
263         } else {
264             return getProcessOpenFileLimit(getProcessID(), 1);
265         }
266     }
267 
268     @Override
269     public long getHardOpenFileLimit() {
270         if (getProcessID() == this.os.getProcessId()) {
271             final Resource.Rlimit rlimit = new Resource.Rlimit();
272             SolarisLibc.INSTANCE.getrlimit(SolarisLibc.RLIMIT_NOFILE, rlimit);
273             return rlimit.rlim_max;
274         } else {
275             return getProcessOpenFileLimit(getProcessID(), 2);
276         }
277     }
278 
279     @Override
280     public int getBitness() {
281         return this.bitness.get();
282     }
283 
284     private int queryBitness() {
285         List<String> pflags = ExecutingCommand.runNative("pflags " + getProcessID());
286         for (String line : pflags) {
287             if (line.contains("data model")) {
288                 if (line.contains("LP32")) {
289                     return 32;
290                 } else if (line.contains("LP64")) {
291                     return 64;
292                 }
293             }
294         }
295         return 0;
296     }
297 
298     @Override
299     public long getAffinityMask() {
300         long bitMask = 0L;
301         String cpuset = ExecutingCommand.getFirstAnswer("pbind -q " + getProcessID());
302         // Sample output:
303         // <empty string if no binding>
304         // pid 101048 strongly bound to processor(s) 0 1 2 3.
305         if (cpuset.isEmpty()) {
306             List<String> allProcs = ExecutingCommand.runNative("psrinfo");
307             for (String proc : allProcs) {
308                 String[] split = ParseUtil.whitespaces.split(proc);
309                 int bitToSet = ParseUtil.parseIntOrDefault(split[0], -1);
310                 if (bitToSet >= 0) {
311                     bitMask |= 1L << bitToSet;
312                 }
313             }
314             return bitMask;
315         } else if (cpuset.endsWith(".") && cpuset.contains("strongly bound to processor(s)")) {
316             String parse = cpuset.substring(0, cpuset.length() - 1);
317             String[] split = ParseUtil.whitespaces.split(parse);
318             for (int i = split.length - 1; i >= 0; i--) {
319                 int bitToSet = ParseUtil.parseIntOrDefault(split[i], -1);
320                 if (bitToSet >= 0) {
321                     bitMask |= 1L << bitToSet;
322                 } else {
323                     // Once we run into the word processor(s) we're done
324                     break;
325                 }
326             }
327         }
328         return bitMask;
329     }
330 
331     @Override
332     public List<OSThread> getThreadDetails() {
333         // Get process files in proc
334         File directory = new File(String.format(Locale.ROOT, "/proc/%d/lwp", getProcessID()));
335         File[] numericFiles = directory.listFiles(file -> Constants.DIGITS.matcher(file.getName()).matches());
336         if (numericFiles == null) {
337             return Collections.emptyList();
338         }
339 
340         return Arrays.stream(numericFiles).parallel().map(
341                 lwpidFile -> new SolarisOSThread(getProcessID(), ParseUtil.parseIntOrDefault(lwpidFile.getName(), 0)))
342                 .filter(VALID_THREAD).collect(Collectors.toList());
343     }
344 
345     @Override
346     public boolean updateAttributes() {
347         SolarisPsInfo info = psinfo.get();
348         if (info == null) {
349             this.state = INVALID;
350             return false;
351         }
352         SolarisPrUsage usage = prusage.get();
353         long now = System.currentTimeMillis();
354         this.state = getStateFromOutput((char) info.pr_lwp.pr_sname);
355         this.parentProcessID = info.pr_ppid;
356         this.userID = Integer.toString(info.pr_euid);
357         this.user = UserGroupInfo.getUser(this.userID);
358         this.groupID = Integer.toString(info.pr_egid);
359         this.group = UserGroupInfo.getGroupName(this.groupID);
360         this.threadCount = info.pr_nlwp;
361         this.priority = info.pr_lwp.pr_pri;
362         // These are in KB, multiply
363         this.virtualSize = info.pr_size.longValue() * 1024;
364         this.residentSetSize = info.pr_rssize.longValue() * 1024;
365         this.startTime = info.pr_start.tv_sec.longValue() * 1000L + info.pr_start.tv_nsec.longValue() / 1_000_000L;
366         // Avoid divide by zero for processes up less than a millisecond
367         long elapsedTime = now - this.startTime;
368         this.upTime = elapsedTime < 1L ? 1L : elapsedTime;
369         this.kernelTime = 0L;
370         this.userTime = info.pr_time.tv_sec.longValue() * 1000L + info.pr_time.tv_nsec.longValue() / 1_000_000L;
371         // 80 character truncation but enough for path and name (usually)
372         this.commandLineBackup = Native.toString(info.pr_psargs);
373         this.path = ParseUtil.whitespaces.split(commandLineBackup)[0];
374         this.name = this.path.substring(this.path.lastIndexOf('/') + 1);
375         if (usage != null) {
376             this.userTime = usage.pr_utime.tv_sec.longValue() * 1000L + usage.pr_utime.tv_nsec.longValue() / 1_000_000L;
377             this.kernelTime = usage.pr_stime.tv_sec.longValue() * 1000L
378                     + usage.pr_stime.tv_nsec.longValue() / 1_000_000L;
379             this.bytesRead = usage.pr_ioch.longValue();
380             this.majorFaults = usage.pr_majf.longValue();
381             this.minorFaults = usage.pr_minf.longValue();
382             this.contextSwitches = usage.pr_ictx.longValue() + usage.pr_vctx.longValue();
383         }
384         return true;
385     }
386 
387     /***
388      * Returns Enum STATE for the state value obtained from status string of thread/process.
389      *
390      * @param stateValue state value from the status string
391      * @return The state
392      */
393     static State getStateFromOutput(char stateValue) {
394         State state;
395         switch (stateValue) {
396         case 'O':
397             state = RUNNING;
398             break;
399         case 'S':
400             state = SLEEPING;
401             break;
402         case 'R':
403         case 'W':
404             state = WAITING;
405             break;
406         case 'Z':
407             state = ZOMBIE;
408             break;
409         case 'T':
410             state = STOPPED;
411             break;
412         default:
413             state = OTHER;
414             break;
415         }
416         return state;
417     }
418 
419     private long getProcessOpenFileLimit(final long processId, final int index) {
420         final List<String> output = ExecutingCommand.runNative("plimit " + processId);
421         if (output.isEmpty()) {
422             return -1; // not supported
423         }
424 
425         final Optional<String> nofilesLine = output.stream().filter(line -> line.trim().startsWith("nofiles"))
426                 .findFirst();
427         if (!nofilesLine.isPresent()) {
428             return -1;
429         }
430 
431         // Split all non-Digits away -> ["", "{soft-limit}, "{hard-limit}"]
432         final String[] split = nofilesLine.get().split("\\D+");
433         return ParseUtil.parseLongOrDefault(split[index], -1);
434     }
435 }