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}