summaryrefslogtreecommitdiff
path: root/asyncio/time_monotonic.py
blob: c820e9228fde577c6015c06b444efb686d656bd5 (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
"""
Backport of time.monotonic() of Python 3.3 (PEP 418) for Python 2.7.

Export one function: time_monotonic(). This clock may or may not be monotonic
depending on the operating system.

Support Windows, Mac OS X, Linux, FreeBSD, OpenBSD and Solaris, but requires
the ctypes module.
"""
import os
import sys

__all__ = ('time_monotonic',)

# default implementation: system clock (non monotonic!)
from time import time as time_monotonic

if os.name == "nt":
    # Windows: use GetTickCount64() or GetTickCount()
    try:
        import ctypes
        from ctypes import windll
        from ctypes.wintypes import DWORD
    except ImportError:
        pass

    # GetTickCount64() requires Windows Vista, Server 2008 or later
    if hasattr(windll.kernel32, 'GetTickCount64'):
        ULONGLONG = ctypes.c_uint64

        GetTickCount64 = windll.kernel32.GetTickCount64
        GetTickCount64.restype = ULONGLONG
        GetTickCount64.argtypes = ()

        def time_monotonic():
            return GetTickCount64() * 1e-3
    else:
        GetTickCount = windll.kernel32.GetTickCount
        GetTickCount.restype = DWORD
        GetTickCount.argtypes = ()

        # Detect GetTickCount() integer overflow (32 bits, roll-over after 49.7
        # days). It increases an internal epoch (reference time) by 2^32 each
        # time that an overflow is detected. The epoch is stored in the
        # process-local state and so the value of time_monotonic() may be
        # different in two Python processes running for more than 49 days.
        def time_monotonic(use_info):
            ticks = GetTickCount()
            if ticks < time_monotonic.last:
                # Integer overflow detected
                time_monotonic.delta += 2**32
            time_monotonic.last = ticks
            return (ticks + time_monotonic.delta) * 1e-3
        time_monotonic.last = 0
        time_monotonic.delta = 0

elif sys.platform == 'darwin':
    # Mac OS X: use mach_absolute_time() and mach_timebase_info()
    try:
        import ctypes
        import ctypes.util
        libc_name = ctypes.util.find_library('c')
    except ImportError:
        libc_name = None
    if libc_name:
        libc = ctypes.CDLL(libc_name, use_errno=True)

        mach_absolute_time = libc.mach_absolute_time
        mach_absolute_time.argtypes = ()
        mach_absolute_time.restype = ctypes.c_uint64

        class mach_timebase_info_data_t(ctypes.Structure):
            _fields_ = (
                ('numer', ctypes.c_uint32),
                ('denom', ctypes.c_uint32),
            )
        mach_timebase_info_data_p = ctypes.POINTER(mach_timebase_info_data_t)

        mach_timebase_info = libc.mach_timebase_info
        mach_timebase_info.argtypes = (mach_timebase_info_data_p,)
        mach_timebase_info.restype = ctypes.c_int

        def time_monotonic():
            return mach_absolute_time() * time_monotonic.factor

        timebase = mach_timebase_info_data_t()
        mach_timebase_info(ctypes.byref(timebase))
        time_monotonic.factor = float(timebase.numer) / timebase.denom * 1e-9
        del timebase

elif sys.platform.startswith(("linux", "freebsd", "openbsd", "sunos")):
    # Linux, FreeBSD, OpenBSD: use clock_gettime(CLOCK_MONOTONIC)
    # Solaris: use clock_gettime(CLOCK_HIGHRES)

    library = None
    try:
        import ctypes
        import ctypes.util
    except ImportError:
        libraries = ()
    else:
        if sys.platform.startswith(("freebsd", "openbsd")):
            libraries = ('c',)
        elif sys.platform.startswith("linux"):
            # Linux: in glibc 2.17+, clock_gettime() is provided by the libc,
            # on older versions, it is provided by librt
            libraries = ('c', 'rt')
        else:
            # Solaris
            libraries = ('rt',)

    for name in libraries:
        filename = ctypes.util.find_library(name)
        if not filename:
            continue
        library = ctypes.CDLL(filename, use_errno=True)
        if not hasattr(library, 'clock_gettime'):
            library = None

    if library is not None:
        time_t = ctypes.c_long
        clockid_t = ctypes.c_int

        class timespec(ctypes.Structure):
            _fields_ = (
                ('tv_sec', time_t),
                ('tv_nsec', ctypes.c_long),
            )
        timespec_p = ctypes.POINTER(timespec)

        clock_gettime = library.clock_gettime
        clock_gettime.argtypes = (clockid_t, timespec_p)
        clock_gettime.restype = ctypes.c_int

        def time_monotonic():
            ts = timespec()
            err = clock_gettime(time_monotonic.clk_id, ctypes.byref(ts))
            if err:
                errno = ctypes.get_errno()
                message = os.strerror(errno)
                return OSError(errno, message)
            return ts.tv_sec + ts.tv_nsec * 1e-9

        if sys.platform.startswith("linux"):
            time_monotonic.clk_id = 1   # CLOCK_MONOTONIC
        elif sys.platform.startswith("freebsd"):
            time_monotonic.clk_id = 4   # CLOCK_MONOTONIC
        elif sys.platform.startswith("openbsd"):
            time_monotonic.clk_id = 3   # CLOCK_MONOTONIC
        elif sys.platform.startswith("sunos"):
            time_monotonic.clk_id = 4   # CLOCK_HIGHRES