1 # Copyright 2016 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import ctypes 6 import ctypes.util 7 import logging 8 import os 9 import platform 10 import sys 11 import time 12 import threading 13 14 15 GET_TICK_COUNT_LAST_NOW = 0 16 # If GET_TICK_COUNTER_LAST_NOW is less than the current time, the clock has 17 # rolled over, and this needs to be accounted for. 18 GET_TICK_COUNT_WRAPAROUNDS = 0 19 # The current detected platform 20 _CLOCK = None 21 _NOW_FUNCTION = None 22 # Mapping of supported platforms and what is returned by sys.platform. 23 _PLATFORMS = { 24 'mac': 'darwin', 25 'linux': 'linux', 26 'windows': 'win32', 27 'cygwin': 'cygwin', 28 'freebsd': 'freebsd', 29 'sunos': 'sunos5', 30 'bsd': 'bsd' 31 } 32 # Mapping of what to pass get_clocktime based on platform. 33 _CLOCK_MONOTONIC = { 34 'linux': 1, 35 'freebsd': 4, 36 'bsd': 3, 37 'sunos5': 4 38 } 39 40 _LINUX_CLOCK = 'LINUX_CLOCK_MONOTONIC' 41 _MAC_CLOCK = 'MAC_MACH_ABSOLUTE_TIME' 42 _WIN_HIRES = 'WIN_QPC' 43 _WIN_LORES = 'WIN_ROLLOVER_PROTECTED_TIME_GET_TIME' 44 45 def InitializeMacNowFunction(plat): 46 """Sets a monotonic clock for the Mac platform. 47 48 Args: 49 plat: Platform that is being run on. Unused in GetMacNowFunction. Passed 50 for consistency between initilaizers. 51 """ 52 del plat # Unused 53 global _CLOCK # pylint: disable=global-statement 54 global _NOW_FUNCTION # pylint: disable=global-statement 55 _CLOCK = _MAC_CLOCK 56 libc = ctypes.CDLL('/usr/lib/libc.dylib', use_errno=True) 57 class MachTimebaseInfoData(ctypes.Structure): 58 """System timebase info. Defined in <mach/mach_time.h>.""" 59 _fields_ = (('numer', ctypes.c_uint32), 60 ('denom', ctypes.c_uint32)) 61 62 mach_absolute_time = libc.mach_absolute_time 63 mach_absolute_time.restype = ctypes.c_uint64 64 65 timebase = MachTimebaseInfoData() 66 libc.mach_timebase_info(ctypes.byref(timebase)) 67 ticks_per_second = timebase.numer / timebase.denom * 1.0e9 68 69 def MacNowFunctionImpl(): 70 return mach_absolute_time() / ticks_per_second 71 _NOW_FUNCTION = MacNowFunctionImpl 72 73 74 def GetClockGetTimeClockNumber(plat): 75 for key in _CLOCK_MONOTONIC: 76 if plat.startswith(key): 77 return _CLOCK_MONOTONIC[key] 78 raise LookupError('Platform not in clock dicitonary') 79 80 def InitializeLinuxNowFunction(plat): 81 """Sets a monotonic clock for linux platforms. 82 83 Args: 84 plat: Platform that is being run on. 85 """ 86 global _CLOCK # pylint: disable=global-statement 87 global _NOW_FUNCTION # pylint: disable=global-statement 88 _CLOCK = _LINUX_CLOCK 89 clock_monotonic = GetClockGetTimeClockNumber(plat) 90 try: 91 # Attempt to find clock_gettime in the C library. 92 clock_gettime = ctypes.CDLL(ctypes.util.find_library('c'), 93 use_errno=True).clock_gettime 94 except AttributeError: 95 # If not able to find int in the C library, look in rt library. 96 clock_gettime = ctypes.CDLL(ctypes.util.find_library('rt'), 97 use_errno=True).clock_gettime 98 99 class Timespec(ctypes.Structure): 100 """Time specification, as described in clock_gettime(3).""" 101 _fields_ = (('tv_sec', ctypes.c_long), 102 ('tv_nsec', ctypes.c_long)) 103 104 def LinuxNowFunctionImpl(): 105 ts = Timespec() 106 if clock_gettime(clock_monotonic, ctypes.pointer(ts)): 107 errno = ctypes.get_errno() 108 raise OSError(errno, os.strerror(errno)) 109 return ts.tv_sec + ts.tv_nsec / 1.0e9 110 111 _NOW_FUNCTION = LinuxNowFunctionImpl 112 113 114 def IsQPCUsable(): 115 """Determines if system can query the performance counter. 116 The performance counter is a high resolution timer on windows systems. 117 Some chipsets have unreliable performance counters, so this checks that one 118 of those chipsets is not present. 119 120 Returns: 121 True if QPC is useable, false otherwise. 122 """ 123 124 # Sample output: 'Intel64 Family 6 Model 23 Stepping 6, GenuineIntel' 125 info = platform.processor() 126 if 'AuthenticAMD' in info and 'Family 15' in info: 127 return False 128 if not hasattr(ctypes, 'windll'): 129 return False 130 try: # If anything goes wrong during this, assume QPC isn't available. 131 frequency = ctypes.c_int64() 132 ctypes.windll.Kernel32.QueryPerformanceFrequency( 133 ctypes.byref(frequency)) 134 if float(frequency.value) <= 0: 135 return False 136 except Exception: # pylint: disable=broad-except 137 logging.exception('Error when determining if QPC is usable.') 138 return False 139 return True 140 141 142 def InitializeWinNowFunction(plat): 143 """Sets a monotonic clock for windows platforms. 144 145 Args: 146 plat: Platform that is being run on. 147 """ 148 global _CLOCK # pylint: disable=global-statement 149 global _NOW_FUNCTION # pylint: disable=global-statement 150 151 if IsQPCUsable(): 152 _CLOCK = _WIN_HIRES 153 qpc_return = ctypes.c_int64() 154 qpc_frequency = ctypes.c_int64() 155 ctypes.windll.Kernel32.QueryPerformanceFrequency( 156 ctypes.byref(qpc_frequency)) 157 qpc_frequency = float(qpc_frequency.value) 158 qpc = ctypes.windll.Kernel32.QueryPerformanceCounter 159 160 def WinNowFunctionImpl(): 161 qpc(ctypes.byref(qpc_return)) 162 return qpc_return.value / qpc_frequency 163 164 else: 165 _CLOCK = _WIN_LORES 166 kernel32 = (ctypes.cdll.kernel32 167 if plat.startswith(_PLATFORMS['cygwin']) 168 else ctypes.windll.kernel32) 169 get_tick_count_64 = getattr(kernel32, 'GetTickCount64', None) 170 171 # Windows Vista or newer 172 if get_tick_count_64: 173 get_tick_count_64.restype = ctypes.c_ulonglong 174 175 def WinNowFunctionImpl(): 176 return get_tick_count_64() / 1000.0 177 178 else: # Pre Vista. 179 get_tick_count = kernel32.GetTickCount 180 get_tick_count.restype = ctypes.c_uint32 181 get_tick_count_lock = threading.Lock() 182 183 def WinNowFunctionImpl(): 184 global GET_TICK_COUNT_LAST_NOW # pylint: disable=global-statement 185 global GET_TICK_COUNT_WRAPAROUNDS # pylint: disable=global-statement 186 with get_tick_count_lock: 187 current_sample = get_tick_count() 188 if current_sample < GET_TICK_COUNT_LAST_NOW: 189 GET_TICK_COUNT_WRAPAROUNDS += 1 190 GET_TICK_COUNT_LAST_NOW = current_sample 191 final_ms = GET_TICK_COUNT_WRAPAROUNDS << 32 192 final_ms += GET_TICK_COUNT_LAST_NOW 193 return final_ms / 1000.0 194 195 _NOW_FUNCTION = WinNowFunctionImpl 196 197 198 def InitializeNowFunction(plat): 199 """Sets a monotonic clock for the current platform. 200 201 Args: 202 plat: Platform that is being run on. 203 """ 204 if plat.startswith(_PLATFORMS['mac']): 205 InitializeMacNowFunction(plat) 206 207 elif (plat.startswith(_PLATFORMS['linux']) 208 or plat.startswith(_PLATFORMS['freebsd']) 209 or plat.startswith(_PLATFORMS['bsd']) 210 or plat.startswith(_PLATFORMS['sunos'])): 211 InitializeLinuxNowFunction(plat) 212 213 elif (plat.startswith(_PLATFORMS['windows']) 214 or plat.startswith(_PLATFORMS['cygwin'])): 215 InitializeWinNowFunction(plat) 216 217 else: 218 raise RuntimeError('%s is not a supported platform.' % plat) 219 220 global _NOW_FUNCTION 221 global _CLOCK 222 assert _NOW_FUNCTION, 'Now function not properly set during initialization.' 223 assert _CLOCK, 'Clock not properly set during initialization.' 224 225 226 def Now(): 227 return _NOW_FUNCTION() * 1e6 # convert from seconds to microseconds 228 229 230 def GetClock(): 231 return _CLOCK 232 233 234 InitializeNowFunction(sys.platform) 235