From a0820481f287958edd5157d7af0662c24ee96fc0 Mon Sep 17 00:00:00 2001 From: Till Bungert Date: Tue, 28 Apr 2026 01:01:47 +0200 Subject: [PATCH] fix(excmd): use realtime for v:starttime, :uptime #39425 Problem: `v:starttime`, `:uptime` use a monotonic high-resolution timer. This only works as long as the timer keeps running (if the computer is suspended the timer is paused). This is somewhat unintuitive, and doesn't match the behavior of the `uptime` shell command. Solution: Implement `os_realtime` to get the real time since the epoch in nanoseconds. --- runtime/doc/news.txt | 2 +- runtime/doc/vvars.txt | 4 ++-- runtime/lua/vim/_core/ex_cmd.lua | 4 +++- runtime/lua/vim/_meta/vvars.gen.lua | 4 ++-- src/nvim/main.c | 2 +- src/nvim/os/time.c | 19 +++++++++++++++++++ src/nvim/vvars.lua | 4 ++-- 7 files changed, 30 insertions(+), 9 deletions(-) diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index d45c2bd765..447e607436 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -217,7 +217,7 @@ UI VIMSCRIPT • |v:exitreason| is set before |QuitPre|. -• |v:starttime| is the process start time (monotonic nanoseconds). +• |v:starttime| is the process start time (nanoseconds from UNIX epoch). ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/vvars.txt b/runtime/doc/vvars.txt index 6a885f6a4f..5d7efac2c8 100644 --- a/runtime/doc/vvars.txt +++ b/runtime/doc/vvars.txt @@ -635,11 +635,11 @@ v:stacktrace *v:starttime* *starttime-variable* v:starttime - Timestamp (monotonic nanoseconds) when the Nvim process + Timestamp (nanoseconds from UNIX epoch) when the Nvim process started. To see the current "uptime": >lua - vim.print(('uptime: %d seconds'):format((vim.uv.hrtime() - vim.v.starttime) / 1e9)) + vim.print(('uptime: %d seconds'):format(os.time() - (vim.v.starttime / 1e9))) < Read-only. diff --git a/runtime/lua/vim/_core/ex_cmd.lua b/runtime/lua/vim/_core/ex_cmd.lua index bff960e39d..81232abf74 100644 --- a/runtime/lua/vim/_core/ex_cmd.lua +++ b/runtime/lua/vim/_core/ex_cmd.lua @@ -259,7 +259,9 @@ function M.ex_terminal(eap, shell_argv) end function M.ex_uptime() - local uptime = math.floor((uv.hrtime() - vim.v.starttime) / 1e9) + -- os.time() might lead to uptime == -1 when this is called too quickly after startup + local now = assert(uv.clock_gettime('realtime')) + local uptime = math.floor((now.sec * 1e9 + now.nsec - vim.v.starttime) / 1e9) local uptime_display = time.fmt_rtime(uptime) api.nvim_echo({ { N_('Up %s'):format(uptime_display) } }, true, {}) end diff --git a/runtime/lua/vim/_meta/vvars.gen.lua b/runtime/lua/vim/_meta/vvars.gen.lua index 7b5ca0e417..bee771bc23 100644 --- a/runtime/lua/vim/_meta/vvars.gen.lua +++ b/runtime/lua/vim/_meta/vvars.gen.lua @@ -667,13 +667,13 @@ vim.v.shell_error = ... --- @type table[] vim.v.stacktrace = ... ---- Timestamp (monotonic nanoseconds) when the Nvim process +--- Timestamp (nanoseconds from UNIX epoch) when the Nvim process --- started. --- --- To see the current "uptime": --- --- ```lua ---- vim.print(('uptime: %d seconds'):format((vim.uv.hrtime() - vim.v.starttime) / 1e9)) +--- vim.print(('uptime: %d seconds'):format(os.time() - (vim.v.starttime / 1e9))) --- ``` --- --- Read-only. diff --git a/src/nvim/main.c b/src/nvim/main.c index a1320648a4..4cfec6bd41 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -197,7 +197,7 @@ void early_init(mparm_T *paramp) estack_init(); cmdline_init(); eval_init(); // init global variables - set_vim_var_nr(VV_STARTTIME, (varnumber_T)os_hrtime()); + set_vim_var_nr(VV_STARTTIME, (varnumber_T)os_realtime()); init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. runtime_init(); diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index f243eda047..2e6bf3e56d 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -32,6 +32,25 @@ uint64_t os_hrtime(void) return uv_hrtime(); } +/// Obtain the current system time from a high-resolution +/// real-time clock source. +/// +/// The real-time clock counts from the UNIX epoch (1970-01-01) +/// and is subject to time adjustments; it can jump back in time. +/// +/// @return Nanoseconds since epoch or 0 +int64_t os_realtime(void) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + uv_timespec64_t ts = { 0 }; + int error_number; + if ((error_number = uv_clock_gettime(UV_CLOCK_REALTIME, &ts)) != 0) { + ELOG("uv_clock_gettime failed: %d %s", error_number, uv_err_name(error_number)); + return 0; + } + return ts.tv_sec * 1000000000L + ts.tv_nsec; +} + /// Gets a millisecond-resolution, monotonically-increasing time relative to an /// arbitrary time in the past. /// diff --git a/src/nvim/vvars.lua b/src/nvim/vvars.lua index 648e5f1310..7096349125 100644 --- a/src/nvim/vvars.lua +++ b/src/nvim/vvars.lua @@ -756,11 +756,11 @@ M.vars = { starttime = { type = 'integer', desc = [=[ - Timestamp (monotonic nanoseconds) when the Nvim process + Timestamp (nanoseconds from UNIX epoch) when the Nvim process started. To see the current "uptime": >lua - vim.print(('uptime: %d seconds'):format((vim.uv.hrtime() - vim.v.starttime) / 1e9)) + vim.print(('uptime: %d seconds'):format(os.time() - (vim.v.starttime / 1e9))) < Read-only. ]=],