Profiling CPU usage from within a Java application
Roll your own CPU usage monitor using simple JNI
By Vladimir Roubtsov, JavaWorld.com, 11/08/02
November 8, 2002
How do you determine CPU usage in Java?
So, here is the good news and the bad news. The bad news is that programmatically querying for CPU usage is impossible using pure Java. There is simply no API for this. A suggested alternative might use Runtime.exec()
to determine the JVM's process ID (PID), call an external, platform-specific command like ps
, and parse its output for the PID of interest. But, this approach is fragile at best.
The good news, however, is that a reliable solution can be accomplished by stepping outside Java and writing a few C code lines that integrate with the Java application via Java Native Interface (JNI). I show below how easy it is by creating a simple JNI library for the Win32 platform. The Resources section contains a link to the library you can customize for your own needs and port to other platforms.
In general, JNI is somewhat complex to use. However, when you call in one direction only—from Java into native code—and communicate using primitive data types, things remain simple. There are many good references (see Resources) on JNI, so I do not provide a JNI tutorial here; I merely outline my implementation steps.
I begin by creating a class com.vladium.utils.SystemInformation
that declares a native method, which returns the number of milliseconds of CPU time used by the current process so far:
public static native long getProcessCPUTime ();
I use the javah tool from the JDK to produce the following C header for my future native implementation:
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls)
On most Win32 platforms, this method can be implemented using the GetProcessTimes()
system call and is literally three lines of C code:
JNIEXPORT jlong JNICALL
Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls)
{
FILETIME creationTime, exitTime, kernelTime, userTime;
GetProcessTimes (s_currentProcess, & creationTime, & exitTime, & kernelTime, & userTime);
return (jlong) ((fileTimeToInt64 (& kernelTime) + fileTimeToInt64 (& userTime)) /
(s_numberOfProcessors * 10000));
}
This method adds CPU time spent executing kernel and user code on behalf of the current process, normalizes it by the number of processors, and converts the result to milliseconds. The fileTimeToInt64()
is a helper function that converts the FILETIME
structure to a 64-bit integer, and s_currentProcess
and s_numberOfProcessors
are global variables that can be conveniently initialized in a JNI method that's called once when the JVM loads the native library:
static HANDLE s_currentProcess;
static int s_numberOfProcessors;
JNIEXPORT jint JNICALL
JNI_OnLoad (JavaVM * vm, void * reserved)
{
SYSTEM_INFO systemInfo;
s_currentProcess = GetCurrentProcess ();
GetSystemInfo (& systemInfo);
s_numberOfProcessors = systemInfo.dwNumberOfProcessors;
return JNI_VERSION_1_2;
}
Note that if you implement getProcessCPUTime()
on a Unix platform, you would likely use the getrusage
system call as your starting point.
Getting back to Java, loading the native library (silib.dll
on Win32) is best accomplished via the static initializer in the SystemInformation
class:
private static final String SILIB = "silib";
static
{
try
{
System.loadLibrary (SILIB);
}
catch (UnsatisfiedLinkError e)
{
System.out.println ("native lib '" + SILIB + "' not found in 'java.library.path': "
+ System.getProperty ("java.library.path"));
throw e; // re-throw
}
}
Note that getProcessCPUTime()
returns CPU time used since the creation of the JVM process. By itself, this data is not particularly useful for profiling. I need more utility Java methods to record data snapshots at various times and report CPU usage between any two time points:
public static final class CPUUsageSnapshot
{
private CPUUsageSnapshot (long time, long CPUTime)
{
m_time = time;
m_CPUTime = CPUTime;
}
public final long m_time, m_CPUTime;
} // end of nested class
public static CPUUsageSnapshot makeCPUUsageSnapshot ()
{
return new CPUUsageSnapshot (System.currentTimeMillis (), getProcessCPUTime ());
}
public static double getProcessCPUUsage (CPUUsageSnapshot start, CPUUsageSnapshot end)
{
return ((double)(end.m_CPUTime - start.m_CPUTime)) / (end.m_time - start.m_time);
}
The "CPU monitor API" is nearly ready for use! As a final touch, I create a singleton thread class, CPUUsageThread
, which automatically takes data snapshots at regular intervals (0.5 seconds by default) and reports them to a set of CPU usage event listeners (the familiar Observer pattern). The CPUmon
class is a demo listener that simply prints the CPU usage to System.out
:
public static void main (String [] args) throws Exception
{
if (args.length == 0)
throw new IllegalArgumentException ("usage: CPUmon <app_main_class> <app_main_args...>");
CPUUsageThread monitor = CPUUsageThread.getCPUThreadUsageThread ();
CPUmon _this = new CPUmon ();
Class app = Class.forName (args [0]);
Method appmain = app.getMethod ("main", new Class [] {String[].class});
String [] appargs = new String [args.length - 1];
System.arraycopy (args, 1, appargs, 0, appargs.length);
monitor.addUsageEventListener (_this);
monitor.start ();
appmain.invoke (null, new Object [] {appargs});
}
Additionally, CPUmon.main()
"wraps" another Java main class with the sole purpose of starting CPUUsageThread
before launching the original application.
As a demonstration, I ran CPUmon
with the SwingSet2 Swing demo from JDK 1.3.1 (don't forget to install silib.dll
into a location covered by either the PATH
OS environment variable or the java.library.path
Java property):
>java -Djava.library.path=. -cp silib.jar;(my JDK install dir)\demo\jfc\SwingSet2\SwingSet2.jar CPUmon SwingSet2
[PID: 339] CPU usage: 46.8%
[PID: 339] CPU usage: 51.4%
[PID: 339] CPU usage: 54.8%
(while loading, the demo uses nearly 100% of one of the two CPUs on my machine)
...
[PID: 339] CPU usage: 46.8%
[PID: 339] CPU usage: 0%
[PID: 339] CPU usage: 0%
(the demo finished loading all of its panels and is mostly idle)
...
[PID: 339] CPU usage: 100%
[PID: 339] CPU usage: 98.4%
[PID: 339] CPU usage: 97%
(I switched to the ColorChooserDemo panel which ran a CPU-intensive
animation that used both of my CPUs)
...
[PID: 339] CPU usage: 81.4%
[PID: 339] CPU usage: 50%
[PID: 339] CPU usage: 50%
(I used Windows NT Task Manager to adjust the CPU affinity for the
"java" process to use a single CPU)
...
Of course, I can watch the same usage numbers via the task manager, but the point here is that I now have a programmatic way to record the same data. It will come in handy for long-running tests and server application diagnostics. The complete library (available in Resources) adds a few other useful native methods, including one for getting the process PID (for integration with external tools).
Author Bio
Vladimir Roubtsov has programmed in a variety of languages for more than 12 years, including Java since 1995. Currently, he develops enterprise software as a senior developer for Trilogy in Austin, Texas. When coding for fun, Vladimir develops software tools based on Java byte code or source code instrumentation.
posted on 2007-03-06 12:55
OMG 阅读(734)
评论(0) 编辑 收藏 所属分类:
J2EE 、
JavaX