summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog15
-rw-r--r--doc/clock.n37
-rw-r--r--generic/tclClock.c48
-rw-r--r--tests/clock.test107
-rw-r--r--tests/winTime.test28
-rw-r--r--win/tclWinTest.c66
-rw-r--r--win/tclWinTime.c361
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 <kennykb@acm.org>
+
+ * 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 <dgp@users.sourceforge.net>
* 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 <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..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 );
-
}