From ec89539ccccd6103665a7a5c7234cf09f27c1c72 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 29 Apr 2012 02:41:27 +0200 Subject: Issue #14428, #14397: Implement the PEP 418 * Rename time.steady() to time.monotonic() * On Windows, time.monotonic() uses GetTickCount/GetTickCount64() instead of QueryPerformanceCounter() * time.monotonic() uses CLOCK_HIGHRES if available * Add time.get_clock_info(), time.perf_counter() and time.process_time() functions --- Doc/library/time.rst | 85 +++++++- Doc/whatsnew/3.3.rst | 18 +- Include/pytime.h | 14 ++ Lib/queue.py | 5 +- Lib/test/test_time.py | 107 ++++++++-- Lib/threading.py | 6 +- Misc/NEWS | 4 + Modules/timemodule.c | 549 ++++++++++++++++++++++++++++++++++++++++++-------- Python/pytime.c | 62 +++++- 9 files changed, 723 insertions(+), 127 deletions(-) diff --git a/Doc/library/time.rst b/Doc/library/time.rst index 70ef114..56ca865 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -155,6 +155,30 @@ The module defines the following functions and data items: .. versionadded:: 3.3 +.. class:: clock_info + + Clock information object created by :func:`get_clock_info`. + + .. attribute:: implementation + + name of the underlying C function used to get the clock value + + .. attribute:: is_monotonic + + ``True`` if the clock cannot go backward, ``False`` otherwise + + .. attribute:: is_adjusted + + ``True`` if the clock can be adjusted (e.g. by a NTP daemon), + ``False`` otherwise + + .. attribute:: resolution + + Resolution of the clock in seconds (:class:`float`) + + .. versionadded:: 3.3 + + .. function:: clock_settime(clk_id, time) Set the time of the specified clock *clk_id*. @@ -236,6 +260,22 @@ The module defines the following functions and data items: Nonzero if a DST timezone is defined. +.. function:: get_clock_info(name) + + Get information on the specified clock as a :class:`clock_info` object. + + Supported clock names: + + + * ``'clock'``: :func:`time.clock` + * ``'monotonic'``: :func:`time.monotonic` + * ``'perf_counter'``: :func:`time.perf_counter` + * ``'process_time'``: :func:`time.process_time` + * ``'time'``: :func:`time.time` + + .. versionadded:: 3.3 + + .. function:: gmtime([secs]) Convert a time expressed in seconds since the epoch to a :class:`struct_time` in @@ -265,20 +305,43 @@ The module defines the following functions and data items: The earliest date for which it can generate a time is platform-dependent. -.. function:: steady(strict=False) +.. function:: monotonic() + + Monotonic clock, i.e. cannot go backward. It is not affected by system + clock updates. The reference point of the returned value is undefined, so + that only the difference between the results of consecutive calls is valid + and is a number of seconds. + + On Windows versions older than Vista, :func:`monotonic` detects + :c:func:`GetTickCount` integer overflow (32 bits, roll-over after 49.7 + days). It increases an internal epoch (reference time by) 2\ :sup:`32` each + time that an overflow is detected. The epoch is stored in the process-local + state and so the value of :func:`monotonic` may be different in two Python + processes running for more than 49 days. On more recent versions of Windows + and on other operating systems, :func:`monotonic` is system-wide. + + Availability: Windows, Mac OS X, Linux, FreeBSD, OpenBSD, Solaris. + + .. versionadded:: 3.3 + + +.. function:: perf_counter() + + Performance counter with the highest available resolution to measure a short + duration. It does include time elapsed during sleep and is system-wide. + The reference point of the returned value is undefined, so that only the + difference between the results of consecutive calls is valid and is a number + of seconds. + + .. versionadded:: 3.3 - .. index:: - single: benchmarking - Return the current time as a floating point number expressed in seconds. - This clock advances at a steady rate relative to real time and it may not be - adjusted. The reference point of the returned value is undefined so only the - difference of consecutive calls is valid. +.. function:: process_time() - If available, a monotonic clock is used. By default, - the function falls back to another clock if the monotonic clock failed or is - not available. If *strict* is True, raise an :exc:`OSError` on error or - :exc:`NotImplementedError` if no monotonic clock is available. + Sum of the system and user CPU time of the current process. It does not + include time elapsed during sleep. It is process-wide by definition. The + reference point of the returned value is undefined, so that only the + difference between the results of consecutive calls is valid. .. versionadded:: 3.3 diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 495243f..3d63127 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -1059,13 +1059,21 @@ sys time ---- -The :mod:`time` module has new functions: +The :pep:`418` added new functions to the :mod:`time` module: -* :func:`~time.clock_getres` and :func:`~time.clock_gettime` functions and - ``CLOCK_xxx`` constants. -* :func:`~time.steady`. +* :func:`~time.get_clock_info`: Get information on a clock. +* :func:`~time.monotonic`: Monotonic clock (cannot go backward), not affected + by system clock updates. +* :func:`~time.perf_counter`: Performance counter with the highest available + resolution to measure a short duration. +* :func:`~time.process_time`: Sum of the system and user CPU time of the + current process. -(Contributed by Victor Stinner in :issue:`10278`) +Other new functions: + +* :func:`~time.clock_getres`, :func:`~time.clock_gettime` and + :func:`~time.clock_settime` functions with ``CLOCK_xxx`` constants. + (Contributed by Victor Stinner in :issue:`10278`) types diff --git a/Include/pytime.h b/Include/pytime.h index 221279b..a0cedb2 100644 --- a/Include/pytime.h +++ b/Include/pytime.h @@ -22,11 +22,25 @@ typedef struct { } _PyTime_timeval; #endif +/* Structure used by time.get_clock_info() */ +typedef struct { + const char *implementation; + int is_monotonic; + int is_adjusted; + double resolution; +} _Py_clock_info_t; + /* Similar to POSIX gettimeofday but cannot fail. If system gettimeofday * fails or is not available, fall back to lower resolution clocks. */ PyAPI_FUNC(void) _PyTime_gettimeofday(_PyTime_timeval *tp); +/* Similar to _PyTime_gettimeofday() but retrieve also information on the + * clock used to get the current time. */ +PyAPI_FUNC(void) _PyTime_gettimeofday_info( + _PyTime_timeval *tp, + _Py_clock_info_t *info); + #define _PyTime_ADD_SECONDS(tv, interval) \ do { \ tv.tv_usec += (long) (((long) interval - interval) * 1000000); \ diff --git a/Lib/queue.py b/Lib/queue.py index 1dc72c4..c3296fe 100644 --- a/Lib/queue.py +++ b/Lib/queue.py @@ -6,7 +6,10 @@ except ImportError: import dummy_threading as threading from collections import deque from heapq import heappush, heappop -from time import steady as time +try: + from time import monotonic as time +except ImportError: + from time import time __all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue'] diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index fb2489c..f00660b 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -5,6 +5,10 @@ import locale import sysconfig import sys import platform +try: + import threading +except ImportError: + threading = None # Max year is only limited by the size of C int. SIZEOF_INT = sysconfig.get_config_var('SIZEOF_INT') or 4 @@ -23,9 +27,20 @@ class TimeTestCase(unittest.TestCase): time.timezone time.tzname + def test_time(self): + time.time() + info = time.get_clock_info('time') + self.assertEqual(info.is_monotonic, False) + if sys.platform != 'win32': + self.assertEqual(info.is_adjusted, True) + def test_clock(self): time.clock() + info = time.get_clock_info('clock') + self.assertEqual(info.is_monotonic, True) + self.assertEqual(info.is_adjusted, False) + @unittest.skipUnless(hasattr(time, 'clock_gettime'), 'need time.clock_gettime()') def test_clock_realtime(self): @@ -56,7 +71,9 @@ class TimeTestCase(unittest.TestCase): except PermissionError: pass - self.assertRaises(OSError, time.clock_settime, time.CLOCK_MONOTONIC, 0) + if hasattr(time, 'CLOCK_MONOTONIC'): + self.assertRaises(OSError, + time.clock_settime, time.CLOCK_MONOTONIC, 0) def test_conversions(self): self.assertEqual(time.ctime(self.t), @@ -342,23 +359,69 @@ class TimeTestCase(unittest.TestCase): pass self.assertEqual(time.strftime('%Z', tt), tzname) - def test_steady(self): - t1 = time.steady() + @unittest.skipUnless(hasattr(time, 'monotonic'), + 'need time.monotonic') + def test_monotonic(self): + t1 = time.monotonic() time.sleep(0.1) - t2 = time.steady() + t2 = time.monotonic() dt = t2 - t1 - # may fail if the system clock was changed self.assertGreater(t2, t1) self.assertAlmostEqual(dt, 0.1, delta=0.2) - def test_steady_strict(self): + info = time.get_clock_info('monotonic') + self.assertEqual(info.is_monotonic, True) + if sys.platform == 'linux': + self.assertEqual(info.is_adjusted, True) + else: + self.assertEqual(info.is_adjusted, False) + + def test_perf_counter(self): + time.perf_counter() + + def test_process_time(self): + start = time.process_time() + time.sleep(0.1) + stop = time.process_time() + self.assertLess(stop - start, 0.01) + + info = time.get_clock_info('process_time') + self.assertEqual(info.is_monotonic, True) + self.assertEqual(info.is_adjusted, False) + + @unittest.skipUnless(threading, + 'need threading') + def test_process_time_threads(self): + class BusyThread(threading.Thread): + def run(self): + while not self.stop: + pass + + thread = BusyThread() + thread.stop = False + t1 = time.process_time() + thread.start() + time.sleep(0.2) + t2 = time.process_time() + thread.stop = True + thread.join() + self.assertGreater(t2 - t1, 0.1) + + @unittest.skipUnless(hasattr(time, 'monotonic'), + 'need time.monotonic') + @unittest.skipUnless(hasattr(time, 'clock_settime'), + 'need time.clock_settime') + def test_monotonic_settime(self): + t1 = time.monotonic() + realtime = time.clock_gettime(time.CLOCK_REALTIME) + # jump backward with an offset of 1 hour try: - t1 = time.steady(strict=True) - except OSError as err: - self.skipTest("the monotonic clock failed: %s" % err) - except NotImplementedError: - self.skipTest("no monotonic clock available") - t2 = time.steady(strict=True) + time.clock_settime(time.CLOCK_REALTIME, realtime - 3600) + except PermissionError as err: + self.skipTest(err) + t2 = time.monotonic() + time.clock_settime(time.CLOCK_REALTIME, realtime) + # monotonic must not be affected by system clock updates self.assertGreaterEqual(t2, t1) def test_localtime_failure(self): @@ -378,6 +441,26 @@ class TimeTestCase(unittest.TestCase): self.assertRaises(OSError, time.localtime, invalid_time_t) self.assertRaises(OSError, time.ctime, invalid_time_t) + def test_get_clock_info(self): + clocks = ['clock', 'perf_counter', 'process_time', 'time'] + if hasattr(time, 'monotonic'): + clocks.append('monotonic') + + for name in clocks: + info = time.get_clock_info(name) + #self.assertIsInstance(info, dict) + self.assertIsInstance(info.implementation, str) + self.assertNotEqual(info.implementation, '') + self.assertIsInstance(info.is_monotonic, bool) + self.assertIsInstance(info.resolution, float) + # 0.0 < resolution <= 1.0 + self.assertGreater(info.resolution, 0.0) + self.assertLessEqual(info.resolution, 1.0) + self.assertIsInstance(info.is_adjusted, bool) + + self.assertRaises(ValueError, time.get_clock_info, 'xxx') + + class TestLocale(unittest.TestCase): def setUp(self): self.oldloc = locale.setlocale(locale.LC_ALL) diff --git a/Lib/threading.py b/Lib/threading.py index 8c2cee9..6c34d49 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -3,7 +3,11 @@ import sys as _sys import _thread -from time import steady as _time, sleep as _sleep +from time import sleep as _sleep +try: + from time import monotonic as _time +except ImportError: + from time import time as _time from traceback import format_exc as _format_exc from _weakrefset import WeakSet diff --git a/Misc/NEWS b/Misc/NEWS index 98081c7..995ec9e 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -81,6 +81,10 @@ Core and Builtins Library ------- +- Issue #14428: Implement the PEP 418. Add time.get_clock_info(), + time.perf_counter() and time.process_time() functions, and rename + time.steady() to time.monotonic(). + - Issue #14646: importlib.util.module_for_loader() now sets __loader__ and __package__ (when possible). diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 771db83..1e843b9 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -4,9 +4,17 @@ #include +#ifdef HAVE_SYS_TIMES_H +#include +#endif + #ifdef HAVE_SYS_TYPES_H #include -#endif /* HAVE_SYS_TYPES_H */ +#endif + +#if defined(HAVE_SYS_RESOURCE_H) +#include +#endif #ifdef QUICKWIN #include @@ -45,12 +53,16 @@ /* Forward declarations */ static int floatsleep(double); -static PyObject* floattime(void); +static PyObject* floattime(_Py_clock_info_t *info); + +#ifdef MS_WINDOWS +static OSVERSIONINFOEX winver; +#endif static PyObject * time_time(PyObject *self, PyObject *unused) { - return floattime(); + return floattime(NULL); } PyDoc_STRVAR(time_doc, @@ -70,7 +82,7 @@ Fractions of a second may be present if the system clock provides them."); #endif static PyObject * -pyclock(void) +floatclock(_Py_clock_info_t *info) { clock_t value; value = clock(); @@ -80,15 +92,22 @@ pyclock(void) "or its value cannot be represented"); return NULL; } + if (info) { + info->implementation = "clock()"; + info->resolution = 1.0 / (double)CLOCKS_PER_SEC; + info->is_monotonic = 1; + info->is_adjusted = 0; + } return PyFloat_FromDouble((double)value / CLOCKS_PER_SEC); } #endif /* HAVE_CLOCK */ #if defined(MS_WINDOWS) && !defined(__BORLANDC__) +#define WIN32_PERF_COUNTER /* Win32 has better clock replacement; we have our own version, due to Mark Hammond and Tim Peters */ -static PyObject * -win32_clock(int fallback) +static int +win_perf_counter(_Py_clock_info_t *info, PyObject **result) { static LONGLONG cpu_frequency = 0; static LONGLONG ctrStart; @@ -102,28 +121,41 @@ win32_clock(int fallback) if (!QueryPerformanceFrequency(&freq) || freq.QuadPart == 0) { /* Unlikely to happen - this works on all intel machines at least! Revert to clock() */ - if (fallback) - return pyclock(); - else - return PyErr_SetFromWindowsErr(0); + *result = NULL; + return -1; } cpu_frequency = freq.QuadPart; } QueryPerformanceCounter(&now); diff = (double)(now.QuadPart - ctrStart); - return PyFloat_FromDouble(diff / (double)cpu_frequency); + if (info) { + info->implementation = "QueryPerformanceCounter()"; + info->resolution = 1.0 / (double)cpu_frequency; + info->is_monotonic = 1; + info->is_adjusted = 0; + } + *result = PyFloat_FromDouble(diff / (double)cpu_frequency); + return 0; } #endif -#if (defined(MS_WINDOWS) && !defined(__BORLANDC__)) || defined(HAVE_CLOCK) +#if defined(WIN32_PERF_COUNTER) || defined(HAVE_CLOCK) +#define PYCLOCK +static PyObject* +pyclock(_Py_clock_info_t *info) +{ +#ifdef WIN32_PERF_COUNTER + PyObject *res; + if (win_perf_counter(info, &res) == 0) + return res; +#endif + return floatclock(info); +} + static PyObject * time_clock(PyObject *self, PyObject *unused) { -#if defined(MS_WINDOWS) && !defined(__BORLANDC__) - return win32_clock(1); -#else - return pyclock(); -#endif + return pyclock(NULL); } PyDoc_STRVAR(clock_doc, @@ -150,7 +182,6 @@ time_clock_gettime(PyObject *self, PyObject *args) PyErr_SetFromErrno(PyExc_IOError); return NULL; } - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); } @@ -787,11 +818,74 @@ the local timezone used by methods such as localtime, but this behaviour\n\ should not be relied on."); #endif /* HAVE_WORKING_TZSET */ +#if defined(MS_WINDOWS) || defined(__APPLE__) \ + || (defined(HAVE_CLOCK_GETTIME) \ + && (defined(CLOCK_HIGHRES) || defined(CLOCK_MONOTONIC))) +#define PYMONOTONIC +#endif + +#ifdef PYMONOTONIC static PyObject* -steady_clock(int strict) +pymonotonic(_Py_clock_info_t *info) { -#if defined(MS_WINDOWS) && !defined(__BORLANDC__) - return win32_clock(!strict); +#if defined(MS_WINDOWS) + static ULONGLONG (*GetTickCount64) (void) = NULL; + static ULONGLONG (CALLBACK *Py_GetTickCount64)(void); + static int has_getickcount64 = -1; + double result; + + if (has_getickcount64 == -1) { + /* GetTickCount64() was added to Windows Vista */ + if (winver.dwMajorVersion >= 6) { + HINSTANCE hKernel32; + hKernel32 = GetModuleHandleW(L"KERNEL32"); + *(FARPROC*)&Py_GetTickCount64 = GetProcAddress(hKernel32, + "GetTickCount64"); + has_getickcount64 = (Py_GetTickCount64 != NULL); + } + else + has_getickcount64 = 0; + } + + if (has_getickcount64) { + ULONGLONG ticks; + ticks = Py_GetTickCount64(); + result = (double)ticks * 1e-3; + } + else { + static DWORD last_ticks = 0; + static DWORD n_overflow = 0; + DWORD ticks; + + ticks = GetTickCount(); + if (ticks < last_ticks) + n_overflow++; + last_ticks = ticks; + + result = ldexp(n_overflow, 32); + result += ticks; + result *= 1e-3; + } + + if (info) { + DWORD timeAdjustment, timeIncrement; + BOOL isTimeAdjustmentDisabled, ok; + if (has_getickcount64) + info->implementation = "GetTickCount64()"; + else + info->implementation = "GetTickCount()"; + info->is_monotonic = 1; + ok = GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, + &isTimeAdjustmentDisabled); + if (!ok) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + info->resolution = timeIncrement * 1e-7; + info->is_adjusted = 0; + } + return PyFloat_FromDouble(result); + #elif defined(__APPLE__) static mach_timebase_info_data_t timebase; uint64_t time; @@ -805,88 +899,338 @@ steady_clock(int strict) time = mach_absolute_time(); secs = (double)time * timebase.numer / timebase.denom * 1e-9; - + if (info) { + info->implementation = "mach_absolute_time()"; + info->resolution = (double)timebase.numer / timebase.denom * 1e-9; + info->is_monotonic = 1; + info->is_adjusted = 0; + } return PyFloat_FromDouble(secs); -#elif defined(HAVE_CLOCK_GETTIME) - static int steady_clk_index = 0; - static int monotonic_clk_index = 0; - int *clk_index; - clockid_t steady_clk_ids[] = { -#ifdef CLOCK_MONOTONIC_RAW - CLOCK_MONOTONIC_RAW, + +#elif defined(HAVE_CLOCK_GETTIME) && (defined(CLOCK_HIGHRES) || defined(CLOCK_MONOTONIC)) + struct timespec tp; +#ifdef CLOCK_HIGHRES + const clockid_t clk_id = CLOCK_HIGHRES; + const char *function = "clock_gettime(CLOCK_HIGHRES)"; +#else + const clockid_t clk_id = CLOCK_MONOTONIC; + const char *function = "clock_gettime(CLOCK_MONOTONIC)"; #endif - CLOCK_MONOTONIC, - CLOCK_REALTIME - }; - clockid_t monotonic_clk_ids[] = { -#ifdef CLOCK_MONOTONIC_RAW - CLOCK_MONOTONIC_RAW, + + if (clock_gettime(clk_id, &tp) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + if (info) { + struct timespec res; + info->is_monotonic = 1; + info->implementation = function; +#if (defined(linux) || defined(__linux) || defined(__linux__)) \ + && !defined(CLOCK_HIGHRES) + /* CLOCK_MONOTONIC is adjusted on Linux */ + info->is_adjusted = 1; +#else + info->is_adjusted = 0; #endif - CLOCK_MONOTONIC - }; - clockid_t *clk_ids; - int clk_ids_len; - int ret; - struct timespec tp; + if (clock_getres(clk_id, &res) == 0) + info->resolution = res.tv_sec + res.tv_nsec * 1e-9; + else + info->resolution = 1e-9; + } + return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); +#endif +} - if (strict) { - clk_index = &monotonic_clk_index; - clk_ids = monotonic_clk_ids; - clk_ids_len = Py_ARRAY_LENGTH(monotonic_clk_ids); +static PyObject * +time_monotonic(PyObject *self, PyObject *unused) +{ + return pymonotonic(NULL); +} + +PyDoc_STRVAR(monotonic_doc, +"monotonic() -> float\n\ +\n\ +Monotonic clock, cannot go backward."); +#endif /* PYMONOTONIC */ + +static PyObject* +perf_counter(_Py_clock_info_t *info) +{ +#if defined(WIN32_PERF_COUNTER) || defined(PYMONOTONIC) + PyObject *res; +#endif +#if defined(WIN32_PERF_COUNTER) + static int use_perf_counter = 1; +#endif +#ifdef PYMONOTONIC + static int use_monotonic = 1; +#endif + +#ifdef WIN32_PERF_COUNTER + if (use_perf_counter) { + if (win_perf_counter(info, &res) == 0) + return res; + use_perf_counter = 0; } - else { - clk_index = &steady_clk_index; - clk_ids = steady_clk_ids; - clk_ids_len = Py_ARRAY_LENGTH(steady_clk_ids); +#endif + +#ifdef PYMONOTONIC + if (use_monotonic) { + res = pymonotonic(info); + if (res != NULL) + return res; + use_monotonic = 0; + PyErr_Clear(); } +#endif + + return floattime(info); +} + +static PyObject * +time_perf_counter(PyObject *self, PyObject *unused) +{ + return perf_counter(NULL); +} - while (0 <= *clk_index) { - clockid_t clk_id = clk_ids[*clk_index]; - ret = clock_gettime(clk_id, &tp); - if (ret == 0) - return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); +PyDoc_STRVAR(perf_counter_doc, +"perf_counter() -> float\n\ +\n\ +Performance counter for benchmarking."); - (*clk_index)++; - if (clk_ids_len <= *clk_index) - (*clk_index) = -1; +static PyObject* +py_process_time(_Py_clock_info_t *info) +{ +#if defined(MS_WINDOWS) + HANDLE process; + FILETIME creation_time, exit_time, kernel_time, user_time; + ULARGE_INTEGER large; + double total; + BOOL ok; + + process = GetCurrentProcess(); + ok = GetProcessTimes(process, &creation_time, &exit_time, &kernel_time, &user_time); + if (!ok) + return PyErr_SetFromWindowsErr(0); + + large.u.LowPart = kernel_time.dwLowDateTime; + large.u.HighPart = kernel_time.dwHighDateTime; + total = (double)large.QuadPart; + large.u.LowPart = user_time.dwLowDateTime; + large.u.HighPart = user_time.dwHighDateTime; + total += (double)large.QuadPart; + if (info) { + info->implementation = "GetProcessTimes()"; + info->resolution = 1e-7; + info->is_monotonic = 1; + info->is_adjusted = 0; } - if (strict) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return PyFloat_FromDouble(total * 1e-7); +#else + +#if defined(HAVE_SYS_RESOURCE_H) + struct rusage ru; +#endif +#ifdef HAVE_TIMES + struct tms t; + static long ticks_per_second = -1; +#endif + +#if defined(HAVE_CLOCK_GETTIME) \ + && (defined(CLOCK_PROCESS_CPUTIME_ID) || defined(CLOCK_PROF)) + struct timespec tp; +#ifdef CLOCK_PROF + const clockid_t clk_id = CLOCK_PROF; + const char *function = "clock_gettime(CLOCK_PROF)"; +#else + const clockid_t clk_id = CLOCK_PROCESS_CPUTIME_ID; + const char *function = "clock_gettime(CLOCK_PROCESS_CPUTIME_ID)"; +#endif + + if (clock_gettime(clk_id, &tp) == 0) { + if (info) { + struct timespec res; + info->implementation = function; + info->is_monotonic = 1; + info->is_adjusted = 0; + if (clock_getres(clk_id, &res) == 0) + info->resolution = res.tv_sec + res.tv_nsec * 1e-9; + else + info->resolution = 1e-9; + } + return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); } - return floattime(); +#endif + +#if defined(HAVE_SYS_RESOURCE_H) + if (getrusage(RUSAGE_SELF, &ru) == 0) { + double total; + total = ru.ru_utime.tv_sec + ru.ru_utime.tv_usec * 1e-6; + total += ru.ru_stime.tv_sec + ru.ru_stime.tv_usec * 1e-6; + if (info) { + info->implementation = "getrusage(RUSAGE_SELF)"; + info->is_monotonic = 1; + info->is_adjusted = 0; + info->resolution = 1e-6; + } + return PyFloat_FromDouble(total); + } +#endif + +#ifdef HAVE_TIMES + if (times(&t) != (clock_t)-1) { + double total; + + if (ticks_per_second == -1) { +#if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK) + ticks_per_second = sysconf(_SC_CLK_TCK); + if (ticks_per_second < 1) + ticks_per_second = -1; +#elif defined(HZ) + ticks_per_second = HZ; #else - if (strict) { - PyErr_SetString(PyExc_NotImplementedError, - "no steady clock available on your platform"); - return NULL; + ticks_per_second = 60; /* magic fallback value; may be bogus */ +#endif + } + + if (ticks_per_second != -1) { + total = (double)t.tms_utime / ticks_per_second; + total += (double)t.tms_stime / ticks_per_second; + if (info) { + info->implementation = "times()"; + info->is_monotonic = 1; + info->is_adjusted = 0; + info->resolution = 1.0 / ticks_per_second; + } + return PyFloat_FromDouble(total); + } } - return floattime(); +#endif + + return floatclock(info); #endif } static PyObject * -time_steady(PyObject *self, PyObject *args, PyObject *kwargs) +time_process_time(PyObject *self, PyObject *unused) { - static char *kwlist[] = {"strict", NULL}; - int strict = 0; + return py_process_time(NULL); +} + +PyDoc_STRVAR(process_time_doc, +"process_time() -> float\n\ +\n\ +Process time for profiling: sum of the kernel and user-space CPU time."); + + +static PyTypeObject ClockInfoType; + +PyDoc_STRVAR(ClockInfo_docstring, + "Clock information"); + +static PyStructSequence_Field ClockInfo_fields[] = { + {"implementation", "name of the underlying C function " + "used to get the clock value"}, + {"is_monotonic", "True if the clock cannot go backward, False otherwise"}, + {"is_adjusted", "True if the clock can be adjusted " + "(e.g. by a NTP daemon), False otherwise"}, + {"resolution", "resolution of the clock in seconds"}, + {NULL, NULL} +}; + +static PyStructSequence_Desc ClockInfo_desc = { + "time.clock_info", + ClockInfo_docstring, + ClockInfo_fields, + 4, +}; + +static PyObject * +time_get_clock_info(PyObject *self, PyObject *args) +{ + char *name; + PyObject *obj; + _Py_clock_info_t info; + PyObject *result; + + if (!PyArg_ParseTuple(args, "s:get_clock_info", &name)) + return NULL; + +#ifdef Py_DEBUG + info.implementation = NULL; + info.is_monotonic = -1; + info.is_adjusted = -1; + info.resolution = -1.0; +#else + info.implementation = ""; + info.is_monotonic = 0; + info.is_adjusted = 0; + info.resolution = 1.0; +#endif - if (!PyArg_ParseTupleAndKeywords( - args, kwargs, "|i:steady", kwlist, - &strict)) + if (strcmp(name, "time") == 0) + obj = floattime(&info); +#ifdef PYCLOCK + else if (strcmp(name, "clock") == 0) + obj = pyclock(&info); +#endif +#ifdef PYMONOTONIC + else if (strcmp(name, "monotonic") == 0) + obj = pymonotonic(&info); +#endif + else if (strcmp(name, "perf_counter") == 0) + obj = perf_counter(&info); + else if (strcmp(name, "process_time") == 0) + obj = py_process_time(&info); + else { + PyErr_SetString(PyExc_ValueError, "unknown clock"); + return NULL; + } + if (obj == NULL) return NULL; + Py_DECREF(obj); - return steady_clock(strict); + result = PyStructSequence_New(&ClockInfoType); + if (result == NULL) + return NULL; + + assert(info.implementation != NULL); + obj = PyUnicode_FromString(info.implementation); + if (obj == NULL) + goto error; + PyStructSequence_SET_ITEM(result, 0, obj); + + assert(info.is_monotonic != -1); + obj = PyBool_FromLong(info.is_monotonic); + if (obj == NULL) + goto error; + PyStructSequence_SET_ITEM(result, 1, obj); + + assert(info.is_adjusted != -1); + obj = PyBool_FromLong(info.is_adjusted); + if (obj == NULL) + goto error; + PyStructSequence_SET_ITEM(result, 2, obj); + + assert(info.resolution > 0.0); + assert(info.resolution <= 1.0); + obj = PyFloat_FromDouble(info.resolution); + if (obj == NULL) + goto error; + PyStructSequence_SET_ITEM(result, 3, obj); + + return result; + +error: + Py_DECREF(result); + return NULL; } -PyDoc_STRVAR(steady_doc, -"steady(strict=False) -> float\n\ +PyDoc_STRVAR(get_clock_info_doc, +"get_clock_info(name: str) -> dict\n\ \n\ -Return the current time as a floating point number expressed in seconds.\n\ -This clock advances at a steady rate relative to real time and it may not\n\ -be adjusted. The reference point of the returned value is undefined so only\n\ -the difference of consecutive calls is valid."); - +Get information of the specified clock."); static void PyInit_timezone(PyObject *m) { @@ -977,10 +1321,8 @@ PyInit_timezone(PyObject *m) { #endif /* __CYGWIN__ */ #endif /* !HAVE_TZNAME || __GLIBC__ || __CYGWIN__*/ -#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_CLOCK_GETRES) -#ifdef CLOCK_REALTIME +#if defined(HAVE_CLOCK_GETTIME) PyModule_AddIntMacro(m, CLOCK_REALTIME); -#endif #ifdef CLOCK_MONOTONIC PyModule_AddIntMacro(m, CLOCK_MONOTONIC); #endif @@ -1002,7 +1344,7 @@ PyInit_timezone(PyObject *m) { static PyMethodDef time_methods[] = { {"time", time_time, METH_NOARGS, time_doc}, -#if (defined(MS_WINDOWS) && !defined(__BORLANDC__)) || defined(HAVE_CLOCK) +#ifdef PYCLOCK {"clock", time_clock, METH_NOARGS, clock_doc}, #endif #ifdef HAVE_CLOCK_GETTIME @@ -1018,8 +1360,6 @@ static PyMethodDef time_methods[] = { #ifdef HAVE_MKTIME {"mktime", time_mktime, METH_O, mktime_doc}, #endif - {"steady", (PyCFunction)time_steady, METH_VARARGS|METH_KEYWORDS, - steady_doc}, #ifdef HAVE_STRFTIME {"strftime", time_strftime, METH_VARARGS, strftime_doc}, #endif @@ -1027,6 +1367,12 @@ static PyMethodDef time_methods[] = { #ifdef HAVE_WORKING_TZSET {"tzset", time_tzset, METH_NOARGS, tzset_doc}, #endif +#ifdef PYMONOTONIC + {"monotonic", time_monotonic, METH_NOARGS, monotonic_doc}, +#endif + {"process_time", time_process_time, METH_NOARGS, process_time_doc}, + {"perf_counter", time_perf_counter, METH_NOARGS, perf_counter_doc}, + {"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc}, {NULL, NULL} /* sentinel */ }; @@ -1104,6 +1450,20 @@ PyInit_time(void) if (!initialized) { PyStructSequence_InitType(&StructTimeType, &struct_time_type_desc); + + /* initialize ClockInfoType */ + PyStructSequence_InitType(&ClockInfoType, &ClockInfo_desc); + Py_INCREF(&ClockInfoType); + PyModule_AddObject(m, "clock_info", (PyObject*)&ClockInfoType); + +#ifdef MS_WINDOWS + winver.dwOSVersionInfoSize = sizeof(winver); + if (!GetVersionEx((OSVERSIONINFO*)&winver)) { + Py_DECREF(m); + PyErr_SetFromWindowsErr(0); + return NULL; + } +#endif } Py_INCREF(&StructTimeType); PyModule_AddObject(m, "struct_time", (PyObject*) &StructTimeType); @@ -1112,7 +1472,7 @@ PyInit_time(void) } static PyObject* -floattime(void) +floattime(_Py_clock_info_t *info) { _PyTime_timeval t; #ifdef HAVE_CLOCK_GETTIME @@ -1123,10 +1483,21 @@ floattime(void) because it would require to link Python to the rt (real-time) library, at least on Linux */ ret = clock_gettime(CLOCK_REALTIME, &tp); - if (ret == 0) + if (ret == 0) { + if (info) { + struct timespec res; + info->implementation = "clock_gettime(CLOCK_REALTIME)"; + info->is_monotonic = 0; + info->is_adjusted = 1; + if (clock_getres(CLOCK_REALTIME, &res) == 0) + info->resolution = res.tv_sec + res.tv_nsec * 1e-9; + else + info->resolution = 1e-9; + } return PyFloat_FromDouble(tp.tv_sec + tp.tv_nsec * 1e-9); + } #endif - _PyTime_gettimeofday(&t); + _PyTime_gettimeofday_info(&t, info); return PyFloat_FromDouble((double)t.tv_sec + t.tv_usec * 1e-6); } diff --git a/Python/pytime.c b/Python/pytime.c index db3f683..ddd3088 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -18,8 +18,8 @@ extern int ftime(struct timeb *); #endif -void -_PyTime_gettimeofday(_PyTime_timeval *tp) +static void +pygettimeofday(_PyTime_timeval *tp, _Py_clock_info_t *info) { #ifdef MS_WINDOWS FILETIME system_time; @@ -35,6 +35,20 @@ _PyTime_gettimeofday(_PyTime_timeval *tp) microseconds = large.QuadPart / 10 - 11644473600000000; tp->tv_sec = microseconds / 1000000; tp->tv_usec = microseconds % 1000000; + if (info) { + DWORD timeAdjustment, timeIncrement; + BOOL isTimeAdjustmentDisabled; + + info->implementation = "GetSystemTimeAsFileTime()"; + info->is_monotonic = 0; + (void) GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, + &isTimeAdjustmentDisabled); + info->resolution = timeIncrement * 1e-7; + if (isTimeAdjustmentDisabled) + info->is_adjusted = 0; + else + info->is_adjusted = 1; + } #else /* There are three ways to get the time: (1) gettimeofday() -- resolution in microseconds @@ -46,14 +60,22 @@ _PyTime_gettimeofday(_PyTime_timeval *tp) Note: clock resolution does not imply clock accuracy! */ #ifdef HAVE_GETTIMEOFDAY + int err; #ifdef GETTIMEOFDAY_NO_TZ - if (gettimeofday(tp) == 0) - return; -#else /* !GETTIMEOFDAY_NO_TZ */ - if (gettimeofday(tp, (struct timezone *)NULL) == 0) + err = gettimeofday(tp); +#else + err = gettimeofday(tp, (struct timezone *)NULL); +#endif + if (err == 0) { + if (info) { + info->implementation = "gettimeofday()"; + info->resolution = 1e-6; + info->is_monotonic = 0; + info->is_adjusted = 1; + } return; -#endif /* !GETTIMEOFDAY_NO_TZ */ -#endif /* !HAVE_GETTIMEOFDAY */ + } +#endif /* HAVE_GETTIMEOFDAY */ #if defined(HAVE_FTIME) { @@ -61,15 +83,39 @@ _PyTime_gettimeofday(_PyTime_timeval *tp) ftime(&t); tp->tv_sec = t.time; tp->tv_usec = t.millitm * 1000; + if (info) { + info->implementation = "ftime()"; + info->resolution = 1e-3; + info->is_monotonic = 0; + info->is_adjusted = 1; + } } #else /* !HAVE_FTIME */ tp->tv_sec = time(NULL); tp->tv_usec = 0; + if (info) { + info->implementation = "time()"; + info->resolution = 1.0; + info->is_monotonic = 0; + info->is_adjusted = 1; + } #endif /* !HAVE_FTIME */ #endif /* MS_WINDOWS */ } +void +_PyTime_gettimeofday(_PyTime_timeval *tp) +{ + pygettimeofday(tp, NULL); +} + +void +_PyTime_gettimeofday_info(_PyTime_timeval *tp, _Py_clock_info_t *info) +{ + pygettimeofday(tp, info); +} + static void error_time_t_overflow(void) { -- cgit v0.12