| 1 | # -*- coding: utf-8 -*- |
| 2 | # |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | """ |
| 5 | monotonic |
| 6 | ~~~~~~~~~ |
| 7 | |
| 8 | This module provides a ``monotonic()`` function which returns the |
| 9 | value (in fractional seconds) of a clock which never goes backwards. |
| 10 | |
| 11 | On Python 3.3 or newer, ``monotonic`` will be an alias of |
| 12 | ``time.monotonic`` from the standard library. On older versions, |
| 13 | it will fall back to an equivalent implementation: |
| 14 | |
| 15 | +-------------+----------------------------------------+ |
| 16 | | Linux, BSD | ``clock_gettime(3)`` | |
| 17 | +-------------+----------------------------------------+ |
| 18 | | Windows | ``GetTickCount`` or ``GetTickCount64`` | |
| 19 | +-------------+----------------------------------------+ |
| 20 | | OS X | ``mach_absolute_time`` | |
| 21 | +-------------+----------------------------------------+ |
| 22 | |
| 23 | If no suitable implementation exists for the current platform, |
| 24 | attempting to import this module (or to import from it) will |
| 25 | cause a ``RuntimeError`` exception to be raised. |
| 26 | |
| 27 | |
| 28 | Copyright 2014, 2015, 2016 Ori Livneh <ori@wikimedia.org> |
| 29 | |
| 30 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 31 | you may not use this file except in compliance with the License. |
| 32 | You may obtain a copy of the License at |
| 33 | |
| 34 | http://www.apache.org/licenses/LICENSE-2.0 |
| 35 | |
| 36 | Unless required by applicable law or agreed to in writing, software |
| 37 | distributed under the License is distributed on an "AS IS" BASIS, |
| 38 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 39 | See the License for the specific language governing permissions and |
| 40 | limitations under the License. |
| 41 | |
| 42 | """ |
| 43 | import time |
| 44 | |
| 45 | |
| 46 | __all__ = ('monotonic',) |
| 47 | |
| 48 | |
| 49 | try: |
| 50 | monotonic = time.monotonic |
| 51 | except AttributeError: |
| 52 | import ctypes |
| 53 | import ctypes.util |
| 54 | import os |
| 55 | import sys |
| 56 | import threading |
| 57 | |
| 58 | |
| 59 | def clock_clock_gettime_c_library(): |
| 60 | return ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True).clock_gettime |
| 61 | |
| 62 | |
| 63 | def clock_clock_gettime_rt_library(): |
| 64 | return ctypes.CDLL(ctypes.util.find_library('rt'), use_errno=True).clock_gettime |
| 65 | |
| 66 | |
| 67 | def clock_clock_gettime_c_library_synology6(): |
| 68 | return ctypes.CDLL('/usr/lib/libc.so.6', use_errno=True).clock_gettime |
| 69 | |
| 70 | |
| 71 | def clock_clock_gettime_rt_library_synology6(): |
| 72 | return ctypes.CDLL('/usr/lib/librt.so.1', use_errno=True).clock_gettime |
| 73 | |
| 74 | |
| 75 | def clock_gettime_linux(): |
| 76 | # see https://github.com/netdata/netdata/issues/7976 |
| 77 | order = [ |
| 78 | clock_clock_gettime_c_library, |
| 79 | clock_clock_gettime_rt_library, |
| 80 | clock_clock_gettime_c_library_synology6, |
| 81 | clock_clock_gettime_rt_library_synology6, |
| 82 | ] |
| 83 | |
| 84 | for gettime in order: |
| 85 | try: |
| 86 | return gettime() |
| 87 | except (RuntimeError, AttributeError, OSError): |
| 88 | continue |
| 89 | raise RuntimeError('can not find c and rt libraries') |
| 90 | |
| 91 | |
| 92 | try: |
| 93 | if sys.platform == 'darwin': # OS X, iOS |
| 94 | # See Technical Q&A QA1398 of the Mac Developer Library: |
| 95 | # <https://developer.apple.com/library/mac/qa/qa1398/> |
| 96 | libc = ctypes.CDLL('/usr/lib/libc.dylib', use_errno=True) |
| 97 | |
| 98 | class mach_timebase_info_data_t(ctypes.Structure): |
| 99 | """System timebase info. Defined in <mach/mach_time.h>.""" |
| 100 | _fields_ = (('numer', ctypes.c_uint32), |
| 101 | ('denom', ctypes.c_uint32)) |
| 102 | |
| 103 | mach_absolute_time = libc.mach_absolute_time |
| 104 | mach_absolute_time.restype = ctypes.c_uint64 |
| 105 | |
| 106 | timebase = mach_timebase_info_data_t() |
| 107 | libc.mach_timebase_info(ctypes.byref(timebase)) |
| 108 | ticks_per_second = timebase.numer / timebase.denom * 1.0e9 |
| 109 | |
| 110 | def monotonic(): |
| 111 | """Monotonic clock, cannot go backward.""" |
| 112 | return mach_absolute_time() / ticks_per_second |
| 113 | |
| 114 | elif sys.platform.startswith('win32') or sys.platform.startswith('cygwin'): |
| 115 | if sys.platform.startswith('cygwin'): |
| 116 | # Note: cygwin implements clock_gettime (CLOCK_MONOTONIC = 4) since |
| 117 | # version 1.7.6. Using raw WinAPI for maximum version compatibility. |
| 118 | |
| 119 | # Ugly hack using the wrong calling convention (in 32-bit mode) |
| 120 | # because ctypes has no windll under cygwin (and it also seems that |
| 121 | # the code letting you select stdcall in _ctypes doesn't exist under |
| 122 | # the preprocessor definitions relevant to cygwin). |
| 123 | # This is 'safe' because: |
| 124 | # 1. The ABI of GetTickCount and GetTickCount64 is identical for |
| 125 | # both calling conventions because they both have no parameters. |
| 126 | # 2. libffi masks the problem because after making the call it doesn't |
| 127 | # touch anything through esp and epilogue code restores a correct |
| 128 | # esp from ebp afterwards. |
| 129 | try: |
| 130 | kernel32 = ctypes.cdll.kernel32 |
| 131 | except OSError: # 'No such file or directory' |
| 132 | kernel32 = ctypes.cdll.LoadLibrary('kernel32.dll') |
| 133 | else: |
| 134 | kernel32 = ctypes.windll.kernel32 |
| 135 | |
| 136 | GetTickCount64 = getattr(kernel32, 'GetTickCount64', None) |
| 137 | if GetTickCount64: |
| 138 | # Windows Vista / Windows Server 2008 or newer. |
| 139 | GetTickCount64.restype = ctypes.c_ulonglong |
| 140 | |
| 141 | def monotonic(): |
| 142 | """Monotonic clock, cannot go backward.""" |
| 143 | return GetTickCount64() / 1000.0 |
| 144 | |
| 145 | else: |
| 146 | # Before Windows Vista. |
| 147 | GetTickCount = kernel32.GetTickCount |
| 148 | GetTickCount.restype = ctypes.c_uint32 |
| 149 | |
| 150 | get_tick_count_lock = threading.Lock() |
| 151 | get_tick_count_last_sample = 0 |
| 152 | get_tick_count_wraparounds = 0 |
| 153 | |
| 154 | def monotonic(): |
| 155 | """Monotonic clock, cannot go backward.""" |
| 156 | global get_tick_count_last_sample |
| 157 | global get_tick_count_wraparounds |
| 158 | |
| 159 | with get_tick_count_lock: |
| 160 | current_sample = GetTickCount() |
| 161 | if current_sample < get_tick_count_last_sample: |
| 162 | get_tick_count_wraparounds += 1 |
| 163 | get_tick_count_last_sample = current_sample |
| 164 | |
| 165 | final_milliseconds = get_tick_count_wraparounds << 32 |
| 166 | final_milliseconds += get_tick_count_last_sample |
| 167 | return final_milliseconds / 1000.0 |
| 168 | |
| 169 | else: |
| 170 | clock_gettime = clock_gettime_linux() |
| 171 | |
| 172 | class timespec(ctypes.Structure): |
| 173 | """Time specification, as described in clock_gettime(3).""" |
| 174 | _fields_ = (('tv_sec', ctypes.c_long), |
| 175 | ('tv_nsec', ctypes.c_long)) |
| 176 | |
| 177 | if sys.platform.startswith('linux'): |
| 178 | CLOCK_MONOTONIC = 1 |
| 179 | elif sys.platform.startswith('freebsd'): |
| 180 | CLOCK_MONOTONIC = 4 |
| 181 | elif sys.platform.startswith('sunos5'): |
| 182 | CLOCK_MONOTONIC = 4 |
| 183 | elif 'bsd' in sys.platform: |
| 184 | CLOCK_MONOTONIC = 3 |
| 185 | elif sys.platform.startswith('aix'): |
| 186 | CLOCK_MONOTONIC = ctypes.c_longlong(10) |
| 187 | |
| 188 | def monotonic(): |
| 189 | """Monotonic clock, cannot go backward.""" |
| 190 | ts = timespec() |
| 191 | if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(ts)): |
| 192 | errno = ctypes.get_errno() |
| 193 | raise OSError(errno, os.strerror(errno)) |
| 194 | return ts.tv_sec + ts.tv_nsec / 1.0e9 |
| 195 | |
| 196 | # Perform a sanity-check. |
| 197 | if monotonic() - monotonic() > 0: |
| 198 | raise ValueError('monotonic() is not monotonic!') |
| 199 | |
| 200 | except Exception as e: |
| 201 | raise RuntimeError('no suitable implementation for this system: ' + repr(e)) |