summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/catapult/common/py_trace_event/py_trace_event/trace_time.py
blob: c5e3fe1ed925303e4b3a8f5129abd6126a31d59f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# 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)