001package com.randomnoun.common.timer; 002 003/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a 004 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html) 005 */ 006 007import org.apache.log4j.Logger; 008 009import com.randomnoun.common.timer.HiResTimer; 010 011/** 012 * This is some increasingly ancient code to provide microsecond timer resolution in Java 013 * using JNI on Windows. 014 * 015 * <p>Java now provides this natively via System.nanoTime(), so all of this can and should be thrown out 016 * some day. 017 * 018 * <p>Historical background: Java previously supplied only millisecond 019 * resolution, and on Windows this was only around 10-20 millisecond precision; 020 * approx 16ms (Win3.1), 55ms (ME/98/95) or 10ms (others)). Other platforms (Linux/Solaris) 021 * appear to provide real millisecond resolution. 022 * 023 * <p>This class uses JNI to access the Windows QueryPerformanceCounter API. It 024 * requires the HiResTimer.dll to be located in the VM's java.library.path. In order to 025 * work in WSAD, it will also examine the contents of the 'workspace.root' System 026 * property, and attempt to reference it from within the developer's build tree. 027 * 028 * <p>If the native library cannot be loaded, this class falls back to Java's 029 * standard timer implementation (System.currentTimeMillis). 030 * 031 * <p>Note that the Websphere reloading ClassLoader cannot deal with JNI, 032 * so this class must exist in an external classpath or JAR which is referenced from 033 * the system classpath. The Ant build task 'buildStartupJar' in ext/build.xml 034 * can compile the JNI and create this startup JAR. 035 * 036 * <p>This file was based on the discussion at 037 * <a href="http://www.fawcette.com/archives/premier/mgznarch/javapro/2001/08aug01/km0108/km0108-1.asp"> 038 * http://www.fawcette.com/archives/premier/mgznarch/javapro/2001/08aug01/km0108/km0108-1.asp</a> 039 * 040 * <p>Some instructions on building JNI in a cygwin/gcc environment was found at 041 * <a href="http://www.inonit.com/cygwin/jni/helloWorld/"> 042 * http://www.inonit.com/cygwin/jni/helloWorld/</a> 043 * 044 * <p>Documentation on Windows APIs can be found in Windows 2003 Core API download, found at 045 * <a href="http://www.microsoft.com/msdownload/platformsdk/sdkupdate/"> 046 * http://www.microsoft.com/msdownload/platformsdk/sdkupdate/</a> 047 * 048 * @author knoxg 049 * 050 */ 051public class HiResTimer 052{ 053 054 055 056 /** Logger instance for this class */ 057 private static Logger logger = Logger.getLogger(HiResTimer.class); 058 059 /** Set to true if the HiResTimer DLL can be found in the java library path */ 060 private static boolean foundLibrary = false; 061 062 /** A HiResTimer instance; used since I want to be able to provide these methods 063 * as static, which I found difficult in JNI directly. 064 */ 065 private static HiResTimer instance = new HiResTimer(); 066 067 068 private native boolean native_isHighResTimerAvailable(); 069 070 /** 071 * JNI method to return the frequency of the system clock (ticks per second) 072 * 073 * @return the frequency of the system clock (ticks per second) 074 */ 075 private native long native_getFrequency(); 076 077 /** 078 * JNI method to return the current timestamp (in frequency units) 079 * 080 * @return the current timestamp (in frequency units) 081 */ 082 private native long native_getTimestamp(); 083 084 /** 085 * Return the frequency of the available timer. 086 * 087 * @return The granularity of the timer, measured in ticks per second. 088 */ 089 public static long getFrequency() 090 { 091 if (foundLibrary) { 092 return instance.native_getFrequency(); 093 } else { 094 // System ostensibly supplies us with 1ms precision (although it doesn't, really) 095 return 1000; 096 } 097 098 } 099 100 /** 101 * Returns the current value of the timer, as measured in the frequency supplied 102 * by {@link #getFrequency()}. The starting time for returned values is undefined 103 * (i.e. there is no fixed point for timestamp = 0); this value can only be used 104 * for relative timing only, or must be correlated with System.currentTimeMillis(). 105 * 106 * @return The current value of the timer. 107 */ 108 public static long getTimestamp() 109 { 110 if (foundLibrary) { 111 return instance.native_getTimestamp(); 112 } else { 113 return System.currentTimeMillis(); 114 } 115 } 116 117 /** Returns true if we can use a natively-supplied timer, false otherwise 118 * 119 * @return true if we can use a natively-supplied timer, false otherwise 120 */ 121 public static boolean isNativeTimerAvailable() 122 { 123 return foundLibrary; 124 } 125 126 /** 127 * Returns true if the Windows HAL (Hardware Abstraction Layer) supports 128 * a high-resolution performance counter. Note that even if this returns false, 129 * we can fall back to a native counter, which is still more precise than 130 * the one that Java supplies. 131 * 132 * @return True if high resolution performance is available, false if not. 133 */ 134 public static boolean isHiResTimerAvailable() 135 { 136 // NB: short-cut boolean logic 137 return foundLibrary && instance.native_isHighResTimerAvailable(); 138 } 139 140 /** Returns the elapsed time between two timestamps, as a integer number of 141 * milliseconds. 142 * 143 * @return the elapsed time between two timestamps, as a integer number of 144 * milliseconds. 145 */ 146 public static long getElapsedMillis(long timestamp1, long timestamp2) 147 { 148 return ((timestamp2 - timestamp1) * 1000) / getFrequency(); 149 } 150 151 /** Returns the elapsed time between two timestamps, as a integer number of 152 * microseconds. 153 * 154 * @return the elapsed time between two timestamps, as a integer number of 155 * microseconds. 156 */ 157 public static long getElapsedNanos(long timestamp1, long timestamp2) 158 { 159 return ((timestamp2 - timestamp1) * 1000000) / getFrequency(); 160 } 161 162 /** 163 * Test program. 164 * 165 * @param args Command-line options 166 * 167 * @throws InterruptedException if the Thread.sleep() call was interrupted 168 */ 169 public static void main(String[] args) 170 throws InterruptedException 171 { 172 System.out.println("Native timer: " + 173 (HiResTimer.isNativeTimerAvailable() ? "available" : "unavailable")); 174 System.out.println("Hi-res timer: " + 175 (HiResTimer.isHiResTimerAvailable() ? "available" : "unavailable")); 176 177 java.text.NumberFormat nf = new java.text.DecimalFormat("0.#########"); 178 179 long freq = HiResTimer.getFrequency(); 180 181 System.out.println("Timer has frequency of approx " + freq + " ticks/sec"); 182 183 // this isn't really the granularity, you know. It's just the *maximum* precision 184 // we can ever expect to get out of the timer. Whether the timer actually reaches 185 // this theoretical maximum is a completely different matter. 186 System.out.println("Timer has granularity of approx " + 187 nf.format(1 / (double)freq) + " seconds."); 188 189 long dStart = HiResTimer.getTimestamp(); 190 191 Thread.sleep(1000); 192 193 long dEnd = HiResTimer.getTimestamp(); 194 195 System.out.println("Thread.sleep() test=" + 196 HiResTimer.getElapsedMillis(dStart, dEnd) + 197 " milliseconds (should be around 1000); = " + 198 HiResTimer.getElapsedNanos(dStart, dEnd) + " microseconds"); 199 200 dStart = HiResTimer.getTimestamp(); 201 Thread.sleep(2000); 202 dEnd = HiResTimer.getTimestamp(); 203 System.out.println("Thread.sleep() test=" + 204 HiResTimer.getElapsedMillis(dStart, dEnd) + 205 " milliseconds (should be around 2000); = " + 206 HiResTimer.getElapsedNanos(dStart, dEnd) + " microseconds"); 207 } 208 209 static 210 { 211 try 212 { 213 // use the java.library.path variable first 214 System.loadLibrary("HiResTimer"); 215 foundLibrary = true; 216 } 217 catch (UnsatisfiedLinkError ule) 218 { 219 // exception intentionally ignored 220 } 221 222 if (!foundLibrary) 223 { 224 // when run in WSAD, workspace.root is something like 225 // C:/Documents and Settings/knoxg/My Documents/IBM/wsappdev51/workspace/Servers/QueryBuilderSvr.wsc 226 // ... so we can try looking in here 227 String workspaceRoot = System.getProperty("workspace.root"); 228 229 // NB, in eclipse wtp.deploy is set to something like 230 // C:\Documents and Settings\knoxg\workspace-3.5sr2\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps 231 // so could try something relative to that as well 232 233 if (workspaceRoot != null) 234 { 235 String location = workspaceRoot + 236 "/../../common/ext/bootstrap/HiResTimer.dll"; 237 238 try { 239 System.load(location); 240 foundLibrary = true; 241 } catch (UnsatisfiedLinkError ule) { 242 logger.debug( 243 "Could not load HiResTimer from java.library.path or '" + 244 location + "'"); 245 246 // ule.printStackTrace(); 247 // NB: we still can't use a DLL even if it's loaded in another ClassLoader 248 // exception intentionally ignored 249 } 250 } 251 } 252 253 if (foundLibrary) { 254 HiResTimer timer = new HiResTimer(); 255 logger.info("Native timer library available: high resolution timer " + 256 (timer.native_isHighResTimerAvailable() ? "available" : "unavailable")); 257 } else { 258 logger.info("Native timer library unavailable"); 259 } 260 } 261}