diff --git a/cli/tests/unit/os_test.ts b/cli/tests/unit/os_test.ts index 5e88f02c19..ae2bee72e3 100644 --- a/cli/tests/unit/os_test.ts +++ b/cli/tests/unit/os_test.ts @@ -239,6 +239,18 @@ Deno.test({ permissions: { sys: false } }, function releasePerm() { }, Deno.errors.PermissionDenied); }); +Deno.test({ permissions: { sys: ["osUptime"] } }, function osUptime() { + const uptime = Deno.osUptime(); + assert(typeof uptime === "number"); + assert(uptime > 0); +}); + +Deno.test({ permissions: { sys: false } }, function osUptimePerm() { + assertThrows(() => { + Deno.osUptime(); + }, Deno.errors.PermissionDenied); +}); + Deno.test( { permissions: { sys: ["systemMemoryInfo"] } }, function systemMemoryInfo() { diff --git a/cli/tests/unit/permissions_test.ts b/cli/tests/unit/permissions_test.ts index 3387913e87..3af2e35eaa 100644 --- a/cli/tests/unit/permissions_test.ts +++ b/cli/tests/unit/permissions_test.ts @@ -22,6 +22,7 @@ Deno.test(async function permissionNetInvalidHost() { Deno.test(async function permissionSysValidKind() { await Deno.permissions.query({ name: "sys", kind: "loadavg" }); await Deno.permissions.query({ name: "sys", kind: "osRelease" }); + await Deno.permissions.query({ name: "sys", kind: "osUptime" }); await Deno.permissions.query({ name: "sys", kind: "networkInterfaces" }); await Deno.permissions.query({ name: "sys", kind: "systemMemoryInfo" }); await Deno.permissions.query({ name: "sys", kind: "hostname" }); diff --git a/cli/tsc/diagnostics.rs b/cli/tsc/diagnostics.rs index 4413398c24..8323fd7ac4 100644 --- a/cli/tsc/diagnostics.rs +++ b/cli/tsc/diagnostics.rs @@ -46,6 +46,7 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[ "ServeInit", "ServeTlsInit", "Handler", + "osUptime", ]; static MSG_MISSING_PROPERTY_DENO: Lazy = Lazy::new(|| { diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index fa68dc1bdd..4dafd73888 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -4120,6 +4120,7 @@ declare namespace Deno { | "systemMemoryInfo" | "networkInterfaces" | "osRelease" + | "osUptime" | "uid" | "gid"; } diff --git a/cli/tsc/dts/lib.deno.unstable.d.ts b/cli/tsc/dts/lib.deno.unstable.d.ts index f1b8d99e90..77d3505037 100644 --- a/cli/tsc/dts/lib.deno.unstable.d.ts +++ b/cli/tsc/dts/lib.deno.unstable.d.ts @@ -1691,6 +1691,21 @@ declare namespace Deno { /** The buffered output from the child process' `stderr`. */ readonly stderr: Uint8Array; } + + /** **UNSTABLE**: New API, yet to be vetted. + * + * Returns the Operating System uptime in number of seconds. + * + * ```ts + * console.log(Deno.osUptime()); + * ``` + * + * Requires `allow-sys` permission. + * + * @tags allow-sys + * @category Runtime Environment + */ + export function osUptime(): number; } /** **UNSTABLE**: New API, yet to be vetted. diff --git a/runtime/js/30_os.js b/runtime/js/30_os.js index 723b521329..3c9768593b 100644 --- a/runtime/js/30_os.js +++ b/runtime/js/30_os.js @@ -25,6 +25,10 @@ return ops.op_os_release(); } + function osUptime() { + return ops.op_os_uptime(); + } + function systemMemoryInfo() { return ops.op_system_memory_info(); } @@ -106,6 +110,7 @@ loadavg, networkInterfaces, osRelease, + osUptime, setExitHandler, systemMemoryInfo, uid, diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js index 94a6114564..f49fb5af14 100644 --- a/runtime/js/90_deno_ns.js +++ b/runtime/js/90_deno_ns.js @@ -119,6 +119,7 @@ refTimer: __bootstrap.timers.refTimer, unrefTimer: __bootstrap.timers.unrefTimer, osRelease: __bootstrap.os.osRelease, + osUptime: __bootstrap.os.osUptime, hostname: __bootstrap.os.hostname, systemMemoryInfo: __bootstrap.os.systemMemoryInfo, networkInterfaces: __bootstrap.os.networkInterfaces, diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index 613b4507db..e82afbf7cd 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -29,6 +29,7 @@ fn init_ops(builder: &mut ExtensionBuilder) -> &mut ExtensionBuilder { op_loadavg::decl(), op_network_interfaces::decl(), op_os_release::decl(), + op_os_uptime::decl(), op_set_env::decl(), op_set_exit_code::decl(), op_system_memory_info::decl(), @@ -423,3 +424,13 @@ fn rss() -> usize { } } } + +#[op] +fn op_os_uptime(state: &mut OpState) -> Result { + super::check_unstable(state, "Deno.osUptime"); + state + .borrow_mut::() + .sys + .check("osUptime", Some("Deno.osUptime()"))?; + Ok(sys_info::os_uptime()) +} diff --git a/runtime/ops/os/sys_info.rs b/runtime/ops/os/sys_info.rs index 40de7cc4b0..ecb40b9a04 100644 --- a/runtime/ops/os/sys_info.rs +++ b/runtime/ops/os/sys_info.rs @@ -300,3 +300,65 @@ pub fn mem_info() -> Option { Some(mem_info) } + +pub fn os_uptime() -> u64 { + #[cfg(target_os = "linux")] + { + let mut info = std::mem::MaybeUninit::uninit(); + // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. + let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; + if res == 0 { + // SAFETY: `sysinfo` initializes the struct. + let info = unsafe { info.assume_init() }; + return info.uptime as u64; + } + } + + #[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "openbsd" + ))] + { + use std::mem; + use std::time::Duration; + use std::time::SystemTime; + let mut request = [libc::CTL_KERN, libc::KERN_BOOTTIME]; + // SAFETY: `boottime` is only accessed if sysctl() succeeds + // and agrees with the `size` set by sysctl(). + let mut boottime: libc::timeval = unsafe { mem::zeroed() }; + let mut size: libc::size_t = mem::size_of_val(&boottime) as libc::size_t; + // SAFETY: `sysctl` is thread-safe. + let res = unsafe { + libc::sysctl( + &mut request[0], + 2, + &mut boottime as *mut libc::timeval as *mut libc::c_void, + &mut size, + std::ptr::null_mut(), + 0, + ) + }; + if res == 0 { + return SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|d| { + (d - Duration::new( + boottime.tv_sec as u64, + boottime.tv_usec as u32 * 1000, + )) + .as_secs() + }) + .unwrap_or_default(); + } + } + + #[cfg(target_family = "windows")] + unsafe { + // Windows is the only one that returns `uptime` in milisecond precision, + // so we need to get the seconds out of it to be in sync with other envs. + return unsafe { winapi::um::sysinfoapi::GetTickCount64() as u64 / 1000 }; + } + + 0 +} diff --git a/runtime/permissions/mod.rs b/runtime/permissions/mod.rs index 5b2af0e0f9..ce4984ee11 100644 --- a/runtime/permissions/mod.rs +++ b/runtime/permissions/mod.rs @@ -309,7 +309,7 @@ pub struct SysDescriptor(pub String); pub fn parse_sys_kind(kind: &str) -> Result<&str, AnyError> { match kind { - "hostname" | "osRelease" | "loadavg" | "networkInterfaces" + "hostname" | "osRelease" | "osUptime" | "loadavg" | "networkInterfaces" | "systemMemoryInfo" | "uid" | "gid" => Ok(kind), _ => Err(type_error(format!("unknown system info kind \"{}\"", kind))), }