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 }