Unverified Kaydet (Commit) 4bd41c9b authored tarafından Antoine Pitrou's avatar Antoine Pitrou Kaydeden (comit) GitHub

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
üst 762b9571
......@@ -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
......
......@@ -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
-------------
......
......@@ -47,6 +47,12 @@ ROUNDING_MODES = (
)
def busy_wait(duration):
deadline = time.monotonic() + duration
while time.monotonic() < deadline:
pass
class TimeTestCase(unittest.TestCase):
def setUp(self):
......@@ -81,6 +87,10 @@ class TimeTestCase(unittest.TestCase):
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 @@ class TimeTestCase(unittest.TestCase):
# 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):
......
......@@ -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},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment