summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin B Kenny <kennykb@acm.org>2003-04-12 20:11:33 (GMT)
committerKevin B Kenny <kennykb@acm.org>2003-04-12 20:11:33 (GMT)
commitb0cc421ee0a6c1691d7db034c52ae9fcb5295627 (patch)
treedfa1a81b176b70021b0ecfa66e96c7a6a66d2ba1
parent47554fcd67f382566a72813c4b11bda27ecfb201 (diff)
downloadtcl-b0cc421ee0a6c1691d7db034c52ae9fcb5295627.zip
tcl-b0cc421ee0a6c1691d7db034c52ae9fcb5295627.tar.gz
tcl-b0cc421ee0a6c1691d7db034c52ae9fcb5295627.tar.bz2
Fixed Bug 710310 (duplicate test numbers in clock.test). Made major
changes to tclWinTime.c and related code to improve loop filter stability.
-rw-r--r--ChangeLog13
-rw-r--r--tests/clock.test60
-rw-r--r--tests/winTime.test28
-rw-r--r--win/tclWinTest.c66
-rw-r--r--win/tclWinTime.c361
5 files changed, 356 insertions, 172 deletions
diff --git a/ChangeLog b/ChangeLog
index 93dc275..ad982e8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2003-04-12 Kevin Kenny <kennykb@acm.org>
+
+ * tests/clock.test: Renumbered test cases to avoid
+ duplicates [Bug 710310].
+ * tests/winTime.test:
+ * win/tclWinTest.c (TestwinclockCmd, TestwinsleepCmd):
+ * win/tclWinTime.c (Tcl_WinTime, UpdateTimeEachSecond,
+ ResetCounterSamples, AccumulateSample,
+ SAMPLES, TimeInfo): Made substantial changes
+ to the phase-locked loop (replaced an IIR filter with an FIR one)
+ in a quest for improved loop stability (Bug not logged at SF, but
+ cited in private communication from Jeff Hobbs).
+
2003-04-11 Don Porter <dgp@users.sourceforge.net>
* generic/tclCmdMZ.c (Tcl_StringObjCmd,STR_IS_INT): Corrected
diff --git a/tests/clock.test b/tests/clock.test
index 745c095..3b2ad27 100644
--- a/tests/clock.test
+++ b/tests/clock.test
@@ -10,7 +10,7 @@
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
-# RCS: @(#) $Id: clock.test,v 1.22 2003/02/01 21:27:55 kennykb Exp $
+# RCS: @(#) $Id: clock.test,v 1.22.2.1 2003/04/12 20:11:34 kennykb Exp $
set env(LC_TIME) POSIX
@@ -207,7 +207,7 @@ test clock-4.18 {clock scan, ISO 8601 point in time format} {
# We use 5am PST, 31-12-1999 as the base for these scans because irrespective
# of your local timezone it should always give us times on December 31, 1999
set 5amPST 946645200
-test clock-4.18 {clock scan, number meridian} {
+test clock-4.19 {clock scan, number meridian} {
set t1 [clock scan "5 am" -base $5amPST -gmt true]
set t2 [clock scan "5 pm" -base $5amPST -gmt true]
set t3 [clock scan "5 a.m." -base $5amPST -gmt true]
@@ -219,86 +219,86 @@ test clock-4.18 {clock scan, number meridian} {
[clock format $t4 -format {%b %d, %Y %H:%M:%S} -gmt true]
} [list "Dec 31, 1999 05:00:00" "Dec 31, 1999 17:00:00" \
"Dec 31, 1999 05:00:00" "Dec 31, 1999 17:00:00"]
-test clock-4.19 {clock scan, number:number meridian} {
+test clock-4.20 {clock scan, number:number meridian} {
clock format [clock scan "5:30 pm" -base $5amPST -gmt true] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Dec 31, 1999 17:30:00"
-test clock-4.20 {clock scan, number:number-timezone} {
+test clock-4.21 {clock scan, number:number-timezone} {
clock format [clock scan "00:00-0800" -gmt true -base $5amPST] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Dec 31, 1999 08:00:00"
-test clock-4.21 {clock scan, number:number:number o_merid} {
+test clock-4.22 {clock scan, number:number:number o_merid} {
clock format [clock scan "8:00:00" -gmt true -base $5amPST] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Dec 31, 1999 08:00:00"
-test clock-4.22 {clock scan, number:number:number o_merid} {
+test clock-4.23 {clock scan, number:number:number o_merid} {
clock format [clock scan "8:00:00 am" -gmt true -base $5amPST] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Dec 31, 1999 08:00:00"
-test clock-4.23 {clock scan, number:number:number o_merid} {
+test clock-4.24 {clock scan, number:number:number o_merid} {
clock format [clock scan "8:00:00 pm" -gmt true -base $5amPST] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Dec 31, 1999 20:00:00"
-test clock-4.24 {clock scan, number:number:number-timezone} {
+test clock-4.25 {clock scan, number:number:number-timezone} {
clock format [clock scan "00:00:30-0800" -gmt true -base $5amPST] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Dec 31, 1999 08:00:30"
-test clock-4.25 {clock scan, DST for days} {
+test clock-4.26 {clock scan, DST for days} {
clock scan "tomorrow" -base [clock scan "19991031 00:00:00"]
} [clock scan "19991101 00:00:00"]
-test clock-4.26 {clock scan, DST for days} {
+test clock-4.27 {clock scan, DST for days} {
clock scan "yesterday" -base [clock scan "19991101 00:00:00"]
} [clock scan "19991031 00:00:00"]
-test clock-4.27 {clock scan, day} knownBug {
+test clock-4.28 {clock scan, day} knownBug {
clock format [clock scan "Monday" -gmt true -base 946627200] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Jan 03, 2000 00:00:00"
-test clock-4.28 {clock scan, number/number} {
+test clock-4.29 {clock scan, number/number} {
clock format [clock scan "1/1" -gmt true -base 946627200] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Jan 01, 1999 00:00:00"
-test clock-4.28 {clock scan, number/number} {
+test clock-4.30 {clock scan, number/number} {
clock format [clock scan "1/1/1999" -gmt true -base 946627200] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Jan 01, 1999 00:00:00"
-test clock-4.28 {clock scan, number/number} {
+test clock-4.31 {clock scan, number/number} {
clock format [clock scan "19990101" -gmt true -base 946627200] \
-format {%b %d, %Y %H:%M:%S} -gmt true
} "Jan 01, 1999 00:00:00"
-test clock-4.29 {clock scan, relative minutes} {
+test clock-4.32 {clock scan, relative minutes} {
clock scan "now + 1 minute" -base 946627200
} 946627260
-test clock-4.30 {clock scan, relative minutes} {
+test clock-4.33 {clock scan, relative minutes} {
clock scan "now +1 minute" -base 946627200
} 946627260
-test clock-4.31 {clock scan, relative minutes} {
+test clock-4.34 {clock scan, relative minutes} {
clock scan "now 1 minute" -base 946627200
} 946627260
-test clock-4.32 {clock scan, relative minutes} {
+test clock-4.35 {clock scan, relative minutes} {
clock scan "now - 1 minute" -base 946627200
} 946627140
-test clock-4.33 {clock scan, relative minutes} {
+test clock-4.36 {clock scan, relative minutes} {
clock scan "now -1 minute" -base 946627200
} 946627140
-test clock-4.34 {clock scan, day of week} {
+test clock-4.37 {clock scan, day of week} {
clock format [clock scan "wednesday" -base [clock scan 20000112]] \
-format {%b %d, %Y}
} "Jan 12, 2000"
-test clock-4.35 {clock scan, next day of week} {
+test clock-4.38 {clock scan, next day of week} {
clock format [clock scan "next wednesday" -base [clock scan 20000112]] \
-format {%b %d, %Y}
} "Jan 19, 2000"
-test clock-4.36 {clock scan, day of week} {
+test clock-4.39 {clock scan, day of week} {
clock format [clock scan "thursday" -base [clock scan 20000112]] \
-format {%b %d, %Y}
} "Jan 13, 2000"
-test clock-4.37 {clock scan, next day of week} {
+test clock-4.40 {clock scan, next day of week} {
clock format [clock scan "next thursday" -base [clock scan 20000112]] \
-format {%b %d, %Y}
} "Jan 20, 2000"
# weekday specification and base.
-test clock-4.38 {2nd monday in november} {
+test clock-4.41 {2nd monday in november} {
set res {}
foreach i {91 92 93 94 95 96} {
set nov8th [clock scan 11/8/$i]
@@ -307,7 +307,7 @@ test clock-4.38 {2nd monday in november} {
}
set res
} {1991-11-11 1992-11-09 1993-11-08 1994-11-14 1995-11-13 1996-11-11}
-test clock-4.39 {2nd monday in november (2nd try)} {
+test clock-4.42 {2nd monday in november (2nd try)} {
set res {}
foreach i {91 92 93 94 95 96} {
set nov1th [clock scan 11/1/$i]
@@ -316,7 +316,7 @@ test clock-4.39 {2nd monday in november (2nd try)} {
}
set res
} {1991-11-11 1992-11-09 1993-11-08 1994-11-14 1995-11-13 1996-11-11}
-test clock-4.40 {last monday in november} {
+test clock-4.43 {last monday in november} {
set res {}
foreach i {91 92 93 94 95 96} {
set dec1th [clock scan 12/1/$i]
@@ -326,7 +326,7 @@ test clock-4.40 {last monday in november} {
set res
} {1991-11-25 1992-11-30 1993-11-29 1994-11-28 1995-11-27 1996-11-25}
-test clock-4.40 {2nd monday in november} knownBug {
+test clock-4.44 {2nd monday in november} knownBug {
set res {}
foreach i {91 92 93 94 95 96} {
set nov8th [clock scan 11/8/$i -gmt 1]
@@ -335,7 +335,7 @@ test clock-4.40 {2nd monday in november} knownBug {
}
set res
} {1991-11-11 1992-11-09 1993-11-08 1994-11-14 1995-11-13 1996-11-11}
-test clock-4.41 {2nd monday in november (2nd try)} knownBug {
+test clock-4.45 {2nd monday in november (2nd try)} knownBug {
set res {}
foreach i {91 92 93 94 95 96} {
set nov1th [clock scan 11/1/$i -gmt 1]
@@ -344,7 +344,7 @@ test clock-4.41 {2nd monday in november (2nd try)} knownBug {
}
set res
} {1991-11-11 1992-11-09 1993-11-08 1994-11-14 1995-11-13 1996-11-11}
-test clock-4.40 {last monday in november} knownBug {
+test clock-4.46 {last monday in november} knownBug {
set res {}
foreach i {91 92 93 94 95 96} {
set dec1th [clock scan 12/1/$i -gmt 1]
@@ -353,7 +353,7 @@ test clock-4.40 {last monday in november} knownBug {
}
set res
} {1991-11-25 1992-11-30 1993-11-29 1994-11-28 1995-11-27 1996-11-25}
-test clock-4.41 {ago with multiple relative units} {
+test clock-4.47 {ago with multiple relative units} {
set base [clock scan "12/31/1999 00:00:00"]
set res [clock scan "2 days 2 hours ago" -base $base]
expr {$base - $res}
diff --git a/tests/winTime.test b/tests/winTime.test
index 8645fc8..ea663e5 100644
--- a/tests/winTime.test
+++ b/tests/winTime.test
@@ -10,7 +10,7 @@
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
-# RCS: @(#) $Id: winTime.test,v 1.8 2003/02/27 23:47:01 hobbs Exp $
+# RCS: @(#) $Id: winTime.test,v 1.8.2.1 2003/04/12 20:11:34 kennykb Exp $
if {[lsearch [namespace children] ::tcltest] == -1} {
package require tcltest
@@ -36,24 +36,26 @@ test winTime-1.2 {TclpGetDate} {pcOnly} {
} {1969}
# Next test tries to make sure that the Tcl clock stays in step
-# with the Windows clock. 3000 iterations really isn't enough,
-# but how many does a tester have patience for?
+# with the Windows clock. 30 sec really isn't enough,
+# but how much time does a tester have patience for?
test winTime-2.1 {Synchronization of Tcl and Windows clocks} {testwinclock} {
# May fail due to OS/hardware discrepancies. See:
# http://support.microsoft.com/default.aspx?scid=kb;en-us;274323
set failed {}
set ok 1
- for { set i 0 } { $i < 3000 } { incr i } {
- foreach { sys_sec sys_usec tcl_sec tcl_usec } [testwinclock] {}
+ foreach start_sec [testwinclock] break
+ while { 1 } {
+ foreach { sys_sec sys_usec tcl_sec tcl_usec } [testwinclock] break
set diff [expr { $tcl_sec - $sys_sec
+ 1.0e-6 * ( $tcl_usec - $sys_usec ) }]
- if { abs($diff) > 0.02 } {
+ if { abs($diff) > 0.06 } {
set failed "Tcl clock differs from system clock by $diff sec"
break
} else {
- after 10
+ testwinsleep 1
}
+ if { $sys_sec - $start_sec >= 30 } break
}
set failed
} {}
@@ -61,15 +63,3 @@ test winTime-2.1 {Synchronization of Tcl and Windows clocks} {testwinclock} {
# cleanup
::tcltest::cleanupTests
return
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/win/tclWinTest.c b/win/tclWinTest.c
index 51b72e7..96b1a28 100644
--- a/win/tclWinTest.c
+++ b/win/tclWinTest.c
@@ -8,7 +8,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclWinTest.c,v 1.8 2002/12/17 02:47:39 davygrvy Exp $
+ * RCS: @(#) $Id: tclWinTest.c,v 1.8.2.1 2003/04/12 20:11:34 kennykb Exp $
*/
#define USE_COMPAT_CONST
@@ -27,6 +27,10 @@ static int TestwinclockCmd _ANSI_ARGS_(( ClientData dummy,
Tcl_Interp* interp,
int objc,
Tcl_Obj *CONST objv[] ));
+static int TestwinsleepCmd _ANSI_ARGS_(( ClientData dummy,
+ Tcl_Interp* interp,
+ int objc,
+ Tcl_Obj *CONST objv[] ));
static Tcl_ObjCmdProc TestExceptionCmd;
@@ -61,6 +65,11 @@ TclplatformtestInit(interp)
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
Tcl_CreateObjCommand(interp, "testwinclock", TestwinclockCmd,
(ClientData) 0, (Tcl_CmdDeleteProc *) NULL);
+ Tcl_CreateObjCommand( interp,
+ "testwinsleep",
+ TestwinsleepCmd,
+ (ClientData) 0,
+ (Tcl_CmdDeleteProc *) NULL );
Tcl_CreateObjCommand(interp, "testexcept", TestExceptionCmd, NULL, NULL);
return TCL_OK;
}
@@ -202,7 +211,7 @@ TestvolumetypeCmd(clientData, interp, objc, objv)
/*
*----------------------------------------------------------------------
*
- * TestclockCmd --
+ * TestwinclockCmd --
*
* Command that returns the seconds and microseconds portions of
* the system clock and of the Tcl clock so that they can be
@@ -242,12 +251,15 @@ TestwinclockCmd( ClientData dummy,
FILETIME sysTime; /* System clock */
Tcl_Obj* result; /* Result of the command */
LARGE_INTEGER t1, t2;
+ LARGE_INTEGER p1, p2;
if ( objc != 1 ) {
Tcl_WrongNumArgs( interp, 1, objv, "" );
return TCL_ERROR;
}
+ QueryPerformanceCounter( &p1 );
+
Tcl_GetTime( &tclTime );
GetSystemTimeAsFileTime( &sysTime );
t1.LowPart = posixEpoch.dwLowDateTime;
@@ -256,6 +268,8 @@ TestwinclockCmd( ClientData dummy,
t2.HighPart = sysTime.dwHighDateTime;
t2.QuadPart -= t1.QuadPart;
+ QueryPerformanceCounter( &p2 );
+
result = Tcl_NewObj();
Tcl_ListObjAppendElement
( interp, result, Tcl_NewIntObj( (int) (t2.QuadPart / 10000000 ) ) );
@@ -265,11 +279,59 @@ TestwinclockCmd( ClientData dummy,
Tcl_ListObjAppendElement( interp, result, Tcl_NewIntObj( tclTime.sec ) );
Tcl_ListObjAppendElement( interp, result, Tcl_NewIntObj( tclTime.usec ) );
+ Tcl_ListObjAppendElement( interp, result, Tcl_NewWideIntObj( p1.QuadPart ) );
+ Tcl_ListObjAppendElement( interp, result, Tcl_NewWideIntObj( p2.QuadPart ) );
+
Tcl_SetObjResult( interp, result );
return TCL_OK;
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Testwinsleepcmd --
+ *
+ * Causes this process to wait for the given number of milliseconds
+ * by means of a direct call to Sleep.
+ *
+ * Usage:
+ * testwinsleep <n>
+ *
+ * Parameters:
+ * n - the number of milliseconds to sleep
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Sleeps for the requisite number of milliseconds.
+ *
+ *----------------------------------------------------------------------
+ */
+static int
+TestwinsleepCmd( ClientData clientData,
+ /* Unused */
+ Tcl_Interp* interp,
+ /* Tcl interpreter */
+ int objc,
+ /* Parameter count */
+ Tcl_Obj * CONST * objv )
+ /* Parameter vector */
+{
+ int ms;
+ if ( objc != 2 ) {
+ Tcl_WrongNumArgs( interp, 1, objv, "ms" );
+ return TCL_ERROR;
+ }
+ if ( Tcl_GetIntFromObj( interp, objv[1], &ms ) != TCL_OK ) {
+ return TCL_ERROR;
+ }
+ Sleep( (DWORD) ms );
+ return TCL_OK;
+}
+
/*
*----------------------------------------------------------------------
*
diff --git a/win/tclWinTime.c b/win/tclWinTime.c
index 63da982..ee3995b 100644
--- a/win/tclWinTime.c
+++ b/win/tclWinTime.c
@@ -9,7 +9,7 @@
* 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 2003/02/14 22:16:27 kennykb Exp $
+ * RCS: @(#) $Id: tclWinTime.c,v 1.14.2.1 2003/04/12 20:11:34 kennykb Exp $
*/
#include "tclWinInt.h"
@@ -19,6 +19,11 @@
#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.
*/
@@ -38,13 +43,6 @@ typedef struct ThreadSpecificData {
static Tcl_ThreadDataKey dataKey;
/*
- * Calibration interval for the high-resolution timer, in msec
- */
-
-static CONST unsigned long clockCalibrateWakeupInterval = 10000;
- /* FIXME: 10 s -- should be about 10 min! */
-
-/*
* Data for managing high-resolution timers.
*/
@@ -65,10 +63,14 @@ typedef struct TimeInfo {
* 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.
@@ -79,22 +81,20 @@ typedef struct TimeInfo {
* virtual time is returned to a caller.
*/
- ULARGE_INTEGER lastFileTime;
- LARGE_INTEGER lastCounter;
+ ULARGE_INTEGER fileTimeLastCall;
+ LARGE_INTEGER perfCounterLastCall;
LARGE_INTEGER curCounterFreq;
- /*
- * The next two values are used only in the calibration thread, to track
- * the frequency of the performance counter.
+ /*
+ * Data used in developing the estimate of performance counter
+ * frequency
*/
+ ULONGLONG fileTimeSample[SAMPLES];
+ /* Last 64 samples of system time */
+ LONGLONG perfCounterSample[SAMPLES];
+ /* Last 64 samples of performance counter */
+ int sampleNo; /* Current sample number */
- LONGLONG lastPerfCounter; /* Performance counter the last time
- * that UpdateClockEachSecond was called */
- LONGLONG lastSysTime; /* System clock at the last time
- * that UpdateClockEachSecond was called */
- LONGLONG estPerfCounterFreq;
- /* Current estimate of the counter frequency
- * using the system clock as the standard */
} TimeInfo;
@@ -116,6 +116,10 @@ static TimeInfo timeInfo = {
#endif
0,
0,
+ 0,
+ { 0 },
+ { 0 },
+ 0,
0
};
@@ -129,6 +133,15 @@ 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_((
+ ULONGLONG fileTime,
+ LONGLONG perfCounter,
+ LONGLONG perfFreq
+ ));
+static LONGLONG AccumulateSample _ANSI_ARGS_((
+ LONGLONG perfCounter,
+ ULONGLONG fileTime
+ ));
/*
*----------------------------------------------------------------------
@@ -267,7 +280,7 @@ Tcl_GetTime(timePtr)
TclpInitLock();
if ( !timeInfo.initialized ) {
timeInfo.perfCounterAvailable
- = QueryPerformanceFrequency( &timeInfo.curCounterFreq );
+ = QueryPerformanceFrequency( &timeInfo.nominalFreq );
/*
* Some hardware abstraction layers use the CPU clock
@@ -296,10 +309,10 @@ Tcl_GetTime(timePtr)
if ( timeInfo.perfCounterAvailable
/* The following lines would do an exact match on
* crystal frequency:
- * && timeInfo.curCounterFreq.QuadPart != (LONGLONG) 1193182
- * && timeInfo.curCounterFreq.QuadPart != (LONGLONG) 3579545
+ * && timeInfo.nominalFreq.QuadPart != (LONGLONG) 1193182
+ * && timeInfo.nominalFreq.QuadPart != (LONGLONG) 3579545
*/
- && timeInfo.curCounterFreq.QuadPart > (LONGLONG) 15000000 ) {
+ && timeInfo.nominalFreq.QuadPart > (LONGLONG) 15000000 ) {
timeInfo.perfCounterAvailable = FALSE;
}
@@ -364,26 +377,29 @@ Tcl_GetTime(timePtr)
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. Discard it. See MSDN Knowledge Base article
+ * have jumped forward. (See MSDN Knowledge Base article
* Q274323 for a description of the hardware problem that makes
- * this test necessary.
+ * 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.lastPerfCounter
- < 11 * timeInfo.estPerfCounterFreq / 10 ) {
+ if ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart
+ < 11 * timeInfo.curCounterFreq.QuadPart / 10 ) {
- curFileTime = timeInfo.lastFileTime.QuadPart
- + ( ( curCounter.QuadPart - timeInfo.lastCounter.QuadPart )
+ curFileTime = timeInfo.fileTimeLastCall.QuadPart
+ + ( ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart )
* 10000000 / timeInfo.curCounterFreq.QuadPart );
- timeInfo.lastFileTime.QuadPart = curFileTime;
- timeInfo.lastCounter.QuadPart = curCounter.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 );
}
@@ -734,8 +750,7 @@ ComputeGMT(tp)
* None. This thread embeds an infinite loop.
*
* Side effects:
- * At an interval of clockCalibrateWakeupInterval ms, this thread
- * performs virtual time discipline.
+ * 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
@@ -753,15 +768,14 @@ CalibrationThread( LPVOID arg )
/* Get initial system time and performance counter */
GetSystemTimeAsFileTime( &curFileTime );
- QueryPerformanceCounter( &timeInfo.lastCounter );
+ QueryPerformanceCounter( &timeInfo.perfCounterLastCall );
QueryPerformanceFrequency( &timeInfo.curCounterFreq );
- timeInfo.lastFileTime.LowPart = curFileTime.dwLowDateTime;
- timeInfo.lastFileTime.HighPart = curFileTime.dwHighDateTime;
-
- /* Initialize the working storage for the calibration callback */
+ timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime;
+ timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime;
- timeInfo.lastPerfCounter = timeInfo.lastCounter.QuadPart;
- timeInfo.estPerfCounterFreq = timeInfo.curCounterFreq.QuadPart;
+ ResetCounterSamples( timeInfo.fileTimeLastCall.QuadPart,
+ timeInfo.perfCounterLastCall.QuadPart,
+ timeInfo.curCounterFreq.QuadPart );
/*
* Wake up the calling thread. When it wakes up, it will release the
@@ -815,34 +829,24 @@ UpdateTimeEachSecond()
/* Current value returned from
* QueryPerformanceCounter */
- LONGLONG perfCounterDiff; /* Difference between the current value
- * and the value of 1 second ago */
-
FILETIME curSysTime; /* Current system time */
LARGE_INTEGER curFileTime; /* File time at the time this callback
* was scheduled. */
- LONGLONG fileTimeDiff; /* Elapsed time on the system clock
- * since the last time this procedure
- * was called */
-
- LONGLONG instantFreq; /* Instantaneous estimate of the
- * performance counter frequency */
-
- LONGLONG delta; /* Increment to add to the estimated
- * performance counter frequency in the
- * loop filter */
+ LONGLONG estFreq; /* Estimated perf counter frequency */
- LONGLONG fuzz; /* Tolerance for the perf counter frequency */
+ LONGLONG vt0; /* Tcl time right now */
+ LONGLONG vt1; /* Tcl time one second from now */
- LONGLONG lowBound; /* Lower bound for the frequency assuming
- * 1000 ppm tolerance */
+ LONGLONG tdiff; /* Difference between system clock and
+ * Tcl time. */
- LONGLONG hiBound; /* Upper bound for the frequency */
+ LONGLONG driftFreq; /* Frequency needed to drift virtual time
+ * into step over 1 second */
/*
- * Get current performance counter and system time.
+ * Sample performance counter and system time.
*/
QueryPerformanceCounter( &curPerfCounter );
@@ -853,86 +857,201 @@ UpdateTimeEachSecond()
EnterCriticalSection( &timeInfo.cs );
/*
- * Find out how many ticks of the performance counter and the
- * system clock have elapsed since we got into this procedure.
- * Estimate the current frequency.
+ * 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.
*/
- perfCounterDiff = curPerfCounter.QuadPart - timeInfo.lastPerfCounter;
- timeInfo.lastPerfCounter = curPerfCounter.QuadPart;
- fileTimeDiff = curFileTime.QuadPart - timeInfo.lastSysTime;
- timeInfo.lastSysTime = curFileTime.QuadPart;
- instantFreq = ( 10000000 * perfCounterDiff / fileTimeDiff );
-
/*
- * Consider this a timing glitch if instant frequency varies
- * significantly from the current estimate.
+ * Store the current sample into the circular buffer of samples,
+ * and estimate the performance counter frequency.
*/
- fuzz = timeInfo.estPerfCounterFreq >> 10;
- lowBound = timeInfo.estPerfCounterFreq - fuzz;
- hiBound = timeInfo.estPerfCounterFreq + fuzz;
- if ( instantFreq < lowBound || instantFreq > hiBound ) {
- LeaveCriticalSection( &timeInfo.cs );
- return;
- }
+ estFreq = AccumulateSample( curPerfCounter.QuadPart,
+ curFileTime.QuadPart );
/*
- * Update the current estimate of performance counter frequency.
- * This code is equivalent to the loop filter of a phase locked
- * loop.
+ * 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 )
*/
-
- delta = ( instantFreq - timeInfo.estPerfCounterFreq ) >> 6;
- timeInfo.estPerfCounterFreq += delta;
+
+ vt0 = 10000000 * ( curPerfCounter.QuadPart
+ - timeInfo.perfCounterLastCall.QuadPart )
+ / timeInfo.curCounterFreq.QuadPart
+ + timeInfo.fileTimeLastCall.QuadPart;
+ vt1 = 20000000 + curFileTime.QuadPart;
/*
- * Update the current virtual time.
+ * 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.
*/
- timeInfo.lastFileTime.QuadPart
- += ( ( curPerfCounter.QuadPart - timeInfo.lastCounter.QuadPart )
- * 10000000 / timeInfo.curCounterFreq.QuadPart );
- timeInfo.lastCounter.QuadPart = curPerfCounter.QuadPart;
+ 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;
+ }
- delta = curFileTime.QuadPart - timeInfo.lastFileTime.QuadPart;
- if ( delta > 10000000 || delta < -10000000 ) {
+ timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart;
- /*
- * If the virtual time slip exceeds one second, then adjusting
- * the counter frequency is hopeless (it'll take over fifteen
- * minutes to line up with the system clock). The most likely
- * cause of this large a slip is a sudden change to the system
- * clock, perhaps because it was being corrected by wristwatch
- * and eyeball. Accept the system time, and set the performance
- * counter frequency to the current estimate.
- */
+ LeaveCriticalSection( &timeInfo.cs );
+
+}
- timeInfo.lastFileTime.QuadPart = curFileTime.QuadPart;
- timeInfo.curCounterFreq.QuadPart = timeInfo.estPerfCounterFreq;
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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( ULONGLONG fileTime,
+ /* Current file time */
+ LONGLONG perfCounter,
+ /* Current performance counter */
+ LONGLONG 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 LONGLONG
+AccumulateSample( LONGLONG perfCounter,
+ ULONGLONG fileTime )
+{
+ ULONGLONG workFTSample; /* File time sample being removed
+ * from or added to the circular buffer */
+
+ LONGLONG workPCSample; /* Performance counter sample being
+ * removed from or added to the circular
+ * buffer */
+
+ ULONGLONG lastFTSample; /* Last file time sample recorded */
+
+ LONGLONG lastPCSample; /* Last performance counter sample recorded */
+
+ LONGLONG FTdiff; /* Difference between last FT and current */
+
+ LONGLONG PCdiff; /* Difference between last PC and current */
+
+ LONGLONG 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;
- /*
- * Compute a counter frequency that will cause virtual time to line
- * up with system time one second from now, assuming that the
- * performance counter continues to tick at timeInfo.estPerfCounterFreq.
- */
+ } else {
+
+ /* Estimate the frequency */
- timeInfo.curCounterFreq.QuadPart
- = 10000000 * timeInfo.estPerfCounterFreq / ( delta + 10000000 );
-
- /*
- * Limit frequency excursions to 1000 ppm from estimate
- */
+ 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 ] = (LONGLONG) fileTime;
- if ( timeInfo.curCounterFreq.QuadPart < lowBound ) {
- timeInfo.curCounterFreq.QuadPart = lowBound;
- } else if ( timeInfo.curCounterFreq.QuadPart > hiBound ) {
- timeInfo.curCounterFreq.QuadPart = hiBound;
- }
+ /* Advance the sample number */
+
+ if ( ++timeInfo.sampleNo >= SAMPLES ) {
+ timeInfo.sampleNo = 0;
+ }
+
+ return estFreq;
}
-
- LeaveCriticalSection( &timeInfo.cs );
-
}