#!/usr/bin/python # # Framework to present system clocks by feature, intended to avoid # the library-as-policy pitfalls of the discussion around PEP 418. # # My 2c: # http://www.gossamer-threads.com/lists/python/dev/977474#977474 # http://www.gossamer-threads.com/lists/python/dev/977495#977495 # or: # http://www.mail-archive.com/python-dev@python.org/msg66174.html # http://www.mail-archive.com/python-dev@python.org/msg66179.html # - Cameron Simpson 02apr2012 # from collections import namedtuple from time import time import os # the module exposing OS clock features _time = os HIGHRES = 0x01 # high resolution MONOTONIC = 0x02 # never goes backwards STEADY = 0x04 # never steps; implies MONOTONIC ADJUSTED = 0x08 # may be adjusted, for example by NTP WALLCLOCK = 0x10 # tracks real world time, will usually be ADJUSTED too RUNTIME = 0x20 # track system run time - stops when system suspended SYNTHETIC = 0x40 # a synthetic clock, computed from other clocks def get_clock(flags=0, clocklist=None): ''' Return a clock based on the supplied `flags`. The returned clock shall have all the requested flags. If no clock matches, return None. ''' for clock in get_clocks(flags=flags, clocklist=clocklist): return clock return None def get_clocks(flags=0, clocklist=None): ''' Yield all clocks matching the supplied `flags`. The returned clocks shall have all the requested flags. ''' if clocklist is None: clocklist = ALL_CLOCKS for clock in clocklist: if clock.flags & flags == flags: yield clock.factory() def monotonic_clock(other_flags=0): ''' Return a monotonic clock, preferably high resolution. ''' return get_clock(MONOTONIC|HIGHRES|other_flags, MONOTONIC_CLOCKS) \ or get_clock(MONOTONIC|other_flags, MONOTONIC_CLOCKS) def steady_clock(other_flags=0): ''' Return a steady clock, preferably high resolution. ''' return get_clock(STEADY|HIGHRES|other_flags, STEADY_CLOCKS) \ or get_clock(STEADY|other_flags, STEADY_CLOCKS) def highres_clock(other_flags=0): ''' Return a high resolution clock, preferably steady. ''' return get_clock(HIGHRES|STEADY|other_flags, HIGHRES_CLOCKS) \ or get_clock(HIGHRES|other_flags, HIGHRES_CLOCKS) _global_monotonic = None def monotonic(): ''' Return the current time according to the default monotonic clock. ''' global _global_monotonic if _global_monotonic is None: _global_monotonic = monotonic_clock() if _global_monotonic is None: raise RunTimeError("no monotonic clock available") return _global_monotonic.now() _global_hires = None def highres(): ''' Return the current time according to the default high resolution clock. ''' global _global_hires if _global_hires is None: _global_hires = highres() if _global_hires is None: raise RunTimeError("no highres clock available") return _global_hires.now() _global_steady = None def steady(): ''' Return the current time according to the default steady clock. ''' global _global_steady if _global_steady is None: _global_steady = steady() if _global_steady is None: raise RunTimeError("no steady clock available") return _global_steady.now() class _Clock_Flags(int): ''' An int with human friendly str() and repr() for clock flags. ''' _flag_names = ( 'HIGHRES', 'MONOTONIC', 'STEADY', 'ADJUSTED', 'WALLCLOCK', 'RUNTIME', 'SYNTHETIC', ) def __str__(self): f = self G = globals() names = [] for name in _Clock_Flags._flag_names: n = G[name] if f & n: names.append(name) f &= ~n if f: names.append('0x%02x' % f) return '|'.join(names) if names else '0' def __repr__(self): return '<%s %02x %s>' % (self.__class__.__name__, self, self) # now assemble the list of platform clocks class _Clock(object): ''' A _Clock is the private base class of clock objects. A clock has the following mandatory attributes: .flags Feature flags describing the clock. A clock may have the following optional attributes: .epoch If present, the offset from time.time()'s epoch of this clock's epoch(). Not all clocks have epochs; some measure elapsed time from an unknown point and only the difference in two measurements is useful. .resolution The resolution of the underlying clock facility's reporting units. The clock can never be more precise than this value. The actual accuracy of the reported time may vary with adjustments and the real accuracy of the underlying OS clock facility (which in turn may be dependent on the precision of some hardware clock). A clock must also supply the following methods: .now() Report the current time in seconds, a float. ''' def __init__(self): ''' Set instance attributes from class attributes, suitable to singleton clocks. ''' klass = self.__class__ self.flags = _Clock_Flags(klass.flags) for attr in 'epoch', 'resolution': try: attrval = getattr(klass, attr) except AttributeError: pass else: setattr(self, attr, attrval) def __repr__(self): props = [self.__class__.__name__] for attr in sorted( [ attr for attr in dir(self) if attr and attr[0].isalpha() and attr not in ('now',)] ): props.append("%s=%s" % (attr, getattr(self, attr))) return "<" + " ".join(props) + ">" ClockEntry = namedtuple('ClockEntry', 'flags factory') ALL_CLOCKS = [] def _SingletonClockEntry( klass ): ''' Construct a ClockEntry for a Singleton class, typical for system clocks. ''' klock = klass() return ClockEntry( klass.flags, lambda: klock ) # always provide good old time.time() # provide no flags - this is a fallback - totally implementation agnostic class _TimeDotTimeClock(_Clock): ''' A clock made from time.time(). ''' flags = 0 epoch = 0 def now(self): return time() ALL_CLOCKS.append( _SingletonClockEntry(_TimeDotTimeClock) ) # load system specific clocks here # in notional order of preference if os.name == "nt": class _NT_GetSystemTimeAsFileTimeClock(_Clock): ''' A clock made from GetSystemTimeAsFileTime(). ''' flags = WALLCLOCK epoch = EPOCH_16010101T000000 # 01jan1601 # a negative value wrt 01jan1970 resolution = 0.0000001 # 100 nanosecond units # accuracy HW dependent? def now(self): # convert 100-nanosecond intervals since 1601 to UNIX style seconds return ( _time._GetSystemTimeAsFileTime() / 10000000 + NT_GetSystemTimeAsFileTimeClock.epoch ) ALL_CLOCKS.append( _SingletonClockEntry(_NT_GetSystemTimeAsFileTimeClock) ) class _NT_GetTickCount64(_Clock): ''' Based on http://msdn.microsoft.com/en-us/library/windows/desktop/ms724411%28v=vs.85%29.aspx Note that this specifically disavows high resolution. ''' flags = RUNTIME|MONOTONIC resolution = 0.001 def now(self): msecs = _time.GetTickCount64() return msecs / 1000 ALL_CLOCKS.append( _SingletonClockEntry(_NT_GetTickCount64) ) else: # presuming clock_gettime() and clock_getres() exposed in the os # module, along with the clock id names if hasattr(_time, "clock_gettime"): try: clk_id = _time.CLOCK_REALTIME except AttributeError: pass else: try: timespec = _time.clock_getres(clk_id) except OSError: pass else: class _UNIX_CLOCK_REALTIME(_Clock): ''' A clock made from clock_gettime(CLOCK_REALTIME). ''' epoch = 0 flags = WALLCLOCK resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000 @staticmethod def now(): timespec = _time.clock_gettime(_time.CLOCK_REALTIME) return timespec.tv_sec + timespec.tv_nsec / 1000000000 ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_CLOCK_REALTIME) ) try: clk_id = _time.CLOCK_MONOTONIC except AttributeError: pass else: try: timespec = _time.clock_getres(clk_id) except OSError: pass else: class _UNIX_CLOCK_MONOTONIC(_Clock): ''' A clock made from clock_gettime(CLOCK_MONOTONIC). ''' flags = MONOTONIC|STEADY|ADJUSTED resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000 @staticmethod def now(): timespec = _time.clock_gettime(_time.CLOCK_MONOTONIC) return timespec.tv_sec + timespec.tv_nsec / 1000000000 ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_CLOCK_MONOTONIC) ) try: clk_id = _time.CLOCK_MONOTONIC_RAW except AttributeError: pass else: try: timespec = _time.clock_getres(clk_id) except OSError: pass else: class _UNIX_CLOCK_MONOTONIC_RAW(_Clock): ''' A clock made from clock_gettime(CLOCK_MONOTONIC_RAW). ''' flags = MONOTONIC|STEADY resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000 @staticmethod def now(): timespec = _time.clock_gettime(_time.CLOCK_MONOTONIC_RAW) return timespec.tv_sec + timespec.tv_nsec / 1000000000 ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_CLOCK_MONOTONIC_RAW) ) try: clk_id = _time.CLOCK_PROCESS_CPUTIME_ID except AttributeError: pass else: try: timespec = _time.clock_getres(clk_id) except OSError: pass else: class _UNIX_CLOCK_PROCESS_CPUTIME_ID(_Clock): ''' A clock made from clock_gettime(CLOCK_PROCESS_CPUTIME_ID). ''' flags = MONOTONIC resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000 @staticmethod def now(): timespec = _time.clock_gettime(_time.CLOCK_PROCESS_CPUTIME_ID) return timespec.tv_sec + timespec.tv_nsec / 1000000000 ALL_CLOCKS.append( _SingletonClockEntry(_CLOCK_PROCESS_CPUTIME_ID) ) try: clk_id = _time.CLOCK_THREAD_CPUTIME_ID except AttributeError: pass else: try: timespec = _time.clock_getres(clk_id) except OSError: pass else: class _UNIX_CLOCK_THREAD_CPUTIME_ID(_Clock): ''' A clock made from clock_gettime(CLOCK_THREAD_CPUTIME_ID). ''' flags = MONOTONIC resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000 @staticmethod def now(): timespec = _time.clock_gettime(_time.CLOCK_THREAD_CPUTIME_ID) return timespec.tv_sec + timespec.tv_nsec / 1000000000 ALL_CLOCKS.append( _SingletonClockEntry(_CLOCK_CLOCK_THREAD_CPUTIME_ID) ) if hasattr(_time, "gettimeofday"): class _UNIX_gettimeofday(_Clock): ''' A clock made from gettimeofday(). ''' epoch = 0 flags = WALLCLOCK resolution = 0.000001 def now(self): timeval = _time.gettimeofday() return timeval.tv_sec + timeval.tv_usec / 1000000 ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_gettimeofday) ) if hasattr(_time, "ftime"): class _UNIX_ftime(_Clock): ''' A clock made from ftime(). ''' epoch = 0 flags = WALLCLOCK|ADJUSTED resolution = 0.001 def now(self): timeb = _time.ftime() return timeb.time + timeb.millitm / 1000 ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_ftime) ) # an example synthetic clock, coming after time.time() # because I think synthetic clocks should be less desired # - they tend to have side effects; but perhaps offered anyway because # they can offer flag combinations not always presented by the system # clocks # a simple synthetic monotonic clock # may skew with respect to other instances # Steven D'Aprano wrote a better one class SyntheticMonotonic(_Clock): flags = SYNTHETIC|MONOTONIC def __init__(self, base_clock=None): _Clock.__init__(self) if base_clock is None: base_clock = _TimeDotTimeClock() self.base_clock = base_clock for attr in 'epoch', 'resolution': try: attrval = getattr(base_clock, attr) except AttributeError: pass else: setattr(self, attr, attrval) self.__last = None self.__base = base_clock def now(self): last = self.__last t = self.__base.now() if last is None or last < t: self.__last = t else: t = last return t ALL_CLOCKS.append( ClockEntry(SyntheticMonotonic.flags, SyntheticMonotonic) ) # With more clocks, these will be ALL_CLOCKS listed in order of preference # for these types i.e. MONOTONIC_CLOCKS will list only monotonic clocks # in order of quality (an arbitrary measure, perhaps). MONOTONIC_CLOCKS = ALL_CLOCKS HIGHRES_CLOCKS = ALL_CLOCKS STEADY_CLOCKS = ALL_CLOCKS if __name__ == '__main__': print("ALL_CLOCKS =", repr(ALL_CLOCKS)) for clock in get_clocks(): print("clock = %r" % (clock,)) print(clock.__class__.__doc__)