time::now(), a Duration’s Readers, and What process::run Reports Beyond the OutputStatus: Accepted
Date: October 19, 2026
Answers: what a Nikaia program needs to measure how long something
takes, and what a program it ran cost, so that a benchmark harness can be
written in Nikaia (CLAUDE.md: helper tools are written in Nikaia)
Related: ADR-150 (a Duration is the language below’s,
re-exported, written 5.seconds()), ADR-243 (process::run),
#672 (calendar time, not decided here); Part III’s std::process and
std::time
let started = time::now() // no such call today
let out = process::run("./brc", [input]; dir: ".")
let took = started.elapsed()
println(f"{took.in_nanos()} ns, {out.max_rss_kib ?? 0} KiB") // none of it exists
std::time holds Duration, its constructors (5.seconds()) and sleep.
A program cannot read a clock, reads a Duration no finer than microseconds, and
learns from process::run only the exit code and the two streams. A
benchmark harness needs all three: the time a run took, the processor time
it used, and the most memory it held.
std::time::Instant::now() and elapsed(), monotonic;
Duration::as_nanos(), as_secs_f64(). Child resource use is not in std;
wait4/getrusage through libc.time.Now() carries a monotonic reading; cmd.ProcessState
has UserTime(), SystemTime() and SysUsage() (Maxrss).time.perf_counter_ns(); resource.getrusage(RUSAGE_CHILDREN)
(a maximum over all children waited for, not per child).System.nanoTime(); ProcessHandle.Info.totalCpuDuration().time::now() reads a monotonic clock as a time::Instant.time::now() returns a time::Instant; i.elapsed() is the Duration
since it, and b.since(a) the Duration from a to b, zero where b is
earlier. An Instant is monotonic: it never goes back. It says nothing
about the calendar (#672 decides that) and means something only within the
run that read it. It is the language below’s std::time::Instant,
re-exported as ADR-150 re-exports Duration.
Duration reads in nanoseconds.d.in_nanos() is the count of whole nanoseconds, an i64 saturated at
i64::MAX (292 years), the sixth of ADR-150 D5’s readers (in_hours to
in_micros) and under its rules. No floating-point reader: a program that
wants seconds divides, and the language below’s as_* names stay out of the
ledger, as ADR-150 D5 keeps them.
process::run reports what the run cost.process::Output gains:
elapsed: a Duration, from just before the program starts to just after
it is waited for, read on the monotonic clock;user_time, system_time: Durations, the processor time the program and
everything it waited for used, in user and in kernel mode;max_rss_kib: an i64?, the most memory the program held resident, in
KiB, as the operating system reports it for that one child; null where the
platform reports none.The figures are the operating system’s for that child alone (wait4 on
Unix), never a maximum over every child the program ran. max_rss_kib is the
kernel’s figure as it stands, not a cleaned one: Linux counts in it the
high-water mark of the process image that exec replaced, which is the
caller’s, so on Linux it is never below the most memory the caller had held
resident before it started the program. A harness that compares programs keeps
its own memory small and reports the figure for a program that does nothing
beside its table. The unsafe the
call takes lives in a crate of its own under crates/unsafe/, as every
unsafe std needs does.
bench module in std (warm-up, repetitions, statistics). A harness
decides its own method; std gives the readings.SystemTime). It can go back, and calendar time is #672’s.getrusage(RUSAGE_CHILDREN). It reports the largest child so far, so a
harness would read the largest run’s memory for every later one.max_rss_kib. The kernel gives no
figure without it; reading /proc/<pid>/status (VmHWM) before the wait
races with the program’s end and exists on Linux only, and starting every
program through a small helper made early costs a process per run.process::run_measured. The readings cost one wait4 and two
clock reads, nothing a program would avoid; one call is simpler to learn.process::Output gains four fields; a program that builds one by hand
(none in this repository) must give them.time::since_epoch()time::since_epoch() reads the wall clock as the time::Duration since the
Unix epoch, zero before it, with no touches (as time::now). This narrows
the wall clock’s rejection in section 4 to measuring: the monotonic clock
(time::now, an Instant) measures spans within a run and never goes back;
the wall clock is what a file’s time is stamped in (fs::Metadata::modified,
ADR-319 section 7), so only it can be compared with that. It can go back, and
a program that measures a span with it is wrong. Both exist because neither
does the other’s job; calendar time stays #672’s.