master
py 201 lines 7.75 KB
Raw
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))