1
2
3
4
5 package oshi.software.os.unix.aix;
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.function.Supplier;
29 import java.util.stream.Collectors;
30 import java.util.stream.Stream;
31
32 import com.sun.jna.platform.unix.Resource;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 import com.sun.jna.Native;
37 import com.sun.jna.platform.unix.aix.Perfstat.perfstat_process_t;
38
39 import oshi.annotation.concurrent.ThreadSafe;
40 import oshi.driver.unix.aix.PsInfo;
41 import oshi.driver.unix.aix.perfstat.PerfstatCpu;
42 import oshi.jna.platform.unix.AixLibc;
43 import oshi.jna.platform.unix.AixLibc.AixLwpsInfo;
44 import oshi.jna.platform.unix.AixLibc.AixPsInfo;
45 import oshi.software.common.AbstractOSProcess;
46 import oshi.software.os.OSThread;
47 import oshi.util.Constants;
48 import oshi.util.ExecutingCommand;
49 import oshi.util.ParseUtil;
50 import oshi.util.UserGroupInfo;
51 import oshi.util.tuples.Pair;
52
53
54
55
56 @ThreadSafe
57 public class AixOSProcess extends AbstractOSProcess {
58
59 private static final Logger LOG = LoggerFactory.getLogger(AixOSProcess.class);
60
61 private Supplier<Integer> bitness = memoize(this::queryBitness);
62 private Supplier<AixPsInfo> psinfo = memoize(this::queryPsInfo, defaultExpiration());
63 private Supplier<String> commandLine = memoize(this::queryCommandLine);
64 private Supplier<Pair<List<String>, Map<String, String>>> cmdEnv = memoize(this::queryCommandlineEnvironment);
65 private final Supplier<Long> affinityMask = memoize(PerfstatCpu::queryCpuAffinityMask, defaultExpiration());
66
67 private String name;
68 private String path = "";
69 private String commandLineBackup;
70 private String user;
71 private String userID;
72 private String group;
73 private String groupID;
74 private State state = State.INVALID;
75 private int parentProcessID;
76 private int threadCount;
77 private int priority;
78 private long virtualSize;
79 private long residentSetSize;
80 private long kernelTime;
81 private long userTime;
82 private long startTime;
83 private long upTime;
84 private long bytesRead;
85 private long bytesWritten;
86
87
88 private Supplier<perfstat_process_t[]> procCpu;
89 private final AixOperatingSystem os;
90
91 public AixOSProcess(int pid, Pair<Long, Long> userSysCpuTime, Supplier<perfstat_process_t[]> procCpu,
92 AixOperatingSystem os) {
93 super(pid);
94 this.procCpu = procCpu;
95 this.os = os;
96 updateAttributes(userSysCpuTime);
97 }
98
99 private AixPsInfo queryPsInfo() {
100 return PsInfo.queryPsInfo(this.getProcessID());
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 this.commandLine.get();
116 }
117
118 private String queryCommandLine() {
119 String cl = String.join(" ", getArguments());
120 return cl.isEmpty() ? this.commandLineBackup : cl;
121 }
122
123 @Override
124 public List<String> getArguments() {
125 return cmdEnv.get().getA();
126 }
127
128 @Override
129 public Map<String, String> getEnvironmentVariables() {
130 return cmdEnv.get().getB();
131 }
132
133 private Pair<List<String>, Map<String, String>> queryCommandlineEnvironment() {
134 return PsInfo.queryArgsEnv(getProcessID(), psinfo.get());
135 }
136
137 @Override
138 public String getCurrentWorkingDirectory() {
139 try {
140 String cwdLink = "/proc" + getProcessID() + "/cwd";
141 String cwd = new File(cwdLink).getCanonicalPath();
142 if (!cwd.equals(cwdLink)) {
143 return cwd;
144 }
145 } catch (IOException e) {
146 LOG.trace("Couldn't find cwd for pid {}: {}", getProcessID(), e.getMessage());
147 }
148 return "";
149 }
150
151 @Override
152 public String getUser() {
153 return this.user;
154 }
155
156 @Override
157 public String getUserID() {
158 return this.userID;
159 }
160
161 @Override
162 public String getGroup() {
163 return this.group;
164 }
165
166 @Override
167 public String getGroupID() {
168 return this.groupID;
169 }
170
171 @Override
172 public State getState() {
173 return this.state;
174 }
175
176 @Override
177 public int getParentProcessID() {
178 return this.parentProcessID;
179 }
180
181 @Override
182 public int getThreadCount() {
183 return this.threadCount;
184 }
185
186 @Override
187 public int getPriority() {
188 return this.priority;
189 }
190
191 @Override
192 public long getVirtualSize() {
193 return this.virtualSize;
194 }
195
196 @Override
197 public long getResidentSetSize() {
198 return this.residentSetSize;
199 }
200
201 @Override
202 public long getKernelTime() {
203 return this.kernelTime;
204 }
205
206 @Override
207 public long getUserTime() {
208 return this.userTime;
209 }
210
211 @Override
212 public long getUpTime() {
213 return this.upTime;
214 }
215
216 @Override
217 public long getStartTime() {
218 return this.startTime;
219 }
220
221 @Override
222 public long getBytesRead() {
223 return this.bytesRead;
224 }
225
226 @Override
227 public long getBytesWritten() {
228 return this.bytesWritten;
229 }
230
231 @Override
232 public long getOpenFiles() {
233 try (Stream<Path> fd = Files.list(Paths.get("/proc/" + getProcessID() + "/fd"))) {
234 return fd.count();
235 } catch (IOException e) {
236 return 0L;
237 }
238 }
239
240 @Override
241 public long getSoftOpenFileLimit() {
242 if (getProcessID() == this.os.getProcessId()) {
243 final Resource.Rlimit rlimit = new Resource.Rlimit();
244 AixLibc.INSTANCE.getrlimit(AixLibc.RLIMIT_NOFILE, rlimit);
245 return rlimit.rlim_cur;
246 } else {
247 return -1L;
248 }
249 }
250
251 @Override
252 public long getHardOpenFileLimit() {
253 if (getProcessID() == this.os.getProcessId()) {
254 final Resource.Rlimit rlimit = new Resource.Rlimit();
255 AixLibc.INSTANCE.getrlimit(AixLibc.RLIMIT_NOFILE, rlimit);
256 return rlimit.rlim_max;
257 } else {
258 return -1L;
259 }
260 }
261
262 @Override
263 public int getBitness() {
264 return this.bitness.get();
265 }
266
267 private int queryBitness() {
268 List<String> pflags = ExecutingCommand.runNative("pflags " + getProcessID());
269 for (String line : pflags) {
270 if (line.contains("data model")) {
271 if (line.contains("LP32")) {
272 return 32;
273 } else if (line.contains("LP64")) {
274 return 64;
275 }
276 }
277 }
278 return 0;
279 }
280
281 @Override
282 public long getAffinityMask() {
283 long mask = 0L;
284
285
286 File directory = new File(String.format(Locale.ROOT, "/proc/%d/lwp", getProcessID()));
287 File[] numericFiles = directory.listFiles(file -> Constants.DIGITS.matcher(file.getName()).matches());
288 if (numericFiles == null) {
289 return mask;
290 }
291
292 for (File lwpidFile : numericFiles) {
293 int lwpidNum = ParseUtil.parseIntOrDefault(lwpidFile.getName(), 0);
294 AixLwpsInfo info = PsInfo.queryLwpsInfo(getProcessID(), lwpidNum);
295 if (info != null) {
296 mask |= info.pr_bindpro;
297 }
298 }
299 mask &= affinityMask.get();
300 return mask;
301 }
302
303 @Override
304 public List<OSThread> getThreadDetails() {
305
306 File directory = new File(String.format(Locale.ROOT, "/proc/%d/lwp", getProcessID()));
307 File[] numericFiles = directory.listFiles(file -> Constants.DIGITS.matcher(file.getName()).matches());
308 if (numericFiles == null) {
309 return Collections.emptyList();
310 }
311
312 return Arrays.stream(numericFiles).parallel()
313 .map(lwpidFile -> new AixOSThread(getProcessID(), ParseUtil.parseIntOrDefault(lwpidFile.getName(), 0)))
314 .filter(VALID_THREAD).collect(Collectors.toList());
315 }
316
317 @Override
318 public boolean updateAttributes() {
319 perfstat_process_t[] perfstat = procCpu.get();
320 for (perfstat_process_t stat : perfstat) {
321 int statpid = (int) stat.pid;
322 if (statpid == getProcessID()) {
323 return updateAttributes(new Pair<>((long) stat.ucpu_time, (long) stat.scpu_time));
324 }
325 }
326 this.state = State.INVALID;
327 return false;
328 }
329
330 private boolean updateAttributes(Pair<Long, Long> userSysCpuTime) {
331 AixPsInfo info = psinfo.get();
332 if (info == null) {
333 this.state = INVALID;
334 return false;
335 }
336
337 long now = System.currentTimeMillis();
338 this.state = getStateFromOutput((char) info.pr_lwp.pr_sname);
339 this.parentProcessID = (int) info.pr_ppid;
340 this.userID = Long.toString(info.pr_euid);
341 this.user = UserGroupInfo.getUser(this.userID);
342 this.groupID = Long.toString(info.pr_egid);
343 this.group = UserGroupInfo.getGroupName(this.groupID);
344 this.threadCount = info.pr_nlwp;
345 this.priority = info.pr_lwp.pr_pri;
346
347 this.virtualSize = info.pr_size * 1024;
348 this.residentSetSize = info.pr_rssize * 1024;
349 this.startTime = info.pr_start.tv_sec * 1000L + info.pr_start.tv_nsec / 1_000_000L;
350
351 long elapsedTime = now - this.startTime;
352 this.upTime = elapsedTime < 1L ? 1L : elapsedTime;
353 this.userTime = userSysCpuTime.getA();
354 this.kernelTime = userSysCpuTime.getB();
355 this.commandLineBackup = Native.toString(info.pr_psargs);
356 this.path = ParseUtil.whitespaces.split(commandLineBackup)[0];
357 this.name = this.path.substring(this.path.lastIndexOf('/') + 1);
358 if (this.name.isEmpty()) {
359 this.name = Native.toString(info.pr_fname);
360 }
361 return true;
362 }
363
364
365
366
367
368
369
370 static State getStateFromOutput(char stateValue) {
371 State state;
372 switch (stateValue) {
373 case 'O':
374 state = INVALID;
375 break;
376 case 'R':
377 case 'A':
378 state = RUNNING;
379 break;
380 case 'I':
381 state = WAITING;
382 break;
383 case 'S':
384 case 'W':
385 state = SLEEPING;
386 break;
387 case 'Z':
388 state = ZOMBIE;
389 break;
390 case 'T':
391 state = STOPPED;
392 break;
393 default:
394 state = OTHER;
395 break;
396 }
397 return state;
398 }
399 }