View Javadoc
1   package com.randomnoun.common.timer;
2   
3   /* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
4    * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
5    */
6   
7   import org.apache.log4j.Logger;
8   
9   import com.randomnoun.common.timer.HiResTimer;
10  
11  /**
12   * This is some increasingly ancient code to provide microsecond timer resolution in Java 
13   * using JNI on Windows.
14   * 
15   * <p>Java now provides this natively via System.nanoTime(), so all of this can and should be thrown out
16   * some day. 
17   * 
18   * <p>Historical background: Java previously supplied only millisecond
19   * resolution, and on Windows this was only around 10-20 millisecond precision;
20   * approx 16ms (Win3.1), 55ms (ME/98/95) or 10ms (others)). Other platforms (Linux/Solaris)
21   * appear to provide real millisecond resolution.
22   *
23   * <p>This class uses JNI to access the Windows QueryPerformanceCounter API. It
24   * requires the HiResTimer.dll to be located in the VM's java.library.path. In order to
25   * work in WSAD, it will also examine the contents of the 'workspace.root' System
26   * property, and attempt to reference it from within the developer's build tree.
27   *
28   * <p>If the native library cannot be loaded, this class falls back to Java's
29   * standard timer implementation (System.currentTimeMillis).
30   *
31   * <p>Note that the Websphere reloading ClassLoader cannot deal with JNI,
32   * so this class must exist in an external classpath or JAR which is referenced from
33   * the system classpath. The Ant build task 'buildStartupJar' in ext/build.xml
34   * can compile the JNI and create this startup JAR.
35   *
36   * <p>This file was based on the discussion at
37   * <a href="http://www.fawcette.com/archives/premier/mgznarch/javapro/2001/08aug01/km0108/km0108-1.asp">
38   * http://www.fawcette.com/archives/premier/mgznarch/javapro/2001/08aug01/km0108/km0108-1.asp</a>
39   *
40   * <p>Some instructions on building JNI in a cygwin/gcc environment was found at
41   * <a href="http://www.inonit.com/cygwin/jni/helloWorld/">
42   * http://www.inonit.com/cygwin/jni/helloWorld/</a>
43   * 
44   * <p>Documentation on Windows APIs can be found in Windows 2003 Core API download, found at
45   * <a href="http://www.microsoft.com/msdownload/platformsdk/sdkupdate/">
46   * http://www.microsoft.com/msdownload/platformsdk/sdkupdate/</a>
47   *
48   * @author  knoxg
49   * 
50   */
51  public class HiResTimer
52  {
53      
54      
55   
56      /** Logger instance for this class */
57      private static Logger logger = Logger.getLogger(HiResTimer.class);
58      
59      /** Set to true if the HiResTimer DLL can be found in the java library path */
60      private static boolean foundLibrary = false;
61  
62      /** A HiResTimer instance; used since I want to be able to provide these methods
63       *  as static, which I found difficult in JNI directly. 
64       */
65      private static HiResTimer instance = new HiResTimer();
66  
67  
68      private native boolean native_isHighResTimerAvailable();
69  
70      /**
71       * JNI method to return the frequency of the system clock (ticks per second)
72       *
73       * @return the frequency of the system clock (ticks per second)
74       */
75      private native long native_getFrequency();
76  
77      /**
78       * JNI method to return the current timestamp (in frequency units)
79       *
80       * @return the current timestamp (in frequency units)
81       */
82      private native long native_getTimestamp();
83  
84      /**
85       * Return the frequency of the available timer.
86       *
87       * @return The granularity of the timer, measured in ticks per second.
88       */
89      public static long getFrequency()
90      {
91          if (foundLibrary) {
92              return instance.native_getFrequency();
93          } else {
94              // System ostensibly supplies us with 1ms precision (although it doesn't, really)
95              return 1000;
96          }
97          
98      }
99  
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 }