How to use SIGAR to remotely fetch another machine's system info? - java

I'm working on a project that requires to show the CPU usage as well as other system information of remote machines.
People suggest using SIGAR to achieve this, but I don't know how to use it. The source code didn't quite make sense to me.
Basically, my question is that: how can I register the MBeans provided by SIGAR to the server when host IP and JMX port are provided, and how to fetch the system info from other computer afterward.
Please correct me if I'm wrong with how JMX works. Thanks in advance.

These are the names of the classes that are the Sigar built in MBeans that you can register:
org.hyperic.sigar.jmx.SigarCpu
org.hyperic.sigar.jmx.SigarCpuInfo
org.hyperic.sigar.jmx.SigarCpuPerc
org.hyperic.sigar.jmx.SigarLoadAverage
org.hyperic.sigar.jmx.SigarMem
org.hyperic.sigar.jmx.SigarProcess
org.hyperic.sigar.jmx.SigarRegistry
org.hyperic.sigar.jmx.SigarSwap
However, it will be quite complicated to deploy these remotely since Sigar depends on a native library which must be in the target JVM's lib-path when the MBeans are loaded. This means, you will need to actively load the library and MBeans on each target host you want to monitor.
You might be able to hack a way of making the target JVMs load this through a remote call, but it is non-trivial and will require you to bypass any security setups in the JVMs since by default, this is something you're not supposed to be able to do.

