diff --git a/CHANGELOG.md b/CHANGELOG.md index 195be4bc8da..674305314a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ [@phillips0616](https://github.com/phillips0616), [@BooSandy1994](https://github.com/BooSandy1994). [@dbwiddis](https://github.com/dbwiddis). +* [#960](https://github.com/oshi/oshi/pull/960): OSProcess constructor with PID. - [@Potat0x](https://github.com/Potat0x). * [#962](https://github.com/oshi/oshi/pull/962): Properly handle null WMI DateTime results. - [@dbwiddis](https://github.com/dbwiddis). * Your contribution here diff --git a/oshi-core/src/main/java/oshi/software/os/OSProcess.java b/oshi-core/src/main/java/oshi/software/os/OSProcess.java index 23e16fbf531..db7e7e15321 100644 --- a/oshi-core/src/main/java/oshi/software/os/OSProcess.java +++ b/oshi-core/src/main/java/oshi/software/os/OSProcess.java @@ -25,6 +25,9 @@ import java.io.Serializable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A process is an instance of a computer program that is being executed. It * contains the program code and its current activity. Depending on the @@ -35,6 +38,10 @@ public class OSProcess implements Serializable { private static final long serialVersionUID = 3L; + private static final Logger LOG = LoggerFactory.getLogger(OSProcess.class); + + private final OperatingSystem operatingSystem; + private String name = ""; private String path = ""; private String commandLine = ""; @@ -95,6 +102,60 @@ public enum State { OTHER } + /** + *

+ * Constructor for OSProcess. + *

+ * + * @param operatingSystem + * a {@link oshi.software.os.OperatingSystem} instance + */ + public OSProcess(OperatingSystem operatingSystem) { + this.operatingSystem = operatingSystem; + } + + /** + *

+ * Constructor for OSProcess given a Process ID. Instantiates an object with + * current statistics for that process ID, and is equivalent to + * {@link oshi.software.os.OperatingSystem#getProcess(int)}. + *

+ *

+ * If a process with that ID does not exist, this constructor will throw an + * {@link java.lang.InstantiationException}. + *

+ * + * @param operatingSystem + * a {@link oshi.software.os.OperatingSystem} instance + * @param processID + * process ID + * @throws InstantiationException + * If a process by that ID does not exist. + */ + public OSProcess(OperatingSystem operatingSystem, int processID) throws InstantiationException { + this.processID = processID; + this.operatingSystem = operatingSystem; + if (!updateAttributes()) { + throw new InstantiationException("A process with ID " + processID + " does not exist."); + } + } + + /** + * Attempts to updates all process attributes. Returns false if the update + * fails, which will occur if the process no longer exists. + * + * @return True if the update was successful, false if the update failed + */ + public boolean updateAttributes() { + OSProcess process = operatingSystem.getProcess(this.processID); + if (process == null) { + LOG.debug("No process found: {}", this.processID); + return false; + } + copyValuesToThisProcess(process); + return true; + } + /** *

* Getter for the field name. @@ -123,13 +184,15 @@ public String getPath() { *

* * @return Returns the process command line. The format of this string is - * platform-dependent and may require the end user to parse the result. + * platform-dependent and may require the end user to parse the + * result. * - * On Linux and macOS systems, the string is null-character-delimited, - * to permit the end user to parse the executable and arguments if - * desired. Further, the macOS variant may include environment variables - * which the end user may wish to exclude from display. On Solaris, the - * string is truncated to 80 characters. + * On Linux and macOS systems, the string is + * null-character-delimited, to permit the end user to parse the + * executable and arguments if desired. Further, the macOS variant + * may include environment variables which the end user may wish to + * exclude from display. On Solaris, the string is truncated to 80 + * characters. */ public String getCommandLine() { return this.commandLine; @@ -153,8 +216,8 @@ public String getCurrentWorkingDirectory() { * Getter for the field user. *

* - * @return Returns the user name. On Windows systems, also returns the domain - * prepended to the username. + * @return Returns the user name. On Windows systems, also returns the + * domain prepended to the username. */ public String getUser() { return this.user; @@ -165,7 +228,8 @@ public String getUser() { * Getter for the field userID. *

* - * @return Returns the userID. On Windows systems, returns the Security ID (SID) + * @return Returns the userID. On Windows systems, returns the Security ID + * (SID) */ public String getUserID() { return this.userID; @@ -178,13 +242,15 @@ public String getUserID() { * * @return Returns the group. * - * On Windows systems, populating this value for processes other than - * the current user requires administrative privileges (and still may - * fail for some system processes) and can incur significant latency. - * The value is only calculated for single process queries using + * On Windows systems, populating this value for processes other + * than the current user requires administrative privileges (and + * still may fail for some system processes) and can incur + * significant latency. The value is only calculated for single + * process queries using * {@link oshi.software.os.OperatingSystem#getProcess(int)}. When - * successful, returns a comma-delimited list of groups with access to - * this process, corresponding to the SIDs in {@link #getGroupID()}. + * successful, returns a comma-delimited list of groups with access + * to this process, corresponding to the SIDs in + * {@link #getGroupID()}. */ public String getGroup() { return this.group; @@ -197,13 +263,15 @@ public String getGroup() { * * @return Returns the groupID. * - * On Windows systems, populating this value for processes other than - * the current user requires administrative privileges (and still may - * fail for some system processes) and can incur significant latency. - * The value is only calculated for single process queries using + * On Windows systems, populating this value for processes other + * than the current user requires administrative privileges (and + * still may fail for some system processes) and can incur + * significant latency. The value is only calculated for single + * process queries using * {@link oshi.software.os.OperatingSystem#getProcess(int)}. When - * successful, returns a comma-delimited list of group SIDs with access - * to this process, corresponding to the names in {@link #getGroup()}. + * successful, returns a comma-delimited list of group SIDs with + * access to this process, corresponding to the names in + * {@link #getGroup()}. */ public String getGroupID() { return this.groupID; @@ -260,20 +328,20 @@ public int getThreadCount() { * * @return Returns the priority of this process. * - * For Linux and Unix, priority is a value in the range -20 to 19 (20 on - * some systems). The default priority is 0; lower priorities cause more - * favorable scheduling. + * For Linux and Unix, priority is a value in the range -20 to 19 + * (20 on some systems). The default priority is 0; lower priorities + * cause more favorable scheduling. * - * For Windows, priority values can range from 0 (lowest priority) to 31 - * (highest priority). + * For Windows, priority values can range from 0 (lowest priority) + * to 31 (highest priority). * - * Mac OS X has 128 priority levels, ranging from 0 (lowest priority) to - * 127 (highest priority). They are divided into several major bands: 0 - * through 51 are the normal levels; the default priority is 31. 52 - * through 79 are the highest priority regular threads; 80 through 95 - * are for kernel mode threads; and 96 through 127 correspond to - * real-time threads, which are treated differently than other threads - * by the scheduler. + * Mac OS X has 128 priority levels, ranging from 0 (lowest + * priority) to 127 (highest priority). They are divided into + * several major bands: 0 through 51 are the normal levels; the + * default priority is 31. 52 through 79 are the highest priority + * regular threads; 80 through 95 are for kernel mode threads; and + * 96 through 127 correspond to real-time threads, which are treated + * differently than other threads by the scheduler. */ public int getPriority() { return this.priority; @@ -284,9 +352,9 @@ public int getPriority() { * Getter for the field virtualSize. *

* - * @return Returns the Virtual Memory Size (VSZ). It includes all memory that - * the process can access, including memory that is swapped out and - * memory that is from shared libraries. + * @return Returns the Virtual Memory Size (VSZ). It includes all memory + * that the process can access, including memory that is swapped out + * and memory that is from shared libraries. */ public long getVirtualSize() { return this.virtualSize; @@ -297,12 +365,12 @@ public long getVirtualSize() { * Getter for the field residentSetSize. *

* - * @return Returns the Resident Set Size (RSS). On Windows, returns the Private - * Working Set size. It is used to show how much memory is allocated to - * that process and is in RAM. It does not include memory that is - * swapped out. It does include memory from shared libraries as long as - * the pages from those libraries are actually in memory. It does - * include all stack and heap memory. + * @return Returns the Resident Set Size (RSS). On Windows, returns the + * Private Working Set size. It is used to show how much memory is + * allocated to that process and is in RAM. It does not include + * memory that is swapped out. It does include memory from shared + * libraries as long as the pages from those libraries are actually + * in memory. It does include all stack and heap memory. */ public long getResidentSetSize() { return this.residentSetSize; @@ -325,8 +393,8 @@ public long getKernelTime() { * Getter for the field userTime. *

* - * @return Returns the number of milliseconds the process has executed in user - * mode. + * @return Returns the number of milliseconds the process has executed in + * user mode. */ public long getUserTime() { return this.userTime; @@ -348,8 +416,8 @@ public long getUpTime() { * Getter for the field startTime. *

* - * @return Returns the start time of the process in number of milliseconds since - * January 1, 1970. + * @return Returns the start time of the process in number of milliseconds + * since January 1, 1970. */ public long getStartTime() { return this.startTime; @@ -500,18 +568,19 @@ public void setThreadCount(int threadCount) { /** * Set the priority of this process. * - * For Linux, priority is a value in the range -20 to 19 (20 on some systems). - * The default priority is 0; lower priorities cause more favorable scheduling. + * For Linux, priority is a value in the range -20 to 19 (20 on some + * systems). The default priority is 0; lower priorities cause more + * favorable scheduling. * * For Windows, priority values can range from 0 (lowest priority) to 31 * (highest priority). * * Mac OS X has 128 priority levels, ranging from 0 (lowest priority) to 127 - * (highest priority). They are divided into several major bands: 0 through 51 - * are the normal levels; the default priority is 31. 52 through 79 are the - * highest priority regular threads; 80 through 95 are for kernel mode threads; - * and 96 through 127 correspond to real-time threads, which are treated - * differently than other threads by the scheduler. + * (highest priority). They are divided into several major bands: 0 through + * 51 are the normal levels; the default priority is 31. 52 through 79 are + * the highest priority regular threads; 80 through 95 are for kernel mode + * threads; and 96 through 127 correspond to real-time threads, which are + * treated differently than other threads by the scheduler. * * @param priority * priority @@ -521,9 +590,9 @@ public void setPriority(int priority) { } /** - * Set the Virtual Memory Size (VSZ). It includes all memory that the process - * can access, including memory that is swapped out and memory that is from - * shared libraries. + * Set the Virtual Memory Size (VSZ). It includes all memory that the + * process can access, including memory that is swapped out and memory that + * is from shared libraries. * * @param virtualSize * virtual size @@ -534,10 +603,10 @@ public void setVirtualSize(long virtualSize) { /** * Set the Resident Set Size (RSS). It is used to show how much memory is - * allocated to that process and is in RAM. It does not include memory that is - * swapped out. It does include memory from shared libraries as long as the - * pages from those libraries are actually in memory. It does include all stack - * and heap memory. + * allocated to that process and is in RAM. It does not include memory that + * is swapped out. It does include memory from shared libraries as long as + * the pages from those libraries are actually in memory. It does include + * all stack and heap memory. * * @param residentSetSize * resident set size @@ -567,8 +636,8 @@ public void setUserTime(long userTime) { } /** - * Set the start time of the process in number of milliseconds since January 1, - * 1970. + * Set the start time of the process in number of milliseconds since January + * 1, 1970. * * @param startTime * start time @@ -608,8 +677,8 @@ public void setBytesWritten(long bytesWritten) { } /** - * Sets the number of open file handles (or network connections) that belongs to - * the process + * Sets the number of open file handles (or network connections) that + * belongs to the process * * @param count * The number of handles @@ -619,8 +688,8 @@ public void setOpenFiles(long count) { } /** - * Sets the number of open file handles (or network connections) that belongs to - * the process + * Sets the number of open file handles (or network connections) that + * belongs to the process * * On FreeBSD and Solaris, this value is only populated if information for a * single process id is requested. @@ -634,8 +703,8 @@ public long getOpenFiles() { /** * Calculates CPU usage of this process. * - * @return The proportion of up time that the process was executing in kernel or - * user mode. + * @return The proportion of up time that the process was executing in + * kernel or user mode. */ public double calculateCpuPercent() { if (this.cpuPercent < 0d) { @@ -674,4 +743,31 @@ public int getBitness() { public void setBitness(int bitness) { this.bitness = bitness; } + + private void copyValuesToThisProcess(OSProcess sourceProcess) { + this.name = sourceProcess.name; + this.path = sourceProcess.path; + this.commandLine = sourceProcess.commandLine; + this.currentWorkingDirectory = sourceProcess.currentWorkingDirectory; + this.user = sourceProcess.user; + this.userID = sourceProcess.userID; + this.group = sourceProcess.group; + this.groupID = sourceProcess.groupID; + this.state = sourceProcess.state; + this.processID = sourceProcess.processID; + this.parentProcessID = sourceProcess.parentProcessID; + this.threadCount = sourceProcess.threadCount; + this.priority = sourceProcess.priority; + this.virtualSize = sourceProcess.virtualSize; + this.residentSetSize = sourceProcess.residentSetSize; + this.kernelTime = sourceProcess.kernelTime; + this.userTime = sourceProcess.userTime; + this.startTime = sourceProcess.startTime; + this.upTime = sourceProcess.upTime; + this.bytesRead = sourceProcess.bytesRead; + this.bytesWritten = sourceProcess.bytesWritten; + this.openFiles = sourceProcess.openFiles; + this.bitness = sourceProcess.bitness; + this.cpuPercent = sourceProcess.cpuPercent; + } } diff --git a/oshi-core/src/main/java/oshi/software/os/linux/LinuxOperatingSystem.java b/oshi-core/src/main/java/oshi/software/os/linux/LinuxOperatingSystem.java index e16b22c1c06..e9d9014db60 100644 --- a/oshi-core/src/main/java/oshi/software/os/linux/LinuxOperatingSystem.java +++ b/oshi-core/src/main/java/oshi/software/os/linux/LinuxOperatingSystem.java @@ -231,7 +231,7 @@ private OSProcess getProcess(int pid, LinuxUserGroupInfo userGroupInfo, boolean // call later, so just get the numeric bits here long[] statArray = ParseUtil.parseStringToLongArray(stat, PROC_PID_STAT_ORDERS, PROC_PID_STAT_LENGTH, ' '); // Fetch cached process if it exists - OSProcess proc = new OSProcess(); + OSProcess proc = new OSProcess(this); proc.setProcessID(pid); // The /proc/pid/cmdline value is null-delimited proc.setCommandLine(FileUtil.getStringFromFile(String.format("/proc/%d/cmdline", pid))); diff --git a/oshi-core/src/main/java/oshi/software/os/mac/MacOperatingSystem.java b/oshi-core/src/main/java/oshi/software/os/mac/MacOperatingSystem.java index 0fbbd62abe9..ab3421e4de5 100644 --- a/oshi-core/src/main/java/oshi/software/os/mac/MacOperatingSystem.java +++ b/oshi-core/src/main/java/oshi/software/os/mac/MacOperatingSystem.java @@ -195,7 +195,7 @@ private OSProcess getProcess(int pid, boolean slowFields) { // NOSONAR squid:S11 } } long now = System.currentTimeMillis(); - OSProcess proc = new OSProcess(); + OSProcess proc = new OSProcess(this); proc.setName(name); proc.setPath(path); switch (taskAllInfo.pbsd.pbi_status) { diff --git a/oshi-core/src/main/java/oshi/software/os/unix/freebsd/FreeBsdOperatingSystem.java b/oshi-core/src/main/java/oshi/software/os/unix/freebsd/FreeBsdOperatingSystem.java index 011ca81ba2f..168cbde7a67 100644 --- a/oshi-core/src/main/java/oshi/software/os/unix/freebsd/FreeBsdOperatingSystem.java +++ b/oshi-core/src/main/java/oshi/software/os/unix/freebsd/FreeBsdOperatingSystem.java @@ -144,7 +144,7 @@ private List getProcessListFromPS(String psCommand, int pid, boolean continue; } long now = System.currentTimeMillis(); - OSProcess fproc = new OSProcess(); + OSProcess fproc = new OSProcess(this); switch (split[0].charAt(0)) { case 'R': fproc.setState(OSProcess.State.RUNNING); diff --git a/oshi-core/src/main/java/oshi/software/os/unix/solaris/SolarisOperatingSystem.java b/oshi-core/src/main/java/oshi/software/os/unix/solaris/SolarisOperatingSystem.java index c22c36ed975..744edd5ce45 100644 --- a/oshi-core/src/main/java/oshi/software/os/unix/solaris/SolarisOperatingSystem.java +++ b/oshi-core/src/main/java/oshi/software/os/unix/solaris/SolarisOperatingSystem.java @@ -131,7 +131,7 @@ private List getProcessListFromPS(String psCommand, int pid, boolean continue; } long now = System.currentTimeMillis(); - OSProcess sproc = new OSProcess(); + OSProcess sproc = new OSProcess(this); switch (split[0].charAt(0)) { case 'O': sproc.setState(OSProcess.State.RUNNING); diff --git a/oshi-core/src/main/java/oshi/software/os/windows/WindowsOperatingSystem.java b/oshi-core/src/main/java/oshi/software/os/windows/WindowsOperatingSystem.java index b1bed2ee3a1..453c51c9eb7 100644 --- a/oshi-core/src/main/java/oshi/software/os/windows/WindowsOperatingSystem.java +++ b/oshi-core/src/main/java/oshi/software/os/windows/WindowsOperatingSystem.java @@ -426,7 +426,7 @@ private List processMapToList(Collection pids, boolean slowF if (pids != null && !pids.contains(pid)) { continue; } - proc = new OSProcess(); + proc = new OSProcess(this); proc.setProcessID(pid); proc.setName(IS_WINDOWS7_OR_GREATER ? processInfo[i].pProcessName : WmiUtil.getString(processWmiResult, ProcessXPProperty.NAME, i)); @@ -637,7 +637,7 @@ private Map buildProcessMapFromRegistry(Collection int pid = pPerfData.getInt(perfCounterBlockOffset + this.idProcessOffset); if (pids == null || pids.contains(pid)) { - OSProcess proc = new OSProcess(); + OSProcess proc = new OSProcess(this); processMap.put(pid, proc); proc.setProcessID(pid); diff --git a/oshi-core/src/test/java/oshi/software/os/OperatingSystemTest.java b/oshi-core/src/test/java/oshi/software/os/OperatingSystemTest.java index 3d129613818..52b0e91534c 100644 --- a/oshi-core/src/test/java/oshi/software/os/OperatingSystemTest.java +++ b/oshi-core/src/test/java/oshi/software/os/OperatingSystemTest.java @@ -24,8 +24,10 @@ package oshi.software.os; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Collection; @@ -190,17 +192,7 @@ public void testGetChildProcesses() { } if (SystemInfo.getCurrentPlatformEnum() != PlatformEnum.SOLARIS) { // Due to race condition, a process may terminate before we count - // its - // children. - if (onePid >= 0) { - assertTrue(0 <= os.getChildProcesses(onePid, 0, null).length); - } - if (nPid >= 0) { - assertTrue(0 <= os.getChildProcesses(nPid, 0, null).length); - } - if (mPid >= 0) { - assertTrue(0 <= os.getChildProcesses(mPid, 0, null).length); - } + // its children. Play the odds. // At least one of these tests should work. if (onePid >= 0 && nPid >= 0 && mPid >= 0) { assertTrue(os.getChildProcesses(onePid, 0, null).length == 1 @@ -219,7 +211,7 @@ public void testOSProcessSetters() { OperatingSystem os = si.getOperatingSystem(); OSProcess oldProcess = os.getProcess(os.getProcessId()); - OSProcess newProcess = new OSProcess(); + OSProcess newProcess = new OSProcess(os); newProcess.setName(oldProcess.getName()); newProcess.setPath(oldProcess.getPath()); newProcess.setCommandLine(oldProcess.getCommandLine()); @@ -279,4 +271,32 @@ public void testGetCommandLine() { assertTrue(processesWithNonEmptyCmdLine >= 1); } + + @Test + public void testConstructProcessWithGivenPid() throws InstantiationException { + SystemInfo si = new SystemInfo(); + OperatingSystem os = si.getOperatingSystem(); + + // Test using current process ID, which we are sure will exist during + // this test + int givenPid = os.getProcessId(); + OSProcess oldProcess = os.getProcess(givenPid); + OSProcess newProcess = new OSProcess(os, givenPid); + + assertEquals(oldProcess.getPath(), newProcess.getPath()); + assertEquals(oldProcess.getProcessID(), newProcess.getProcessID()); + assertTrue(newProcess.updateAttributes()); + + // Change the pid to a nonexistent one + oldProcess.setProcessID(-1); + assertFalse(oldProcess.updateAttributes()); + + // Try to instantiate with a nonexistent PID + try { + newProcess = new OSProcess(os, -1); + fail("Expected an InstantiationException"); + } catch (InstantiationException expected) { + assertEquals("A process with ID -1 does not exist.", expected.getMessage()); + } + } }