oshi.software.os.unix.solaris.SolarisOSProcess Maven / Gradle / Ivy
/*
* Copyright 2020-2023 The OSHI Project Contributors
* SPDX-License-Identifier: MIT
*/
package oshi.software.os.unix.solaris;
import static oshi.software.os.OSProcess.State.INVALID;
import static oshi.software.os.OSProcess.State.OTHER;
import static oshi.software.os.OSProcess.State.RUNNING;
import static oshi.software.os.OSProcess.State.SLEEPING;
import static oshi.software.os.OSProcess.State.STOPPED;
import static oshi.software.os.OSProcess.State.WAITING;
import static oshi.software.os.OSProcess.State.ZOMBIE;
import static oshi.software.os.OSThread.ThreadFiltering.VALID_THREAD;
import static oshi.util.Memoizer.defaultExpiration;
import static oshi.util.Memoizer.memoize;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.sun.jna.platform.unix.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jna.Native;
import oshi.annotation.concurrent.ThreadSafe;
import oshi.driver.unix.solaris.PsInfo;
import oshi.jna.platform.unix.SolarisLibc;
import oshi.jna.platform.unix.SolarisLibc.SolarisPrUsage;
import oshi.jna.platform.unix.SolarisLibc.SolarisPsInfo;
import oshi.software.common.AbstractOSProcess;
import oshi.software.os.OSThread;
import oshi.util.Constants;
import oshi.util.ExecutingCommand;
import oshi.util.ParseUtil;
import oshi.util.UserGroupInfo;
import oshi.util.tuples.Pair;
/**
* OSProcess implementation
*/
@ThreadSafe
public class SolarisOSProcess extends AbstractOSProcess {
private static final Logger LOG = LoggerFactory.getLogger(SolarisOSProcess.class);
private final SolarisOperatingSystem os;
private Supplier bitness = memoize(this::queryBitness);
private Supplier psinfo = memoize(this::queryPsInfo, defaultExpiration());
private Supplier commandLine = memoize(this::queryCommandLine);
private Supplier, Map>> cmdEnv = memoize(this::queryCommandlineEnvironment);
private Supplier prusage = memoize(this::queryPrUsage, defaultExpiration());
private String name;
private String path = "";
private String commandLineBackup;
private String user;
private String userID;
private String group;
private String groupID;
private State state = State.INVALID;
private int parentProcessID;
private int threadCount;
private int priority;
private long virtualSize;
private long residentSetSize;
private long kernelTime;
private long userTime;
private long startTime;
private long upTime;
private long bytesRead;
private long bytesWritten;
private long minorFaults;
private long majorFaults;
private long contextSwitches = 0; // default
public SolarisOSProcess(int pid, SolarisOperatingSystem os) {
super(pid);
this.os = os;
updateAttributes();
}
private SolarisPsInfo queryPsInfo() {
return PsInfo.queryPsInfo(this.getProcessID());
}
private SolarisPrUsage queryPrUsage() {
return PsInfo.queryPrUsage(this.getProcessID());
}
@Override
public String getName() {
return this.name;
}
@Override
public String getPath() {
return this.path;
}
@Override
public String getCommandLine() {
return this.commandLine.get();
}
private String queryCommandLine() {
String cl = String.join(" ", getArguments());
return cl.isEmpty() ? this.commandLineBackup : cl;
}
@Override
public List getArguments() {
return cmdEnv.get().getA();
}
@Override
public Map getEnvironmentVariables() {
return cmdEnv.get().getB();
}
private Pair, Map> queryCommandlineEnvironment() {
return PsInfo.queryArgsEnv(getProcessID(), psinfo.get());
}
@Override
public String getCurrentWorkingDirectory() {
try {
String cwdLink = "/proc" + getProcessID() + "/cwd";
String cwd = new File(cwdLink).getCanonicalPath();
if (!cwd.equals(cwdLink)) {
return cwd;
}
} catch (IOException e) {
LOG.trace("Couldn't find cwd for pid {}: {}", getProcessID(), e.getMessage());
}
return "";
}
@Override
public String getUser() {
return this.user;
}
@Override
public String getUserID() {
return this.userID;
}
@Override
public String getGroup() {
return this.group;
}
@Override
public String getGroupID() {
return this.groupID;
}
@Override
public State getState() {
return this.state;
}
@Override
public int getParentProcessID() {
return this.parentProcessID;
}
@Override
public int getThreadCount() {
return this.threadCount;
}
@Override
public int getPriority() {
return this.priority;
}
@Override
public long getVirtualSize() {
return this.virtualSize;
}
@Override
public long getResidentSetSize() {
return this.residentSetSize;
}
@Override
public long getKernelTime() {
return this.kernelTime;
}
@Override
public long getUserTime() {
return this.userTime;
}
@Override
public long getUpTime() {
return this.upTime;
}
@Override
public long getStartTime() {
return this.startTime;
}
@Override
public long getBytesRead() {
return this.bytesRead;
}
@Override
public long getBytesWritten() {
return this.bytesWritten;
}
@Override
public long getMinorFaults() {
return this.minorFaults;
}
@Override
public long getMajorFaults() {
return this.majorFaults;
}
@Override
public long getContextSwitches() {
return this.contextSwitches;
}
@Override
public long getOpenFiles() {
try (Stream fd = Files.list(Paths.get("/proc/" + getProcessID() + "/fd"))) {
return fd.count();
} catch (IOException e) {
return 0L;
}
}
@Override
public long getSoftOpenFileLimit() {
if (getProcessID() == this.os.getProcessId()) {
final Resource.Rlimit rlimit = new Resource.Rlimit();
SolarisLibc.INSTANCE.getrlimit(SolarisLibc.RLIMIT_NOFILE, rlimit);
return rlimit.rlim_cur;
} else {
return getProcessOpenFileLimit(getProcessID(), 1);
}
}
@Override
public long getHardOpenFileLimit() {
if (getProcessID() == this.os.getProcessId()) {
final Resource.Rlimit rlimit = new Resource.Rlimit();
SolarisLibc.INSTANCE.getrlimit(SolarisLibc.RLIMIT_NOFILE, rlimit);
return rlimit.rlim_max;
} else {
return getProcessOpenFileLimit(getProcessID(), 2);
}
}
@Override
public int getBitness() {
return this.bitness.get();
}
private int queryBitness() {
List pflags = ExecutingCommand.runNative("pflags " + getProcessID());
for (String line : pflags) {
if (line.contains("data model")) {
if (line.contains("LP32")) {
return 32;
} else if (line.contains("LP64")) {
return 64;
}
}
}
return 0;
}
@Override
public long getAffinityMask() {
long bitMask = 0L;
String cpuset = ExecutingCommand.getFirstAnswer("pbind -q " + getProcessID());
// Sample output:
//
// pid 101048 strongly bound to processor(s) 0 1 2 3.
if (cpuset.isEmpty()) {
List allProcs = ExecutingCommand.runNative("psrinfo");
for (String proc : allProcs) {
String[] split = ParseUtil.whitespaces.split(proc);
int bitToSet = ParseUtil.parseIntOrDefault(split[0], -1);
if (bitToSet >= 0) {
bitMask |= 1L << bitToSet;
}
}
return bitMask;
} else if (cpuset.endsWith(".") && cpuset.contains("strongly bound to processor(s)")) {
String parse = cpuset.substring(0, cpuset.length() - 1);
String[] split = ParseUtil.whitespaces.split(parse);
for (int i = split.length - 1; i >= 0; i--) {
int bitToSet = ParseUtil.parseIntOrDefault(split[i], -1);
if (bitToSet >= 0) {
bitMask |= 1L << bitToSet;
} else {
// Once we run into the word processor(s) we're done
break;
}
}
}
return bitMask;
}
@Override
public List getThreadDetails() {
// Get process files in proc
File directory = new File(String.format(Locale.ROOT, "/proc/%d/lwp", getProcessID()));
File[] numericFiles = directory.listFiles(file -> Constants.DIGITS.matcher(file.getName()).matches());
if (numericFiles == null) {
return Collections.emptyList();
}
return Arrays.stream(numericFiles).parallel().map(
lwpidFile -> new SolarisOSThread(getProcessID(), ParseUtil.parseIntOrDefault(lwpidFile.getName(), 0)))
.filter(VALID_THREAD).collect(Collectors.toList());
}
@Override
public boolean updateAttributes() {
SolarisPsInfo info = psinfo.get();
if (info == null) {
this.state = INVALID;
return false;
}
SolarisPrUsage usage = prusage.get();
long now = System.currentTimeMillis();
this.state = getStateFromOutput((char) info.pr_lwp.pr_sname);
this.parentProcessID = info.pr_ppid;
this.userID = Integer.toString(info.pr_euid);
this.user = UserGroupInfo.getUser(this.userID);
this.groupID = Integer.toString(info.pr_egid);
this.group = UserGroupInfo.getGroupName(this.groupID);
this.threadCount = info.pr_nlwp;
this.priority = info.pr_lwp.pr_pri;
// These are in KB, multiply
this.virtualSize = info.pr_size.longValue() * 1024;
this.residentSetSize = info.pr_rssize.longValue() * 1024;
this.startTime = info.pr_start.tv_sec.longValue() * 1000L + info.pr_start.tv_nsec.longValue() / 1_000_000L;
// Avoid divide by zero for processes up less than a millisecond
long elapsedTime = now - this.startTime;
this.upTime = elapsedTime < 1L ? 1L : elapsedTime;
this.kernelTime = 0L;
this.userTime = info.pr_time.tv_sec.longValue() * 1000L + info.pr_time.tv_nsec.longValue() / 1_000_000L;
// 80 character truncation but enough for path and name (usually)
this.commandLineBackup = Native.toString(info.pr_psargs);
this.path = ParseUtil.whitespaces.split(commandLineBackup)[0];
this.name = this.path.substring(this.path.lastIndexOf('/') + 1);
if (usage != null) {
this.userTime = usage.pr_utime.tv_sec.longValue() * 1000L + usage.pr_utime.tv_nsec.longValue() / 1_000_000L;
this.kernelTime = usage.pr_stime.tv_sec.longValue() * 1000L
+ usage.pr_stime.tv_nsec.longValue() / 1_000_000L;
this.bytesRead = usage.pr_ioch.longValue();
this.majorFaults = usage.pr_majf.longValue();
this.minorFaults = usage.pr_minf.longValue();
this.contextSwitches = usage.pr_ictx.longValue() + usage.pr_vctx.longValue();
}
return true;
}
/***
* Returns Enum STATE for the state value obtained from status string of thread/process.
*
* @param stateValue state value from the status string
* @return The state
*/
static State getStateFromOutput(char stateValue) {
State state;
switch (stateValue) {
case 'O':
state = RUNNING;
break;
case 'S':
state = SLEEPING;
break;
case 'R':
case 'W':
state = WAITING;
break;
case 'Z':
state = ZOMBIE;
break;
case 'T':
state = STOPPED;
break;
default:
state = OTHER;
break;
}
return state;
}
private long getProcessOpenFileLimit(final long processId, final int index) {
final List output = ExecutingCommand.runNative("plimit " + processId);
if (output.isEmpty()) {
return -1; // not supported
}
final Optional nofilesLine = output.stream().filter(line -> line.trim().startsWith("nofiles"))
.findFirst();
if (!nofilesLine.isPresent()) {
return -1;
}
// Split all non-Digits away -> ["", "{soft-limit}, "{hard-limit}"]
final String[] split = nofilesLine.get().split("\\D+");
return ParseUtil.parseLongOrDefault(split[index], -1);
}
}