org.spf4j.unix.JVMArguments Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spf4j-core Show documentation
Show all versions of spf4j-core Show documentation
A continuously growing collection of utilities to measure performance, get better diagnostics,
improve performance, or do things more reliably, faster that other open source libraries...
The newest version!
/*
* Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Additionally licensed with:
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spf4j.unix;
import com.google.common.io.Files;
import com.sun.jna.StringArray;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import static com.sun.jna.Pointer.NULL;
import com.sun.jna.ptr.IntByReference;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.io.File;
import java.io.ByteArrayOutputStream;
import java.io.RandomAccessFile;
import java.io.DataInputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.function.Function;
import javax.annotation.Nullable;
import org.spf4j.base.Runtime;
import org.spf4j.unix.CLibrary.FILE;
/**
* List of arguments for Java VM and application. based on class from akuma lib (http://akuma.kohsuke.org).
*/
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
// default char encoding is used on purpose.
public final class JVMArguments {
private final List arguments;
public JVMArguments(final int size) {
arguments = new ArrayList<>(size);
}
public JVMArguments(final Collection extends String> c) {
arguments = new ArrayList<>(c);
}
public String getExecutable() {
return arguments.get(0);
}
/**
* Removes the first System property.
* @param pname the name of the system property to remove.
* @return the value of the removed system property. or null if there is no such property.
*/
@Nullable
public String removeSystemProperty(final String pname) {
Iterator itr = arguments.iterator();
itr.next(); // skip command.
int nl = pname.length();
while (itr.hasNext()) {
String s = itr.next();
if (s.startsWith("-D") && s.regionMatches(2, pname, 0, nl)) {
int l = nl + 2;
if (s.length() == l) {
itr.remove();
return "";
} else if (s.charAt(l) == '=') {
itr.remove();
return s.substring(l + 1);
}
}
}
return null;
}
@Nullable
public String getSystemProperty(final String pname) {
Iterator itr = arguments.iterator();
itr.next(); // skip command.
int nl = pname.length();
while (itr.hasNext()) {
String s = itr.next();
if (s.startsWith("-D") && s.regionMatches(2, pname, 0, nl)) {
int l = nl + 2;
if (s.length() == l) {
return "";
} else if (s.charAt(l) == '=') {
return s.substring(l + 1);
}
}
}
return null;
}
@Nullable
public void createOrUpdateSystemProperty(final String pname, final Function replacer) {
ListIterator itr = arguments.listIterator();
itr.next(); // skip command.
int nl = pname.length();
while (itr.hasNext()) {
String s = itr.next();
if (s.startsWith("-D") && s.regionMatches(2, pname, 0, nl)) {
int l = nl + 2;
if (s.length() == l) {
itr.set("-D" + pname + '=' + replacer.apply(""));
return;
} else if (s.charAt(l) == '=') {
itr.set("-D" + pname + '=' + replacer.apply(s.substring(l + 1)));
return;
}
}
}
arguments.add(1, "-D" + pname + '=' + replacer.apply(null));
}
public boolean hasSystemProperty(final String pname) {
Iterator itr = arguments.iterator();
itr.next(); // skip command.
int nl = pname.length();
while (itr.hasNext()) {
String s = itr.next();
if (s.startsWith("-D") && s.regionMatches(2, pname, 0, nl)) {
int l = nl + 2;
if (s.length() == l) {
return true;
} else if (s.charAt(l) == '=') {
return true;
}
}
}
return false;
}
/**
* remove all system properties starting with a prefix.
* @param pname the prefix
* @return number of system properties removed.
*/
public int removeAllSystemPropertiesStartingWith(final String pname) {
String name = "-D" + pname;
int nrRemoved = 0;
Iterator itr = arguments.iterator();
itr.next();
while (itr.hasNext()) {
String s = itr.next();
if (s.startsWith(name)) {
itr.remove();
nrRemoved++;
}
}
return nrRemoved;
}
public void setSystemProperty(final String name, final String value) {
removeSystemProperty(name);
// index 0 is the executable name
arguments.add(1, "-D" + name + '=' + value);
}
public void setVMArgument(final String argument) {
if (!hasVMArgument(argument)) {
arguments.add(1, argument);
}
}
public boolean removeVMArgument(final String argument) {
Iterator itr = arguments.iterator();
itr.next();
while (itr.hasNext()) {
String s = itr.next();
if (s.equals(argument)) {
itr.remove();
return true;
}
}
return false;
}
public boolean removeVMArgumentStartingWith(final String argument) {
Iterator itr = arguments.iterator();
itr.next();
while (itr.hasNext()) {
String s = itr.next();
if (s.startsWith(argument)) {
itr.remove();
return true;
}
}
return false;
}
public boolean hasVMArgument(final String argument) {
Iterator itr = arguments.iterator();
itr.next();
while (itr.hasNext()) {
String s = itr.next();
if (s.equals(argument)) {
return true;
}
}
return false;
}
public boolean hasVMArgumentStartingWith(final String argumentPrefix) {
Iterator itr = arguments.iterator();
itr.next();
while (itr.hasNext()) {
String s = itr.next();
if (s.startsWith(argumentPrefix)) {
return true;
}
}
return false;
}
public void add(final String arg) {
arguments.add(arg);
}
/**
* Removes the n items from the end. Useful for removing all the Java arguments to rebuild them.
*/
public void removeTail(final int n) {
int size = arguments.size();
arguments.removeAll(arguments.subList(size - n, size));
}
public StringArray toStringArray() {
return new StringArray(arguments.toArray(new String[arguments.size()]));
}
/**
* Gets the process argument list of the current process.
*/
public static JVMArguments current() throws IOException {
return of(-1);
}
/**
* Gets the process argument list of the specified process ID.
*
* @param pid -1 to indicate the current process.
*/
public static JVMArguments of(final int pid) throws IOException {
String os = Runtime.OS_NAME;
switch (os) {
case "Linux":
return ofLinux(pid);
case "SunOS":
return ofSolaris(pid);
case "Mac OS X":
return ofMac(pid);
case "FreeBSD":
return ofFreeBSD(pid);
default:
throw new UnsupportedOperationException("Unsupported Operating System " + os);
}
}
private static JVMArguments ofLinux(final int ppid) throws IOException {
int pid = resolvePID(ppid);
String cmdline = Files.asCharSource(new File("/proc/" + pid + "/cmdline"), Charset.defaultCharset()).read();
return new JVMArguments(java.util.Arrays.asList(cmdline.split("\0")));
}
private static int resolvePID(final int pid) {
if (pid == -1) {
return Runtime.PID;
} else {
return pid;
}
}
private static JVMArguments ofSolaris(final int ppid) throws IOException {
// /proc shows different contents based on the caller's memory model, so we need to know if we are 32 or 64.
// 32 JVMs are the norm, so err on the 32bit side.
boolean areWe64 = "64".equals(System.getProperty("sun.arch.data.model"));
int pid = resolvePID(ppid);
try (RandomAccessFile psinfo = new RandomAccessFile(new File("/proc/" + pid + "/psinfo"), "r")) {
// see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/procfs.h
//typedef struct psinfo {
// int pr_flag; /* process flags */
// int pr_nlwp; /* number of lwps in the process */
// pid_t pr_pid; /* process id */
// pid_t pr_ppid; /* process id of parent */
// pid_t pr_pgid; /* process id of process group leader */
// pid_t pr_sid; /* session id */
// uid_t pr_uid; /* real user id */
// uid_t pr_euid; /* effective user id */
// gid_t pr_gid; /* real group id */
// gid_t pr_egid; /* effective group id */
// uintptr_t pr_addr; /* address of process */
// size_t pr_size; /* size of process image in Kbytes */
// size_t pr_rssize; /* resident set size in Kbytes */
// dev_t pr_ttydev; /* controlling tty device (or PRNODEV) */
// ushort_t pr_pctcpu; /* % of recent cpu time used by all lwps */
// ushort_t pr_pctmem; /* % of system memory used by process */
// timestruc_t pr_start; /* process start time, from the epoch */
// timestruc_t pr_time; /* cpu time for this process */
// timestruc_t pr_ctime; /* cpu time for reaped children */
// char pr_fname[PRFNSZ]; /* name of exec'ed file */
// char pr_psargs[PRARGSZ]; /* initial characters of arg list */
// int pr_wstat; /* if zombie, the wait() status */
// int pr_argc; /* initial argument count */
// uintptr_t pr_argv; /* address of initial argument vector */
// uintptr_t pr_envp; /* address of initial environment vector */
// char pr_dmodel; /* data model of the process */
// lwpsinfo_t pr_lwp; /* information for representative lwp */
//} psinfo_t;
// see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/sys/types.h
// for the size of the various datatype.
// see http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/cmd/ptools/pargs/pargs.c
// for how to read this information
psinfo.seek(8);
if (adjust(psinfo.readInt()) != pid) {
throw new IOException("psinfo PID mismatch: " + pid + ", " + psinfo); // sanity check
}
/* The following program computes the offset:
#include
#include
int main() {
printf("psinfo_t = %d\n", sizeof(psinfo_t));
psinfo_t *x;
x = 0;
printf("%x\n", &(x->pr_argc));
}
*/
psinfo.seek(areWe64 ? 0xEC : 0xBC); // now jump to pr_argc
int argc = adjust(psinfo.readInt());
long argp = areWe64 ? adjust(psinfo.readLong()) : to64(adjust(psinfo.readInt()));
File asFile = new File("/proc/" + pid + "/as");
if (areWe64) {
// 32bit and 64bit basically does the same thing, but because the stream position
// is computed with signed long, doing 64bit seek to a position bigger than Long.MAX_VALUE
// requres some real hacking. Hence two different code path.
// (RandomAccessFile uses Java long for offset, so it just can't get to anywhere beyond Long.MAX_VALUE)
FILE fp = CLibrary.INSTANCE.fopen(asFile.getPath(), "r");
try {
JVMArguments args = new JVMArguments(16);
Memory m = new Memory(8);
for (int n = 0; n < argc; n++) {
// read a pointer to one entry
seek64(fp, argp + ((long) n) * 8);
m.setLong(0, 0); // just to make sure failed read won't result in bogus value
CLibrary.INSTANCE.fread(m, 1, 8, fp);
long p = m.getLong(0);
args.add(readLine(fp, p));
}
return args;
} finally {
CLibrary.INSTANCE.fclose(fp);
}
} else {
try (RandomAccessFile as = new RandomAccessFile(asFile, "r")) {
JVMArguments args = new JVMArguments(16);
for (int n = 0; n < argc; n++) {
// read a pointer to one entry
as.seek(argp + n * 4);
int p = adjust(as.readInt());
args.add(readLine(as, p));
}
return args;
}
}
}
}
/**
* Seek to the specified position. This method handles offset bigger than {@link Long#MAX_VALUE} correctly.
*
* @param upos This value is interpreted as unsigned 64bit integer (even though it's typed 'long')
*/
private static void seek64(final FILE fp, final long pupos) {
long upos = pupos;
CLibrary.INSTANCE.fseek(fp, 0, 0); // start at the beginning
while (upos < 0) {
long chunk = Long.MAX_VALUE;
upos -= chunk;
CLibrary.INSTANCE.fseek(fp, chunk, 1);
}
CLibrary.INSTANCE.fseek(fp, upos, 1);
}
/**
* {@link DataInputStream} reads a value in big-endian, so convert it to the correct value on little-endian systems.
*/
private static int adjust(final int i) {
if (Runtime.IS_LITTLE_ENDIAN) {
return (i << 24) | ((i << 8) & 0x00FF0000) | ((i >> 8) & 0x0000FF00) | (i >>> 24);
} else {
return i;
}
}
private static long adjust(final long i) {
if (Runtime.IS_LITTLE_ENDIAN) {
return (i << 56)
| ((i << 40) & 0x00FF000000000000L)
| ((i << 24) & 0x0000FF0000000000L)
| ((i << 8) & 0x000000FF00000000L)
| ((i >> 8) & 0x00000000FF000000L)
| ((i >> 24) & 0x0000000000FF0000L)
| ((i >> 40) & 0x000000000000FF00L)
| (i >> 56);
} else {
return i;
}
}
/**
* int to long conversion with zero-padding.
*/
private static long to64(final int i) {
return i & 0xFFFFFFFFL;
}
private static String readLine(final RandomAccessFile as, final int p) throws IOException {
as.seek(to64(p));
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int ch;
while ((ch = as.read()) > 0) {
buf.write(ch);
}
return buf.toString();
}
private static String readLine(final FILE as, final long p) {
seek64(as, p);
Memory m = new Memory(1);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
while (true) {
if (CLibrary.INSTANCE.fread(m, 1, 1, as) == 0) {
break;
}
byte b = m.getByte(0);
if (b == 0) {
break;
}
buf.write(b);
}
return buf.toString();
}
/**
* Mac support
*
* See http://developer.apple.com/qa/qa2001/qa1123.html http://www.osxfaq.com/man/3/kvm_getprocs.ws
* http://matburt.net/?p=16 (libkvm is removed from OSX) where is kinfo_proc?
* http://lists.apple.com/archives/xcode-users/2008/Mar/msg00781.html
*
* This code uses sysctl to get the arg/env list:
* http://www.psychofx.com/psi/trac/browser/psi/trunk/src/arch/macosx/macosx_process.c which came from
* http://www.opensource.apple.com/darwinsource/10.4.2/top-15/libtop.c
*
* sysctl is defined in libc.
*
* PS source code for Mac: http://www.opensource.apple.com/darwinsource/10.4.1/adv_cmds-79.1/ps.tproj/
*/
@SuppressFBWarnings("PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS")
private static JVMArguments ofMac(final int pid) {
// local constants
final int ctlKern = 1;
final int kernArgMax = 8;
final int kernProcArgs2 = 49;
final int sizeOfInt = Native.getNativeSize(int.class);
IntByReference ibf = new IntByReference();
IntByReference argmaxRef = new IntByReference(0);
IntByReference size = new IntByReference(sizeOfInt);
// for some reason, I was never able to get sysctlbyname work.
// if(LIBC.sysctlbyname("kern.argmax", argmaxRef.getPointer(), size, NULL, ibf)!=0)
if (CLibrary.INSTANCE.sysctl(new int[]{ctlKern, kernArgMax}, 2, argmaxRef.getPointer(), size, NULL, ibf) != 0) {
throw new UnsupportedOperationException("Failed to get kernl.argmax: "
+ CLibrary.INSTANCE.strerror(Native.getLastError()));
}
int argmax = argmaxRef.getValue();
StringArrayMemory m = new StringArrayMemory(argmax, sizeOfInt);
size.setValue(argmax);
if (CLibrary.INSTANCE.sysctl(new int[]{ctlKern, kernProcArgs2, resolvePID(pid)}, 3, m, size, NULL, ibf) != 0) {
throw new UnsupportedOperationException("Failed to obtain ken.procargs2: "
+ CLibrary.INSTANCE.strerror(Native.getLastError()));
}
/*
* Make a sysctl() call to get the raw argument space of the
* process. The layout is documented in start.s, which is part
* of the Csu project. In summary, it looks like:
*
* /---------------\ 0x00000000
* : :
* : :
* |---------------|
* | argc |
* |---------------|
* | arg[0] |
* |---------------|
* : :
* : :
* |---------------|
* | arg[argc - 1] |
* |---------------|
* | 0 |
* |---------------|
* | env[0] |
* |---------------|
* : :
* : :
* |---------------|
* | env[n] |
* |---------------|
* | 0 |
* |---------------| <-- Beginning of data returned by sysctl()
* | exec_path | is here.
* |:::::::::::::::|
* | |
* | String area. |
* | |
* |---------------| <-- Top of stack.
* : :
* : :
* \---------------/ 0xffffffff
*/
int nargs = m.readInt();
JVMArguments args = new JVMArguments(nargs);
m.readString(); // exec path
for (int i = 0; i < nargs; i++) {
m.skip0();
args.add(m.readString());
}
return args;
}
@SuppressFBWarnings("EQ_DOESNT_OVERRIDE_EQUALS")
private static final class StringArrayMemory extends Memory {
private long offset = 0;
private final int sizeOfInt;
StringArrayMemory(final long l, final int sizeOfInt) {
super(l);
this.sizeOfInt = sizeOfInt;
}
private int readInt() {
int r = getInt(offset);
offset += sizeOfInt;
return r;
}
private String readString() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte ch;
while ((ch = getByte(offset++)) != '\0') {
baos.write(ch);
}
return baos.toString();
}
void skip0() {
// skip trailing '\0's
while (getByte(offset) == '\0') {
offset++;
}
}
}
private static JVMArguments ofFreeBSD(final int pid) {
// taken from sys/sysctl.h
final int ctlKern = 1;
final int kernArgMax = 8;
final int kernProc = 14;
final int kernProcArgs = 7;
IntByReference ibr = new IntByReference();
IntByReference sysctlArgMax = new IntByReference();
IntByReference size = new IntByReference();
size.setValue(4);
if (CLibrary.INSTANCE.sysctl(new int[]{ctlKern, kernArgMax},
2, sysctlArgMax.getPointer(), size, NULL, ibr) != 0) {
throw new UnsupportedOperationException("Failed to sysctl kern.argmax");
}
int argmax = sysctlArgMax.getValue();
Memory m = new Memory(argmax);
size.setValue(argmax);
if (CLibrary.INSTANCE.sysctl(new int[]{ctlKern, kernProc, kernProcArgs, resolvePID(pid)},
4, m, size, NULL, ibr) != 0) {
throw new UnsupportedOperationException();
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ArrayList lArgs = new ArrayList();
byte ch;
int offset = 0;
while (offset < size.getValue()) {
while ((ch = m.getByte(offset++)) != '\0') {
baos.write(ch);
}
lArgs.add(baos.toString());
baos.reset();
}
return new JVMArguments(lArgs);
}
@Override
public String toString() {
return "JVMArguments{" + "arguments=" + arguments + '}';
}
@Override
public int hashCode() {
return 11 * 7 + Objects.hashCode(this.arguments);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final JVMArguments other = (JVMArguments) obj;
return Objects.equals(this.arguments, other.arguments);
}
public String[] toArray() {
return arguments.toArray(new String[arguments.size()]);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy