runtime/monotonic_time.py
changeset 3420 da8cceaa247d
child 3750 f62625418bff
equal deleted inserted replaced
3419:76995a304fe0 3420:da8cceaa247d
       
     1 # vi: set ft=python sw=4 ts=4 et:
       
     2 
       
     3 '''monotonic time for Python 2 and 3, on Linux, FreeBSD, Mac OS X, and Windows.
       
     4 
       
     5 Copyright 2010, 2011, 2017 Gavin Beatty <gavinbeatty@gmail.com>
       
     6 
       
     7 Permission is hereby granted, free of charge, to any person obtaining a copy of
       
     8 this software and associated documentation files (the "Software"), to deal in
       
     9 the Software without restriction, including without limitation the rights to
       
    10 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
       
    11 of the Software, and to permit persons to whom the Software is furnished to do
       
    12 so, subject to the following conditions:
       
    13 
       
    14 The above copyright notice and this permission notice shall be included in all
       
    15 copies or substantial portions of the Software.
       
    16 
       
    17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       
    18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       
    19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       
    20 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       
    21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       
    22 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       
    23 SOFTWARE.
       
    24 '''
       
    25 
       
    26 from __future__ import print_function
       
    27 from __future__ import unicode_literals
       
    28 
       
    29 __author__ = 'Gavin Beatty <gavinbeatty@gmail.com>'
       
    30 __version__ = '2.1.0.dev0'
       
    31 __date__ = '2017-05-28'
       
    32 __all__ = ['monotonic']
       
    33 
       
    34 import contextlib
       
    35 import ctypes
       
    36 import errno
       
    37 import os
       
    38 import platform
       
    39 import sys
       
    40 import time
       
    41 
       
    42 _machine64 = (platform.machine(), sys.maxsize > 2**32)
       
    43 
       
    44 
       
    45 class _NS():
       
    46     pass
       
    47 
       
    48 
       
    49 class _mach_timespec(ctypes.Structure):
       
    50     _fields_ = [('tv_sec', ctypes.c_uint), ('tv_nsec', ctypes.c_int)]
       
    51 
       
    52 
       
    53 class _posix_timespec(ctypes.Structure):
       
    54     _fields_ = [('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long)]
       
    55 
       
    56 
       
    57 def _timespec_to_seconds(ts):
       
    58     return float(ts.tv_sec) + float(ts.tv_nsec) * 1e-9
       
    59 
       
    60 
       
    61 def _get_ctypes_libmacho_macho_functions():
       
    62     libmacho = ctypes.CDLL('/usr/lib/system/libmacho.dylib', use_errno=True)
       
    63     macho = _NS()
       
    64     macho.get_host = libmacho.mach_host_self
       
    65     macho.get_host.argtypes = []
       
    66     macho.get_host.restype = ctypes.c_uint
       
    67     macho.get_clock = libmacho.host_get_clock_service
       
    68     macho.get_clock.argtypes = [ctypes.c_uint,
       
    69                                 ctypes.c_int,
       
    70                                 ctypes.POINTER(ctypes.c_uint)
       
    71                                 ]
       
    72     macho.get_time = libmacho.clock_get_time
       
    73     macho.get_time.argtypes = [ctypes.c_uint, ctypes.POINTER(_mach_timespec)]
       
    74     macho.deallocate = libmacho.mach_port_deallocate
       
    75     macho.deallocate.argtypes = [ctypes.c_uint, ctypes.c_uint]
       
    76     return libmacho, macho
       
    77 
       
    78 
       
    79 def _get_ctypes_clock_gettime(library):
       
    80     clock_gettime = ctypes.CDLL(library, use_errno=True).clock_gettime
       
    81     clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(_posix_timespec)]
       
    82     return clock_gettime
       
    83 
       
    84 
       
    85 def _call_ctypes_clock_gettime(clock_gettime, clockid):
       
    86     timespec = _posix_timespec()
       
    87     ret = clock_gettime(clockid, ctypes.pointer(timespec))
       
    88     if int(ret) != 0:
       
    89         errno_ = ctypes.get_errno()
       
    90         raise OSError(errno_, os.strerror(errno_))
       
    91     return timespec
       
    92 
       
    93 
       
    94 _py_monotonic = getattr(time, 'monotonic', None)
       
    95 if _py_monotonic is not None:
       
    96     monotonic = _py_monotonic
       
    97 elif sys.platform.startswith('linux'):
       
    98     _clock_gettime = _get_ctypes_clock_gettime('librt.so.1')
       
    99 
       
   100     def monotonic():
       
   101         clockid = ctypes.c_int(1)
       
   102         timespec = _call_ctypes_clock_gettime(_clock_gettime, clockid)
       
   103         return _timespec_to_seconds(timespec)
       
   104 elif sys.platform.startswith('freebsd'):
       
   105     _clock_gettime = _get_ctypes_clock_gettime('libc.so')
       
   106 
       
   107     def monotonic():
       
   108         clockid = ctypes.c_int(4)
       
   109         timespec = _call_ctypes_clock_gettime(_clock_gettime, clockid)
       
   110         return _timespec_to_seconds(timespec)
       
   111 elif sys.platform.startswith('darwin') and _machine64 == ('x86_64', True):
       
   112     _libmacho, _macho = _get_ctypes_libmacho_macho_functions()
       
   113 
       
   114     @contextlib.contextmanager
       
   115     def _deallocate(task, port):
       
   116         try:
       
   117             yield
       
   118         finally:
       
   119             if int(_macho.deallocate(task, port)) == 0:
       
   120                 return
       
   121             errno_ = ctypes.get_errno()
       
   122             raise OSError(errno_, os.strerror(errno_))
       
   123 
       
   124     def monotonic():
       
   125         task = ctypes.c_uint.in_dll(_libmacho, 'mach_task_self_')
       
   126         host = _macho.get_host()
       
   127         with _deallocate(task, host):
       
   128             clock = ctypes.c_uint(0)
       
   129             clockid = ctypes.c_int(0)
       
   130             ret = _macho.get_clock(host, clockid, ctypes.pointer(clock))
       
   131             if int(ret) != 0:
       
   132                 errno_ = ctypes.get_errno()
       
   133                 raise OSError(errno_, os.strerror(errno_))
       
   134             with _deallocate(task, clock):
       
   135                 timespec = _mach_timespec()
       
   136                 ret = _macho.get_time(clock, ctypes.pointer(timespec))
       
   137                 if int(ret) != 0:
       
   138                     errno_ = ctypes.get_errno()
       
   139                     raise OSError(errno_, os.strerror(errno_))
       
   140                 return _timespec_to_seconds(timespec)
       
   141 elif sys.platform.startswith('win32'):
       
   142     _GetTickCount = getattr(ctypes.windll.kernel32, 'GetTickCount64', None)
       
   143 
       
   144     if _GetTickCount is not None:
       
   145         _GetTickCount.restype = ctypes.c_uint64
       
   146     else:
       
   147         _GetTickCount = ctypes.windll.kernel32.GetTickCount
       
   148         _GetTickCount.restype = ctypes.c_uint32
       
   149 
       
   150     def monotonic():
       
   151         return float(_GetTickCount()) * 1e-3
       
   152 else:
       
   153     def monotonic():
       
   154         msg = 'monotonic not supported on your platform'
       
   155         raise OSError(errno.ENOSYS, msg)
       
   156 
       
   157 
       
   158 if __name__ == '__main__':
       
   159     print(monotonic())