1
2
3
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
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;
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
303
304
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
324 break;
325 }
326 }
327 }
328 return bitMask;
329 }
330
331 @Override
332 public List<OSThread> getThreadDetails() {
333
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
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
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
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
389
390
391
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;
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
432 final String[] split = nofilesLine.get().split("\\D+");
433 return ParseUtil.parseLongOrDefault(split[index], -1);
434 }
435 }