59

I want to log how long something takes in real walltime. Currently I'm doing this:

startTime = time.time()
someSQLOrSomething()
print "That took %.3f seconds" % (time.time() - startTime)

But that will fail (produce incorrect results) if the time is adjusted while the SQL query (or whatever it is) is running.

I don't want to just benchmark it. I want to log it in a live application in order to see trends on a live system.

I want something like clock_gettime(CLOCK_MONOTONIC,...), but in Python. And preferably without having to write a C module that calls clock_gettime().

Thomas
  • 4,208
  • 2
  • 29
  • 31
  • 6
    Well I don't really know how often it's actually adjusted. I run NTP. But with a mononotic clock I won't have to run into stuff like the Oracle RAC bug where it rebooted the system if the time was set backwards. Besides small NTP adjustments there are leap seconds that can go back and forward. – Thomas Jul 30 '09 at 12:41
  • 8
    S.Lott: incorrect. "A leap second is a positive or negative one-second adjustment [...]". It's trivial to look up. It's the first sentence on the "Leap second" article on Wikipedia. So when a leap second is added, NTP will readjust you system time backwards (because your system is fast. It didn't count 23:59:60), meaning a time.time()-based measurment can be negative. Trust me, many Oracle servers rebooted due to the bug I mentioned above last newyears. And I just used Oracle as an example where some programs can't handle time readjustments. – Thomas Jul 30 '09 at 22:18
  • I don't know why (unpatched) Oracle 10 does that. It just does, and Oracle (the company) confirms it. – Thomas Jul 30 '09 at 22:20
  • Just wanted to add a comment here of a use case that we encountered. Within our setup that consists of a number of vmware systems, we have noticed that time "adjustments" do happen regularly enough in the guest vms, especially with the host's load avg. is high. This results in things like ``supervisord`` that appear to depend on time.time() crashing resulting in orphaning the processes that it started. We've ^fixed^ this issue by applying the patch - https://github.com/Supervisor/supervisor/pull/468 – lonetwin Aug 14 '14 at 04:46
  • @Thomas: In theory, there could be negative leap seconds. In practice, all leap seconds are positive. See [The leap second: its history and possible future](https://www.cl.cam.ac.uk/~mgk25/time/metrologia-leapsecond.pdf). – jfs Jan 18 '15 at 12:37

5 Answers5

82

That function is simple enough that you can use ctypes to access it:

#!/usr/bin/env python

__all__ = ["monotonic_time"]

import ctypes, os

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_, os.strerror(errno_))
    return t.tv_sec + t.tv_nsec * 1e-9

if __name__ == "__main__":
    print monotonic_time()
Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
Armin Ronacher
  • 31,998
  • 13
  • 65
  • 69
  • Wow. Remove "self." and it worked perfectly. Very impressive. It requires ctypes which is add-on for Python 2.4, but it will do quite nicely. Thanks. – Thomas Jul 30 '09 at 10:44
  • Oh, and CLOCK_MONOTONIC seems to be 4 on FreeBSD and 1 on Linux. – Thomas Jul 30 '09 at 11:13
  • Nice solution. Is there any reason you use ctypes.pointer instead of ctypes.byref? – Kiv Jul 30 '09 at 12:36
  • @Thomas: fixed that, removed the self @Kiv: byref should work, can't test that right now, but it should do the trick. I don't remember when byref() does not work, so i went the safe path here. – Armin Ronacher Jul 31 '09 at 13:37
  • Could you (or someone else with edit powers) edit this answer and change 10e9 to 1e9 as Mitch suggest in other answer? – Thomas Oct 10 '09 at 20:56
  • I added code inspired by this, and actually under use here: http://code.google.com/p/anacrolix/source/browse/projects/pimu/monotime.py – Matt Joiner Jun 22 '10 at 06:12
  • 16
    use `CLOCK_MONOTONIC_RAW==4` (since Linux 2.6.28; Linux-specific) to avoid NTP adjustments. – jfs Dec 05 '11 at 18:04
  • Why use `CLOCK_MONOTONIC_RAW` instead of `CLOCK_PROCESS_CPUTIME_ID`? What's really the difference? (documentation here: http://man7.org/linux/man-pages/man2/clock_gettime.2.html). C example using `CLOCK_PROCESS_CPUTIME_ID` here: http://stackoverflow.com/questions/6749621/how-to-create-a-high-resolution-timer-in-linux-to-measure-program-performance – Gabriel Staples Aug 09 '16 at 02:51
  • Answer to my own question (https://www.python.org/dev/peps/pep-0418/#process-time): `CLOCK_PROCESS_CPUTIME_ID`: "The process time cannot be set. It is not monotonic: the clocks stop while the process is idle." It does *not* count during sleep or suspend. Based on this research, and the link above, I want to use `CLOCK_HIGHRES` (to include suspend time too) or `CLOCK_MONOTONIC` I think, to be "slewed" on Linux (I think that means to adjust it for accuracy here...like a calibration???) Source: https://www.python.org/dev/peps/pep-0418/#monotonic-clocks. – Gabriel Staples Aug 09 '16 at 03:12
  • I changed my mind again, I'd choose `CLOCK_MONOTONIC_RAW` instead, just as you have done, since it is "Similar to CLOCK_MONOTONIC, but provides access to a raw hardware-based time that is not subject to NTP [Network Time Protocol] adjustments or the incremental adjustments performed by adjtime(3)." (http://man7.org/linux/man-pages/man2/clock_gettime.2.html). So, I've answered my own question, provided some valuable links to help others, and come full circle to accept which clock you have chosen and to like that clock the best, for basic low-level time-stamps and precise interval measurements. – Gabriel Staples Aug 09 '16 at 03:19
  • 1
    Thanks @ArminRonacher for your answer, I've incorporated it into a Windows/Linux-compatible module I posted here: http://stackoverflow.com/a/38319607/4561887 – Gabriel Staples Aug 13 '16 at 18:57
  • How to do it in macOS ? – fuat Nov 23 '19 at 18:44
50

In Python 3.3+ there is time.monotonic (see also PEP 418).

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
Paddy3118
  • 4,704
  • 27
  • 38
  • In CPython, I assume this internally uses `CLOCK_MONOTONIC` and **not** `CLOCK_MONOTONIC_RAW`, with the latter not even being available in Python 3.3. – Asclepius Nov 04 '13 at 19:56
  • @A-B-B: [`time` module knows about `CLOCK_MONOTONIC_RAW`](http://hg.python.org/cpython/file/e20f98a8ed71/Modules/timemodule.c#l1377) though it doesn't use it as far as I can see. You could [define clock that uses it via `ctypes` even on Python 2.7](https://gist.github.com/zed/5073409) – jfs Dec 11 '13 at 21:59
  • 2
    If I am reading the documentation right, it appears that in Python 3.3 you can get `CLOCK_MONOTONIC_RAW` by calling `time.clock_gettime(time.CLOCK_MONOTONIC_RAW)`, which is great so when you use it to measure small time intervals you never get error introduced when the network updates the time via NTP (Network Time Protocol) adjustments. Python time reference: https://docs.python.org/3/library/time.html#time.CLOCK_MONOTONIC_RAW. <-- NOTE: FOR UNIX/LINUX ONLY. For Windows, just call `time.clock()`, which already has microsecond or better resolution since it uses the QueryPerformanceCounter(). – Gabriel Staples Aug 09 '16 at 03:31
9

As pointed out in this question, avoiding NTP readjustments on Linux requires CLOCK_MONOTONIC_RAW. That's defined as 4 on Linux (since 2.6.28).

Portably getting the correct constant #defined in a C header from Python is tricky; there is h2py, but that doesn't really help you get the value at runtime.

Community
  • 1
  • 1
Daira Hopwood
  • 2,264
  • 22
  • 14
  • 1
    I believe the chosen answer was incorrect and did not explain the jumps, see [my comment on it](http://stackoverflow.com/questions/3657289/linux-clock-gettimeclock-monotonic-strange-non-monotonic-behavior#comment12057005_3657385). Both CLOCK_MONOTONIC and CLOCK_MONOTONIC_RAW are monotonic, and the only way they differ is that the former corrects hardware clock speed using NTP. – Tobu Mar 01 '12 at 15:59
4

Here's how I get monotonic time in Python 2.7:

Install the monotonic package:

pip install monotonic

Then in Python:

import monotonic; mtime = monotonic.time.time #now mtime() can be used in place of time.time()
t0 = mtime()
#...do something
elapsed = mtime()-t0 #gives correct elapsed time, even if system clock changed.

EDIT: check that the above works on your target OS before trusting it. The monotonic library seems to handle clock changes in some OSes and not others.

Luke
  • 5,329
  • 2
  • 29
  • 34
  • This is not monotonic. It fails if one changes the system clock between calls, just like calls to `time.time()` Tested on OSX. – Gabriel Jan 10 '21 at 15:30
  • @Gabriel It works within a single Python process; I've tested it and built a robot that depends on it. I suspect that if it didn't work for you, it's because you restarted Python such that you ended up with a new Python process. – Luke Jan 30 '21 at 23:48
  • It works as long as you don't touch your time/date manually, or it doesn't get touched automatically by the operating system. – Gabriel Feb 03 '21 at 23:00
  • 1
    Hmm you're right; it seems to be OS dependent. Changing system time definitely didn't affect it on a Raspberry Pi with Raspbian, but on Ubuntu 20 it looks like it does, contrary to the library's documentation. Disappointing. – Luke Feb 15 '21 at 20:34
2

time.monotonic() might be useful:

Return the value (in fractional seconds) of a monotonic clock, i.e. a clock that cannot go backwards. The clock 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.

AmirHossein
  • 1,310
  • 1
  • 12
  • 19