[Python-checkins] bpo-32025: Add time.thread_time() (#4410)
Antoine Pitrou
webhook-mailer at python.org
Wed Nov 15 16:52:25 EST 2017
https://github.com/python/cpython/commit/4bd41c9b52ea0c730e9e294caaf003e54c088c6e
commit: 4bd41c9b52ea0c730e9e294caaf003e54c088c6e
branch: master
author: Antoine Pitrou <pitrou at free.fr>
committer: GitHub <noreply at github.com>
date: 2017-11-15T22:52:21+01:00
summary:
bpo-32025: Add time.thread_time() (#4410)
* bpo-32025: Add time.thread_time()
* Add missing #endif
* Add NEWS blurb
* Add docs and whatsnew
* Address review comments
* Review comments
files:
A Misc/NEWS.d/next/Library/2017-11-15-20-03-45.bpo-32025.lnIKYT.rst
M Doc/library/time.rst
M Doc/whatsnew/3.7.rst
M Lib/test/test_time.py
M Modules/timemodule.c
diff --git a/Doc/library/time.rst b/Doc/library/time.rst
index 4ffb4d22cd7..ccbb3f37877 100644
--- a/Doc/library/time.rst
+++ b/Doc/library/time.rst
@@ -241,6 +241,7 @@ Functions
* ``'monotonic'``: :func:`time.monotonic`
* ``'perf_counter'``: :func:`time.perf_counter`
* ``'process_time'``: :func:`time.process_time`
+ * ``'thread_time'``: :func:`time.thread_time`
* ``'time'``: :func:`time.time`
The result has the following attributes:
@@ -603,6 +604,32 @@ Functions
of the calendar date may be accessed as attributes.
+.. function:: thread_time() -> float
+
+ .. index::
+ single: CPU time
+ single: processor time
+ single: benchmarking
+
+ Return the value (in fractional seconds) of the sum of the system and user
+ CPU time of the current thread. It does not include time elapsed during
+ sleep. It is thread-specific by definition. The reference point of the
+ returned value is undefined, so that only the difference between the results
+ of consecutive calls in the same thread is valid.
+
+ Availability: Windows, Linux, Unix systems supporting
+ ``CLOCK_THREAD_CPUTIME_ID``.
+
+ .. versionadded:: 3.7
+
+
+.. function:: thread_time_ns() -> int
+
+ Similar to :func:`thread_time` but return time as nanoseconds.
+
+ .. versionadded:: 3.7
+
+
.. function:: time_ns() -> int
Similar to :func:`time` but returns time as an integer number of nanoseconds
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index cbc166d519f..a2fea50d091 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -372,6 +372,10 @@ Add new clock identifiers:
the time the system has been running and not suspended, providing accurate
uptime measurement, both absolute and interval.
+Added functions :func:`time.thread_time` and :func:`time.thread_time_ns`
+to get per-thread CPU time measurements.
+(Contributed by Antoine Pitrou in :issue:`32025`.)
+
unittest.mock
-------------
diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py
index b44646da709..eda3885ad57 100644
--- a/Lib/test/test_time.py
+++ b/Lib/test/test_time.py
@@ -47,6 +47,12 @@ class _PyTime(enum.IntEnum):
)
+def busy_wait(duration):
+ deadline = time.monotonic() + duration
+ while time.monotonic() < deadline:
+ pass
+
+
class TimeTestCase(unittest.TestCase):
def setUp(self):
@@ -81,6 +87,10 @@ def check_ns(sec, ns):
check_ns(time.process_time(),
time.process_time_ns())
+ if hasattr(time, 'thread_time'):
+ check_ns(time.thread_time(),
+ time.thread_time_ns())
+
if hasattr(time, 'clock_gettime'):
check_ns(time.clock_gettime(time.CLOCK_REALTIME),
time.clock_gettime_ns(time.CLOCK_REALTIME))
@@ -486,10 +496,57 @@ def test_process_time(self):
# on Windows
self.assertLess(stop - start, 0.020)
+ # process_time() should include CPU time spent in any thread
+ start = time.process_time()
+ busy_wait(0.100)
+ stop = time.process_time()
+ self.assertGreaterEqual(stop - start, 0.020) # machine busy?
+
+ t = threading.Thread(target=busy_wait, args=(0.100,))
+ start = time.process_time()
+ t.start()
+ t.join()
+ stop = time.process_time()
+ self.assertGreaterEqual(stop - start, 0.020) # machine busy?
+
info = time.get_clock_info('process_time')
self.assertTrue(info.monotonic)
self.assertFalse(info.adjustable)
+ def test_thread_time(self):
+ if not hasattr(time, 'thread_time'):
+ if sys.platform.startswith(('linux', 'win')):
+ self.fail("time.thread_time() should be available on %r"
+ % (sys.platform,))
+ else:
+ self.skipTest("need time.thread_time")
+
+ # thread_time() should not include time spend during a sleep
+ start = time.thread_time()
+ time.sleep(0.100)
+ stop = time.thread_time()
+ # use 20 ms because thread_time() has usually a resolution of 15 ms
+ # on Windows
+ self.assertLess(stop - start, 0.020)
+
+ # thread_time() should include CPU time spent in current thread...
+ start = time.thread_time()
+ busy_wait(0.100)
+ stop = time.thread_time()
+ self.assertGreaterEqual(stop - start, 0.020) # machine busy?
+
+ # ...but not in other threads
+ t = threading.Thread(target=busy_wait, args=(0.100,))
+ start = time.thread_time()
+ t.start()
+ t.join()
+ stop = time.thread_time()
+ self.assertLess(stop - start, 0.020)
+
+ info = time.get_clock_info('thread_time')
+ self.assertTrue(info.monotonic)
+ self.assertFalse(info.adjustable)
+
@unittest.skipUnless(hasattr(time, 'clock_settime'),
'need time.clock_settime')
def test_monotonic_settime(self):
diff --git a/Misc/NEWS.d/next/Library/2017-11-15-20-03-45.bpo-32025.lnIKYT.rst b/Misc/NEWS.d/next/Library/2017-11-15-20-03-45.bpo-32025.lnIKYT.rst
new file mode 100644
index 00000000000..f3fe3b5a96d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-11-15-20-03-45.bpo-32025.lnIKYT.rst
@@ -0,0 +1 @@
+Add time.thread_time() and time.thread_time_ns()
diff --git a/Modules/timemodule.c b/Modules/timemodule.c
index 37abeb95077..5cae03dd416 100644
--- a/Modules/timemodule.c
+++ b/Modules/timemodule.c
@@ -1258,6 +1258,112 @@ Process time for profiling as nanoseconds:\n\
sum of the kernel and user-space CPU time.");
+#if defined(MS_WINDOWS)
+#define HAVE_THREAD_TIME
+static int
+_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
+{
+ HANDLE thread;
+ FILETIME creation_time, exit_time, kernel_time, user_time;
+ ULARGE_INTEGER large;
+ _PyTime_t ktime, utime, t;
+ BOOL ok;
+
+ thread = GetCurrentThread();
+ ok = GetThreadTimes(thread, &creation_time, &exit_time,
+ &kernel_time, &user_time);
+ if (!ok) {
+ PyErr_SetFromWindowsErr(0);
+ return -1;
+ }
+
+ if (info) {
+ info->implementation = "GetThreadTimes()";
+ info->resolution = 1e-7;
+ info->monotonic = 1;
+ info->adjustable = 0;
+ }
+
+ large.u.LowPart = kernel_time.dwLowDateTime;
+ large.u.HighPart = kernel_time.dwHighDateTime;
+ ktime = large.QuadPart;
+
+ large.u.LowPart = user_time.dwLowDateTime;
+ large.u.HighPart = user_time.dwHighDateTime;
+ utime = large.QuadPart;
+
+ /* ktime and utime have a resolution of 100 nanoseconds */
+ t = _PyTime_FromNanoseconds((ktime + utime) * 100);
+ *tp = t;
+ return 0;
+}
+
+#elif defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_PROCESS_CPUTIME_ID)
+#define HAVE_THREAD_TIME
+static int
+_PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
+{
+ struct timespec ts;
+ const clockid_t clk_id = CLOCK_THREAD_CPUTIME_ID;
+ const char *function = "clock_gettime(CLOCK_THREAD_CPUTIME_ID)";
+
+ if (clock_gettime(clk_id, &ts)) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+ if (info) {
+ struct timespec res;
+ info->implementation = function;
+ info->monotonic = 1;
+ info->adjustable = 0;
+ if (clock_getres(clk_id, &res)) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+ }
+ info->resolution = res.tv_sec + res.tv_nsec * 1e-9;
+ }
+
+ if (_PyTime_FromTimespec(tp, &ts) < 0) {
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+#ifdef HAVE_THREAD_TIME
+static PyObject *
+time_thread_time(PyObject *self, PyObject *unused)
+{
+ _PyTime_t t;
+ if (_PyTime_GetThreadTimeWithInfo(&t, NULL) < 0) {
+ return NULL;
+ }
+ return _PyFloat_FromPyTime(t);
+}
+
+PyDoc_STRVAR(thread_time_doc,
+"thread_time() -> float\n\
+\n\
+Thread time for profiling: sum of the kernel and user-space CPU time.");
+
+static PyObject *
+time_thread_time_ns(PyObject *self, PyObject *unused)
+{
+ _PyTime_t t;
+ if (_PyTime_GetThreadTimeWithInfo(&t, NULL) < 0) {
+ return NULL;
+ }
+ return _PyTime_AsNanosecondsObject(t);
+}
+
+PyDoc_STRVAR(thread_time_ns_doc,
+"thread_time() -> int\n\
+\n\
+Thread time for profiling as nanoseconds:\n\
+sum of the kernel and user-space CPU time.");
+#endif
+
+
static PyObject *
time_get_clock_info(PyObject *self, PyObject *args)
{
@@ -1311,6 +1417,13 @@ time_get_clock_info(PyObject *self, PyObject *args)
return NULL;
}
}
+#ifdef HAVE_THREAD_TIME
+ else if (strcmp(name, "thread_time") == 0) {
+ if (_PyTime_GetThreadTimeWithInfo(&t, &info) < 0) {
+ return NULL;
+ }
+ }
+#endif
else {
PyErr_SetString(PyExc_ValueError, "unknown clock");
return NULL;
@@ -1519,6 +1632,10 @@ static PyMethodDef time_methods[] = {
{"monotonic_ns", time_monotonic_ns, METH_NOARGS, monotonic_ns_doc},
{"process_time", time_process_time, METH_NOARGS, process_time_doc},
{"process_time_ns", time_process_time_ns, METH_NOARGS, process_time_ns_doc},
+#ifdef HAVE_THREAD_TIME
+ {"thread_time", time_thread_time, METH_NOARGS, thread_time_doc},
+ {"thread_time_ns", time_thread_time_ns, METH_NOARGS, thread_time_ns_doc},
+#endif
{"perf_counter", time_perf_counter, METH_NOARGS, perf_counter_doc},
{"perf_counter_ns", time_perf_counter_ns, METH_NOARGS, perf_counter_ns_doc},
{"get_clock_info", time_get_clock_info, METH_VARARGS, get_clock_info_doc},
More information about the Python-checkins
mailing list