123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- # -*- coding: utf-8 -*-
- """
- monotonic
- ~~~~~~~~~
- This module provides a ``monotonic()`` function which returns the
- value (in fractional seconds) of a clock which never goes backwards.
- On Python 3.3 or newer, ``monotonic`` will be an alias of
- ``time.monotonic`` from the standard library. On older versions,
- it will fall back to an equivalent implementation:
- +-------------+----------------------------------------+
- | Linux, BSD | ``clock_gettime(3)`` |
- +-------------+----------------------------------------+
- | Windows | ``GetTickCount`` or ``GetTickCount64`` |
- +-------------+----------------------------------------+
- | OS X | ``mach_absolute_time`` |
- +-------------+----------------------------------------+
- If no suitable implementation exists for the current platform,
- attempting to import this module (or to import from it) will
- cause a ``RuntimeError`` exception to be raised.
- Copyright 2014, 2015, 2016 Ori Livneh <ori@wikimedia.org>
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- """
- import time
- __all__ = ('monotonic',)
- try:
- monotonic = time.monotonic
- except AttributeError:
- import ctypes
- import ctypes.util
- import os
- import sys
- import threading
- try:
- if sys.platform == 'darwin': # OS X, iOS
- # See Technical Q&A QA1398 of the Mac Developer Library:
- # <https://developer.apple.com/library/mac/qa/qa1398/>
- libc = ctypes.CDLL('/usr/lib/libc.dylib', use_errno=True)
- class mach_timebase_info_data_t(ctypes.Structure):
- """System timebase info. Defined in <mach/mach_time.h>."""
- _fields_ = (('numer', ctypes.c_uint32),
- ('denom', ctypes.c_uint32))
- mach_absolute_time = libc.mach_absolute_time
- mach_absolute_time.restype = ctypes.c_uint64
- timebase = mach_timebase_info_data_t()
- libc.mach_timebase_info(ctypes.byref(timebase))
- nanoseconds_in_second = 1.0e9
- def monotonic():
- """Monotonic clock, cannot go backward."""
- nanoseconds = mach_absolute_time() * timebase.numer / timebase.denom
- return nanoseconds / nanoseconds_in_second
- elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
- if sys.platform.startswith('cygwin'):
- # Note: cygwin implements clock_gettime (CLOCK_MONOTONIC = 4) since
- # version 1.7.6. Using raw WinAPI for maximum version compatibility.
- # Ugly hack using the wrong calling convention (in 32-bit mode)
- # because ctypes has no windll under cygwin (and it also seems that
- # the code letting you select stdcall in _ctypes doesn't exist under
- # the preprocessor definitions relevant to cygwin).
- # This is 'safe' because:
- # 1. The ABI of GetTickCount and GetTickCount64 is identical for
- # both calling conventions because they both have no parameters.
- # 2. libffi masks the problem because after making the call it doesn't
- # touch anything through esp and epilogue code restores a correct
- # esp from ebp afterwards.
- try:
- kernel32 = ctypes.cdll.kernel32
- except OSError: # 'No such file or directory'
- kernel32 = ctypes.cdll.LoadLibrary('kernel32.dll')
- else:
- kernel32 = ctypes.windll.kernel32
- GetTickCount64 = getattr(kernel32, 'GetTickCount64', None)
- if GetTickCount64:
- # Windows Vista / Windows Server 2008 or newer.
- GetTickCount64.restype = ctypes.c_ulonglong
- def monotonic():
- """Monotonic clock, cannot go backward."""
- return GetTickCount64() / 1000.0
- else:
- # Before Windows Vista.
- GetTickCount = kernel32.GetTickCount
- GetTickCount.restype = ctypes.c_uint32
- get_tick_count_lock = threading.Lock()
- get_tick_count_last_sample = 0
- get_tick_count_wraparounds = 0
- def monotonic():
- """Monotonic clock, cannot go backward."""
- global get_tick_count_last_sample
- global get_tick_count_wraparounds
- with get_tick_count_lock:
- current_sample = GetTickCount()
- if current_sample < get_tick_count_last_sample:
- get_tick_count_wraparounds += 1
- get_tick_count_last_sample = current_sample
- final_milliseconds = get_tick_count_wraparounds << 32
- final_milliseconds += get_tick_count_last_sample
- return final_milliseconds / 1000.0
- else:
- try:
- clock_gettime = ctypes.CDLL(ctypes.util.find_library('c'),
- use_errno=True).clock_gettime
- except Exception:
- clock_gettime = ctypes.CDLL(ctypes.util.find_library('rt'),
- use_errno=True).clock_gettime
- class timespec(ctypes.Structure):
- """Time specification, as described in clock_gettime(3)."""
- _fields_ = (('tv_sec', ctypes.c_long),
- ('tv_nsec', ctypes.c_long))
- if sys.platform.startswith('linux'):
- CLOCK_MONOTONIC = 1
- elif sys.platform.startswith('freebsd'):
- CLOCK_MONOTONIC = 4
- elif sys.platform.startswith('sunos5'):
- CLOCK_MONOTONIC = 4
- elif 'bsd' in sys.platform:
- CLOCK_MONOTONIC = 3
- elif sys.platform.startswith('aix'):
- CLOCK_MONOTONIC = ctypes.c_longlong(10)
- def monotonic():
- """Monotonic clock, cannot go backward."""
- ts = timespec()
- if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(ts)):
- errno = ctypes.get_errno()
- raise OSError(errno, os.strerror(errno))
- return ts.tv_sec + ts.tv_nsec / 1.0e9
- # Perform a sanity-check.
- if monotonic() - monotonic() > 0:
- raise ValueError('monotonic() is not monotonic!')
- except Exception as e:
- raise RuntimeError('no suitable implementation for this system: ' + repr(e))
|