| # 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 |
| DETECTED_PLATFORM = 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 |
| } |
| |
| |
| def GetMacNowFunction(plat): |
| """ Get a monotonic clock for the Mac platform. |
| |
| Args: |
| plat: Platform that is being run on. Unused in GetMacNowFunction. Passed |
| for consistency between initilaizers. |
| Returns: |
| Function pointer to monotonic clock for mac platform. |
| """ |
| del plat # Unused |
| global DETECTED_PLATFORM # pylint: disable=global-statement |
| DETECTED_PLATFORM = 'mac' |
| 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 GetMacNowFunctionImpl(): |
| return mach_absolute_time() / ticks_per_second |
| return GetMacNowFunctionImpl |
| |
| |
| def GetClockGetTimeClockNumber(plat): |
| for key in _CLOCK_MONOTONIC: |
| if plat.startswith(key): |
| return _CLOCK_MONOTONIC[key] |
| raise LookupError('Platform not in clock dicitonary') |
| |
| def GetLinuxNowFunction(plat): |
| """ Get a monotonic clock for linux platforms. |
| |
| Args: |
| plat: Platform that is being run on. |
| Returns: |
| Function pointer to monotonic clock for linux platform. |
| """ |
| global DETECTED_PLATFORM # pylint: disable=global-statement |
| DETECTED_PLATFORM = 'linux' |
| 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 GetLinuxNowFunctionImpl(): |
| 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 |
| |
| return GetLinuxNowFunctionImpl |
| |
| |
| 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 |
| 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 GetWinNowFunction(plat): |
| """ Get a monotonic clock for windows platforms. |
| |
| Args: |
| plat: Platform that is being run on. |
| Returns: |
| Function pointer to monotonic clock for windows platform. |
| """ |
| global DETECTED_PLATFORM # pylint: disable=global-statement |
| DETECTED_PLATFORM = 'windows' |
| if IsQPCUsable(): |
| 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 GetWinNowFunctionImpl(): |
| qpc(ctypes.byref(qpc_return)) |
| return qpc_return.value / qpc_frequency |
| |
| else: |
| 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 GetWinNowFunctionImpl(): |
| 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 GetWinNowFunctionImpl(): |
| 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 |
| return GetWinNowFunctionImpl |
| |
| |
| def InitializeNowFunction(plat): |
| """ Get a monotonic clock for the current platform. |
| |
| Args: |
| plat: Platform that is being run on. |
| Returns: |
| Function pointer to monotonic clock function for current platform. |
| """ |
| if plat.startswith(_PLATFORMS['mac']): |
| return GetMacNowFunction(plat) |
| |
| elif (plat.startswith(_PLATFORMS['linux']) |
| or plat.startswith(_PLATFORMS['freebsd']) |
| or plat.startswith(_PLATFORMS['bsd']) |
| or plat.startswith(_PLATFORMS['sunos'])): |
| return GetLinuxNowFunction(plat) |
| |
| elif (plat.startswith(_PLATFORMS['windows']) |
| or plat.startswith(_PLATFORMS['cygwin'])): |
| return GetWinNowFunction(plat) |
| |
| else: |
| raise RuntimeError('%s is not a supported platform.' % plat) |
| |
| def Now(): |
| return monotonic() |
| |
| monotonic = InitializeNowFunction(sys.platform) |