Java tutorial
/* * Copyright 2014-2015 JKOOL, LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jkoolcloud.tnt4j.utils; import java.io.IOException; import java.net.InetAddress; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import org.apache.commons.net.ntp.NTPUDPClient; import org.apache.commons.net.ntp.TimeInfo; import com.jkoolcloud.tnt4j.core.OpLevel; import com.jkoolcloud.tnt4j.sink.DefaultEventSinkFactory; import com.jkoolcloud.tnt4j.sink.EventSink; /** * This class implements a time service that delivers synchronized time using NTP. * Developers should use {@code TimeService.currentTimeMillis()} instead of calling * {@code System.currentTimeMillis()} to obtain synchronized and adjusted current time. * To enable NTP time synchronization set the following property: * {@code tnt4j.time.server=ntp-server:port}, * otherwise {@code System.currentTimeMillis()} is returned. * * @version $Revision: 1 $ */ public class TimeService { private static EventSink logger = DefaultEventSinkFactory.defaultEventSink(TimeService.class); protected static final int ONE_K = 1000; protected static final int ONE_M = 1000000; protected static final boolean TIME_SERVER_VERBOSE = Boolean.getBoolean("tnt4j.time.server.verbose"); private static final String TIME_SERVER = System.getProperty("tnt4j.time.server"); private static final long TIME_SERVER_TIMEOUT = Long.getLong("tnt4j.time.server.timeout", 10000); static long timeOverheadNanos = 0; static long timeOverheadMillis = 0; static long adjustment = 0; static long updatedTime = 0; static ScheduledExecutorService scheduler; static ClockDriftMonitorTask clockSyncTask = null; static NTPUDPClient timeServer = new NTPUDPClient(); static TimeInfo timeInfo; static { try { timeOverheadNanos = calculateOverhead(ONE_M); timeOverheadMillis = (timeOverheadNanos / ONE_M); updateTime(); } catch (Throwable e) { logger.log(OpLevel.ERROR, "Unable to obtain NTP time: time.server={0}, timeout={1}", TIME_SERVER, TIME_SERVER_TIMEOUT, e); } finally { scheduleUpdates(); } } private TimeService() { } /** * Schedule automatic clock synchronization with NTP and internal clocks * */ private static synchronized void scheduleUpdates() { if (scheduler == null) { scheduler = Executors.newScheduledThreadPool(1, new TimeServiceThreadFactory("TimeService/clock-sync")); clockSyncTask = new ClockDriftMonitorTask(logger); scheduler.submit(clockSyncTask); } } /** * Obtain NTP connection host:port of the time server. * * @return time server connection string */ public static String getTimeServer() { return TIME_SERVER; } /** * Obtain time stamp when the NTP time was synchronized * * @return time stamp when NTP was updated */ public static long getLastUpdatedMillis() { return updatedTime; } /** * Obtain configured NTP server timeout * * @return time server timeout in milliseconds */ public static long getTimeServerTimeout() { return TIME_SERVER_TIMEOUT; } /** * Obtain NTP time and synchronize with NTP server * * @throws IOException if error accessing time server */ public static void updateTime() throws IOException { if (TIME_SERVER != null) { timeServer.setDefaultTimeout((int) TIME_SERVER_TIMEOUT); String[] pair = TIME_SERVER.split(":"); InetAddress hostAddr = InetAddress.getByName(pair[0]); timeInfo = pair.length < 2 ? timeServer.getTime(hostAddr) : timeServer.getTime(hostAddr, Integer.parseInt(pair[1])); timeInfo.computeDetails(); adjustment = timeInfo.getOffset() - timeOverheadMillis; updatedTime = currentTimeMillis(); if (TIME_SERVER_VERBOSE) { logger.log(OpLevel.DEBUG, "Time server={0}, timeout.ms={1}, offset.ms={2}, delay.ms={3}, clock.adjust.ms={4}, overhead.nsec={5}", TIME_SERVER, TIME_SERVER_TIMEOUT, timeInfo.getOffset(), timeInfo.getDelay(), adjustment, timeOverheadNanos); } } } /** * Obtain measured overhead of calling <code>TimeService.currentTimeMillis()</code> in nanoseconds. * * @return total measured overhead in nanoseconds */ public static long getOverheadNanos() { return timeOverheadNanos; } /** * Obtain number of milliseconds since NTP time was synchronized * * @return time (ms) since last NTP synchronization */ public static long getUpdateAgeMillis() { return TimeService.getLastUpdatedMillis() > 0 ? TimeService.currentTimeMillis() - TimeService.getLastUpdatedMillis() : -1; } /** * Obtain NTP synchronized current time in milliseconds * * @return current NTP synchronized time in milliseconds */ public static long currentTimeMillis() { return System.currentTimeMillis() + adjustment; } /** * Obtain NTP synchronized current time in microseconds precision * (but necessarily accuracy) * * @return current NTP synchronized time in microseconds */ public static long currentTimeUsecs() { return (System.currentTimeMillis() + adjustment) * ONE_K; } /** * Obtain currently measured clock drift in milliseconds * * @return clock drift in milliseconds */ public static long getDriftMillis() { return clockSyncTask.getDriftMillis(); } /** * Obtain measured total clock drift in milliseconds since start up * * @return total clock drift since start up */ public static long getTotalDriftMillis() { return clockSyncTask.getTotalDriftMillis(); } /** * Obtain total number of times clocks have been updated to adjust * for drift. * * @return number of times updated to adjust for cock drift */ public static long getDriftUpdateCount() { return clockSyncTask.getDriftUpdateCount(); } /** * Obtain currently measured clock drift interval in milliseconds * * @return clock drift interval in milliseconds */ public static long getDriftIntervalMillis() { return clockSyncTask.getIntervalMillis(); } /** * Calculate overhead of <code>TimeService.currentTimeMillis()</code> based on a given number of * iterations. * * @param runs number of iterations * @return calculated overhead of getting timestamp */ public static long calculateOverhead(long runs) { long start = System.nanoTime(); _calculateOverheadCost(runs); for (int i = 0; i < runs; i++) { currentTimeMillis(); } return ((System.nanoTime() - start) / runs); } private static long _calculateOverheadCost(long runs) { return runs; } } class TimeServiceThreadFactory implements ThreadFactory { int count = 0; String prefix; TimeServiceThreadFactory(String pfix) { prefix = pfix; } @Override public Thread newThread(Runnable r) { Thread task = new Thread(r, prefix + "-" + count++); task.setDaemon(true); return task; } } class ClockDriftMonitorTask implements Runnable { private static final long TIME_CLOCK_DRIFT_SAMPLE = Integer.getInteger("tnt4j.time.server.drift.sample.ms", 10000); private static final long TIME_CLOCK_DRIFT_LIMIT = Integer.getInteger("tnt4j.time.server.drift.limit.ms", 1); long interval, drift, updateCount = 0, totalDrift; EventSink logger; ClockDriftMonitorTask(EventSink lg) { logger = lg; } public long getIntervalMillis() { return interval; } public long getDriftMillis() { return drift; } public long getTotalDriftMillis() { return totalDrift; } public long getDriftUpdateCount() { return updateCount; } private void syncClocks() { try { TimeService.updateTime(); Useconds.CURRENT.sync(); updateCount++; if (TimeService.TIME_SERVER_VERBOSE) { logger.log(OpLevel.DEBUG, "Updated clocks: drift.ms={0}, interval.ms={1}, total.drift.ms={2}, updates={3}", drift, interval, totalDrift, updateCount); } } catch (Throwable ex) { logger.log(OpLevel.ERROR, "Failed to update clocks: last.updated={0}, age.ms={1}", new Date(TimeService.getLastUpdatedMillis()), TimeService.getUpdateAgeMillis(), ex); } } @Override public void run() { long start = System.nanoTime(); long base = System.currentTimeMillis() - (start / TimeService.ONE_M); while (true) { try { Thread.sleep(TIME_CLOCK_DRIFT_SAMPLE); } catch (InterruptedException e) { } long now = System.nanoTime(); drift = System.currentTimeMillis() - (now / TimeService.ONE_M) - base; totalDrift += Math.abs(drift); interval = (now - start) / TimeService.ONE_M; if (Math.abs(drift) >= TIME_CLOCK_DRIFT_LIMIT) { syncClocks(); start = System.nanoTime(); base = System.currentTimeMillis() - (start / TimeService.ONE_M); } } } }