Panda3D
trueClock.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file trueClock.cxx
10  * @author drose
11  * @date 2000-07-04
12  */
13 
14 #include "trueClock.h"
15 #include "config_express.h"
16 #include "numeric_types.h"
17 
18 #include <math.h> // for fabs()
19 
20 using std::max;
21 using std::min;
22 
23 TrueClock *TrueClock::_global_ptr = nullptr;
24 
25 #if defined(WIN32_VC) || defined(WIN64_VC)
26 
27 // The Win32 implementation.
28 
29 #include <sys/timeb.h>
30 #ifndef WIN32_LEAN_AND_MEAN
31 #define WIN32_LEAN_AND_MEAN 1
32 #endif
33 #include <windows.h>
34 
35 static const double _0001 = 1.0 / 1000.0;
36 static const double _00000001 = 1.0 / 10000000.0;
37 
38 // This is the interval of time, in seconds, over which to measure the high-
39 // precision clock rate vs. the time-of-day rate, when paranoid-clock is in
40 // effect. Reducing it makes the clock respond more quickly to changes in
41 // rate, but setting it too small may introduce erratic behavior, especially
42 // if the user has ntp configured.
43 static const double paranoid_clock_interval = 3.0;
44 
45 // It will be considered a clock jump error if either the high-precision clock
46 // or the time-of-day clock change by this number of seconds without the other
47 // jumping by a similar amount.
48 static const double paranoid_clock_jump_error = 2.0;
49 
50 // If the we detect a clock jump error but the corrected clock skew is
51 // currently more than this amount, we hack the clock scale to try to
52 // compensate.
53 static const double paranoid_clock_jump_error_max_delta = 1.0;
54 
55 // If the measured time_scale appears to change by more than this factor, it
56 // will be reported to the log. Changes to time_scale less than this factor
57 // are assumed to be within the margin of error.
58 static const double paranoid_clock_report_scale_factor = 0.1;
59 
60 // If the high-precision clock, after applying time_scale correction, is still
61 // more than this number of seconds above or below the time-of-day clock, it
62 // will be sped up or slowed down slightly until it is back in sync.
63 static const double paranoid_clock_chase_threshold = 0.5;
64 
65 // This is the minimum factor by which the high-precision clock will be sped
66 // up or slowed down when it gets out of sync by paranoid-clock-chase-
67 // threshold.
68 static const double paranoid_clock_chase_factor = 0.1;
69 
70 /**
71  *
72  */
73 double TrueClock::
74 get_long_time() {
75  int tc = GetTickCount();
76  return (double)(tc - _init_tc) * _0001;
77 }
78 
79 /**
80  *
81  */
82 double TrueClock::
83 get_short_raw_time() {
84  double time;
85 
86  if (_has_high_res) {
87 /*
88  * Use the high-resolution clock. This is of questionable value, since (a) on
89  * some OS's and hardware, the low 24 bits can occasionally roll over without
90  * setting the carry bit, causing the time to jump backwards, and (b)
91  * reportedly it can set the carry bit incorrectly sometimes, causing the time
92  * to jump forwards, and (c) even when it doesn't do that, it's not very
93  * accurate and seems to lose seconds of time per hour, and (d) someone could
94  * be running a program such as Speed Gear which munges this value anyway.
95  */
96  int64_t count;
97  QueryPerformanceCounter((LARGE_INTEGER *)&count);
98 
99  time = (double)(count - _init_count) * _recip_frequency;
100 
101  } else {
102  // No high-resolution clock; return the best information we have. This
103  // doesn't suffer from the rollover problems that QueryPerformanceCounter
104  // does, but it's not very precise--only precise to 50ms on Win98, and
105  // 10ms on XP-based systems--and Speed Gear still munges it.
106  int tc = GetTickCount();
107  time = (double)(tc - _init_tc) * _0001;
108  }
109 
110  return time;
111 }
112 
113 /**
114  *
115  */
116 typedef BOOL (WINAPI * PFNSETPROCESSAFFINITYMASK)(HANDLE, DWORD_PTR);
117 typedef BOOL (WINAPI * PFNGETPROCESSAFFINITYMASK)(HANDLE, DWORD_PTR*, DWORD_PTR*);
118 
119 bool TrueClock::
120 set_cpu_affinity(uint32_t mask) const {
121  HMODULE hker = GetModuleHandle("kernel32");
122  if (hker != 0) {
123  PFNGETPROCESSAFFINITYMASK gp = (PFNGETPROCESSAFFINITYMASK)
124  GetProcAddress(hker, "GetProcessAffinityMask");
125  PFNSETPROCESSAFFINITYMASK sp = (PFNSETPROCESSAFFINITYMASK)
126  GetProcAddress(hker, "SetProcessAffinityMask");
127  if (gp != 0 && sp != 0) {
128  DWORD proc_mask;
129  DWORD sys_mask;
130  if (gp(GetCurrentProcess(), (PDWORD_PTR)&proc_mask, (PDWORD_PTR)&sys_mask)) {
131  // make sure we don't reference CPUs that don't exist
132  proc_mask = mask & sys_mask;
133  if (proc_mask) {
134  return sp(GetCurrentProcess(), proc_mask) != 0;
135  }
136  }
137  }
138  }
139  return false;
140 }
141 
142 /**
143  *
144  */
145 TrueClock::
146 TrueClock() {
147  _error_count = 0;
148  _has_high_res = false;
149 
150  _time_scale = 1.0;
151  _time_offset = 0.0;
152  _tod_offset = 0.0;
153  _time_scale_changed = false;
154  _last_reported_time_scale = 1.0;
155  _report_time_scale_time = 0.0;
156 
157  ConfigVariableBool lock_to_one_cpu
158  ("lock-to-one-cpu", false,
159  PRC_DESC("Set this to true if you want the entire process to use one "
160  "CPU, even on multi-core and multi-CPU workstations. This is "
161  "mainly a hack to solve a bug in which QueryPerformanceCounter "
162  "returns inconsistent results on multi-core machines. "));
163 
164  if (lock_to_one_cpu) {
165  set_cpu_affinity(0x01);
166  }
167 
168  if (get_use_high_res_clock()) {
169  int64_t int_frequency;
170  _has_high_res =
171  (QueryPerformanceFrequency((LARGE_INTEGER *)&int_frequency) != 0);
172  if (_has_high_res) {
173  if (int_frequency <= 0) {
174  clock_cat.error()
175  << "TrueClock::get_real_time() - frequency is negative!" << std::endl;
176  _has_high_res = false;
177 
178  } else {
179  _frequency = (double)int_frequency;
180  _recip_frequency = 1.0 / _frequency;
181 
182  QueryPerformanceCounter((LARGE_INTEGER *)&_init_count);
183  }
184  }
185  }
186 
187  // Also store the initial tick count. We'll need this for get_long_time(),
188  // as well as for get_short_time() if we're not using the high resolution
189  // clock.
190  _init_tc = GetTickCount();
191 
192  // And we will need the current time of day to cross-check either of the
193  // above clocks if paranoid-clock is enabled.
194  GetSystemTimeAsFileTime((FILETIME *)&_init_tod);
195 
196  _chase_clock = CC_keep_even;
197 
198  // In case we'll be cross-checking the clock, we'd better start out with at
199  // least one timestamp, so we'll know if the clock jumps just after startup.
200  _timestamps.push_back(Timestamp(0.0, 0.0));
201 
202  if (!_has_high_res) {
203  clock_cat.warning()
204  << "No high resolution clock available." << std::endl;
205  }
206 }
207 
208 /**
209  * Ensures that the reported timestamp from the high-precision (or even the
210  * low-precision) clock is valid by verifying against the time-of-day clock.
211  *
212  * This attempts to detect sudden jumps in time that might be caused by a
213  * failure of the high-precision clock to roll over properly.
214  *
215  * It also corrects for long-term skew of the clock by measuring the timing
216  * discrepency against the wall clock and projecting that discrepency into the
217  * future. This also should defeat programs such as Speed Gear that work by
218  * munging the value returned by QueryPerformanceCounter() and GetTickCount(),
219  * but not the wall clock time.
220  *
221  * However, relying on wall clock time presents its own set of problems, since
222  * the time of day might be adjusted slightly forward or back from time to
223  * time in response to ntp messages, or it might even be suddenly reset at any
224  * time by the user. So we do the best we can.
225  */
226 double TrueClock::
227 correct_time(double time) {
228  // First, get the current time of day measurement.
229  uint64_t int_tod;
230  GetSystemTimeAsFileTime((FILETIME *)&int_tod);
231  double tod = (double)(int_tod - _init_tod) * _00000001;
232 
233  nassertr(!_timestamps.empty(), time);
234 
235  // Make sure we didn't experience a sudden jump from the last measurement.
236  double time_delta = (time - _timestamps.back()._time) * _time_scale;
237  double tod_delta = (tod - _timestamps.back()._tod);
238 
239  if (time_delta < -0.0001 ||
240  fabs(time_delta - tod_delta) > paranoid_clock_jump_error) {
241  // A step backward in the high-precision clock, or more than a small jump
242  // on only one of the clocks, is cause for alarm. We allow a trivial step
243  // backward in the high-precision clock, since this does appear to happen
244  // in a threaded environment.
245 
246  if (clock_cat.is_debug()) {
247  clock_cat.debug()
248  << "Clock error detected; elapsed time " << time_delta
249  << "s on high-resolution counter, and " << tod_delta
250  << "s on time-of-day clock.\n";
251  }
252  ++_error_count;
253 
254  // If both are negative, we call it 0. If one is negative, we trust the
255  // other one (up to paranoid_clock_jump_error). If both are nonnegative,
256  // we trust the smaller of the two.
257  double time_adjust = 0.0;
258  double tod_adjust = 0.0;
259 
260  if (time_delta < 0.0 && tod < 0.0) {
261  // Trust neither.
262  time_adjust = -time_delta;
263  tod_adjust = -tod_delta;
264 
265  } else if (time_delta < 0.0 || (tod_delta >= 0.0 && tod_delta < time_delta)) {
266  // Trust tod, up to a point.
267  double new_tod_delta = min(tod_delta, paranoid_clock_jump_error);
268  time_adjust = new_tod_delta - time_delta;
269  tod_adjust = new_tod_delta - tod_delta;
270 
271  } else {
272  // Trust time, up to a point.
273  double new_time_delta = min(time_delta, paranoid_clock_jump_error);
274  time_adjust = new_time_delta - time_delta;
275  tod_adjust = new_time_delta - tod_delta;
276  }
277 
278  _time_offset += time_adjust;
279  time_delta += time_adjust;
280  _tod_offset += tod_adjust;
281  tod_delta += tod_adjust;
282 
283  // Apply the adjustments to the timestamp queue. We could just completely
284  // empty the timestamp queue, but that makes it hard to catch up if we are
285  // getting lots of these "momentary" errors in a row.
286  Timestamps::iterator ti;
287  for (ti = _timestamps.begin(); ti != _timestamps.end(); ++ti) {
288  (*ti)._time -= time_adjust / _time_scale;
289  (*ti)._tod -= tod_adjust;
290  }
291 
292  // And now we can record this timestamp, which is now consistent with the
293  // previous timestamps in the queue.
294  _timestamps.push_back(Timestamp(time, tod));
295 
296 /*
297  * Detecting and filtering this kind of momentary error can help protect us
298  * from legitimate problems cause by OS or BIOS bugs (which might introduce
299  * errors into the high precision clock), or from sudden changes to the time-
300  * of-day by the user, but we have to be careful because if the user uses a
301  * Speed Gear-type program to speed up the clock by an extreme amount, it can
302  * look like a lot of such "momentary" errors in a row--and if we throw them
303  * all out, we won't compute _time_scale correctly. To avoid this, we hack
304  * _time_scale here if we seem to be getting out of sync.
305  */
306  double corrected_time = time * _time_scale + _time_offset;
307  double corrected_tod = tod + _tod_offset;
308  if (corrected_time - corrected_tod > paranoid_clock_jump_error_max_delta &&
309  _time_scale > 0.00001) {
310  clock_cat.info()
311  << "Force-adjusting time_scale to catch up to errors.\n";
312  set_time_scale(time, _time_scale * 0.5);
313  }
314 
315  } else if (tod_delta < 0.0) {
316  // A small backwards jump on the time-of-day clock is not a concern, since
317  // this is technically allowed with ntp enabled. We simply ignore the
318  // event.
319 
320  } else {
321  // Ok, we don't think there was a sudden jump, so carry on.
322 
323  // The timestamp queue here records the measured timestamps over the past
324  // _priority_interval seconds. Its main purpose is to keep a running
325  // observation of _time_scale, so we can detect runtime changes of the
326  // clock's scale, for instance if the user is using a program like Speed
327  // Gear and pulls the slider during runtime.
328 
329  // Consider the oldest timestamp in our queue.
330  Timestamp oldest = _timestamps.front();
331  double time_age = (time - oldest._time);
332  double tod_age = (tod - oldest._tod);
333 
334  double keep_interval = paranoid_clock_interval;
335 
336  if (tod_age > keep_interval / 2.0 && time_age > 0.0) {
337  // Adjust the _time_scale value to match the ratio between the elapsed
338  // time on the high-resolution clock, and the time-of-day clock.
339  double new_time_scale = tod_age / time_age;
340 
341  // When we adjust _time_scale, we have to be careful to adjust
342  // _time_offset at the same time, so we don't introduce a sudden jump in
343  // time.
344  set_time_scale(time, new_time_scale);
345 
346  // Check to see if the time scale has changed significantly since we
347  // last reported it.
348  double ratio = _time_scale / _last_reported_time_scale;
349  if (fabs(ratio - 1.0) > paranoid_clock_report_scale_factor) {
350  _time_scale_changed = true;
351  _last_reported_time_scale = _time_scale;
352  // Actually report it a little bit later, to give the time scale a
353  // chance to settle down.
354  _report_time_scale_time = tod + _tod_offset + keep_interval;
355  if (clock_cat.is_debug()) {
356  clock_cat.debug()
357  << "Will report time scale, now " << 100.0 / _time_scale
358  << "%, tod_age = " << tod_age << ", time_age = " << time_age
359  << ", ratio = " << ratio << "\n";
360  }
361  }
362  }
363 
364  // Clean out old entries in the timestamps queue.
365  if (tod_age > keep_interval) {
366  while (!_timestamps.empty() &&
367  tod - _timestamps.front()._tod > keep_interval) {
368  _timestamps.pop_front();
369  }
370  }
371 
372  // Record this timestamp.
373  _timestamps.push_back(Timestamp(time, tod));
374  }
375 
376  double corrected_time = time * _time_scale + _time_offset;
377  double corrected_tod = tod + _tod_offset;
378 
379  if (_time_scale_changed && corrected_tod >= _report_time_scale_time) {
380  double percent = 100.0 / _time_scale;
381  // Round percent to the nearest 5% to reduce confusion in the logs.
382  percent = floor(percent / 20.0 + 0.5) * 20.0;
383  clock_cat.info()
384  << "Clock appears to be running at " << percent << "% real time.\n";
385  _last_reported_time_scale = _time_scale;
386  _time_scale_changed = false;
387  }
388 
389  // By the time we get here, we have a corrected_time and a corrected_tod
390  // value, both of which should be advancing at about the same rate.
391  // However, there might be accumulated skew between them, since there is
392  // some lag in the above algorithm that corrects the _time_scale, and clock
393  // skew can accumulate while the algorithm is catching up.
394 
395  // Therefore, we have one more line of defense: we check at this point for
396  // skew, and correct for it by slowing the clock down or speeding it up a
397  // bit as needed, until we even out the clocks again. Rather than adjusting
398  // the clock speed with _time_scale here, we simply slide _time_offset
399  // forward and back as needed--that way we don't interfere with the above
400  // algorithm, which is trying to compute _time_scale accurately.
401 
402  switch (_chase_clock) {
403  case CC_slow_down:
404  if (corrected_time < corrected_tod) {
405  // We caught up.
406  _chase_clock = CC_keep_even;
407  if (clock_cat.is_debug()) {
408  clock_cat.debug()
409  << "Clock back down to real time.\n";
410  // Let's report the clock error now, so an app can resync now that
411  // we're at a good time.
412  ++_error_count;
413  }
414 
415  } else {
416  // Slow down the clock by sliding the offset a bit backward.
417  double fixup = 1.0 - (1.0 / (corrected_time - corrected_tod));
418  double correction = time_delta * max(fixup, paranoid_clock_chase_factor);
419  _time_offset -= correction;
420  corrected_time -= correction;
421  }
422  break;
423 
424  case CC_keep_even:
425  if ((corrected_tod - corrected_time) > paranoid_clock_chase_threshold) {
426  // Oops, we're dropping behind; need to speed up.
427  _chase_clock = CC_speed_up;
428 
429  if (clock_cat.is_debug()) {
430  clock_cat.debug()
431  << "Clock is behind by " << (corrected_tod - corrected_time)
432  << "s; speeding up to correct.\n";
433  }
434  } else if ((corrected_time - corrected_tod) > paranoid_clock_chase_threshold) {
435  // Oops, we're going too fast; need to slow down.
436  _chase_clock = CC_slow_down;
437 
438  if (clock_cat.is_debug()) {
439  clock_cat.debug()
440  << "Clock is ahead by " << (corrected_time - corrected_tod)
441  << "s; slowing down to correct.\n";
442  }
443  }
444  break;
445 
446  case CC_speed_up:
447  if (corrected_time > corrected_tod) {
448  // We caught up.
449  _chase_clock = CC_keep_even;
450  if (clock_cat.is_debug()) {
451  clock_cat.debug()
452  << "Clock back up to real time.\n";
453  // Let's report the clock error now, so an app can resync now that
454  // we're at a good time.
455  ++_error_count;
456  }
457 
458  } else {
459  // Speed up the clock by sliding the offset a bit forward.
460  double fixup = 1.0 - (1.0 / (corrected_tod - corrected_time));
461  double correction = time_delta * max(fixup, paranoid_clock_chase_factor);
462  _time_offset += correction;
463  corrected_time += correction;
464  }
465  break;
466  }
467 
468  if (clock_cat.is_spam()) {
469  clock_cat.spam()
470  << "time " << time << " tod " << corrected_tod
471  << " corrected time " << corrected_time << "\n";
472  }
473 
474  return corrected_time;
475 }
476 
477 /**
478  * Changes the _time_scale value, recomputing _time_offset at the same time so
479  * we don't introduce a sudden jump in time.
480  */
481 void TrueClock::
482 set_time_scale(double time, double new_time_scale) {
483  nassertv(new_time_scale > 0.0);
484  _time_offset = time * _time_scale + _time_offset - (time * new_time_scale);
485  _time_scale = new_time_scale;
486 }
487 
488 #else // !WIN32_VC
489 
490 // The Posix implementation.
491 
492 #include <sys/time.h>
493 #include <stdio.h> // for perror
494 
495 static long _init_sec;
496 
497 /**
498  *
499  */
500 double TrueClock::
501 get_long_time() {
502  struct timeval tv;
503 
504  int result;
505 
506 #ifdef GETTIMEOFDAY_ONE_PARAM
507  result = gettimeofday(&tv);
508 #else
509  result = gettimeofday(&tv, nullptr);
510 #endif
511 
512  if (result < 0) {
513  // Error in gettimeofday().
514  return 0.0;
515  }
516 
517  // We subtract out the time at which the clock was initialized, because we
518  // don't care about the number of seconds all the way back to 1970, and we
519  // want to leave the double with as much precision as it can get.
520  return (double)(tv.tv_sec - _init_sec) + (double)tv.tv_usec / 1000000.0;
521 }
522 
523 /**
524  *
525  */
526 double TrueClock::
527 get_short_raw_time() {
528  struct timeval tv;
529 
530  int result;
531 
532 #ifdef GETTIMEOFDAY_ONE_PARAM
533  result = gettimeofday(&tv);
534 #else
535  result = gettimeofday(&tv, nullptr);
536 #endif
537 
538  if (result < 0) {
539  // Error in gettimeofday().
540  return 0.0;
541  }
542 
543  // We subtract out the time at which the clock was initialized, because we
544  // don't care about the number of seconds all the way back to 1970, and we
545  // want to leave the double with as much precision as it can get.
546  return (double)(tv.tv_sec - _init_sec) + (double)tv.tv_usec / 1000000.0;
547 }
548 
549 /**
550  *
551  */
552 bool TrueClock::
553 set_cpu_affinity(uint32_t mask) const {
554  return false;
555 }
556 
557 /**
558  *
559  */
560 TrueClock::
561 TrueClock() {
562  _error_count = 0;
563  struct timeval tv;
564 
565  int result;
566 #ifdef GETTIMEOFDAY_ONE_PARAM
567  result = gettimeofday(&tv);
568 #else
569  result = gettimeofday(&tv, nullptr);
570 #endif
571 
572  if (result < 0) {
573  perror("gettimeofday");
574  _init_sec = 0;
575  } else {
576  _init_sec = tv.tv_sec;
577  }
578 }
579 
580 #endif
This is a convenience class to specialize ConfigVariable as a boolean type.
An interface to whatever real-time clock we might have available in the current environment.
Definition: trueClock.h:33
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.