1 /*
2 * tclWinTime.c --
3 *
4 * Contains Windows specific versions of Tcl functions that
5 * obtain time values from the operating system.
6 *
7 * Copyright 1995-1998 by Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id: tclWinTime.c,v 1.14.2.2 2003/04/15 21:06:36 kennykb Exp $
13 */
14
15 #include <windows.h>
16 #include "Lang.h"
17 #include <sys/timeb.h>
18 #include "tkWin.h"
19
20 #if defined(TCL_EVENT_IMPLEMENT)
21
22 #define SECSPERDAY (60L * 60L * 24L)
23 #define SECSPERYEAR (SECSPERDAY * 365L)
24 #define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY)
25
26 /*
27 * Number of samples over which to estimate the performance counter
28 */
29 #define SAMPLES 64
30
31 /*
32 * The following arrays contain the day of year for the last day of
33 * each month, where index 1 is January.
34 */
35
36 static int normalDays[] = {
37 -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364
38 };
39
40 static int leapDays[] = {
41 -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
42 };
43
44 typedef struct ThreadSpecificData {
45 char tzName[64]; /* Time zone name */
46 struct tm tm; /* time information */
47 } ThreadSpecificData;
48 static Tcl_ThreadDataKey dataKey;
49
50 /*
51 * Data for managing high-resolution timers.
52 */
53
54 typedef struct TimeInfo {
55
56 CRITICAL_SECTION cs; /* Mutex guarding this structure */
57
58 int initialized; /* Flag == 1 if this structure is
59 * initialized. */
60
61 int perfCounterAvailable; /* Flag == 1 if the hardware has a
62 * performance counter */
63
64 HANDLE calibrationThread; /* Handle to the thread that keeps the
65 * virtual clock calibrated. */
66
67 HANDLE readyEvent; /* System event used to
68 * trigger the requesting thread
69 * when the clock calibration procedure
70 * is initialized for the first time */
71
72 HANDLE exitEvent; /* Event to signal out of an exit handler
73 * to tell the calibration loop to
74 * terminate */
75
76 LARGE_INTEGER nominalFreq; /* Nominal frequency of the system
77 * performance counter, that is, the value
78 * returned from QueryPerformanceFrequency. */
79
80 /*
81 * The following values are used for calculating virtual time.
82 * Virtual time is always equal to:
83 * lastFileTime + (current perf counter - lastCounter)
84 * * 10000000 / curCounterFreq
85 * and lastFileTime and lastCounter are updated any time that
86 * virtual time is returned to a caller.
87 */
88
89 ULARGE_INTEGER fileTimeLastCall;
90 LARGE_INTEGER perfCounterLastCall;
91 LARGE_INTEGER curCounterFreq;
92
93 /*
94 * Data used in developing the estimate of performance counter
95 * frequency
96 */
97 Tcl_WideUInt fileTimeSample[SAMPLES];
98 /* Last 64 samples of system time */
99 Tcl_WideInt perfCounterSample[SAMPLES];
100 /* Last 64 samples of performance counter */
101 int sampleNo; /* Current sample number */
102
103
104 } TimeInfo;
105
106 static TimeInfo timeInfo = {
107 { NULL },
108 0,
109 0,
110 (HANDLE) NULL,
111 (HANDLE) NULL,
112 (HANDLE) NULL,
113 #ifdef HAVE_CAST_TO_UNION
114 (LARGE_INTEGER) (Tcl_WideInt) 0,
115 (ULARGE_INTEGER) (DWORDLONG) 0,
116 (LARGE_INTEGER) (Tcl_WideInt) 0,
117 (LARGE_INTEGER) (Tcl_WideInt) 0,
118 #else
119 0,
120 0,
121 0,
122 0,
123 #endif
124 { 0 },
125 { 0 },
126 0
127 };
128
129 CONST static FILETIME posixEpoch = { 0xD53E8000, 0x019DB1DE };
130
131 /*
132 * Declarations for functions defined later in this file.
133 */
134
135 static struct tm * ComputeGMT _ANSI_ARGS_((const time_t *tp));
136 static void StopCalibration _ANSI_ARGS_(( ClientData ));
137 static DWORD WINAPI CalibrationThread _ANSI_ARGS_(( LPVOID arg ));
138 static void UpdateTimeEachSecond _ANSI_ARGS_(( void ));
139 static void ResetCounterSamples _ANSI_ARGS_((
140 Tcl_WideUInt fileTime,
141 Tcl_WideInt perfCounter,
142 Tcl_WideInt perfFreq
143 ));
144 static Tcl_WideInt AccumulateSample _ANSI_ARGS_((
145 Tcl_WideInt perfCounter,
146 Tcl_WideUInt fileTime
147 ));
148
149 /*
150 *----------------------------------------------------------------------
151 *
152 * TclpGetSeconds --
153 *
154 * This procedure returns the number of seconds from the epoch.
155 * On most Unix systems the epoch is Midnight Jan 1, 1970 GMT.
156 *
157 * Results:
158 * Number of seconds from the epoch.
159 *
160 * Side effects:
161 * None.
162 *
163 *----------------------------------------------------------------------
164 */
165
166 unsigned long
TclpGetSeconds()167 TclpGetSeconds()
168 {
169 Tcl_Time t;
170 Tcl_GetTime( &t );
171 return t.sec;
172 }
173
174 /*
175 *----------------------------------------------------------------------
176 *
177 * TclpGetClicks --
178 *
179 * This procedure returns a value that represents the highest
180 * resolution clock available on the system. There are no
181 * guarantees on what the resolution will be. In Tcl we will
182 * call this value a "click". The start time is also system
183 * dependant.
184 *
185 * Results:
186 * Number of clicks from some start time.
187 *
188 * Side effects:
189 * None.
190 *
191 *----------------------------------------------------------------------
192 */
193
194 unsigned long
TclpGetClicks()195 TclpGetClicks()
196 {
197 /*
198 * Use the Tcl_GetTime abstraction to get the time in microseconds,
199 * as nearly as we can, and return it.
200 */
201
202 Tcl_Time now; /* Current Tcl time */
203 unsigned long retval; /* Value to return */
204
205 Tcl_GetTime( &now );
206 retval = ( now.sec * 1000000 ) + now.usec;
207 return retval;
208
209 }
210
211 #ifndef _LANG
212 /*
213 *----------------------------------------------------------------------
214 *
215 * TclpGetTimeZone --
216 *
217 * Determines the current timezone. The method varies wildly
218 * between different Platform implementations, so its hidden in
219 * this function.
220 *
221 * Results:
222 * Minutes west of GMT.
223 *
224 * Side effects:
225 * None.
226 *
227 *----------------------------------------------------------------------
228 */
229
230 int
TclpGetTimeZone(currentTime)231 TclpGetTimeZone (currentTime)
232 unsigned long currentTime;
233 {
234 int timeZone;
235
236 tzset();
237 timeZone = _timezone / 60;
238
239 return timeZone;
240 }
241
242 #endif /* _LANG */
243 /*
244 *----------------------------------------------------------------------
245 *
246 * Tcl_GetTime --
247 *
248 * Gets the current system time in seconds and microseconds
249 * since the beginning of the epoch: 00:00 UCT, January 1, 1970.
250 *
251 * Results:
252 * Returns the current time in timePtr.
253 *
254 * Side effects:
255 * On the first call, initializes a set of static variables to
256 * keep track of the base value of the performance counter, the
257 * corresponding wall clock (obtained through ftime) and the
258 * frequency of the performance counter. Also spins a thread
259 * whose function is to wake up periodically and monitor these
260 * values, adjusting them as necessary to correct for drift
261 * in the performance counter's oscillator.
262 *
263 *----------------------------------------------------------------------
264 */
265
266 void
Tcl_GetTime(timePtr)267 Tcl_GetTime(timePtr)
268 Tcl_Time *timePtr; /* Location to store time information. */
269 {
270
271 struct timeb t;
272
273 int useFtime = 1; /* Flag == TRUE if we need to fall back
274 * on ftime rather than using the perf
275 * counter */
276
277 /* Initialize static storage on the first trip through. */
278
279 /*
280 * Note: Outer check for 'initialized' is a performance win
281 * since it avoids an extra mutex lock in the common case.
282 */
283
284 if ( !timeInfo.initialized ) {
285 TclpInitLock();
286 if ( !timeInfo.initialized ) {
287 timeInfo.perfCounterAvailable
288 = QueryPerformanceFrequency( &timeInfo.nominalFreq );
289
290 /*
291 * Some hardware abstraction layers use the CPU clock
292 * in place of the real-time clock as a performance counter
293 * reference. This results in:
294 * - inconsistent results among the processors on
295 * multi-processor systems.
296 * - unpredictable changes in performance counter frequency
297 * on "gearshift" processors such as Transmeta and
298 * SpeedStep.
299 *
300 * There seems to be no way to test whether the performance
301 * counter is reliable, but a useful heuristic is that
302 * if its frequency is 1.193182 MHz or 3.579545 MHz, it's
303 * derived from a colorburst crystal and is therefore
304 * the RTC rather than the TSC.
305 *
306 * A sloppier but serviceable heuristic is that the RTC crystal
307 * is normally less than 15 MHz while the TSC crystal is
308 * virtually assured to be greater than 100 MHz. Since Win98SE
309 * appears to fiddle with the definition of the perf counter
310 * frequency (perhaps in an attempt to calibrate the clock?)
311 * we use the latter rule rather than an exact match.
312 */
313
314 if ( timeInfo.perfCounterAvailable
315 /* The following lines would do an exact match on
316 * crystal frequency:
317 * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 1193182
318 * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt) 3579545
319 */
320 && timeInfo.nominalFreq.QuadPart > (Tcl_WideInt) 15000000 ) {
321 timeInfo.perfCounterAvailable = FALSE;
322 }
323
324 /*
325 * If the performance counter is available, start a thread to
326 * calibrate it.
327 */
328
329 if ( timeInfo.perfCounterAvailable ) {
330 DWORD id;
331 InitializeCriticalSection( &timeInfo.cs );
332 timeInfo.readyEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
333 timeInfo.exitEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
334 timeInfo.calibrationThread = CreateThread( NULL,
335 256,
336 CalibrationThread,
337 (LPVOID) NULL,
338 0,
339 &id );
340 SetThreadPriority( timeInfo.calibrationThread,
341 THREAD_PRIORITY_HIGHEST );
342
343 /*
344 * Wait for the thread just launched to start running,
345 * and create an exit handler that kills it so that it
346 * doesn't outlive unloading tclXX.dll
347 */
348
349 WaitForSingleObject( timeInfo.readyEvent, INFINITE );
350 CloseHandle( timeInfo.readyEvent );
351 Tcl_CreateExitHandler( StopCalibration, (ClientData) NULL );
352 }
353 timeInfo.initialized = TRUE;
354 }
355 TclpInitUnlock();
356 }
357
358 if ( timeInfo.perfCounterAvailable ) {
359
360 /*
361 * Query the performance counter and use it to calculate the
362 * current time.
363 */
364
365 LARGE_INTEGER curCounter;
366 /* Current performance counter */
367
368 Tcl_WideInt curFileTime;
369 /* Current estimated time, expressed
370 * as 100-ns ticks since the Windows epoch */
371
372 static LARGE_INTEGER posixEpoch;
373 /* Posix epoch expressed as 100-ns ticks
374 * since the windows epoch */
375
376 Tcl_WideInt usecSincePosixEpoch;
377 /* Current microseconds since Posix epoch */
378
379 posixEpoch.LowPart = 0xD53E8000;
380 posixEpoch.HighPart = 0x019DB1DE;
381
382 EnterCriticalSection( &timeInfo.cs );
383
384 QueryPerformanceCounter( &curCounter );
385
386 /*
387 * If it appears to be more than 1.1 seconds since the last trip
388 * through the calibration loop, the performance counter may
389 * have jumped forward. (See MSDN Knowledge Base article
390 * Q274323 for a description of the hardware problem that makes
391 * this test necessary.) If the counter jumps, we don't want
392 * to use it directly. Instead, we must return system time.
393 * Eventually, the calibration loop should recover.
394 */
395 if ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart
396 < 11 * timeInfo.curCounterFreq.QuadPart / 10 ) {
397
398 curFileTime = timeInfo.fileTimeLastCall.QuadPart
399 + ( ( curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart )
400 * 10000000 / timeInfo.curCounterFreq.QuadPart );
401 timeInfo.fileTimeLastCall.QuadPart = curFileTime;
402 timeInfo.perfCounterLastCall.QuadPart = curCounter.QuadPart;
403 usecSincePosixEpoch = ( curFileTime - posixEpoch.QuadPart ) / 10;
404 timePtr->sec = (time_t) ( usecSincePosixEpoch / 1000000 );
405 timePtr->usec = (unsigned long ) ( usecSincePosixEpoch % 1000000 );
406 useFtime = 0;
407 }
408
409 LeaveCriticalSection( &timeInfo.cs );
410 }
411
412 if ( useFtime ) {
413
414 /* High resolution timer is not available. Just use ftime */
415
416 ftime(&t);
417 timePtr->sec = t.time;
418 timePtr->usec = t.millitm * 1000;
419 }
420 }
421
422 /*
423 *----------------------------------------------------------------------
424 *
425 * StopCalibration --
426 *
427 * Turns off the calibration thread in preparation for exiting the
428 * process.
429 *
430 * Results:
431 * None.
432 *
433 * Side effects:
434 * Sets the 'exitEvent' event in the 'timeInfo' structure to ask
435 * the thread in question to exit, and waits for it to do so.
436 *
437 *----------------------------------------------------------------------
438 */
439
440 static void
StopCalibration(ClientData unused)441 StopCalibration( ClientData unused )
442 /* Client data is unused */
443 {
444 SetEvent( timeInfo.exitEvent );
445 WaitForSingleObject( timeInfo.calibrationThread, INFINITE );
446 CloseHandle( timeInfo.exitEvent );
447 CloseHandle( timeInfo.calibrationThread );
448 }
449
450 /*
451 *----------------------------------------------------------------------
452 *
453 * TclpGetTZName --
454 *
455 * Gets the current timezone string.
456 *
457 * Results:
458 * Returns a pointer to a static string, or NULL on failure.
459 *
460 * Side effects:
461 * None.
462 *
463 *----------------------------------------------------------------------
464 */
465
466 #ifndef _LANG
467 char *
TclpGetTZName(int dst)468 TclpGetTZName(int dst)
469 {
470 int len;
471 char *zone, *p;
472 TIME_ZONE_INFORMATION tz;
473 Tcl_Encoding encoding;
474 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
475 char *name = tsdPtr->tzName;
476
477 /*
478 * tzset() under Borland doesn't seem to set up tzname[] at all.
479 * tzset() under MSVC has the following weird observed behavior:
480 * First time we call "clock format [clock seconds] -format %Z -gmt 1"
481 * we get "GMT", but on all subsequent calls we get the current time
482 * zone string, even though env(TZ) is GMT and the variable _timezone
483 * is 0.
484 */
485
486 name[0] = '\0';
487
488 zone = getenv("TZ");
489 if (zone != NULL) {
490 /*
491 * TZ is of form "NST-4:30NDT", where "NST" would be the
492 * name of the standard time zone for this area, "-4:30" is
493 * the offset from GMT in hours, and "NDT is the name of
494 * the daylight savings time zone in this area. The offset
495 * and DST strings are optional.
496 */
497
498 len = strlen(zone);
499 if (len > 3) {
500 len = 3;
501 }
502 if (dst != 0) {
503 /*
504 * Skip the offset string and get the DST string.
505 */
506
507 p = zone + len;
508 p += strspn(p, "+-:0123456789");
509 if (*p != '\0') {
510 zone = p;
511 len = strlen(zone);
512 if (len > 3) {
513 len = 3;
514 }
515 }
516 }
517 Tcl_ExternalToUtf(NULL, NULL, zone, len, 0, NULL, name,
518 sizeof(tsdPtr->tzName), NULL, NULL, NULL);
519 }
520 if (name[0] == '\0') {
521 if (GetTimeZoneInformation(&tz) == TIME_ZONE_ID_UNKNOWN) {
522 /*
523 * MSDN: On NT this is returned if DST is not used in
524 * the current TZ
525 */
526 dst = 0;
527 }
528 encoding = Tcl_GetEncoding(NULL, "unicode");
529 Tcl_ExternalToUtf(NULL, encoding,
530 (char *) ((dst) ? tz.DaylightName : tz.StandardName), -1,
531 0, NULL, name, sizeof(tsdPtr->tzName), NULL, NULL, NULL);
532 Tcl_FreeEncoding(encoding);
533 }
534 return name;
535 }
536 #endif
537
538
539 #ifndef _LANG
540 /*
541 *----------------------------------------------------------------------
542 *
543 * TclpGetDate --
544 *
545 * This function converts between seconds and struct tm. If
546 * useGMT is true, then the returned date will be in Greenwich
547 * Mean Time (GMT). Otherwise, it will be in the local time zone.
548 *
549 * Results:
550 * Returns a static tm structure.
551 *
552 * Side effects:
553 * None.
554 *
555 *----------------------------------------------------------------------
556 */
557
558 struct tm *
TclpGetDate(t,useGMT)559 TclpGetDate(t, useGMT)
560 TclpTime_t t;
561 int useGMT;
562 {
563 const time_t *tp = (const time_t *) t;
564 struct tm *tmPtr;
565 long time;
566
567 if (!useGMT) {
568 tzset();
569
570 /*
571 * If we are in the valid range, let the C run-time library
572 * handle it. Otherwise we need to fake it. Note that this
573 * algorithm ignores daylight savings time before the epoch.
574 */
575
576 if (*tp >= 0) {
577 return localtime(tp);
578 }
579
580 time = *tp - _timezone;
581
582 /*
583 * If we aren't near to overflowing the long, just add the bias and
584 * use the normal calculation. Otherwise we will need to adjust
585 * the result at the end.
586 */
587
588 if (*tp < (LONG_MAX - 2 * SECSPERDAY)
589 && *tp > (LONG_MIN + 2 * SECSPERDAY)) {
590 tmPtr = ComputeGMT(&time);
591 } else {
592 tmPtr = ComputeGMT(tp);
593
594 tzset();
595
596 /*
597 * Add the bias directly to the tm structure to avoid overflow.
598 * Propagate seconds overflow into minutes, hours and days.
599 */
600
601 time = tmPtr->tm_sec - _timezone;
602 tmPtr->tm_sec = (int)(time % 60);
603 if (tmPtr->tm_sec < 0) {
604 tmPtr->tm_sec += 60;
605 time -= 60;
606 }
607
608 time = tmPtr->tm_min + time/60;
609 tmPtr->tm_min = (int)(time % 60);
610 if (tmPtr->tm_min < 0) {
611 tmPtr->tm_min += 60;
612 time -= 60;
613 }
614
615 time = tmPtr->tm_hour + time/60;
616 tmPtr->tm_hour = (int)(time % 24);
617 if (tmPtr->tm_hour < 0) {
618 tmPtr->tm_hour += 24;
619 time -= 24;
620 }
621
622 time /= 24;
623 tmPtr->tm_mday += time;
624 tmPtr->tm_yday += time;
625 tmPtr->tm_wday = (tmPtr->tm_wday + time) % 7;
626 }
627 } else {
628 tmPtr = ComputeGMT(tp);
629 }
630 return tmPtr;
631 }
632
633 #endif
634 /*
635 *----------------------------------------------------------------------
636 *
637 * ComputeGMT --
638 *
639 * This function computes GMT given the number of seconds since
640 * the epoch (midnight Jan 1 1970).
641 *
642 * Results:
643 * Returns a (per thread) statically allocated struct tm.
644 *
645 * Side effects:
646 * Updates the values of the static struct tm.
647 *
648 *----------------------------------------------------------------------
649 */
650
651 static struct tm *
ComputeGMT(tp)652 ComputeGMT(tp)
653 const time_t *tp;
654 {
655 struct tm *tmPtr;
656 long tmp, rem;
657 int isLeap;
658 int *days;
659 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
660
661 tmPtr = &tsdPtr->tm;
662
663 /*
664 * Compute the 4 year span containing the specified time.
665 */
666
667 tmp = *tp / SECSPER4YEAR;
668 rem = *tp % SECSPER4YEAR;
669
670 /*
671 * Correct for weird mod semantics so the remainder is always positive.
672 */
673
674 if (rem < 0) {
675 tmp--;
676 rem += SECSPER4YEAR;
677 }
678
679 /*
680 * Compute the year after 1900 by taking the 4 year span and adjusting
681 * for the remainder. This works because 2000 is a leap year, and
682 * 1900/2100 are out of the range.
683 */
684
685 tmp = (tmp * 4) + 70;
686 isLeap = 0;
687 if (rem >= SECSPERYEAR) { /* 1971, etc. */
688 tmp++;
689 rem -= SECSPERYEAR;
690 if (rem >= SECSPERYEAR) { /* 1972, etc. */
691 tmp++;
692 rem -= SECSPERYEAR;
693 if (rem >= SECSPERYEAR + SECSPERDAY) { /* 1973, etc. */
694 tmp++;
695 rem -= SECSPERYEAR + SECSPERDAY;
696 } else {
697 isLeap = 1;
698 }
699 }
700 }
701 tmPtr->tm_year = tmp;
702
703 /*
704 * Compute the day of year and leave the seconds in the current day in
705 * the remainder.
706 */
707
708 tmPtr->tm_yday = rem / SECSPERDAY;
709 rem %= SECSPERDAY;
710
711 /*
712 * Compute the time of day.
713 */
714
715 tmPtr->tm_hour = rem / 3600;
716 rem %= 3600;
717 tmPtr->tm_min = rem / 60;
718 tmPtr->tm_sec = rem % 60;
719
720 /*
721 * Compute the month and day of month.
722 */
723
724 days = (isLeap) ? leapDays : normalDays;
725 for (tmp = 1; days[tmp] < tmPtr->tm_yday; tmp++) {
726 }
727 tmPtr->tm_mon = --tmp;
728 tmPtr->tm_mday = tmPtr->tm_yday - days[tmp];
729
730 /*
731 * Compute day of week. Epoch started on a Thursday.
732 */
733
734 tmPtr->tm_wday = (*tp / SECSPERDAY) + 4;
735 if ((*tp % SECSPERDAY) < 0) {
736 tmPtr->tm_wday--;
737 }
738 tmPtr->tm_wday %= 7;
739 if (tmPtr->tm_wday < 0) {
740 tmPtr->tm_wday += 7;
741 }
742
743 return tmPtr;
744 }
745
746 /*
747 *----------------------------------------------------------------------
748 *
749 * CalibrationThread --
750 *
751 * Thread that manages calibration of the hi-resolution time
752 * derived from the performance counter, to keep it synchronized
753 * with the system clock.
754 *
755 * Parameters:
756 * arg -- Client data from the CreateThread call. This parameter
757 * points to the static TimeInfo structure.
758 *
759 * Return value:
760 * None. This thread embeds an infinite loop.
761 *
762 * Side effects:
763 * At an interval of 1 s, this thread performs virtual time discipline.
764 *
765 * Note: When this thread is entered, TclpInitLock has been called
766 * to safeguard the static storage. There is therefore no synchronization
767 * in the body of this procedure.
768 *
769 *----------------------------------------------------------------------
770 */
771
772 static DWORD WINAPI
CalibrationThread(LPVOID arg)773 CalibrationThread( LPVOID arg )
774 {
775 FILETIME curFileTime;
776 DWORD waitResult;
777
778 /* Get initial system time and performance counter */
779
780 GetSystemTimeAsFileTime( &curFileTime );
781 QueryPerformanceCounter( &timeInfo.perfCounterLastCall );
782 QueryPerformanceFrequency( &timeInfo.curCounterFreq );
783 timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime;
784 timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime;
785
786 ResetCounterSamples( timeInfo.fileTimeLastCall.QuadPart,
787 timeInfo.perfCounterLastCall.QuadPart,
788 timeInfo.curCounterFreq.QuadPart );
789
790 /*
791 * Wake up the calling thread. When it wakes up, it will release the
792 * initialization lock.
793 */
794
795 SetEvent( timeInfo.readyEvent );
796
797 /* Run the calibration once a second */
798
799 for ( ; ; ) {
800
801 /* If the exitEvent is set, break out of the loop. */
802
803 waitResult = WaitForSingleObjectEx(timeInfo.exitEvent, 1000, FALSE);
804 if ( waitResult == WAIT_OBJECT_0 ) {
805 break;
806 }
807 UpdateTimeEachSecond();
808 }
809
810 /* lint */
811 return (DWORD) 0;
812 }
813
814 /*
815 *----------------------------------------------------------------------
816 *
817 * UpdateTimeEachSecond --
818 *
819 * Callback from the waitable timer in the clock calibration thread
820 * that updates system time.
821 *
822 * Parameters:
823 * info -- Pointer to the static TimeInfo structure
824 *
825 * Results:
826 * None.
827 *
828 * Side effects:
829 * Performs virtual time calibration discipline.
830 *
831 *----------------------------------------------------------------------
832 */
833
834 static void
UpdateTimeEachSecond()835 UpdateTimeEachSecond()
836 {
837
838 LARGE_INTEGER curPerfCounter;
839 /* Current value returned from
840 * QueryPerformanceCounter */
841
842 FILETIME curSysTime; /* Current system time */
843
844 LARGE_INTEGER curFileTime; /* File time at the time this callback
845 * was scheduled. */
846
847 Tcl_WideInt estFreq; /* Estimated perf counter frequency */
848
849 Tcl_WideInt vt0; /* Tcl time right now */
850 Tcl_WideInt vt1; /* Tcl time one second from now */
851
852 Tcl_WideInt tdiff; /* Difference between system clock and
853 * Tcl time. */
854
855 Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time
856 * into step over 1 second */
857
858 /*
859 * Sample performance counter and system time.
860 */
861
862 QueryPerformanceCounter( &curPerfCounter );
863 GetSystemTimeAsFileTime( &curSysTime );
864 curFileTime.LowPart = curSysTime.dwLowDateTime;
865 curFileTime.HighPart = curSysTime.dwHighDateTime;
866
867 EnterCriticalSection( &timeInfo.cs );
868
869 /*
870 * Several things may have gone wrong here that have to
871 * be checked for.
872 * (1) The performance counter may have jumped.
873 * (2) The system clock may have been reset.
874 *
875 * In either case, we'll need to reinitialize the circular buffer
876 * with samples relative to the current system time and the NOMINAL
877 * performance frequency (not the actual, because the actual has
878 * probably run slow in the first case). Our estimated frequency
879 * will be the nominal frequency.
880 */
881
882 /*
883 * Store the current sample into the circular buffer of samples,
884 * and estimate the performance counter frequency.
885 */
886
887 estFreq = AccumulateSample( curPerfCounter.QuadPart,
888 (Tcl_WideUInt) curFileTime.QuadPart );
889
890 /*
891 * We want to adjust things so that time appears to be continuous.
892 * Virtual file time, right now, is
893 *
894 * vt0 = 10000000 * ( curPerfCounter - perfCounterLastCall )
895 * / curCounterFreq
896 * + fileTimeLastCall
897 *
898 * Ideally, we would like to drift the clock into place over a
899 * period of 2 sec, so that virtual time 2 sec from now will be
900 *
901 * vt1 = 20000000 + curFileTime
902 *
903 * The frequency that we need to use to drift the counter back into
904 * place is estFreq * 20000000 / ( vt1 - vt0 )
905 */
906
907 vt0 = 10000000 * ( curPerfCounter.QuadPart
908 - timeInfo.perfCounterLastCall.QuadPart )
909 / timeInfo.curCounterFreq.QuadPart
910 + timeInfo.fileTimeLastCall.QuadPart;
911 vt1 = 20000000 + curFileTime.QuadPart;
912
913 /*
914 * If we've gotten more than a second away from system time,
915 * then drifting the clock is going to be pretty hopeless.
916 * Just let it jump. Otherwise, compute the drift frequency and
917 * fill in everything.
918 */
919
920 tdiff = vt0 - curFileTime.QuadPart;
921 if ( tdiff > 10000000 || tdiff < -10000000 ) {
922 timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart;
923 timeInfo.curCounterFreq.QuadPart = estFreq;
924 } else {
925 driftFreq = estFreq * 20000000 / ( vt1 - vt0 );
926 if ( driftFreq > 1003 * estFreq / 1000 ) {
927 driftFreq = 1003 * estFreq / 1000;
928 }
929 if ( driftFreq < 997 * estFreq / 1000 ) {
930 driftFreq = 997 * estFreq / 1000;
931 }
932 timeInfo.fileTimeLastCall.QuadPart = vt0;
933 timeInfo.curCounterFreq.QuadPart = driftFreq;
934 }
935
936 timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart;
937
938 LeaveCriticalSection( &timeInfo.cs );
939
940 }
941
942 /*
943 *----------------------------------------------------------------------
944 *
945 * ResetCounterSamples --
946 *
947 * Fills the sample arrays in 'timeInfo' with dummy values that will
948 * yield the current performance counter and frequency.
949 *
950 * Results:
951 * None.
952 *
953 * Side effects:
954 * The array of samples is filled in so that it appears that there
955 * are SAMPLES samples at one-second intervals, separated by precisely
956 * the given frequency.
957 *
958 *----------------------------------------------------------------------
959 */
960
961 static void
ResetCounterSamples(Tcl_WideUInt fileTime,Tcl_WideInt perfCounter,Tcl_WideInt perfFreq)962 ResetCounterSamples( Tcl_WideUInt fileTime,
963 /* Current file time */
964 Tcl_WideInt perfCounter,
965 /* Current performance counter */
966 Tcl_WideInt perfFreq )
967 /* Target performance frequency */
968 {
969 int i;
970 for ( i = SAMPLES-1; i >= 0; --i ) {
971 timeInfo.perfCounterSample[i] = perfCounter;
972 timeInfo.fileTimeSample[i] = fileTime;
973 perfCounter -= perfFreq;
974 fileTime -= 10000000;
975 }
976 timeInfo.sampleNo = 0;
977 }
978
979 /*
980 *----------------------------------------------------------------------
981 *
982 * AccumulateSample --
983 *
984 * Updates the circular buffer of performance counter and system
985 * time samples with a new data point.
986 *
987 * Results:
988 * None.
989 *
990 * Side effects:
991 * The new data point replaces the oldest point in the circular
992 * buffer, and the descriptive statistics are updated to accumulate
993 * the new point.
994 *
995 * Several things may have gone wrong here that have to
996 * be checked for.
997 * (1) The performance counter may have jumped.
998 * (2) The system clock may have been reset.
999 *
1000 * In either case, we'll need to reinitialize the circular buffer
1001 * with samples relative to the current system time and the NOMINAL
1002 * performance frequency (not the actual, because the actual has
1003 * probably run slow in the first case).
1004 */
1005
1006 static Tcl_WideInt
AccumulateSample(Tcl_WideInt perfCounter,Tcl_WideUInt fileTime)1007 AccumulateSample( Tcl_WideInt perfCounter,
1008 Tcl_WideUInt fileTime )
1009 {
1010 Tcl_WideUInt workFTSample; /* File time sample being removed
1011 * from or added to the circular buffer */
1012
1013 Tcl_WideInt workPCSample; /* Performance counter sample being
1014 * removed from or added to the circular
1015 * buffer */
1016
1017 Tcl_WideUInt lastFTSample; /* Last file time sample recorded */
1018
1019 Tcl_WideInt lastPCSample; /* Last performance counter sample recorded */
1020
1021 Tcl_WideInt FTdiff; /* Difference between last FT and current */
1022
1023 Tcl_WideInt PCdiff; /* Difference between last PC and current */
1024
1025 Tcl_WideInt estFreq; /* Estimated performance counter frequency */
1026
1027 /* Test for jumps and reset the samples if we have one. */
1028
1029 if ( timeInfo.sampleNo == 0 ) {
1030 lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo
1031 + SAMPLES - 1 ];
1032 lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo
1033 + SAMPLES - 1 ];
1034 } else {
1035 lastPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo - 1 ];
1036 lastFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo - 1 ];
1037 }
1038 PCdiff = perfCounter - lastPCSample;
1039 FTdiff = fileTime - lastFTSample;
1040 if ( PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10
1041 || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10
1042 || FTdiff < 9000000
1043 || FTdiff > 11000000 ) {
1044 ResetCounterSamples( fileTime, perfCounter,
1045 timeInfo.nominalFreq.QuadPart );
1046 return timeInfo.nominalFreq.QuadPart;
1047
1048 } else {
1049
1050 /* Estimate the frequency */
1051
1052 workPCSample = timeInfo.perfCounterSample[ timeInfo.sampleNo ];
1053 workFTSample = timeInfo.fileTimeSample[ timeInfo.sampleNo ];
1054 estFreq = 10000000 * ( perfCounter - workPCSample )
1055 / ( fileTime - workFTSample );
1056 timeInfo.perfCounterSample[ timeInfo.sampleNo ] = perfCounter;
1057 timeInfo.fileTimeSample[ timeInfo.sampleNo ] = (Tcl_WideInt) fileTime;
1058
1059 /* Advance the sample number */
1060
1061 if ( ++timeInfo.sampleNo >= SAMPLES ) {
1062 timeInfo.sampleNo = 0;
1063 }
1064
1065 return estFreq;
1066 }
1067 }
1068
1069 #endif
1070
1071