Logo Search packages:      
Sourcecode: tcl8.4 version File versions  Download package

tclWinTime.c

/* 
 * tclWinTime.c --
 *
 *    Contains Windows specific versions of Tcl functions that
 *    obtain time values from the operating system.
 *
 * Copyright 1995-1998 by Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tclWinTime.c,v 1.14.2.8 2005/03/30 05:31:13 hobbs Exp $
 */

#include "tclWinInt.h"

#define SECSPERDAY (60L * 60L * 24L)
#define SECSPERYEAR (SECSPERDAY * 365L)
#define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY)

/*
 * Number of samples over which to estimate the performance counter
 */
#define SAMPLES 64

/*
 * The following arrays contain the day of year for the last day of
 * each month, where index 1 is January.
 */

static int normalDays[] = {
    -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364
};

static int leapDays[] = {
    -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
};

typedef struct ThreadSpecificData {
    char tzName[64];          /* Time zone name */
    struct tm tm;       /* time information */
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;

/*
 * Data for managing high-resolution timers.
 */

typedef struct TimeInfo {

    CRITICAL_SECTION cs;      /* Mutex guarding this structure */

    int initialized;          /* Flag == 1 if this structure is
                         * initialized. */

    int perfCounterAvailable; /* Flag == 1 if the hardware has a
                         * performance counter */

    HANDLE calibrationThread; /* Handle to the thread that keeps the
                         * virtual clock calibrated. */

    HANDLE readyEvent;        /* System event used to
                         * trigger the requesting thread
                         * when the clock calibration procedure
                         * is initialized for the first time */

    HANDLE exitEvent;         /* Event to signal out of an exit handler
                         * to tell the calibration loop to
                         * terminate */

    LARGE_INTEGER nominalFreq;      /* Nominal frequency of the system
                         * performance counter, that is, the value
                         * returned from QueryPerformanceFrequency. */

    /*
     * The following values are used for calculating virtual time.
     * Virtual time is always equal to:
     *    lastFileTime + (current perf counter - lastCounter) 
     *                        * 10000000 / curCounterFreq
     * and lastFileTime and lastCounter are updated any time that
     * virtual time is returned to a caller.
     */

    ULARGE_INTEGER fileTimeLastCall;
    LARGE_INTEGER perfCounterLastCall;
    LARGE_INTEGER curCounterFreq;

    /*
     * Data used in developing the estimate of performance counter
     * frequency
     */
    Tcl_WideUInt fileTimeSample[SAMPLES];
                        /* Last 64 samples of system time */
    Tcl_WideInt perfCounterSample[SAMPLES];
                        /* Last 64 samples of performance counter */
    int sampleNo;       /* Current sample number */


} TimeInfo;

static TimeInfo timeInfo = {
    { NULL },
    0,
    0,
    (HANDLE) NULL,
    (HANDLE) NULL,
    (HANDLE) NULL,
#ifdef HAVE_CAST_TO_UNION
    (LARGE_INTEGER) (Tcl_WideInt) 0,
    (ULARGE_INTEGER) (DWORDLONG) 0,
    (LARGE_INTEGER) (Tcl_WideInt) 0,
    (LARGE_INTEGER) (Tcl_WideInt) 0,
#else
    0,
    0,
    0,
    0,
#endif
    { 0 },
    { 0 },
    0
};

CONST static FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE };
    
/*
 * Declarations for functions defined later in this file.
 */

static struct tm *      ComputeGMT _ANSI_ARGS_((const time_t *tp));
static void       StopCalibration _ANSI_ARGS_(( ClientData ));
static DWORD WINAPI     CalibrationThread _ANSI_ARGS_(( LPVOID arg ));
static void             UpdateTimeEachSecond _ANSI_ARGS_(( void ));
static void       ResetCounterSamples _ANSI_ARGS_((
                      Tcl_WideUInt fileTime, 
                            Tcl_WideInt perfCounter,
                      Tcl_WideInt perfFreq
                  ));
