/*
* Created on Apr 16, 2004 Created by Alon Rohter Copyright (C) 2004, 2005, 2006
* Aelitis, All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version. This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details. You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 46,603.30 euros 8 Allee Lenotre, La Grille Royale,
* 78600 Le Mesnil le Roi, France.
*
*/
package org.gudy.azureus2.core3.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* Utility class to retrieve current system time, and catch clock backward time
* changes.
*/
public class SystemTime {
public static final long TIME_GRANULARITY_MILLIS = 25; //internal update time ms
private static SystemTimeProvider instance;
// can't do that without some safeguarding code.
// monotime does guarantee that time neither goes backwards nor performs leaps into the future.
// the HPC doesn't jump backward but can jump forward in time
private static final boolean SOD_IT_LETS_USE_HPC = false;// = Constants.isCVSVersion();
static
{
try
{
if (System.getProperty("azureus.time.use.raw.provider", "0").equals("1"))
{
System.out.println("Warning: Using Raw Provider");
instance = new RawProvider();
} else
{
instance = new SteppedProvider();
}
} catch (Throwable e)
{
// might be in applet...
instance = new SteppedProvider();
}
}
public static void useRawProvider() {
if (!(instance instanceof RawProvider))
{
// Debug.out( "Whoa, someone already created a non-raw provider!" );
instance = new RawProvider();
}
}
private static volatile List systemTimeConsumers = new ArrayList();
private static volatile List monotoneTimeConsumers = new ArrayList();
private static volatile List clock_change_list = new ArrayList();
private static long hpc_base_time;
private static long hpc_last_time;
private static boolean no_hcp_logged;
protected interface SystemTimeProvider {
public long getTime();
public long getMonoTime();
public long
getSteppedMonoTime();
}
protected static class SteppedProvider implements SystemTimeProvider {
private static final int STEPS_PER_SECOND = (int) (1000 / TIME_GRANULARITY_MILLIS);
private static final long HPC_START = getHighPrecisionCounter()/1000000L;
private final Thread updater;
private volatile long stepped_time;
private volatile long currentTimeOffset = System.currentTimeMillis();
private AtomicLong last_approximate_time = new AtomicLong();
//private volatile long last_approximate_time;
private volatile int access_count;
private volatile int slice_access_count;
private volatile int access_average_per_slice;
private volatile int drift_adjusted_granularity;
private volatile long stepped_mono_time;
private SteppedProvider()
{
// System.out.println("SystemTime: using stepped time provider");
stepped_time = 0;
updater = new Thread("SystemTime")
{
public void run() {
long adjustedTimeOffset = currentTimeOffset;
// these averages rely on monotone time, thus won't be affected by system time changes
// final Average access_average = Average.getInstance(1000, 10);
// final Average drift_average = Average.getInstance(1000, 10);
long lastOffset = adjustedTimeOffset;
long lastSecond = -1000;
int tick_count = 0;
while (true)
{
long rawTime = System.currentTimeMillis();
/*
* keep the monotone time in sync with the raw system
* time, for this we need to know the offset of the
* current time to the system time
*/
long newMonotoneTime = rawTime - adjustedTimeOffset;
long delta = newMonotoneTime - stepped_time;
/*
* unless the system time jumps, then we just guess the
* time that has passed and adjust the update, so that
* the next round can be in sync with the system time
* again
*/
if (delta < 0 || delta > 1000)
{
/*
* jump occured, update monotone time offset, but
* not the current time one, that only happens every
* second
*/
stepped_time += TIME_GRANULARITY_MILLIS;
adjustedTimeOffset = rawTime - stepped_time;
} else
{ // time is good, keep it
stepped_time = newMonotoneTime;
}
tick_count++;
if (tick_count == STEPS_PER_SECOND)
{
if (lastOffset != adjustedTimeOffset)
{
final long change = adjustedTimeOffset - lastOffset;
Iterator it = clock_change_list.iterator();
//Debug.outNoStack("Clock change of " + change + "ms detected");
while (it.hasNext())
{
((ChangeListener) it.next()).clockChanged(rawTime, change);
}
lastOffset = adjustedTimeOffset;
// make the internal offset publicly visible after consumers have been notified
currentTimeOffset = adjustedTimeOffset;
}
// averaging magic to estimate the amount of time that passes between each getTime invocation
long drift = stepped_time - lastSecond - 1000;
lastSecond = stepped_time;
// drift_average.addValue(drift);
// drift_adjusted_granularity = (int) (TIME_GRANULARITY_MILLIS + (drift_average.getAverage() / STEPS_PER_SECOND));
// access_average.addValue(access_count);
// access_average_per_slice = (int) (access_average.getAverage() / STEPS_PER_SECOND);
//System.out.println( "access count = " + access_count + ", average = " + access_average.getAverage() + ", per slice = " + access_average_per_slice + ", drift = " + drift +", average = " + drift_average.getAverage() + ", dag =" + drift_adjusted_granularity );
access_count = 0;
tick_count = 0;
}
slice_access_count = 0;
stepped_mono_time = stepped_time;
// copy reference since we use unsynced COW semantics
List consumersRef = monotoneTimeConsumers;
for (int i = 0; i < consumersRef.size(); i++)
{
TickConsumer cons = (TickConsumer) consumersRef.get(i);
try
{
cons.consume(stepped_time);
} catch (Throwable e)
{
// Debug.printStackTrace(e);
}
}
/*
* notify consumers with the external offset, internal
* offset is only meant for updates
*/
consumersRef = systemTimeConsumers;
long adjustedTime = stepped_time + currentTimeOffset;
for (int i = 0; i < consumersRef.size(); i++)
{
TickConsumer cons = (TickConsumer) consumersRef.get(i);
try
{
cons.consume(adjustedTime);
} catch (Throwable e)
{
// Debug.printStackTrace(e);
}
}
try
{
Thread.sleep(TIME_GRANULARITY_MILLIS);
} catch (Exception e)
{
// Debug.printStackTrace(e);
}
}
}
};
updater.setDaemon(true);
// we don't want this thread to lag much as it'll stuff up the upload/download rate mechanisms (for example)
updater.setPriority(Thread.MAX_PRIORITY);
updater.start();
}
public long getTime() {
return getMonoTime() + currentTimeOffset;
}
public long getMonoTime() {
if ( SOD_IT_LETS_USE_HPC ){
return( ( getHighPrecisionCounter()/1000000) - HPC_START );
}else{
long adjusted_time;
long averageSliceStep = access_average_per_slice;
if (averageSliceStep > 0)
{
long sliceStep = (drift_adjusted_granularity * slice_access_count) / averageSliceStep;
if (sliceStep >= drift_adjusted_granularity)
{
sliceStep = drift_adjusted_granularity - 1;
}
adjusted_time = sliceStep + stepped_time;
} else
adjusted_time = stepped_time;
access_count++;
slice_access_count++;
// make sure we don't go backwards and our reference value for going backwards doesn't go backwards either
long approxBuffered = last_approximate_time.get();
if (adjusted_time < approxBuffered)
adjusted_time = approxBuffered;
else
last_approximate_time.compareAndSet(approxBuffered, adjusted_time);
return adjusted_time;
}
}
public long getSteppedMonoTime() {
if ( SOD_IT_LETS_USE_HPC ){
return( getHighPrecisionCounter()/1000000 );
}else{
return( stepped_mono_time );
}
}
}
protected static class RawProvider implements SystemTimeProvider {
private static final int STEPS_PER_SECOND = (int) (1000 / TIME_GRANULARITY_MILLIS);
private final Thread updater;
private RawProvider()
{
System.out.println("SystemTime: using raw time provider");
updater = new Thread("SystemTime")
{
long last_time;
public void run() {
while (true)
{
long current_time = getTime();
if (last_time != 0)
{
long offset = current_time - last_time;
if (offset < 0 || offset > 5000)
{
// clock's changed
Iterator it = clock_change_list.iterator();
while (it.hasNext())
{
((ChangeListener) it.next()).clockChanged(current_time, offset);
}
}
}
last_time = current_time;
List consumer_list_ref = systemTimeConsumers;
for (int i = 0; i < consumer_list_ref.size(); i++)
{
TickConsumer cons = (TickConsumer) consumer_list_ref.get(i);
try
{
cons.consume(current_time);
} catch (Throwable e)
{
// Debug.printStackTrace(e);
}
}
consumer_list_ref = monotoneTimeConsumers;
long mono_time = getMonoTime();
for (int i = 0; i < consumer_list_ref.size(); i++)
{
TickConsumer cons = (TickConsumer) consumer_list_ref.get(i);
try
{
cons.consume(mono_time);
} catch (Throwable e)
{
// Debug.printStackTrace(e);
}
}
try
{
Thread.sleep(TIME_GRANULARITY_MILLIS);
} catch (Exception e)
{
// Debug.printStackTrace(e);
}
}
}
};
updater.setDaemon(true);
// we don't want this thread to lag much as it'll stuff up the upload/download rate mechanisms (for example)
updater.setPriority(Thread.MAX_PRIORITY);
updater.start();
}
public long getTime() {
return System.currentTimeMillis();
}
/**
* This implementation does not guarantee monotonous time increases with
* 100% accuracy as the adjustedTimeOffset is only adjusted every
* TIME_GRANULARITY_MILLIS
*/
public long getMonoTime() {
return getHighPrecisionCounter()/1000000;
}
public long getSteppedMonoTime() {
return getMonoTime();
}
}
/**
* Note that this can this time can jump into the future or past due to
* clock adjustments use getMonotonousTime() if you need steady increases
*
* @return current system time in millisecond since epoch
*/
public static long getCurrentTime() {
return (instance.getTime());
}
/**
* Time that is guaranteed to grow monotonously and also ignores larger
* jumps into the future which might be caused by adjusting the system clock<br>
* <br>
*
* <b>Do not mix times retrieved by this method with normal time!</b>
*
* @return the amount of real time passed since the program start in
* milliseconds
*/
public static long getMonotonousTime() {
return instance.getMonoTime();
}
/**
* Like getMonotonousTime but only updated at TIME_GRANULARITY_MILLIS intervals (not interpolated)
* As such it is likely to be cheaper to obtain
* @return
*/
public static long getSteppedMonotonousTime() {
return instance.getSteppedMonoTime();
}
public static long getOffsetTime(long offsetMS) {
return instance.getTime() + offsetMS;
}
public static void registerConsumer(TickConsumer c) {
synchronized (instance)
{
List new_list = new ArrayList(systemTimeConsumers);
new_list.add(c);
systemTimeConsumers = new_list;
}
}
public static void unregisterConsumer(TickConsumer c) {
synchronized (instance)
{
List new_list = new ArrayList(systemTimeConsumers);
new_list.remove(c);
systemTimeConsumers = new_list;
}
}
public static void registerMonotonousConsumer(TickConsumer c) {
synchronized (instance)
{
List new_list = new ArrayList(monotoneTimeConsumers);
new_list.add(c);
monotoneTimeConsumers = new_list;
}
}
public static void unregisterMonotonousConsumer(TickConsumer c) {
synchronized (instance)
{
List new_list = new ArrayList(monotoneTimeConsumers);
new_list.remove(c);
monotoneTimeConsumers = new_list;
}
}
public static void registerClockChangeListener(ChangeListener c) {
synchronized (instance)
{
List new_list = new ArrayList(clock_change_list);
new_list.add(c);
clock_change_list = new_list;
}
}
public static void unregisterClockChangeListener(ChangeListener c) {
synchronized (instance)
{
List new_list = new ArrayList(clock_change_list);
new_list.remove(c);
clock_change_list = new_list;
}
}
public interface TickConsumer {
public void consume(long current_time);
}
public interface ChangeListener {
public void clockChanged(long current_time, long change_millis);
}
public static long
getHighPrecisionCounter()
{
return( System.nanoTime());
}
public static void main(String[] args) {
for (int i = 0; i < 1; i++)
{
//final int f_i = i;
new Thread()
{
public void run() {
/*
* Average access_average = Average.getInstance( 1000, 10 );
*
* long last = SystemTime.getCurrentTime();
*
* int count = 0;
*
* while( true ){
*
* long now = SystemTime.getCurrentTime();
*
* long diff = now - last;
*
* System.out.println( "diff=" + diff );
*
* last = now;
*
* access_average.addValue( diff );
*
* count++;
*
* if ( count == 33 ){
*
* System.out.println( "AVERAGE " + f_i + " = " +
* access_average.getAverage());
*
* count = 0; }
*
* try{ Thread.sleep( 3 );
*
* }catch( Throwable e ){ } }
*/
long cstart = SystemTime.getCurrentTime();
long mstart = SystemTime.getMonotonousTime();
System.out.println("alter system clock to see differences between monotonous and current time");
long cLastRound = cstart;
long mLastRound = mstart;
while (true)
{
long mnow = SystemTime.getMonotonousTime();
long cnow = SystemTime.getCurrentTime();
//if(mLastRound > mnow)
System.out.println("current: " + (cnow - cstart) + " monotonous:" + (mnow - mstart) + " delta current:" + (cnow - cLastRound) + " delta monotonous:" + (mnow - mLastRound));
cLastRound = cnow;
mLastRound = mnow;
try
{
Thread.sleep(15);
} catch (Throwable e)
{}
}
}
}.start();
}
}
}