From d9d5ceb548007b7defbb5645b67360bab19d188a Mon Sep 17 00:00:00 2001 From: Kevin B Kenny Date: Sat, 12 Apr 2003 19:08:33 +0000 Subject: Implemented TIP #124 (clock clicks -microseconds and Tcl_WideInt return values). Fixed Bug 710310 (duplicate test numbers in clock.test). Made major changes to tclWinTime.c and related code to improve loop filter stability. --- ChangeLog | 15 +++ doc/clock.n | 37 ++++-- generic/tclClock.c | 48 +++---- tests/clock.test | 107 +++++++++++----- tests/winTime.test | 28 ++--- win/tclWinTest.c | 66 +++++++++- win/tclWinTime.c | 361 +++++++++++++++++++++++++++++++++++------------------ 7 files changed, 456 insertions(+), 206 deletions(-) diff --git a/ChangeLog b/ChangeLog index d77af36..e574e0b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +2003-04-12 Kevin Kenny + + * doc/clock.n: + * generic/tclClock.c (Tcl_ClockObjCmd): + * tests/clock.test: Implementation of TIP #124. Also 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 * generic/tclCmdMZ.c (Tcl_StringObjCmd,STR_IS_INT): Corrected diff --git a/doc/clock.n b/doc/clock.n index e609e3c..0823c0e 100644 --- a/doc/clock.n +++ b/doc/clock.n @@ -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.n,v 1.11 2002/04/22 22:41:46 hobbs Exp $ +'\" RCS: @(#) $Id: clock.n,v 1.12 2003/04/12 19:08:54 kennykb Exp $ '\" .so man.macros .TH clock n 8.4 Tcl "Tcl Built-In Commands" @@ -31,16 +31,35 @@ out by the command. The legal \fIoptions\fR (which may be abbreviated) are: .TP .VS 8.3 -\fBclock clicks\fR ?\fB\-milliseconds\fR? -Return a high-resolution time value as a system-dependent integer -value. The unit of the value is system-dependent but should be the -highest resolution clock available on the system such as a CPU cycle -counter. If \fB\-milliseconds\fR is specified, then the value is -guaranteed to be of millisecond granularity. -This value should only be used for the relative measurement -of elapsed time. +\fBclock clicks\fR ?\fI\-option\fR? +If no \fIoption\fR argument is supplied, returns a high-resolution +time value as a system-dependent integer value. The unit of the value +is system-dependent but should be the highest resolution clock available +on the system such as a CPU cycle counter. .VE 8.3 .TP +.VS 8.5 +If the \fIoption\fR argument is \fB\-milliseconds\fR, then the value is +guaranteed to be an approximate count of milliseconds returned as +a wide integer; the rule should +always hold that \fBclock clicks -milliseconds\fR divided by 1000 is the +same as \fBclock seconds\fR. +.TP +It the \fIoption\fR argument is \fB-microseconds\fR, then the value is +guaranteed to be an approximate count of microseconds returned as a wide +integer; the rule should hold that \fBclock clicks -microseconds\fR +divided by 1000 is the same as \fBclock clicks -milliseconds\fR. +.TP +On some hardware, the counts of milliseconds and microseconds may diverge +from the system clock for short periods; the reason is that they can +be derived from different sources, and a complex procedure is required +to calibrate them. Moreover, Tcl makes an effort never to have the clock +leap forward nor appear to run backward, preferring instead to slow or +speed up the clock frequency slightly until it's back in synchronization. +For this reason, most Tcl programmers need never worry about such +phenomena as leap seconds. +.VE 8.5 +.TP \fBclock format \fIclockValue\fR ?\fB\-format \fIstring\fR? ?\fB\-gmt \fIboolean\fR? Converts an integer time value, typically returned by \fBclock seconds\fR, \fBclock scan\fR, or the \fBatime\fR, \fBmtime\fR, diff --git a/generic/tclClock.c b/generic/tclClock.c index 55c3bab..d9f6a1e 100644 --- a/generic/tclClock.c +++ b/generic/tclClock.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclClock.c,v 1.20 2003/02/01 21:27:55 kennykb Exp $ + * RCS: @(#) $Id: tclClock.c,v 1.21 2003/04/12 19:08:54 kennykb Exp $ */ #include "tcl.h" @@ -61,18 +61,21 @@ Tcl_ClockObjCmd (client, interp, objc, objv) Tcl_Obj *CONST *objPtr; int useGMT = 0; char *format = "%a %b %d %X %Z %Y"; + int clickType = 2; int dummy; unsigned long baseClock, clockVal; long zone; Tcl_Obj *baseObjPtr = NULL; char *scanStr; - int n; + Tcl_Time now; /* Current time */ static CONST char *switches[] = {"clicks", "format", "scan", "seconds", (char *) NULL}; enum command { COMMAND_CLICKS, COMMAND_FORMAT, COMMAND_SCAN, COMMAND_SECONDS }; + static CONST char *clicksSwitches[] = {"-milliseconds", "-microseconds", + (char*) NULL}; static CONST char *formatSwitches[] = {"-format", "-gmt", (char *) NULL}; static CONST char *scanSwitches[] = {"-base", "-gmt", (char *) NULL}; @@ -88,35 +91,36 @@ Tcl_ClockObjCmd (client, interp, objc, objv) } switch ((enum command) index) { case COMMAND_CLICKS: { /* clicks */ - int forceMilli = 0; if (objc == 3) { - format = Tcl_GetStringFromObj(objv[2], &n); - if ( ( n >= 2 ) - && ( strncmp( format, "-milliseconds", - (unsigned int) n) == 0 ) ) { - forceMilli = 1; - } else { - Tcl_AppendStringsToObj(resultPtr, - "bad switch \"", format, - "\": must be -milliseconds", (char *) NULL); + if ( Tcl_GetIndexFromObj( interp, objv[2], clicksSwitches, + "option", 0, &clickType ) + != TCL_OK ) { return TCL_ERROR; } } else if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, "?-milliseconds?"); return TCL_ERROR; } - if (forceMilli) { - /* - * We can enforce at least millisecond granularity - */ - Tcl_Time time; - Tcl_GetTime(&time); - Tcl_SetLongObj(resultPtr, - (long) (time.sec*1000 + time.usec/1000)); - } else { - Tcl_SetLongObj(resultPtr, (long) TclpGetClicks()); + switch ( clickType ) { + case 0: /* milliseconds */ + Tcl_GetTime( &now ); + Tcl_SetWideIntObj( resultPtr, + ( (Tcl_WideInt) now.sec * 1000 + + now.usec / 1000 ) ); + break; + case 1: /* microseconds */ + Tcl_GetTime( &now ); + Tcl_SetWideIntObj( resultPtr, + ( (Tcl_WideInt) now.sec * 1000000 + + now.usec ) ); + break; + case 2: /* native clicks */ + Tcl_SetWideIntObj( resultPtr, + TclpGetClicks() ); + break; } + return TCL_OK; } diff --git a/tests/clock.test b/tests/clock.test index 745c095..a78f58f 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.23 2003/04/12 19:08:55 kennykb Exp $ set env(LC_TIME) POSIX @@ -39,7 +39,7 @@ test clock-2.2 {clock clicks tests} { } {1} test clock-2.3 {clock clicks tests} { list [catch {clock clicks foo} msg] $msg -} {1 {bad switch "foo": must be -milliseconds}} +} {1 {bad option "foo": must be -milliseconds or -microseconds}} test clock-2.4 {clock clicks tests} { expr [clock clicks -milliseconds]+1 concat {} @@ -53,10 +53,51 @@ test clock-2.5 {clock clicks tests, millisecond timing test} { } {1} test clock-2.6 {clock clicks, milli with too much abbreviation} { list [catch { clock clicks {} } msg] $msg -} {1 {bad switch "": must be -milliseconds}} +} {1 {bad option "": must be -milliseconds or -microseconds}} test clock-2.7 {clock clicks, milli with too much abbreviation} { list [catch { clock clicks - } msg] $msg -} {1 {bad switch "-": must be -milliseconds}} +} {1 {ambiguous option "-": must be -milliseconds or -microseconds}} + +test clock-2.8 {clock clicks test, microsecond timing test} { + set start [clock clicks -micro] + after 10 + set end [clock clicks -micro] + expr {($end > $start) && (($end - $start) <= 60000)} +} {1} + +test clock-2.9 {clock clicks test, millis align with seconds} { + set t1 [clock seconds] + while { 1 } { + set t2 [clock clicks -millis] + set t3 [clock seconds] + if { $t3 == $t1 } break + set t1 $t3 + } + expr { $t2 / 1000 == $t3 } +} {1} + +test clock-2.10 {clock clicks test, micros align with seconds} { + set t1 [clock seconds] + while { 1 } { + set t2 [clock clicks -micros] + set t3 [clock seconds] + if { $t3 == $t1 } break + set t1 $t3 + } + expr { $t2 / 1000000 == $t3 } +} {1} + +test clock-2.11 {clock clicks test, millis align with micros} { + set t1 [clock clicks -millis] + while { 1 } { + set t2 [clock clicks -micros] + set t3 [clock clicks -millis] + if { $t3 == $t1 } break + set t1 $t3 + } + expr { $t2 / 1000 == $t3 } +} {1} + # clock format test clock-3.1 {clock format tests} {unixOnly} { @@ -207,7 +248,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 +260,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 +348,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 +357,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 +367,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 +376,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 +385,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 +394,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..787fb58 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.9 2003/04/12 19:08:55 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..63775f3 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.9 2003/04/12 19:08:55 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 + * + * 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..5e3b66b 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.15 2003/04/12 19:08:56 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 ); - } -- cgit v0.12