jenkins-bot has submitted this change and it was merged. Change subject: Add a monotonic clock implementation ......................................................................
Add a monotonic clock implementation I needed a monotonic clock for a change I wanted to make to PeriodicThread. Python 3.3 introduced `time.monotonic`, but there's nothing equivalent for earlier versions of Python. So I set out to write a fallback implementation. One thing led to another, and I ended up with a small, self-standing library, which I published to GitHub and PyPI: - https://github.com/atdt/monotonic - https://pypi.python.org/pypi/monotonic So now there's the question of how to integrate it with EventLogging. We could treat it like a normal external dependency, but that's a lot of inconvenience for a library that fits in a single file and of which I am the primary author. So I decided to just bundle it here instead. Change-Id: If6166d2353f51d30a447b0a6405dbe8871431502 --- M server/eventlogging/compat.py A server/eventlogging/lib/__init__.py A server/eventlogging/lib/monotonic.py M server/tests/test_compat.py 4 files changed, 147 insertions(+), 2 deletions(-) Approvals: Ori.livneh: Looks good to me, approved jenkins-bot: Verified diff --git a/server/eventlogging/compat.py b/server/eventlogging/compat.py index e85da14..608bc42 100644 --- a/server/eventlogging/compat.py +++ b/server/eventlogging/compat.py @@ -14,12 +14,22 @@ # pylint: disable=E0611, F0401, E1101 from __future__ import unicode_literals +import ctypes +import ctypes.util import functools import hashlib import operator import sys +import time import uuid +import warnings +try: + from .lib.monotonic import monotonic as monotonic_clock +except ImportError: + warnings.warn('Using non-monotonic time.time() as last-resort fallback ' + 'for eventlogging.monotonic_clock()', RuntimeWarning) + monotonic_clock = time.time try: import simplejson as json @@ -27,9 +37,11 @@ import json -__all__ = ('http_get', 'items', 'json', 'unquote_plus', 'urisplit', - 'urlopen', 'uuid5') +__all__ = ('http_get', 'items', 'json', 'monotonic_clock', 'unquote_plus', + 'urisplit', 'urlopen', 'uuid5') +LIBC = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True) +CLOCK_MONOTONIC_RAW = 4 PY3 = sys.version_info[0] == 3 if PY3: diff --git a/server/eventlogging/lib/__init__.py b/server/eventlogging/lib/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/server/eventlogging/lib/__init__.py diff --git a/server/eventlogging/lib/monotonic.py b/server/eventlogging/lib/monotonic.py new file mode 100644 index 0000000..d6dff9f --- /dev/null +++ b/server/eventlogging/lib/monotonic.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +""" + monotonic + ~~~~~~~~~ + + This module provides a ``monotonic()`` function which returns the + value (in fractional seconds) of a clock which never goes backwards. + + On Python 3.3 or newer, ``monotonic`` will be an alias of + ``time.monotonic`` from the standard library. On older versions, + it will fall back to an equivalent implementation: + + +-------------+--------------------+ + | Linux, BSD | clock_gettime(3) | + +-------------+--------------------+ + | Windows | GetTickCount64 | + +-------------+--------------------+ + | OS X | mach_absolute_time | + +-------------+--------------------+ + + If no suitable implementation exists for the current platform, + attempting to import this module (or to import from it) will + cause a RuntimeError exception to be raised. + + + Copyright 2014 Ori Livneh <o...@wikimedia.org> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +""" +from __future__ import absolute_import, division + +import ctypes +import ctypes.util +import os +import sys +import time + + +__all__ = ('monotonic',) + +try: + monotonic = time.monotonic +except AttributeError: + try: + if sys.platform == 'darwin': # OS X, iOS + # See Technical Q&A QA1398 of the Mac Developer Library: + # <https://developer.apple.com/library/mac/qa/qa1398/> + libc = ctypes.CDLL('libc.dylib', use_errno=True) + + class mach_timebase_info_data_t(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 = mach_timebase_info_data_t() + libc.mach_timebase_info(ctypes.byref(timebase)) + ticks_per_second = timebase.numer / timebase.denom * 1.0e9 + + def monotonic(): + """Monotonic clock, cannot go backward.""" + return mach_absolute_time() / ticks_per_second + + elif sys.platform.startswith('win32'): + # Windows Vista / Windows Server 2008 or newer. + GetTickCount64 = ctypes.windll.kernel32.GetTickCount64 + GetTickCount64.restype = ctypes.c_ulonglong + + def monotonic(): + """Monotonic clock, cannot go backward.""" + return GetTickCount64() / 1000.0 + + else: + try: + clock_gettime = ctypes.CDLL(ctypes.util.find_library('c'), + use_errno=True).clock_gettime + except AttributeError: + 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)) + + ts = timespec() + + if sys.platform.startswith('linux'): + CLOCK_MONOTONIC = 1 + elif sys.platform.startswith('freebsd'): + CLOCK_MONOTONIC = 4 + elif 'bsd' in sys.platform: + CLOCK_MONOTONIC = 3 + + def monotonic(): + """Monotonic clock, cannot go backward.""" + 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 + + # Perform a sanity-check. + if monotonic() - monotonic() >= 0: + raise ValueError('monotonic() is not monotonic!') + + except Exception: + raise RuntimeError('no suitable implementation for this system') diff --git a/server/tests/test_compat.py b/server/tests/test_compat.py index 5939ed9..f25eb3a 100644 --- a/server/tests/test_compat.py +++ b/server/tests/test_compat.py @@ -10,6 +10,7 @@ import multiprocessing import os +import time import unittest import wsgiref.simple_server @@ -57,3 +58,15 @@ self.fail('Server did not start within 2 seconds') response = eventlogging.http_get('http://127.0.0.1:44080') self.assertEqual(response, 'secret') + + +class MonotonicClockTestCase(unittest.TestCase): + """Test cases for ``monotonic_clock``.""" + + @unittest.skipIf(eventlogging.monotonic_clock == time.time, + 'using non-monotonic time.time() as fallback') + def test_monotonic_clock(self): + """``monotonic_clock`` is indeed monotonic.""" + t1 = eventlogging.monotonic_clock() + t2 = eventlogging.monotonic_clock() + self.assertGreater(t2, t1) -- To view, visit https://gerrit.wikimedia.org/r/175320 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: If6166d2353f51d30a447b0a6405dbe8871431502 Gerrit-PatchSet: 5 Gerrit-Project: mediawiki/extensions/EventLogging Gerrit-Branch: master Gerrit-Owner: Ori.livneh <o...@wikimedia.org> Gerrit-Reviewer: Nuria <nu...@wikimedia.org> Gerrit-Reviewer: Ori.livneh <o...@wikimedia.org> Gerrit-Reviewer: QChris <christ...@quelltextlich.at> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits