Edouard@3420: # vi: set ft=python sw=4 ts=4 et:
Edouard@3420: 
Edouard@3420: '''monotonic time for Python 2 and 3, on Linux, FreeBSD, Mac OS X, and Windows.
Edouard@3420: 
Edouard@3420: Copyright 2010, 2011, 2017 Gavin Beatty <gavinbeatty@gmail.com>
Edouard@3420: 
Edouard@3420: Permission is hereby granted, free of charge, to any person obtaining a copy of
Edouard@3420: this software and associated documentation files (the "Software"), to deal in
Edouard@3420: the Software without restriction, including without limitation the rights to
Edouard@3420: use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
Edouard@3420: of the Software, and to permit persons to whom the Software is furnished to do
Edouard@3420: so, subject to the following conditions:
Edouard@3420: 
Edouard@3420: The above copyright notice and this permission notice shall be included in all
Edouard@3420: copies or substantial portions of the Software.
Edouard@3420: 
Edouard@3420: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
Edouard@3420: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Edouard@3420: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Edouard@3420: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
Edouard@3420: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
Edouard@3420: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
Edouard@3420: SOFTWARE.
Edouard@3420: '''
Edouard@3420: 
Edouard@3420: from __future__ import print_function
Edouard@3420: from __future__ import unicode_literals
Edouard@3420: 
Edouard@3420: __author__ = 'Gavin Beatty <gavinbeatty@gmail.com>'
Edouard@3420: __version__ = '2.1.0.dev0'
Edouard@3420: __date__ = '2017-05-28'
Edouard@3420: __all__ = ['monotonic']
Edouard@3420: 
Edouard@3420: import contextlib
Edouard@3420: import ctypes
Edouard@3420: import errno
Edouard@3420: import os
Edouard@3420: import platform
Edouard@3420: import sys
Edouard@3420: import time
Edouard@3420: 
Edouard@3420: _machine64 = (platform.machine(), sys.maxsize > 2**32)
Edouard@3420: 
Edouard@3420: 
Edouard@3420: class _NS():
Edouard@3420:     pass
Edouard@3420: 
Edouard@3420: 
Edouard@3420: class _mach_timespec(ctypes.Structure):
Edouard@3420:     _fields_ = [('tv_sec', ctypes.c_uint), ('tv_nsec', ctypes.c_int)]
Edouard@3420: 
Edouard@3420: 
Edouard@3420: class _posix_timespec(ctypes.Structure):
Edouard@3420:     _fields_ = [('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long)]
Edouard@3420: 
Edouard@3420: 
Edouard@3420: def _timespec_to_seconds(ts):
Edouard@3420:     return float(ts.tv_sec) + float(ts.tv_nsec) * 1e-9
Edouard@3420: 
Edouard@3420: 
Edouard@3420: def _get_ctypes_libmacho_macho_functions():
Edouard@3420:     libmacho = ctypes.CDLL('/usr/lib/system/libmacho.dylib', use_errno=True)
Edouard@3420:     macho = _NS()
Edouard@3420:     macho.get_host = libmacho.mach_host_self
Edouard@3420:     macho.get_host.argtypes = []
Edouard@3420:     macho.get_host.restype = ctypes.c_uint
Edouard@3420:     macho.get_clock = libmacho.host_get_clock_service
Edouard@3420:     macho.get_clock.argtypes = [ctypes.c_uint,
Edouard@3420:                                 ctypes.c_int,
Edouard@3420:                                 ctypes.POINTER(ctypes.c_uint)
Edouard@3420:                                 ]
Edouard@3420:     macho.get_time = libmacho.clock_get_time
Edouard@3420:     macho.get_time.argtypes = [ctypes.c_uint, ctypes.POINTER(_mach_timespec)]
Edouard@3420:     macho.deallocate = libmacho.mach_port_deallocate
Edouard@3420:     macho.deallocate.argtypes = [ctypes.c_uint, ctypes.c_uint]
Edouard@3420:     return libmacho, macho
Edouard@3420: 
Edouard@3420: 
Edouard@3420: def _get_ctypes_clock_gettime(library):
Edouard@3420:     clock_gettime = ctypes.CDLL(library, use_errno=True).clock_gettime
Edouard@3420:     clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(_posix_timespec)]
Edouard@3420:     return clock_gettime
Edouard@3420: 
Edouard@3420: 
Edouard@3420: def _call_ctypes_clock_gettime(clock_gettime, clockid):
Edouard@3420:     timespec = _posix_timespec()
Edouard@3420:     ret = clock_gettime(clockid, ctypes.pointer(timespec))
Edouard@3420:     if int(ret) != 0:
Edouard@3420:         errno_ = ctypes.get_errno()
Edouard@3420:         raise OSError(errno_, os.strerror(errno_))
Edouard@3420:     return timespec
Edouard@3420: 
Edouard@3420: 
Edouard@3420: _py_monotonic = getattr(time, 'monotonic', None)
Edouard@3420: if _py_monotonic is not None:
Edouard@3420:     monotonic = _py_monotonic
Edouard@3420: elif sys.platform.startswith('linux'):
Edouard@3420:     _clock_gettime = _get_ctypes_clock_gettime('librt.so.1')
Edouard@3420: 
Edouard@3420:     def monotonic():
Edouard@3420:         clockid = ctypes.c_int(1)
Edouard@3420:         timespec = _call_ctypes_clock_gettime(_clock_gettime, clockid)
Edouard@3420:         return _timespec_to_seconds(timespec)
Edouard@3420: elif sys.platform.startswith('freebsd'):
Edouard@3420:     _clock_gettime = _get_ctypes_clock_gettime('libc.so')
Edouard@3420: 
Edouard@3420:     def monotonic():
Edouard@3420:         clockid = ctypes.c_int(4)
Edouard@3420:         timespec = _call_ctypes_clock_gettime(_clock_gettime, clockid)
Edouard@3420:         return _timespec_to_seconds(timespec)
Edouard@3420: elif sys.platform.startswith('darwin') and _machine64 == ('x86_64', True):
Edouard@3420:     _libmacho, _macho = _get_ctypes_libmacho_macho_functions()
Edouard@3420: 
Edouard@3420:     @contextlib.contextmanager
Edouard@3420:     def _deallocate(task, port):
Edouard@3420:         try:
Edouard@3420:             yield
Edouard@3420:         finally:
Edouard@3420:             if int(_macho.deallocate(task, port)) == 0:
Edouard@3420:                 return
Edouard@3420:             errno_ = ctypes.get_errno()
Edouard@3420:             raise OSError(errno_, os.strerror(errno_))
Edouard@3420: 
Edouard@3420:     def monotonic():
Edouard@3420:         task = ctypes.c_uint.in_dll(_libmacho, 'mach_task_self_')
Edouard@3420:         host = _macho.get_host()
Edouard@3420:         with _deallocate(task, host):
Edouard@3420:             clock = ctypes.c_uint(0)
Edouard@3420:             clockid = ctypes.c_int(0)
Edouard@3420:             ret = _macho.get_clock(host, clockid, ctypes.pointer(clock))
Edouard@3420:             if int(ret) != 0:
Edouard@3420:                 errno_ = ctypes.get_errno()
Edouard@3420:                 raise OSError(errno_, os.strerror(errno_))
Edouard@3420:             with _deallocate(task, clock):
Edouard@3420:                 timespec = _mach_timespec()
Edouard@3420:                 ret = _macho.get_time(clock, ctypes.pointer(timespec))
Edouard@3420:                 if int(ret) != 0:
Edouard@3420:                     errno_ = ctypes.get_errno()
Edouard@3420:                     raise OSError(errno_, os.strerror(errno_))
Edouard@3420:                 return _timespec_to_seconds(timespec)
Edouard@3420: elif sys.platform.startswith('win32'):
Edouard@3420:     _GetTickCount = getattr(ctypes.windll.kernel32, 'GetTickCount64', None)
Edouard@3420: 
Edouard@3420:     if _GetTickCount is not None:
Edouard@3420:         _GetTickCount.restype = ctypes.c_uint64
Edouard@3420:     else:
Edouard@3420:         _GetTickCount = ctypes.windll.kernel32.GetTickCount
Edouard@3420:         _GetTickCount.restype = ctypes.c_uint32
Edouard@3420: 
Edouard@3420:     def monotonic():
Edouard@3420:         return float(_GetTickCount()) * 1e-3
Edouard@3420: else:
Edouard@3420:     def monotonic():
Edouard@3420:         msg = 'monotonic not supported on your platform'
Edouard@3420:         raise OSError(errno.ENOSYS, msg)
Edouard@3420: 
Edouard@3420: 
Edouard@3420: if __name__ == '__main__':
Edouard@3420:     print(monotonic())