Home | History | Annotate | Download | only in py_trace_event
      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