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

Reply via email to