summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsnek <snek@deno.com>2024-11-08 23:20:24 +0100
committerGitHub <noreply@github.com>2024-11-08 23:20:24 +0100
commit73fbd61bd016eebbf2776dc94c15a26bf39668d6 (patch)
treec7e5f4ee47f94f2279e745b1825959f82a66a22d
parentd4f1bd3dacf54c4625eef7828341b39286ead8cb (diff)
fix: performance.timeOrigin (#26787)
`performance.timeOrigin` was being set from when JS started executing, but `op_now` measures from an `std::time::Instant` stored in `OpState`, which is created at a completely different time. This caused `performance.timeOrigin` to be very incorrect. This PR corrects the origin and also cleans up some of the timer code. Compared to `Date.now()`, `performance`'s time origin is now consistently within 5us (0.005ms) of system time. ![image](https://github.com/user-attachments/assets/0a7be04a-4f6d-4816-bd25-38a2e6136926)
-rw-r--r--cli/ops/bench.rs4
-rw-r--r--ext/web/02_timers.js13
-rw-r--r--ext/web/15_performance.js24
-rw-r--r--ext/web/lib.rs6
-rw-r--r--ext/web/timers.rs65
-rw-r--r--runtime/js/99_main.js5
6 files changed, 73 insertions, 44 deletions
diff --git a/cli/ops/bench.rs b/cli/ops/bench.rs
index 79a7ddef0..1f4a4bd9b 100644
--- a/cli/ops/bench.rs
+++ b/cli/ops/bench.rs
@@ -2,7 +2,6 @@
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
-use std::time;
use deno_core::error::generic_error;
use deno_core::error::type_error;
@@ -13,6 +12,7 @@ use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_runtime::deno_permissions::ChildPermissionsArg;
use deno_runtime::deno_permissions::PermissionsContainer;
+use deno_runtime::deno_web::StartTime;
use tokio::sync::mpsc::UnboundedSender;
use uuid::Uuid;
@@ -148,7 +148,7 @@ fn op_dispatch_bench_event(state: &mut OpState, #[serde] event: BenchEvent) {
#[op2(fast)]
#[number]
fn op_bench_now(state: &mut OpState) -> Result<u64, std::num::TryFromIntError> {
- let ns = state.borrow::<time::Instant>().elapsed().as_nanos();
+ let ns = state.borrow::<StartTime>().elapsed().as_nanos();
let ns_u64 = u64::try_from(ns)?;
Ok(ns_u64)
}
diff --git a/ext/web/02_timers.js b/ext/web/02_timers.js
index 89acaca42..6058febd5 100644
--- a/ext/web/02_timers.js
+++ b/ext/web/02_timers.js
@@ -1,12 +1,9 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { core, primordials } from "ext:core/mod.js";
-import { op_defer, op_now } from "ext:core/ops";
+import { op_defer } from "ext:core/ops";
const {
- Uint8Array,
- Uint32Array,
PromisePrototypeThen,
- TypedArrayPrototypeGetBuffer,
TypeError,
indirectEval,
ReflectApply,
@@ -18,13 +15,6 @@ const {
import * as webidl from "ext:deno_webidl/00_webidl.js";
-const hrU8 = new Uint8Array(8);
-const hr = new Uint32Array(TypedArrayPrototypeGetBuffer(hrU8));
-function opNow() {
- op_now(hrU8);
- return (hr[0] * 1000 + hr[1] / 1e6);
-}
-
// ---------------------------------------------------------------------------
function checkThis(thisArg) {
@@ -151,7 +141,6 @@ export {
clearInterval,
clearTimeout,
defer,
- opNow,
refTimer,
setImmediate,
setInterval,
diff --git a/ext/web/15_performance.js b/ext/web/15_performance.js
index ea5557278..9e0e310a5 100644
--- a/ext/web/15_performance.js
+++ b/ext/web/15_performance.js
@@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { primordials } from "ext:core/mod.js";
+import { op_now, op_time_origin } from "ext:core/ops";
const {
ArrayPrototypeFilter,
ArrayPrototypePush,
@@ -10,19 +11,34 @@ const {
Symbol,
SymbolFor,
TypeError,
+ TypedArrayPrototypeGetBuffer,
+ Uint8Array,
+ Uint32Array,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { structuredClone } from "./02_structured_clone.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import { EventTarget } from "./02_event.js";
-import { opNow } from "./02_timers.js";
import { DOMException } from "./01_dom_exception.js";
const illegalConstructorKey = Symbol("illegalConstructorKey");
let performanceEntries = [];
let timeOrigin;
+const hrU8 = new Uint8Array(8);
+const hr = new Uint32Array(TypedArrayPrototypeGetBuffer(hrU8));
+
+function setTimeOrigin() {
+ op_time_origin(hrU8);
+ timeOrigin = hr[0] * 1000 + hr[1] / 1e6;
+}
+
+function now() {
+ op_now(hrU8);
+ return hr[0] * 1000 + hr[1] / 1e6;
+}
+
webidl.converters["PerformanceMarkOptions"] = webidl
.createDictionaryConverter(
"PerformanceMarkOptions",
@@ -90,10 +106,6 @@ webidl.converters["DOMString or PerformanceMeasureOptions"] = (
return webidl.converters.DOMString(V, prefix, context, opts);
};
-function setTimeOrigin(origin) {
- timeOrigin = origin;
-}
-
function findMostRecent(
name,
type,
@@ -135,8 +147,6 @@ function filterByNameType(
);
}
-const now = opNow;
-
const _name = Symbol("[[name]]");
const _entryType = Symbol("[[entryType]]");
const _startTime = Symbol("[[startTime]]");
diff --git a/ext/web/lib.rs b/ext/web/lib.rs
index 4935af5bd..af0fc2c27 100644
--- a/ext/web/lib.rs
+++ b/ext/web/lib.rs
@@ -52,7 +52,8 @@ pub use crate::message_port::Transferable;
use crate::timers::op_defer;
use crate::timers::op_now;
-use crate::timers::StartTime;
+use crate::timers::op_time_origin;
+pub use crate::timers::StartTime;
pub use crate::timers::TimersPermission;
deno_core::extension!(deno_web,
@@ -84,6 +85,7 @@ deno_core::extension!(deno_web,
compression::op_compression_write,
compression::op_compression_finish,
op_now<P>,
+ op_time_origin<P>,
op_defer,
stream_resource::op_readable_stream_resource_allocate,
stream_resource::op_readable_stream_resource_allocate_sized,
@@ -123,7 +125,7 @@ deno_core::extension!(deno_web,
if let Some(location) = options.maybe_location {
state.put(Location(location));
}
- state.put(StartTime::now());
+ state.put(StartTime::default());
}
);
diff --git a/ext/web/timers.rs b/ext/web/timers.rs
index a9ab7c97e..06444ed34 100644
--- a/ext/web/timers.rs
+++ b/ext/web/timers.rs
@@ -4,7 +4,10 @@
use deno_core::op2;
use deno_core::OpState;
+use std::time::Duration;
use std::time::Instant;
+use std::time::SystemTime;
+use std::time::UNIX_EPOCH;
pub trait TimersPermission {
fn allow_hrtime(&mut self) -> bool;
@@ -17,21 +20,28 @@ impl TimersPermission for deno_permissions::PermissionsContainer {
}
}
-pub type StartTime = Instant;
+pub struct StartTime(Instant);
-// Returns a milliseconds and nanoseconds subsec
-// since the start time of the deno runtime.
-// If the High precision flag is not set, the
-// nanoseconds are rounded on 2ms.
-#[op2(fast)]
-pub fn op_now<TP>(state: &mut OpState, #[buffer] buf: &mut [u8])
+impl Default for StartTime {
+ fn default() -> Self {
+ Self(Instant::now())
+ }
+}
+
+impl std::ops::Deref for StartTime {
+ type Target = Instant;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+fn expose_time<TP>(state: &mut OpState, duration: Duration, out: &mut [u8])
where
TP: TimersPermission + 'static,
{
- let start_time = state.borrow::<StartTime>();
- let elapsed = start_time.elapsed();
- let seconds = elapsed.as_secs();
- let mut subsec_nanos = elapsed.subsec_nanos();
+ let seconds = duration.as_secs() as u32;
+ let mut subsec_nanos = duration.subsec_nanos();
// If the permission is not enabled
// Round the nano result on 2 milliseconds
@@ -40,14 +50,33 @@ where
let reduced_time_precision = 2_000_000; // 2ms in nanoseconds
subsec_nanos -= subsec_nanos % reduced_time_precision;
}
- if buf.len() < 8 {
- return;
+
+ if out.len() >= 8 {
+ out[0..4].copy_from_slice(&seconds.to_ne_bytes());
+ out[4..8].copy_from_slice(&subsec_nanos.to_ne_bytes());
}
- let buf: &mut [u32] =
- // SAFETY: buffer is at least 8 bytes long.
- unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as _, 2) };
- buf[0] = seconds as u32;
- buf[1] = subsec_nanos;
+}
+
+#[op2(fast)]
+pub fn op_now<TP>(state: &mut OpState, #[buffer] buf: &mut [u8])
+where
+ TP: TimersPermission + 'static,
+{
+ let start_time = state.borrow::<StartTime>();
+ let elapsed = start_time.elapsed();
+ expose_time::<TP>(state, elapsed, buf);
+}
+
+#[op2(fast)]
+pub fn op_time_origin<TP>(state: &mut OpState, #[buffer] buf: &mut [u8])
+where
+ TP: TimersPermission + 'static,
+{
+ // https://w3c.github.io/hr-time/#dfn-estimated-monotonic-time-of-the-unix-epoch
+ let wall_time = SystemTime::now();
+ let monotonic_time = state.borrow::<StartTime>().elapsed();
+ let epoch = wall_time.duration_since(UNIX_EPOCH).unwrap() - monotonic_time;
+ expose_time::<TP>(state, epoch, buf);
}
#[allow(clippy::unused_async)]
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 4d391dcc3..6ddaa1335 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -27,7 +27,6 @@ const {
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ArrayPrototypeMap,
- DateNow,
Error,
ErrorPrototype,
FunctionPrototypeBind,
@@ -642,7 +641,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
removeImportedOps();
- performance.setTimeOrigin(DateNow());
+ performance.setTimeOrigin();
globalThis_ = globalThis;
// Remove bootstrapping data from the global scope
@@ -858,7 +857,7 @@ function bootstrapWorkerRuntime(
7: nodeDebug,
} = runtimeOptions;
- performance.setTimeOrigin(DateNow());
+ performance.setTimeOrigin();
globalThis_ = globalThis;
// Remove bootstrapping data from the global scope