static Tcl_WideInt            AccumulateSample _ANSI_ARGS_((
                      Tcl_WideInt perfCounter,
                      Tcl_WideUInt fileTime
                  ));

/*
 *----------------------------------------------------------------------
 *
 * TclpGetSeconds --
 *
 *    This procedure returns the number of seconds from the epoch.
 *    On most Unix systems the epoch is Midnight Jan 1, 1970 GMT.
 *
 * Results:
 *    Number of seconds from the epoch.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

unsigned long
TclpGetSeconds()
{
    Tcl_Time t;
    Tcl_GetTime( &t );
    return t.sec;
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetClicks --
 *
 *    This procedure returns a value that represents the highest
 *    resolution clock available on the system.  There are no
 *    guarantees on what the resolution will be.  In Tcl we will
 *    call this value a "click".  The start time is also system
 *    dependant.
 *
 * Results:
 *    Number of clicks from some start time.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

unsigned long
TclpGetClicks()
{
    /*
     * Use the Tcl_GetTime abstraction to get the time in microseconds,
     * as nearly as we can, and return it.
     */

    Tcl_Time now;       /* Current Tcl time */
    unsigned long retval;     /* Value to return */

    Tcl_GetTime( &now );
    retval = ( now.sec * 1000000 ) + now.usec;
    return retval;

}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetTimeZone --
 *
 *    Determines the current timezone.  The method varies wildly
 *    between different Platform implementations, so its hidden in
 *    this function.
 *
 * Results:
 *    Minutes west of GMT.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

