Use a monotonic wall clock for performance tests also in py2
authorChristian Herdtweck <christian.herdtweck@intra2net.com>
Wed, 6 Dec 2017 12:16:01 +0000 (13:16 +0100)
committerChristian Herdtweck <christian.herdtweck@intra2net.com>
Wed, 6 Dec 2017 12:20:13 +0000 (13:20 +0100)
This fixes the problem enountered sometimes in unittest test_log_read. The
clock was not precise enough (10ms beat)

src/test_helpers.py

index ec6e82f..18a233c 100644 (file)
@@ -35,7 +35,7 @@ import time
 from datetime import datetime as dt
 from itertools import tee
 from warnings import warn
-from sys import stderr, version_info, exit as sys_exit
+from sys import stderr, platform, version_info, exit as sys_exit
 from os import getpid, kill, _exit as brutal_exit_function_DO_NOT_USE
 import signal
 
@@ -439,13 +439,16 @@ def watch_disc_fill(paths=None, interval=0.1):
 
 
 def get_perf_counter():
-    """ returns time.perf_counter or time.clock depending on python version
+    """ return the best monotonic clock for performance measure
 
-    before py 3.3: return function :py:function:`time.clock`
+    returned clocks are monotonic and measure "absoulte"/wall-clock time
+    differences (as opposed to time processor spends on the particular python
+    code, so sleep(1) should result in a perf counter diff of 1).
+
+    before py 3.3: return monotonic clock from linux system
     after that: returns function :py:function:`time.perf_counter`
 
-    .. warn:: these will behave differently, so do not make assumption on change
-             or time during sleep or what 0 value means
+    .. warn:: absolute values mean nothing, only differences between calls!
 
     For simple single-command use cases: use ipython's %timeit magic command.
 
@@ -460,8 +463,43 @@ def get_perf_counter():
     """
 
     if version_info.major == 2:
-        return time.clock
+        if platform.startswith('linux'):
+            return monotonic_clock_py2()
+        else:
+            return time.time
     elif version_info.major == 3 and version_info.minor < 3:
         return time.clock
     else:
         return time.perf_counter
+
+
+def monotonic_clock_py2():
+    """ get a monotonic clock in py2 using ctypes and clock_gettime
+
+    copied from:
+    https://stackoverflow.com/questions/1205722/
+    how-do-i-get-monotonic-time-durations-in-python
+    """
+
+    import ctypes
+    from os import strerror
+    CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h>
+
+    class timespec(ctypes.Structure):
+        _fields_ = [
+            ('tv_sec', ctypes.c_long),
+            ('tv_nsec', ctypes.c_long)
+        ]
+
+    librt = ctypes.CDLL('librt.so.1', use_errno=True)
+    clock_gettime = librt.clock_gettime
+    clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
+
+    def monotonic_time():
+        t = timespec()
+        if clock_gettime(CLOCK_MONOTONIC_RAW , ctypes.pointer(t)) != 0:
+            errno_ = ctypes.get_errno()
+            raise OSError(errno_, strerror(errno_))
+        return t.tv_sec + t.tv_nsec * 1e-9
+
+    return monotonic_time