View Javadoc
1   /*
2    * Copyright 2020-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
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   * OSProcess implementation
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      // Memoized copy from OperatingSystem
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; // not supported
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; // not supported
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         // Need to capture pr_bndpro for all threads
285         // Get process files in proc
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         // Iterate files
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         // Get process files in proc
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         // These are in KB, multiply
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         // Avoid divide by zero for processes up less than a millisecond
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      * Returns Enum STATE for the state value obtained from status string of thread/process.
366      *
367      * @param stateValue state value from the status string
368      * @return The state
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 }