14

This question has already been answered for the browser here, but window.performance.now() is obviously not available in Node.js.

Some applications need a steady clock, i.e., a clock that monotonically increases through time, not subject to system clock drifts. For instance, Java has System.nanoTime() and C++ has std::chrono::steady_clock. Is such clock available in Node.js?

Lucio Paiva
  • 19,015
  • 11
  • 82
  • 104

2 Answers2

20

Turns out the equivalent in Node.js is process.hrtime(). As per the documentation:

[The time returned from process.hrtime() is] relative to an arbitrary time in the past, and not related to the time of day and therefore not subject to clock drift.


Example

Let's say we want to periodically call some REST endpoint once a second, process its outcome and print something to a log file. Consider the endpoint may take a while to respond, e.g., from hundreds of milliseconds to more than one second. We don't want to have two concurrent requests going on, so setInterval() does not exactly meet our needs.

One good approach is to call our function one first time, do the request, process it and then call setTimeout() and reschedule for another run. But we want to do that once a second, taking into account the time we spent making the request. Here's one way to do it using our steady clock (which will guarantee we won't be fooled by system clock drifts):

function time() {
    const nanos = process.hrtime.bigint();
    return Number(nanos / 1_000_000n);
}

async function run() {
    const startTime = time();

    const response = await doRequest();
    await processResponse(response);

    const endTime = time();
    // wait just the right amount of time so we run once second; 
    // if we took more than one second, run again immediately
    const nextRunInMillis = Math.max(0, 1000 - (endTime - startTime));
    setTimeout(run, nextRunInMillis);
}

run();

I made this helper function time() which converts the value returned by process.hrtime.bigint() to a timestamp with milliseconds resolution; just enough resolution for this application.

Lucio Paiva
  • 19,015
  • 11
  • 82
  • 104
  • It's nice you have found the equivalent in Node.js, But out of curiosity why precision of `(new Date()).getTime()` would not be enough?. – Keith Oct 26 '17 at 22:34
  • 2
    @Keith `new Date()` relies on the system clock. This clock can shift due to daylight savings time boundaries, an NTP refresh, or any number of other reasons. Node.JS provides a high resolution timer based on CPU ticks (not the system clock) which is impervious to this type of manipulation. – Kevin Burdett Oct 26 '17 at 22:39
  • @Keith Matter of fact its precision and resolution are enough for a task that runs once a second. The problem is that `(new Date()).getTime()` (or even better, `Date.now()`) is affected by system clock drifts. For instance, if your system clock gets adjusted by some automatic clock adjustment job (which happens regularly in operating systems), the elapsed time between measures won't be what one would expect. – Lucio Paiva Oct 26 '17 at 22:42
  • 1
    `This clock can shift due to daylight savings time`, No it's constant, it's a UTC value, DST has no effect on it. But something changing the system clock, that makes sense. – Keith Oct 26 '17 at 22:46
  • @Keith you're right about `Date.now()` returning a UTC value, good catch. Most languages have some sort of steady clock. I edited my question to give an example of equivalent clocks in Java and C++. – Lucio Paiva Oct 26 '17 at 22:55
  • From the linked docs `Stability: 3 - Legacy. Use process.hrtime.bigint() instead.` – frederj Dec 28 '21 at 18:39
  • Thanks @frederj, I have just updated the example to use the bigint version. – Lucio Paiva Dec 28 '21 at 20:16
2

NodeJS 10.7.0 added process.hrtime.bigint().

You can then do this:


function monotimeRef() {
  return process.hrtime.bigint();
}

function monotimeDiff(ref) {
  return Number(process.hrtime.bigint() - ref) / 10**9;
}

Demonstrating the usage in a Node REPL:

// Measure reference time.
> let t0 = monotimeRef();
undefined

[ ... let some time pass ... ]

// Measure time passed since reference time,
// in seconds.
> monotimeDiff(t0)
12.546663115

Note:

  • Number() converts a BigInt to a regular Number type, allowing for translating from nanoseconds to seconds with the normal division operator.
  • monotimeDiff() returns the wall time difference passed with nanosecond resolution as a floating point number (as of converting to Number before doing the division).
  • This assumes that the measured time duration does not grow beyond 2^53 ns, which is actually just about 104 days (2**53 ns / 10**9 ns/s / 86400.0 s/day = 104.3 day).
Dr. Jan-Philip Gehrcke
  • 33,287
  • 14
  • 85
  • 130
  • Nice addition to this question. You could also divide by `10**9` *before* converting to `Number`, thus avoiding the 104-day limit: `return Number((process.hrtime.bigint() - ref) / 10n**9n)`. Also, I'm not sure if all Javascript engines will optimize `10**9` into a constant. I would create a constant outside of the function, just in case. – Lucio Paiva Nov 20 '19 at 10:59
  • "You could also divide by 10**9 before converting to Number" -- that approach loses sub-second resolution. In other words, that approach would measure the time difference only with a resolution of 1 second. BigInt divided by BigInt yields a BigInt, the fractional part is lost. I am adding a statement to the first bullet above clarifying that the goal of the approach I show is to retain nanosecond resolution. – Dr. Jan-Philip Gehrcke Nov 20 '19 at 18:10
  • 1
    Of course, you are totally correct. I completely missed the integer division. What we could do then is to divide it by `10n**6n`. That way we'd still have millisecond precision, enough for most use cases. Anyway, nice contribution. Here's my upvote :-) – Lucio Paiva Nov 21 '19 at 00:39
  • First I misunderstood your comment ... yes, this could work. But then you can also work with a count of nanoseconds in the first place, haha! In code I love to have floating point values with unit _second_ because that's easy to reason about :-). – Dr. Jan-Philip Gehrcke Nov 21 '19 at 02:06