You can sort of hack the system part to get an easy deployment for Sigjar:
private String before;
private Sigar sigar;
/**
* Constructor - don't forget to call unload later!
*/
public SetlogSigar() throws Exception {
before = System.getProperty("java.library.path");
String path = "";
String add = getJarFolder();
if (before.contains(";"))
path = before + ";./;" + add;
else
path = before + ":./:" + add;
setSystemPath(path);
sigar = new Sigar();
}
/**
* This is needed to dynamically update the JAVA Path environment in order to load the needed native library
* Yes -rather an ugly hack...
*/
private String getJarFolder() {
// get name and path
String path = SetlogSigar.class.getProtectionDomain().getCodeSource().getLocation().getPath();
String decodedPath = path;
try {
decodedPath = URLDecoder.decode(path, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
File f = new File(decodedPath);
String absolutePath = f.getParentFile().getParentFile().getParentFile().getParent()+"/lib";
return absolutePath;
}
/**
* Unloads the JNI bindings
*/
public void unload() {
this.sigar.close();
setSystemPath(before);
}
This hack dynamically adds the folder where sigjar.jar is located to the environment variable. Just place all native libs in there and deployment gets less complicated.

Seems to me that you will have to write some wrapping objects to expose the various SIGAR outputs as JMX mbean attributes. How you do that that depends highly on what you are using to expose your JMX beans. I would write one wrapping object for each of the various different types of SIGAR output: memory, disk, ...
I've written a SimpleJMX library that might help. I'll use its format to provide an example object that you can use to expose the info via JMX. You can adapt it to whatever mechanism you are using to publish JMX means. I'm not familiar with SIGAR enough to know if my sigar code below is correct to get a ProcMem instance.
#JmxResource(description = "Show SIGAR Info", domainName = "foo")
public class SigarProcMem {
private ProcMem procMem;
{
// sorry, I'm not up on sigar so I'm not sure if this works
Sigar sigar = new Sigar();
procMem = sigar.getProcMem(sigar.getPid());
}
#JmxAttributeMethod(description = "Resident memory")
public long residentMemory() {
return procMem.getResident();
}
#JmxAttributeMethod(description = "Get the Total process virtual memory")
public long totalVirtualMemory() {
return procMem.getSize();
}
}

Related

Finding default user.home when it has been overridden in Java VM options [duplicate]

What is the best way to find the user's home directory in Java?
The difficulty is that the solution should be cross-platform; it should work on Windows 2000, XP, Vista, OS X, Linux, and other Unix variants. I am looking for a snippet of code that can accomplish this for all platforms, and a way to detect the platform.
Per Java bug 4787931, system property user.home does not work correctly on Windows XP, so using this system property is not an acceptable solution as it is not cross-platform.
The bug you reference (bug 4787391) has been fixed in Java 8. Even if you are using an older version of Java, the System.getProperty("user.home") approach is probably still the best. The user.home approach seems to work in a very large number of cases. A 100% bulletproof solution on Windows is hard, because Windows has a shifting concept of what the home directory means.
If user.home isn't good enough for you I would suggest choosing a definition of home directory for windows and using it, getting the appropriate environment variable with System.getenv(String).
Actually with Java 8 the right way is to use:
System.getProperty("user.home");
The bug JDK-6519127 has been fixed and the "Incompatibilities between JDK 8 and JDK 7" section of the release notes states:
Area: Core Libs / java.lang
Synopsis
The steps used to determine the user's home directory on Windows have changed to follow the Microsoft recommended approach. This change
might be observable on older editions of Windows or where registry
settings or environment variables are set to other directories. Nature
of Incompatibility
behavioral RFE
6519127
Despite the question being old I leave this for future reference.
System.getProperty("user.home");
See the JavaDoc.
The concept of a HOME directory seems to be a bit vague when it comes to Windows. If the environment variables (HOMEDRIVE/HOMEPATH/USERPROFILE) aren't enough, you may have to resort to using native functions via JNI or JNA. SHGetFolderPath allows you to retrieve special folders, like My Documents (CSIDL_PERSONAL) or Local Settings\Application Data (CSIDL_LOCAL_APPDATA).
Sample JNA code:
public class PrintAppDataDir {
public static void main(String[] args) {
if (com.sun.jna.Platform.isWindows()) {
HWND hwndOwner = null;
int nFolder = Shell32.CSIDL_LOCAL_APPDATA;
HANDLE hToken = null;
int dwFlags = Shell32.SHGFP_TYPE_CURRENT;
char[] pszPath = new char[Shell32.MAX_PATH];
int hResult = Shell32.INSTANCE.SHGetFolderPath(hwndOwner, nFolder,
hToken, dwFlags, pszPath);
if (Shell32.S_OK == hResult) {
String path = new String(pszPath);
int len = path.indexOf('\0');
path = path.substring(0, len);
System.out.println(path);
} else {
System.err.println("Error: " + hResult);
}
}
}
private static Map<String, Object> OPTIONS = new HashMap<String, Object>();
static {
OPTIONS.put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
OPTIONS.put(Library.OPTION_FUNCTION_MAPPER,
W32APIFunctionMapper.UNICODE);
}
static class HANDLE extends PointerType implements NativeMapped {
}
static class HWND extends HANDLE {
}
static interface Shell32 extends Library {
public static final int MAX_PATH = 260;
public static final int CSIDL_LOCAL_APPDATA = 0x001c;
public static final int SHGFP_TYPE_CURRENT = 0;
public static final int SHGFP_TYPE_DEFAULT = 1;
public static final int S_OK = 0;
static Shell32 INSTANCE = (Shell32) Native.loadLibrary("shell32",
Shell32.class, OPTIONS);
/**
* see http://msdn.microsoft.com/en-us/library/bb762181(VS.85).aspx
*
* HRESULT SHGetFolderPath( HWND hwndOwner, int nFolder, HANDLE hToken,
* DWORD dwFlags, LPTSTR pszPath);
*/
public int SHGetFolderPath(HWND hwndOwner, int nFolder, HANDLE hToken,
int dwFlags, char[] pszPath);
}
}
Others have answered the question before me but a useful program to print out all available properties is:
for (Map.Entry<?,?> e : System.getProperties().entrySet()) {
System.out.println(String.format("%s = %s", e.getKey(), e.getValue()));
}
Alternative would be to use Apache CommonsIO FileUtils.getUserDirectory() instead of System.getProperty("user.home"). It will get you the same result and there is no chance to introduce a typo when specifying system property.
There is a big chance you already have Apache CommonsIO library in your project. Don't introduce it if you plan to use it only for getting user home directory.
As I was searching for Scala version, all I could find was McDowell's JNA code above. I include my Scala port here, as there currently isn't anywhere more appropriate.
import com.sun.jna.platform.win32._
object jna {
def getHome: java.io.File = {
if (!com.sun.jna.Platform.isWindows()) {
new java.io.File(System.getProperty("user.home"))
}
else {
val pszPath: Array[Char] = new Array[Char](WinDef.MAX_PATH)
new java.io.File(Shell32.INSTANCE.SHGetSpecialFolderPath(null, pszPath, ShlObj.CSIDL_MYDOCUMENTS, false) match {
case true => new String(pszPath.takeWhile(c => c != '\0'))
case _ => System.getProperty("user.home")
})
}
}
}
As with the Java version, you will need to add Java Native Access, including both jar files, to your referenced libraries.
It's nice to see that JNA now makes this much easier than when the original code was posted.
I would use the algorithm detailed in the bug report using System.getenv(String), and fallback to using the user.dir property if none of the environment variables indicated a valid existing directory. This should work cross-platform.
I think, under Windows, what you are really after is the user's notional "documents" directory.
If you want something that works well on windows there is a package called WinFoldersJava which wraps the native call to get the 'special' directories on Windows. We use it frequently and it works well.

Check if tools.jar is available and load it dynamically during runtime

I'm working on a monitoring application, which uses Sigar for monitoring to monitor different kind of applications. One problem with Sigar is that when monitoring the heap usage of a Java application (JVM) I only get the maximum heap size but not the actually used heap size of the JVM.
So I extended my monitoring application to use JMX to connect to a JVM and retrieve the CPU as well as the heap usage. This works fine so far, but
I want to automise everything as much as possible and I don't want to start all my applications, being monitored, with JMX activated, but activate it dynamically when needed with the following piece of code:
private void connectToJVM(final String pid) throws IOException, AgentLoadException, AgentInitializationException {
List<VirtualMachineDescriptor> vms = VirtualMachine.list();
for (VirtualMachineDescriptor desc : vms) {
if (!desc.id().equals(pid)) {
continue;
}
VirtualMachine vm;
try {
vm = VirtualMachine.attach(desc);
} catch (AttachNotSupportedException e) {
continue;
}
Properties props = vm.getAgentProperties();
String connectorAddress = props.getProperty(CONNECTOR_ADDRESS);
if (connectorAddress == null) {
String agent = vm.getSystemProperties().getProperty("java.home") + File.separator + "lib"
+ File.separator + "management-agent.jar";
vm.loadAgent(agent);
// agent is started, get the connector address
connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
}
vm.detach();
JMXServiceURL url = new JMXServiceURL(connectorAddress);
this.jmxConnector = JMXConnectorFactory.connect(url);
}
}
This works fine so far but the problem is that I have now a dependency to the tools.jar from the JDK.
My question is now can I somehow check during runtime if the tools.jar is available in the JAVA_HOME path and load it when it is? Because if it isn't available I just want to do the normal monitoring with Sigar, but if it is available I want to use JMX for monitoring Java applications.
My project is a maven project and I'm using the maven-shade-plugin to create a executable jar with all dependencies in it.
Currently I'm using a dirty hack I found in the internet which uses reflection to add the tools.jar dynamically to the system classpath if it exists. But I'm wondering if it is possible to do it differently as well?
Thanks in advance for your support.
I do a similar thing in my project, look here.
The idea is to load your utility class by differrent ClassLoader which has tools.jar in path.
File javaHome = new File(System.getProperty("java.home"));
String toolsPath = javaHome.getName().equalsIgnoreCase("jre") ? "../lib/tools.jar" : "lib/tools.jar";
URL[] urls = new URL[] {
getClass().getProtectionDomain().getCodeSource().getLocation(),
new File(javaHome, toolsPath).getCanonicalFile().toURI().toURL(),
};
URLClassLoader loader = new URLClassLoader(urls, null);
Class<?> utilityClass = loader.loadClass("some.package.MyUtilityClass");
utilityClass.getMethod("connect").invoke(null);
Finding tools.jar on the filesystem is a little more tricky than #apangin's solution.
Different JDK's stick the tools.jar in different places as shown by this method, which claims to support the IBM JDK and HotSpot on Mac.
But even the code I've referenced looks out of date. It suggests all mac JDK's use classes.jar, but my Mac 1.7 and 1.8 JDK's instead use tools.jar.
This other answer of mine shows locations of tools.jar and classes.jar files for mac some 1.6, 1.7 and 1.8 JDKs.
The code I ended up using is from: org.gridkit.lab::jvm Attach Api
https://mvnrepository.com/artifact/org.gridkit.lab/jvm-attach-api/1.2
Source code: http://central.maven.org/maven2/org/gridkit/lab/jvm-attach-api/1.2/
From that source code, you simply need one file: AttachAPI.java
/**
* Copyright 2013 Alexey Ragozin
*
* 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.gridkit.lab.jvm.attach;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
/**
* #author Alexey Ragozin (alexey.ragozin#gmail.com)
*/
class AttachAPI {
private static final LogStream LOG_ERROR = LogStream.error();
private static boolean started;
static {
try {
String javaHome = System.getProperty("java.home");
String toolsJarURL = "file:" + javaHome + "/../lib/tools.jar";
// Make addURL public
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
if (sysloader.getResourceAsStream("/com/sun/tools/attach/VirtualMachine.class") == null) {
method.invoke(sysloader, (Object) new URL(toolsJarURL));
Thread.currentThread().getContextClassLoader().loadClass("com.sun.tools.attach.VirtualMachine");
Thread.currentThread().getContextClassLoader().loadClass("com.sun.tools.attach.AttachNotSupportedException");
}
} catch (Exception e) {
LOG_ERROR.log("Java home points to " + System.getProperty("java.home") + " make sure it is not a JRE path");
LOG_ERROR.log("Failed to add tools.jar to classpath", e);
}
started = true;
};
public static void ensureToolsJar() {
if (!started) {
LOG_ERROR.log("Attach API not initialized");
}
}
}
To use this class, put it somewhere in your project and ensure you change its package accordingly. In the example below, I have placed the file in the same folder as my MyApp.java file but I've not amended the AttachAPI.java file's package statement to reflect that since I wanted to leave it pristine.
Lastly, in your main class, ensure you have a block such as the follows:
public class MyApp
{
static {
AttachAPI.ensureToolsJar();
}
public static void ensureToolsJar() {
// do nothing, just ensure call to static initializer
}
}
...
Now you will no longer need to specify a path to the tools.jar on the command line and can launch you app with simply a java -jar MyApp.jar

how to get ExePath/MainClassPath/MainJarPath of java App at runtime?

The "Path of the running EXE" is well defined for Windows Applications written in C#, C++, VB, etc.
-
Java Applications on Windows are not "EXE-Applications" but a class-file or a jar-file is started rather than an EXE-file.
So for java Applications the term "ExePath" should be translated to "MainClassPath" or to "JarPath" resp.
-
In some cases a programmer needs to know the physical Path of the Application's jar or MainClass.
(e.g. when you develop a large project both in java and in c# with identical classes and identical methods)
-
thanks to other stackoverflow users this statement does the job:
String exePath = URLDecoder.decode(this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8")
and now comes my question:
If I put the same code into any helper/utils jar-library then it will return the path of the helperlib.jar, it will NOT return the path of my MainClass/AppJar !
-
So the final getExePath() helper method should look something like:
return(URLDecoder.decode(Thread.currentThread().getStartingThread().getMainClass().getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8"));
(if there only were methods like getStartingThread() and getMainClass() in java ...)
-
please, point me to the final solution, how can I implement these steps:
get the starting Thread
get the Main Class of the starting Thread
get the Path of the Main Class
if I get you right, just put the method with a parameter of the desired class into your helperlib-class ...
e.g.:
public static String getExePath(Object main) {
String path = "";
try {
path = URLDecoder.decode(main.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return path;
}
then you can call it e.g. from inside your main-jar with the parameter 'this' ...
System.out.println("AppPath:\n" + helperlib.getExePath(this));
... and you got the path of the class specified in you parameter
hope it helps and sorry for my bad english ... ;)

Launching Java Subprocess using parent process Classpath

I want to launch a java subprocess, with the same java classpath and dynamically loaded classes as the current java process. The following is not enough, because it doesn't include any dynamically loaded classes:
String classpath = System.getProperty("java.class.path");
Currently I'm searching for each needed class with the code below. However, on some machines this fails for some classes/libs, the source variable is null. Is there a more reliable and simpler way to get the location of libs that are used by the current jvm process?
String stax = ClassFinder.classPath("javax.xml.stream.Location");
public static String classPath(String qualifiedClassName) throws NotFoundException {
try {
Class qc = Class.forName( qualifiedClassName );
CodeSource source = qc.getProtectionDomain().getCodeSource();
if ( source != null ) {
URL location = source.getLocation();
String f = location.getPath();
f = URLDecoder.decode(f, "UTF-8"); // decode URL to avoid spaces being replaced by %20
return f.substring(1);
} else {
throw new ClassFinder().new NotFoundException(qualifiedClassName+" (unknown source, likely rt.jar)");
}
} catch ( Exception e ) {
throw new ClassFinder().new NotFoundException(qualifiedClassName);
}
}
See my previous question which covers getting the classpath as well as how to launch a sub-process.
I want to launch a java subprocess, with the same java classpath and dynamically loaded classes as the current java process.
You mean invoke a new JVM?
Given that...
it is possible to plug in all sorts of agents and instrumentation into a JVM that can transform classes at load time
it is possible to take a byte array and turn it into a class
it is possible to have complex class loader hierarchies with varying visibility between classes and have the same classes loaded multiple times
...there is no general, magic, catch-all and foolproof way to do this. You should design your application and its class loading mechanisms to achieve this goal. If you allow 3rd party plug-ins, you'll have to document how this works and how they have to register their libraries.
If you look at the javadoc for Class.getClassLoader, you'll see that the "bootstrap" classloader is typically represented as the null. "String.class.getClassLoader()" will return null on the normal sun jvm implementations. i think this implementation detail carries over into the CodeSource stuff. As such, I wouldn't imagine you would need to worry about any class which comes from the bootstrap classloader as long as your sub-process uses the same jvm impl as the current process.

What is the best way to find the user's home directory in Java?

What is the best way to find the user's home directory in Java?
The difficulty is that the solution should be cross-platform; it should work on Windows 2000, XP, Vista, OS X, Linux, and other Unix variants. I am looking for a snippet of code that can accomplish this for all platforms, and a way to detect the platform.
Per Java bug 4787931, system property user.home does not work correctly on Windows XP, so using this system property is not an acceptable solution as it is not cross-platform.
The bug you reference (bug 4787391) has been fixed in Java 8. Even if you are using an older version of Java, the System.getProperty("user.home") approach is probably still the best. The user.home approach seems to work in a very large number of cases. A 100% bulletproof solution on Windows is hard, because Windows has a shifting concept of what the home directory means.
If user.home isn't good enough for you I would suggest choosing a definition of home directory for windows and using it, getting the appropriate environment variable with System.getenv(String).
Actually with Java 8 the right way is to use:
System.getProperty("user.home");
The bug JDK-6519127 has been fixed and the "Incompatibilities between JDK 8 and JDK 7" section of the release notes states:
Area: Core Libs / java.lang
Synopsis
The steps used to determine the user's home directory on Windows have changed to follow the Microsoft recommended approach. This change
might be observable on older editions of Windows or where registry
settings or environment variables are set to other directories. Nature
of Incompatibility
behavioral RFE
6519127
Despite the question being old I leave this for future reference.
System.getProperty("user.home");
See the JavaDoc.
The concept of a HOME directory seems to be a bit vague when it comes to Windows. If the environment variables (HOMEDRIVE/HOMEPATH/USERPROFILE) aren't enough, you may have to resort to using native functions via JNI or JNA. SHGetFolderPath allows you to retrieve special folders, like My Documents (CSIDL_PERSONAL) or Local Settings\Application Data (CSIDL_LOCAL_APPDATA).
Sample JNA code:
public class PrintAppDataDir {
public static void main(String[] args) {
if (com.sun.jna.Platform.isWindows()) {
HWND hwndOwner = null;
int nFolder = Shell32.CSIDL_LOCAL_APPDATA;
HANDLE hToken = null;
int dwFlags = Shell32.SHGFP_TYPE_CURRENT;
char[] pszPath = new char[Shell32.MAX_PATH];
int hResult = Shell32.INSTANCE.SHGetFolderPath(hwndOwner, nFolder,
hToken, dwFlags, pszPath);
if (Shell32.S_OK == hResult) {
String path = new String(pszPath);
int len = path.indexOf('\0');
path = path.substring(0, len);
System.out.println(path);
} else {
System.err.println("Error: " + hResult);
}
}
}
private static Map<String, Object> OPTIONS = new HashMap<String, Object>();
static {
OPTIONS.put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
OPTIONS.put(Library.OPTION_FUNCTION_MAPPER,
W32APIFunctionMapper.UNICODE);
}
static class HANDLE extends PointerType implements NativeMapped {
}
static class HWND extends HANDLE {
}
static interface Shell32 extends Library {
public static final int MAX_PATH = 260;
public static final int CSIDL_LOCAL_APPDATA = 0x001c;
public static final int SHGFP_TYPE_CURRENT = 0;
public static final int SHGFP_TYPE_DEFAULT = 1;
public static final int S_OK = 0;
static Shell32 INSTANCE = (Shell32) Native.loadLibrary("shell32",
Shell32.class, OPTIONS);
/**
* see http://msdn.microsoft.com/en-us/library/bb762181(VS.85).aspx
*
* HRESULT SHGetFolderPath( HWND hwndOwner, int nFolder, HANDLE hToken,
* DWORD dwFlags, LPTSTR pszPath);
*/
public int SHGetFolderPath(HWND hwndOwner, int nFolder, HANDLE hToken,
int dwFlags, char[] pszPath);
}
}
Others have answered the question before me but a useful program to print out all available properties is:
for (Map.Entry<?,?> e : System.getProperties().entrySet()) {
System.out.println(String.format("%s = %s", e.getKey(), e.getValue()));
}
Alternative would be to use Apache CommonsIO FileUtils.getUserDirectory() instead of System.getProperty("user.home"). It will get you the same result and there is no chance to introduce a typo when specifying system property.
There is a big chance you already have Apache CommonsIO library in your project. Don't introduce it if you plan to use it only for getting user home directory.
As I was searching for Scala version, all I could find was McDowell's JNA code above. I include my Scala port here, as there currently isn't anywhere more appropriate.
import com.sun.jna.platform.win32._
object jna {
def getHome: java.io.File = {
if (!com.sun.jna.Platform.isWindows()) {
new java.io.File(System.getProperty("user.home"))
}
else {
val pszPath: Array[Char] = new Array[Char](WinDef.MAX_PATH)
new java.io.File(Shell32.INSTANCE.SHGetSpecialFolderPath(null, pszPath, ShlObj.CSIDL_MYDOCUMENTS, false) match {
case true => new String(pszPath.takeWhile(c => c != '\0'))
case _ => System.getProperty("user.home")
})
}
}
}
As with the Java version, you will need to add Java Native Access, including both jar files, to your referenced libraries.
It's nice to see that JNA now makes this much easier than when the original code was posted.
I would use the algorithm detailed in the bug report using System.getenv(String), and fallback to using the user.dir property if none of the environment variables indicated a valid existing directory. This should work cross-platform.
I think, under Windows, what you are really after is the user's notional "documents" directory.
If you want something that works well on windows there is a package called WinFoldersJava which wraps the native call to get the 'special' directories on Windows. We use it frequently and it works well.

Categories

Resources