int
TclpGetTimeZone (currentTime)
    Tcl_WideInt currentTime;
{
    int timeZone;

    tzset();
    timeZone = _timezone / 60;

    return timeZone;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_GetTime --
 *
 *    Gets the current system time in seconds and microseconds
 *    since the beginning of the epoch: 00:00 UCT, January 1, 1970.
 *
 * Results:
 *    Returns the current time in timePtr.
 *
 * Side effects:
 *    On the first call, initializes a set of static variables to
 *    keep track of the base value of the performance counter, the
 *    corresponding wall clock (obtained through ftime) and the
 *    frequency of the performance counter.  Also spins a thread
 *    whose function is to wake up periodically and monitor these
 *    values, adjusting them as necessary to correct for drift
 *    in the performance counter's oscillator.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_GetTime(timePtr)
    Tcl_Time *timePtr;        /* Location to store time information. */
{
    struct timeb t;

    int useFtime = 1;         /* Flag == TRUE if we need to fall back
                         * on ftime rather than using the perf
                         * counter */

    /* Initialize static storage on the first trip through. */

    /*
     * Note: Outer check for 'initialized' is a performance win
     * since it avoids an extra mutex lock in the common case.
     */

    if ( !timeInfo.initialized ) { 
      TclpInitLock();
      if ( !timeInfo.initialized ) {
          timeInfo.perfCounterAvailable
            = QueryPerformanceFrequency( &timeInfo.nominalFreq );

          /*
           * Some hardware abstraction layers use the CPU clock
           * in place of the real-time clock as a performance counter
           * reference.  This results in:
           *    - inconsistent results among the processors on
           *      multi-processor systems.
           *    - unpredictable changes in performance counter frequency
           *      on "gearshift" processors such as Transmeta and
           *      SpeedStep.
           *
           * There seems to be no way to test whether the performance
           * counter is reliable, but a useful heuristic is that
           * if its frequency is 1.193182 MHz or 3.579545 MHz, it's
           * derived from a colorburst crystal and is therefore
           * the RTC rather than the TSC.
           *
           * A sloppier but serviceable heuristic is that the RTC crystal
           * is normally less than 15 MHz while the TSC crystal is
           * virtually assured to be greater than 100 MHz.  Since Win98SE
           * appears to fiddle with the definition of the perf counter
           * frequency (perhaps in an attempt to calibrate the clock?)
           * we use the latter rule rather than an exact match.
           */

          if ( timeInfo.perfCounterAvailable
             /* The following lines would do an exact match on
              * crystal frequency:
              * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 1193182
              * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 3579545
              */
             && timeInfo.nominalFreq.QuadPart > (Tcl_WideInt) 15000000 ) {

            /*
             * As an exception, if every logical processor on the system
             * is on the same chip, we use the performance counter anyway,
             * presuming that everyone's TSC is locked to the same
             * oscillator.
             */

            SYSTEM_INFO systemInfo;
            unsigned int regs[4];
            GetSystemInfo( &systemInfo );
            if ( TclWinCPUID( 0, regs ) == TCL_OK

                 && regs[1] == 0x756e6547 /* "Genu" */
                 && regs[3] == 0x49656e69 /* "ineI" */
                 && regs[2] == 0x6c65746e /* "ntel" */

                 && TclWinCPUID( 1, regs ) == TCL_OK 

                 && ( (regs[0] & 0x00000F00) == 0x00000F00 /* Pentium 4 */
                    || ( (regs[0] & 0x00F00000)    /* Extended family */
                         && (regs[3] & 0x10000000) ) ) /* Hyperthread */
                 && ( ( ( regs[1] & 0x00FF0000 ) >> 16 ) /* CPU count */
                    == systemInfo.dwNumberOfProcessors ) 

                ) {
                timeInfo.perfCounterAvailable = TRUE;
            } else {
            timeInfo.perfCounterAvailable = FALSE;
          }

          }

          /*
           * If the performance counter is available, start a thread to
           * calibrate it.
           */

          if ( timeInfo.perfCounterAvailable ) {
            DWORD id;
            InitializeCriticalSection( &timeInfo.cs );
            timeInfo.readyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
            timeInfo.exitEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
            timeInfo.calibrationThread = CreateThread( NULL,
                                             256,
                                             CalibrationThread,
                                             (LPVOID) NULL,
                                             0,
                                             &id );
            SetThreadPriority( timeInfo.calibrationThread,
                           THREAD_PRIORITY_HIGHEST );

            /*
             * Wait for the thread just launched to start running,
             * and create an exit handler that kills it so that it
             * doesn't outlive unloading tclXX.dll
             */

            WaitForSingleObject( timeInfo.readyEvent, INFINITE );
            CloseHandle( timeInfo.readyEvent );
            Tcl_CreateExitHandler( StopCalibration, (ClientData) NULL );
          }
          timeInfo.initialized = TRUE;
      }
      TclpInitUnlock();
    }

    if ( timeInfo.perfCounterAvailable ) {
      /*
       * Query the performance counter and use it to calculate the
       * current time.
       */

      LARGE_INTEGER curCounter;
                        /* Current performance counter */

      Tcl_WideInt curFileTime;
                        /* Current estimated time, expressed
                         * as 100-ns ticks since the Windows epoch */

      static LARGE_INTEGER posixEpoch;
                        /* Posix epoch expressed as 100-ns ticks
                         * since the windows epoch */

      Tcl_WideInt usecSincePosixEpoch;
                        /* Current microseconds since Posix epoch */

      posixEpoch.LowPart = 0xD53E8000;
      posixEpoch.HighPart = 0x019DB1DE;

      EnterCriticalSection( &timeInfo.cs );

      QueryPerformanceCounter( &curCounter );

      /* 
       * If it appears to be more than 1.1 seconds since the last trip
       * through the calibration loop, the performance counter may
       * have jumped forward. (See MSDN Knowledge Base article
       * Q274323 for a description of the hardware problem that makes
       * this test necessary.) If the counter jumps, we don't want
       * to use it directly. Instead, we must return system time.
       * Eventually, the calibration loop should recover.
       */
      if ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart
           < 11 * timeInfo.curCounterFreq.QuadPart / 10 ) {

          curFileTime = timeInfo.fileTimeLastCall.QuadPart
            + ( ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart )
                * 10000000 / timeInfo.curCounterFreq.QuadPart );
          timeInfo.fileTimeLastCall.QuadPart = curFileTime;
          timeInfo.perfCounterLastCall.QuadPart = curCounter.QuadPart;
          usecSincePosixEpoch = ( curFileTime - posixEpoch.QuadPart ) / 10;
          timePtr->sec = (time_t) ( usecSincePosixEpoch / 1000000 );
          timePtr->usec = (unsigned long ) ( usecSincePosixEpoch % 1000000 );
          useFtime = 0;
      }

      LeaveCriticalSection( &timeInfo.cs );
    }

    if ( useFtime ) {
      /* High resolution timer is not available.  Just use ftime */

      ftime(&t);
      timePtr->sec = t.time;
      timePtr->usec = t.millitm * 1000;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * StopCalibration --
 *
 *    Turns off the calibration thread in preparation for exiting the
 *    process.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Sets the 'exitEvent' event in the 'timeInfo' structure to ask
 *    the thread in question to exit, and waits for it to do so.
 *
 *----------------------------------------------------------------------
 */

static void
StopCalibration( ClientData unused )
                        /* Client data is unused */
{
    SetEvent( timeInfo.exitEvent );
    WaitForSingleObject( timeInfo.calibrationThread, INFINITE );
    CloseHandle( timeInfo.exitEvent );
    CloseHandle( timeInfo.calibrationThread );
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetTZName --
 *
 *    Gets the current timezone string.
 *
 * Results:
 *    Returns a pointer to a static string, or NULL on failure.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

char *
TclpGetTZName(int dst)
{
    int len;
    char *zone, *p;
    TIME_ZONE_INFORMATION tz;
    Tcl_Encoding encoding;
    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
    char *name = tsdPtr->tzName;

    /*
     * tzset() under Borland doesn't seem to set up tzname[] at all.
     * tzset() under MSVC has the following weird observed behavior:
     *       First time we call "clock format [clock seconds] -format %Z -gmt 1"
     *       we get "GMT", but on all subsequent calls we get the current time 
     *       zone string, even though env(TZ) is GMT and the variable _timezone 
     *   is 0.
     */

    name[0] = '\0';

    zone = getenv("TZ");
    if (zone != NULL) {
      /*
       * TZ is of form "NST-4:30NDT", where "NST" would be the
       * name of the standard time zone for this area, "-4:30" is
       * the offset from GMT in hours, and "NDT is the name of 
       * the daylight savings time zone in this area.  The offset 
       * and DST strings are optional.
       */

      len = strlen(zone);
      if (len > 3) {
          len = 3;
      }
      if (dst != 0) {
          /*
           * Skip the offset string and get the DST string.
           */

          p = zone + len;
          p += strspn(p, "+-:0123456789");
          if (*p != '\0') {
            zone = p;
            len = strlen(zone);
            if (len > 3) {
                len = 3;
            }
          }
      }
      Tcl_ExternalToUtf(NULL, NULL, zone, len, 0, NULL, name,
            sizeof(tsdPtr->tzName), NULL, NULL, NULL);
    }
    if (name[0] == '\0') {
      if (GetTimeZoneInformation(&tz) == TIME_ZONE_ID_UNKNOWN) {
          /*
           * MSDN: On NT this is returned if DST is not used in
           * the current TZ
           */
          dst = 0;
      }
      encoding = Tcl_GetEncoding(NULL, "unicode");
      Tcl_ExternalToUtf(NULL, encoding, 
            (char *) ((dst) ? tz.DaylightName : tz.StandardName), -1, 
            0, NULL, name, sizeof(tsdPtr->tzName), NULL, NULL, NULL);
      Tcl_FreeEncoding(encoding);
    } 
    return name;
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGetDate --
 *
 *    This function converts between seconds and struct tm.  If
 *    useGMT is true, then the returned date will be in Greenwich
 *    Mean Time (GMT).  Otherwise, it will be in the local time zone.
 *
 * Results:
 *    Returns a static tm structure.
 *
 * Side effects:
 *    None.
 *
 *----------------------------------------------------------------------
 */

struct tm *
TclpGetDate(t, useGMT)
    TclpTime_t t;
    int useGMT;
{
    const time_t *tp = (const time_t *) t;
    struct tm *tmPtr;
    time_t time;

    if (!useGMT) {
      tzset();

      /*
       * If we are in the valid range, let the C run-time library
       * handle it.  Otherwise we need to fake it.  Note that this
       * algorithm ignores daylight savings time before the epoch.
       */

      if (*tp >= 0) {
          return localtime(tp);
      }

      time = *tp - _timezone;

      /*
       * If we aren't near to overflowing the long, just add the bias and
       * use the normal calculation.  Otherwise we will need to adjust
       * the result at the end.
       */

      if (*tp < (LONG_MAX - 2 * SECSPERDAY)
            && *tp > (LONG_MIN + 2 * SECSPERDAY)) {
          tmPtr = ComputeGMT(&time);
      } else {
          tmPtr = ComputeGMT(tp);

          tzset();

          /*
           * Add the bias directly to the tm structure to avoid overflow.
           * Propagate seconds overflow into minutes, hours and days.
           */

          time = tmPtr->tm_sec - _timezone;
          tmPtr->tm_sec = (int)(time % 60);
          if (tmPtr->tm_sec < 0) {
            tmPtr->tm_sec += 60;
            time -= 60;
          }

          time = tmPtr->tm_min + time/60;
          tmPtr->tm_min = (int)(time % 60);
          if (tmPtr->tm_min < 0) {
            tmPtr->tm_min += 60;
            time -= 60;
          }

          time = tmPtr->tm_hour + time/60;
          tmPtr->tm_hour = (int)(time % 24);
          if (tmPtr->tm_hour < 0) {
            tmPtr->tm_hour += 24;
            time -= 24;
          }

          time /= 24;
          tmPtr->tm_mday += time;
          tmPtr->tm_yday += time;
          tmPtr->tm_wday = (tmPtr->tm_wday + time) % 7;
      }
    } else {
      tmPtr = ComputeGMT(tp);
    }
    return tmPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * ComputeGMT --
 *
 *    This function computes GMT given the number of seconds since
 *    the epoch (midnight Jan 1 1970).
 *
 * Results:
 *    Returns a (per thread) statically allocated struct tm.
 *
 * Side effects:
 *    Updates the values of the static struct tm.
 *
 *----------------------------------------------------------------------
 */

static struct tm *
ComputeGMT(tp)
    const time_t *tp;
{
    struct tm *tmPtr;
    long tmp, rem;
    int isLeap;
    int *days;
    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);

    tmPtr = &tsdPtr->tm;

    /*
     * Compute the 4 year span containing the specified time.
     */

    tmp = *tp / SECSPER4YEAR;
    rem = *tp % SECSPER4YEAR;

    /*
     * Correct for weird mod semantics so the remainder is always positive.
     */

    if (rem < 0) {
      tmp--;
      rem += SECSPER4YEAR;
    }

    /*
     * Compute the year after 1900 by taking the 4 year span and adjusting
     * for the remainder.  This works because 2000 is a leap year, and
     * 1900/2100 are out of the range.
     */

    tmp = (tmp * 4) + 70;
    isLeap = 0;
    if (rem >= SECSPERYEAR) {               /* 1971, etc. */
      tmp++;
      rem -= SECSPERYEAR;
      if (rem >= SECSPERYEAR) {             /* 1972, etc. */
          tmp++;
          rem -= SECSPERYEAR;
          if (rem >= SECSPERYEAR + SECSPERDAY) { /* 1973, etc. */
            tmp++;
            rem -= SECSPERYEAR + SECSPERDAY;
          } else {
            isLeap = 1;
          }
      }
    }
    tmPtr->tm_year = tmp;

    /*
     * Compute the day of year and leave the seconds in the current day in
     * the remainder.
     */

    tmPtr->tm_yday = rem / SECSPERDAY;
    rem %= SECSPERDAY;
    
    /*
     * Compute the time of day.
     */

    tmPtr->tm_hour = rem / 3600;
    rem %= 3600;
    tmPtr->tm_min = rem / 60;
    tmPtr->tm_sec = rem % 60;

    /*
     * Compute the month and day of month.
     */

    days = (isLeap) ? leapDays : normalDays;
    for (tmp = 1; days[tmp] < tmPtr->tm_yday; tmp++) {
    }
    tmPtr->tm_mon = --tmp;
    tmPtr->tm_mday = tmPtr->tm_yday - days[tmp];

    /*
     * Compute day of week.  Epoch started on a Thursday.
     */

    tmPtr->tm_wday = (*tp / SECSPERDAY) + 4;
    if ((*tp % SECSPERDAY) < 0) {
      tmPtr->tm_wday--;
    }
    tmPtr->tm_wday %= 7;
    if (tmPtr->tm_wday < 0) {
      tmPtr->tm_wday += 7;
    }

    return tmPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * CalibrationThread --
 *
 *    Thread that manages calibration of the hi-resolution time
 *    derived from the performance counter, to keep it synchronized
 *    with the system clock.
 *
 * Parameters:
 *    arg -- Client data from the CreateThread call.  This parameter
 *             points to the static TimeInfo structure.
 *
 * Return value:
 *    None.  This thread embeds an infinite loop.
 *
 * Side effects:
 *    At an interval of 1 s, this thread performs virtual time discipline.
 *
 * Note: When this thread is entered, TclpInitLock has been called
 * to safeguard the static storage.  There is therefore no synchronization
 * in the body of this procedure.
 *
 *----------------------------------------------------------------------
 */

static DWORD WINAPI
CalibrationThread( LPVOID arg )
{
    FILETIME curFileTime;
    DWORD waitResult;

    /* Get initial system time and performance counter */

    GetSystemTimeAsFileTime( &curFileTime );
    QueryPerformanceCounter( &timeInfo.perfCounterLastCall );
    QueryPerformanceFrequency( &timeInfo.curCounterFreq );
    timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime;
    timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime;

    ResetCounterSamples( timeInfo.fileTimeLastCall.QuadPart,
                   timeInfo.perfCounterLastCall.QuadPart,
                   timeInfo.curCounterFreq.QuadPart );

    /*
     * Wake up the calling thread.  When it wakes up, it will release the
     * initialization lock.
     */

    SetEvent( timeInfo.readyEvent );

    /* Run the calibration once a second */

    for ( ; ; ) {

      /* If the exitEvent is set, break out of the loop. */

      waitResult = WaitForSingleObjectEx(timeInfo.exitEvent, 1000, FALSE);
      if ( waitResult == WAIT_OBJECT_0 ) {
          break;
      }
      UpdateTimeEachSecond();
    }

    /* lint */
    return (DWORD) 0;
}

/*
 *----------------------------------------------------------------------
 *
 * UpdateTimeEachSecond --
 *
 *    Callback from the waitable timer in the clock calibration thread
 *    that updates system time.
 *
 * Parameters:
 *    info -- Pointer to the static TimeInfo structure
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Performs virtual time calibration discipline.
 *
 *----------------------------------------------------------------------
 */

static void
UpdateTimeEachSecond()
{

    LARGE_INTEGER curPerfCounter;
                        /* Current value returned from
                         * QueryPerformanceCounter */

    FILETIME curSysTime;      /* Current system time */

    LARGE_INTEGER curFileTime;      /* File time at the time this callback
                         * was scheduled. */

    Tcl_WideInt estFreq;      /* Estimated perf counter frequency */

    Tcl_WideInt vt0;          /* Tcl time right now */
    Tcl_WideInt vt1;          /* Tcl time one second from now */

    Tcl_WideInt tdiff;        /* Difference between system clock and
                         * Tcl time. */

    Tcl_WideInt driftFreq;    /* Frequency needed to drift virtual time
                         * into step over 1 second */

    /*
     * Sample performance counter and system time.
     */

    QueryPerformanceCounter( &curPerfCounter );
    GetSystemTimeAsFileTime( &curSysTime );
    curFileTime.LowPart = curSysTime.dwLowDateTime;
    curFileTime.HighPart = curSysTime.dwHighDateTime;

    EnterCriticalSection( &timeInfo.cs );

    /*
     * Several things may have gone wrong here that have to
     * be checked for.
     * (1) The performance counter may have jumped.
     * (2) The system clock may have been reset.
     *
     * In either case, we'll need to reinitialize the circular buffer
     * with samples relative to the current system time and the NOMINAL
     * performance frequency (not the actual, because the actual has
     * probably run slow in the first case). Our estimated frequency
     * will be the nominal frequency.
     */

    /*
     * Store the current sample into the circular buffer of samples,
     * and estimate the performance counter frequency.
     */

    estFreq = AccumulateSample( curPerfCounter.QuadPart,
                        (Tcl_WideUInt) curFileTime.QuadPart );

    /*
     * We want to adjust things so that time appears to be continuous.
     * Virtual file time, right now, is 
     *
     * vt0 = 10000000 * ( curPerfCounter - perfCounterLastCall )
     *       / curCounterFreq
     *       + fileTimeLastCall
     *
     * Ideally, we would like to drift the clock into place over a
     * period of 2 sec, so that virtual time 2 sec from now will be
     *
     * vt1 = 20000000 + curFileTime
     * 
     * The frequency that we need to use to drift the counter back into
     * place is estFreq * 20000000 / ( vt1 - vt0 )
     */
    
    vt0 = 10000000 * ( curPerfCounter.QuadPart
                   - timeInfo.perfCounterLastCall.QuadPart )
      / timeInfo.curCounterFreq.QuadPart
      + timeInfo.fileTimeLastCall.QuadPart;
    vt1 = 20000000 + curFileTime.QuadPart;

    /*
     * If we've gotten more than a second away from system time,
     * then drifting the clock is going to be pretty hopeless.
     * Just let it jump. Otherwise, compute the drift frequency and
     * fill in everything.
     */

    tdiff = vt0 - curFileTime.QuadPart;
    if ( tdiff > 10000000 || tdiff < -10000000 ) {
      timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart;
      timeInfo.curCounterFreq.QuadPart = estFreq;
    } else {
      driftFreq = estFreq * 20000000 / ( vt1 - vt0 );
      if ( driftFreq > 1003 * estFreq / 1000 ) {
          driftFreq = 1003 * estFreq / 1000;
      }
      if ( driftFreq < 997 * estFreq / 1000 ) {
          driftFreq = 997 * estFreq / 1000;
      }
      timeInfo.fileTimeLastCall.QuadPart = vt0;
      timeInfo.curCounterFreq.QuadPart = driftFreq;
    }

    timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart;

    LeaveCriticalSection( &timeInfo.cs );

}

/*
 *----------------------------------------------------------------------
 *
 * ResetCounterSamples --
 *
 *    Fills the sample arrays in 'timeInfo' with dummy values that will
 *    yield the current performance counter and frequency.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The array of samples is filled in so that it appears that there
 *    are SAMPLES samples at one-second intervals, separated by precisely
 *    the given frequency.
 *
 *----------------------------------------------------------------------
 */

static void
ResetCounterSamples( Tcl_WideUInt fileTime,
                        /* Current file time */
                 Tcl_WideInt perfCounter,
                        /* Current performance counter */
                 Tcl_WideInt perfFreq )
                        /* Target performance frequency */
{
    int i;
    for ( i = SAMPLES-1; i >= 0; --i ) {
      timeInfo.perfCounterSample[i] = perfCounter;
      timeInfo.fileTimeSample[i] = fileTime;
      perfCounter -= perfFreq;
      fileTime -= 10000000;
    }
    timeInfo.sampleNo = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * AccumulateSample --
 *
 *    Updates the circular buffer of performance counter and system
 *    time samples with a new data point.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    The new data point replaces the oldest point in the circular
 *    buffer, and the descriptive statistics are updated to accumulate
 *    the new point.
 *
 * Several things may have gone wrong here that have to
 * be checked for.
 * (1) The performance counter may have jumped.
 * (2) The system clock may have been reset.
 *
 * In either case, we'll need to reinitialize the circular buffer
 * with samples relative to the current system time and the NOMINAL
 * performance frequency (not the actual, because the actual has
 * probably run slow in the first case).
 */

static Tcl_WideInt
AccumulateSample( Tcl_WideInt perfCounter,
              Tcl_WideUInt fileTime )
{
    Tcl_WideUInt workFTSample;      /* File time sample being removed
                         * from or added to the circular buffer */

    Tcl_WideInt workPCSample; /* Performance counter sample being
                         * removed from or added to the circular 
                         * buffer */

    Tcl_WideUInt lastFTSample;      /* Last file time sample recorded */

    Tcl_WideInt lastPCSample; /* Last performance counter sample recorded */

    Tcl_WideInt FTdiff;       /* Difference between last FT and current */

    Tcl_WideInt PCdiff;       /* Difference between last PC and current */

    Tcl_WideInt estFreq;      /* Estimated performance counter frequency */

    /* Test for jumps and reset the samples if we have one. */

    if ( timeInfo.sampleNo == 0 ) {
      lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo
                                       + SAMPLES - 1 ];
      lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo
                                    + SAMPLES - 1 ];
    } else {
      lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo - 1 ];
      lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo - 1 ];
    }
    PCdiff = perfCounter - lastPCSample;
    FTdiff = fileTime - lastFTSample;
    if ( PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10
       || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10
       || FTdiff < 9000000
       || FTdiff > 11000000 ) {
      ResetCounterSamples( fileTime, perfCounter,
                       timeInfo.nominalFreq.QuadPart );
      return timeInfo.nominalFreq.QuadPart;

    } else {
    
      /* Estimate the frequency */
      
      workPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo ];
      workFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo ];
      estFreq = 10000000 * ( perfCounter - workPCSample )
          / ( fileTime - workFTSample );
      timeInfo.perfCounterSample[ timeInfo.sampleNo ] = perfCounter;
      timeInfo.fileTimeSample[ timeInfo.sampleNo ] = (Tcl_WideInt) fileTime;
      
      /* Advance the sample number */
      
      if ( ++timeInfo.sampleNo >= SAMPLES ) {
          timeInfo.sampleNo = 0;
      } 
      
      return estFreq;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TclpGmtime --
 *
 *    Wrapper around the 'gmtime' library function to make it thread
 *    safe.
 *
 * Results:
 *    Returns a pointer to a 'struct tm' in thread-specific data.
 *
 * Side effects:
 *    Invokes gmtime or gmtime_r as appropriate.
 *
 *----------------------------------------------------------------------
 */

struct tm *
TclpGmtime( tt )
    CONST TclpTime_t tt;
{
    CONST time_t *timePtr = (CONST time_t *) tt;
                        /* Pointer to the number of seconds
                         * since the local system's epoch */
    /*
     * The MS implementation of gmtime is thread safe because
     * it returns the time in a block of thread-local storage,
     * and Windows does not provide a Posix gmtime_r function.
     */
    return gmtime( timePtr );
}

/*
 *----------------------------------------------------------------------
 *
 * TclpLocaltime --
 *
 *    Wrapper around the 'localtime' library function to make it thread
 *    safe.
 *
 * Results:
 *    Returns a pointer to a 'struct tm' in thread-specific data.
 *
 * Side effects:
 *    Invokes localtime or localtime_r as appropriate.
 *
 *----------------------------------------------------------------------
 */

struct tm *
TclpLocaltime( tt )
    CONST TclpTime_t tt;
{
    CONST time_t *timePtr = (CONST time_t *) tt;
                        /* Pointer to the number of seconds
                         * since the local system's epoch */

    /*
     * The MS implementation of localtime is thread safe because
     * it returns the time in a block of thread-local storage,
     * and Windows does not provide a Posix localtime_r function.
     */
    return localtime( timePtr );
}

Generated by  Doxygen 1.6.0   Back to index