| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import ctypes |
| import ctypes.util |
| import logging |
| import os |
| import platform |
| import sys |
| import time |
| import threading |
| |
| |
| GET_TICK_COUNT_LAST_NOW = 0 |
| # If GET_TICK_COUNTER_LAST_NOW is less than the current time, the clock has |
| # rolled over, and this needs to be accounted for. |
| GET_TICK_COUNT_WRAPAROUNDS = 0 |
| # The current detected platform |
| _CLOCK = None |
| _NOW_FUNCTION = None |
| # Mapping of supported platforms and what is returned by sys.platform. |
| _PLATFORMS = { |
| 'mac': 'darwin', |
| 'linux': 'linux', |
| 'windows': 'win32', |
| 'cygwin': 'cygwin', |
| 'freebsd': 'freebsd', |
| 'sunos': 'sunos5', |
| 'bsd': 'bsd' |
| } |
| # Mapping of what to pass get_clocktime based on platform. |
| _CLOCK_MONOTONIC = { |
| 'linux': 1, |
| 'freebsd': 4, |
| 'bsd': 3, |
| 'sunos5': 4 |
| } |
| |
| _LINUX_CLOCK = 'LINUX_CLOCK_MONOTONIC' |
| _MAC_CLOCK = 'MAC_MACH_ABSOLUTE_TIME' |
| _WIN_HIRES = 'WIN_QPC' |
| _WIN_LORES = 'WIN_ROLLOVER_PROTECTED_TIME_GET_TIME' |
| |
| def InitializeMacNowFunction(plat): |
| """Sets a monotonic clock for the Mac platform. |
| |
| Args: |
| plat: Platform that is being run on. Unused in GetMacNowFunction. Passed |
| for consistency between initilaizers. |
| """ |
| del plat # Unused |
| global _CLOCK # pylint: disable=global-statement |
| global _NOW_FUNCTION # pylint: disable=global-statement |
| _CLOCK = _MAC_CLOCK |
| libc = ctypes.CDLL('/usr/lib/libc.dylib', use_errno=True) |
| class MachTimebaseInfoData(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 = MachTimebaseInfoData() |
| libc.mach_timebase_info(ctypes.byref(timebase)) |
| ticks_per_second = timebase.numer / timebase.denom * 1.0e9 |
| |
| def MacNowFunctionImpl(): |
| return mach_absolute_time() / ticks_per_second |
| _NOW_FUNCTION = MacNowFunctionImpl |
| |
| |
| def GetClockGetTimeClockNumber(plat): |
| for key in _CLOCK_MONOTONIC: |
| if plat.startswith(key): |
| return _CLOCK_MONOTONIC[key] |
| raise LookupError('Platform not in clock dicitonary') |
| |
| def InitializeLinuxNowFunction(plat): |
| """Sets a monotonic clock for linux platforms. |
| |
| Args: |
| plat: Platform that is being run on. |
| """ |
| global _CLOCK # pylint: disable=global-statement |
| global _NOW_FUNCTION # pylint: disable=global-statement |
| _CLOCK = _LINUX_CLOCK |
| clock_monotonic = GetClockGetTimeClockNumber(plat) |
| try: |
| # Attempt to find clock_gettime in the C library. |
| clock_gettime = ctypes.CDLL(ctypes.util.find_library('c'), |
| use_errno=True).clock_gettime |
| except AttributeError: |
| # If not able to find int in the C library, look in rt library. |
| 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)) |
| |
| def LinuxNowFunctionImpl(): |
| 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 |
| |
| _NOW_FUNCTION = LinuxNowFunctionImpl |
| |
| |
| def IsQPCUsable(): |
| """Determines if system can query the performance counter. |
| The performance counter is a high resolution timer on windows systems. |
| Some chipsets have unreliable performance counters, so this checks that one |
| of those chipsets is not present. |
| |
| Returns: |
| True if QPC is useable, false otherwise. |
| """ |
| |
| # Sample output: 'Intel64 Family 6 Model 23 Stepping 6, GenuineIntel' |
| info = platform.processor() |
| if 'AuthenticAMD' in info and 'Family 15' in info: |
| return False |
| if not hasattr(ctypes, 'windll'): |
| return False |
| try: # If anything goes wrong during this, assume QPC isn't available. |
| frequency = ctypes.c_int64() |
| ctypes.windll.Kernel32.QueryPerformanceFrequency( |
| ctypes.byref(frequency)) |
| if float(frequency.value) <= 0: |
| return False |
| except Exception: # pylint: disable=broad-except |
| logging.exception('Error when determining if QPC is usable.') |
| return False |
| return True |
| |
| |
| def InitializeWinNowFunction(plat): |
| """Sets a monotonic clock for windows platforms. |
| |
| Args: |
| plat: Platform that is being run on. |
| """ |
| global _CLOCK # pylint: disable=global-statement |
| global _NOW_FUNCTION # pylint: disable=global-statement |
| |
| if IsQPCUsable(): |
| _CLOCK = _WIN_HIRES |
| qpc_return = ctypes.c_int64() |
| qpc_frequency = ctypes.c_int64() |
| ctypes.windll.Kernel32.QueryPerformanceFrequency( |
| ctypes.byref(qpc_frequency)) |
| qpc_frequency = float(qpc_frequency.value) |
| qpc = ctypes.windll.Kernel32.QueryPerformanceCounter |
| |
| def WinNowFunctionImpl(): |
| qpc(ctypes.byref(qpc_return)) |
| return qpc_return.value / qpc_frequency |
| |
| else: |
| _CLOCK = _WIN_LORES |
| kernel32 = (ctypes.cdll.kernel32 |
| if plat.startswith(_PLATFORMS['cygwin']) |
| else ctypes.windll.kernel32) |
| get_tick_count_64 = getattr(kernel32, 'GetTickCount64', None) |
| |
| # Windows Vista or newer |
| if get_tick_count_64: |
| get_tick_count_64.restype = ctypes.c_ulonglong |
| |
| def WinNowFunctionImpl(): |
| return get_tick_count_64() / 1000.0 |
| |
| else: # Pre Vista. |
| get_tick_count = kernel32.GetTickCount |
| get_tick_count.restype = ctypes.c_uint32 |
| get_tick_count_lock = threading.Lock() |
| |
| def WinNowFunctionImpl(): |
| global GET_TICK_COUNT_LAST_NOW # pylint: disable=global-statement |
| global GET_TICK_COUNT_WRAPAROUNDS # pylint: disable=global-statement |
| with get_tick_count_lock: |
| current_sample = get_tick_count() |
| if current_sample < GET_TICK_COUNT_LAST_NOW: |
| GET_TICK_COUNT_WRAPAROUNDS += 1 |
| GET_TICK_COUNT_LAST_NOW = current_sample |
| final_ms = GET_TICK_COUNT_WRAPAROUNDS << 32 |
| final_ms += GET_TICK_COUNT_LAST_NOW |
| return final_ms / 1000.0 |
| |
| _NOW_FUNCTION = WinNowFunctionImpl |
| |
| |
| def InitializeNowFunction(plat): |
| """Sets a monotonic clock for the current platform. |
| |
| Args: |
| plat: Platform that is being run on. |
| """ |
| if plat.startswith(_PLATFORMS['mac']): |
| InitializeMacNowFunction(plat) |
| |
| elif (plat.startswith(_PLATFORMS['linux']) |
| or plat.startswith(_PLATFORMS['freebsd']) |
| or plat.startswith(_PLATFORMS['bsd']) |
| or plat.startswith(_PLATFORMS['sunos'])): |
| InitializeLinuxNowFunction(plat) |
| |
| elif (plat.startswith(_PLATFORMS['windows']) |
| or plat.startswith(_PLATFORMS['cygwin'])): |
| InitializeWinNowFunction(plat) |
| |
| else: |
| raise RuntimeError('%s is not a supported platform.' % plat) |
| |
| global _NOW_FUNCTION |
| global _CLOCK |
| assert _NOW_FUNCTION, 'Now function not properly set during initialization.' |
| assert _CLOCK, 'Clock not properly set during initialization.' |
| |
| |
| def Now(): |
| return _NOW_FUNCTION() * 1e6 # convert from seconds to microseconds |
| |
| |
| def GetClock(): |
| return _CLOCK |
| |
| |
| InitializeNowFunction(sys.platform) |