In Ruby, why should you time an operation with Process.clock_gettime(Process::CLOCK_MONOTONIC) rather than subtracting two Time.now values?
answer
- wall clock can be reset
- never goes backwards
- origin is arbitrary
- default unit :float_second
- CPU time is a different clock
basics
~10 sTime.now reads the wall clock, which NTP or an operator can step forwards or backwards mid-measurement. Process.clock_gettime(Process::CLOCK_MONOTONIC) never goes backwards, so the difference of two readings is a reliable elapsed time in seconds.
solid answer
~40 s`Time.now` is **wall-clock** time: it answers "what time is it", and the system may step it when NTP corrects drift, an admin changes it, or a VM resumes. Subtracting two `Time.now` values can then give a negative or inflated duration. `Process.clock_gettime(Process::CLOCK_MONOTONIC)` reads a clock that only moves forward, with an arbitrary origin such as boot time, so only differences between readings mean anything. It returns a `Float` of seconds by default; pass a unit such as `:millisecond` or `:nanosecond` for an `Integer`. Use it for timeouts, retry back-off, latency logging and rate limits. Keep `Time.now` for timestamps a person or another system reads, and use `:CLOCK_PROCESS_CPUTIME_ID` when you want CPU time rather than elapsed time.
code
ruby · 9 linest0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
sleep 0.25
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
# => about 0.25 (Float seconds)
cpu0 = Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID, :millisecond)
sleep 0.25
Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID, :millisecond) - cpu0
# => close to 0: sleeping uses no CPUgo deeper
Recall that Time.now can jump and that Process.clock_gettime with CLOCK_MONOTONIC is the tool for measuring how long something took.
Explain what makes the wall clock jump, why the monotonic origin is arbitrary, the default Float seconds unit, and the integer unit options.
Show where wall-clock durations break production: negative timeouts, skewed latency metrics, retry deadlines. Use CPU time against elapsed time to separate waiting from computing.
Make clock choice a codebase rule: wrap monotonic timing in one helper for deadlines and metrics so no team reinvents duration code with Time.now.
## Two different questions A program asks two different time questions, and they need two different clocks: 1. **"What time is it?"** A timestamp for a log line, a record or a user. This is **wall-clock** or *real* time, the clock `Time.now` reads. 2. **"How long did this take?"** An elapsed duration for a timeout, a back-off, a latency metric. This needs a clock that measures intervals faithfully: a **monotonic** clock. ## Why wall-clock subtraction goes wrong The wall clock is allowed to **jump**. Operating systems change it when: - a time-synchronisation daemon steps the clock to correct drift; - an administrator or a container host sets the time; - a virtual machine or laptop resumes from suspend and catches up. If a jump happens between two `Time.now` readings, `finish - start` is off by the size of the jump. It can even be **negative**, which turns a timeout check into an infinite wait or makes a latency histogram report nonsense. The bug is rare, which makes it hard to reproduce: it appears only when a correction lands inside the measured window. ## Process.clock_gettime `Process.clock_gettime(clock_id, unit = :float_second)` wraps the POSIX `clock_gettime()` call. | Clock | Measures | Use it for | |---|---|---| | `Process::CLOCK_MONOTONIC` | elapsed time, never backwards, arbitrary origin | durations, timeouts, back-off | | `Process::CLOCK_REALTIME` | wall-clock seconds since the epoch | rarely; `Time.now` is recommended instead | | `Process::CLOCK_PROCESS_CPUTIME_ID` | CPU time consumed by this process | telling CPU work from waiting | The clock can be given as the constant or as a symbol such as `:CLOCK_MONOTONIC`. Which clocks exist depends on the operating system; an unsupported one raises `Errno::EINVAL`. `CLOCK_MONOTONIC` is available on Linux, macOS, the BSDs and Windows, with emulations where needed. The **origin** of the monotonic clock is system-dependent (often boot time), so a single reading is meaningless on its own. Never store it, compare it across processes or machines, or turn it into a date. ## Units The optional second argument picks the unit: - `:float_second` (default), `:float_millisecond`, `:float_microsecond` return a `Float`; - `:second`, `:millisecond`, `:microsecond`, `:nanosecond` return an `Integer`. Integers avoid floating-point rounding when you sum many small intervals or need exact nanoseconds. ## A reusable pattern ```ruby def elapsed_ms start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) yield Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - start end ``` The same idea powers a deadline: compute `deadline = now + budget` once on the monotonic clock, then compare each new reading against it inside a retry loop. ## Deadlines in retry loops A retry loop with an overall budget is where the monotonic clock matters most: 1. Read the monotonic clock once and compute `deadline = start + budget_seconds`. 2. Before each attempt, compute `remaining = deadline - now` from a fresh monotonic reading. 3. Stop when `remaining` reaches zero, and pass `remaining` to any call that accepts a timeout, so the whole loop respects one budget. With `Time.now` instead, a clock step backwards extends the budget by the size of the step, and a step forwards ends it early. ## Mistakes seen in review - Passing a monotonic reading to `Time.at`: the result is a meaningless date, typically in early 1970, because the reading counts from an arbitrary origin. - Mixing clocks: subtracting a `Time.now.to_f` start value from a monotonic reading. - Summing many `Float` intervals where exact totals matter, instead of an integer unit such as `:nanosecond`. - Measuring CPU-bound work by elapsed time on a busy machine, where other processes inflate the result. ## Wall time versus CPU time Elapsed monotonic time includes waiting on I/O, locks and sleeping. If a request takes 800 ms of monotonic time but only 20 ms of `CLOCK_PROCESS_CPUTIME_ID`, it spent its time waiting, not computing. Taking both readings around the same block is a cheap first diagnostic before reaching for a profiler. ## Summary - `Time.now` for timestamps; the monotonic clock for durations. - Differences only; the origin is arbitrary. - Pick an integer unit when precision matters. - CPU-time clocks answer a different question from elapsed time.
- In Ruby, can you log the value of Process.clock_gettime(Process::CLOCK_MONOTONIC) as a timestamp?No. Its origin is system-dependent, often boot time, so a single reading has no calendar meaning and differs between machines and reboots. Log a `Time.now` timestamp, in ISO 8601, for when something happened, and a monotonic difference for how long it took.
- In Ruby, how do you tell whether a slow block was computing or waiting?Read both `Process::CLOCK_MONOTONIC` and `Process::CLOCK_PROCESS_CPUTIME_ID` before and after it. A large monotonic difference with a small CPU-time difference means the block was waiting on I/O, locks or sleep; similar values mean it was burning CPU.
The wall clock is the office clock that anyone may reset; the monotonic clock is a stopwatch in your pocket. You would never time a race by glancing at a clock someone might adjust halfway through.
saying these in an interview costs you the question
- Time.now - start is always accurate, because the system clock only moves forward.
- A monotonic reading is seconds since the Unix epoch.
- Process.clock_gettime returns an Integer of nanoseconds by default.
- CLOCK_PROCESS_CPUTIME_ID measures wall-clock time for the process.
- Monotonic readings can be compared between two servers.