mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
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)
This commit is contained in:
parent
d4f1bd3dac
commit
73fbd61bd0
6 changed files with 73 additions and 44 deletions
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::time;
|
|
||||||
|
|
||||||
use deno_core::error::generic_error;
|
use deno_core::error::generic_error;
|
||||||
use deno_core::error::type_error;
|
use deno_core::error::type_error;
|
||||||
|
@ -13,6 +12,7 @@ use deno_core::ModuleSpecifier;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use deno_runtime::deno_permissions::ChildPermissionsArg;
|
use deno_runtime::deno_permissions::ChildPermissionsArg;
|
||||||
use deno_runtime::deno_permissions::PermissionsContainer;
|
use deno_runtime::deno_permissions::PermissionsContainer;
|
||||||
|
use deno_runtime::deno_web::StartTime;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ fn op_dispatch_bench_event(state: &mut OpState, #[serde] event: BenchEvent) {
|
||||||
#[op2(fast)]
|
#[op2(fast)]
|
||||||
#[number]
|
#[number]
|
||||||
fn op_bench_now(state: &mut OpState) -> Result<u64, std::num::TryFromIntError> {
|
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)?;
|
let ns_u64 = u64::try_from(ns)?;
|
||||||
Ok(ns_u64)
|
Ok(ns_u64)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
import { core, primordials } from "ext:core/mod.js";
|
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 {
|
const {
|
||||||
Uint8Array,
|
|
||||||
Uint32Array,
|
|
||||||
PromisePrototypeThen,
|
PromisePrototypeThen,
|
||||||
TypedArrayPrototypeGetBuffer,
|
|
||||||
TypeError,
|
TypeError,
|
||||||
indirectEval,
|
indirectEval,
|
||||||
ReflectApply,
|
ReflectApply,
|
||||||
|
@ -18,13 +15,6 @@ const {
|
||||||
|
|
||||||
import * as webidl from "ext:deno_webidl/00_webidl.js";
|
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) {
|
function checkThis(thisArg) {
|
||||||
|
@ -151,7 +141,6 @@ export {
|
||||||
clearInterval,
|
clearInterval,
|
||||||
clearTimeout,
|
clearTimeout,
|
||||||
defer,
|
defer,
|
||||||
opNow,
|
|
||||||
refTimer,
|
refTimer,
|
||||||
setImmediate,
|
setImmediate,
|
||||||
setInterval,
|
setInterval,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
import { primordials } from "ext:core/mod.js";
|
import { primordials } from "ext:core/mod.js";
|
||||||
|
import { op_now, op_time_origin } from "ext:core/ops";
|
||||||
const {
|
const {
|
||||||
ArrayPrototypeFilter,
|
ArrayPrototypeFilter,
|
||||||
ArrayPrototypePush,
|
ArrayPrototypePush,
|
||||||
|
@ -10,19 +11,34 @@ const {
|
||||||
Symbol,
|
Symbol,
|
||||||
SymbolFor,
|
SymbolFor,
|
||||||
TypeError,
|
TypeError,
|
||||||
|
TypedArrayPrototypeGetBuffer,
|
||||||
|
Uint8Array,
|
||||||
|
Uint32Array,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
|
|
||||||
import * as webidl from "ext:deno_webidl/00_webidl.js";
|
import * as webidl from "ext:deno_webidl/00_webidl.js";
|
||||||
import { structuredClone } from "./02_structured_clone.js";
|
import { structuredClone } from "./02_structured_clone.js";
|
||||||
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
|
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
|
||||||
import { EventTarget } from "./02_event.js";
|
import { EventTarget } from "./02_event.js";
|
||||||
import { opNow } from "./02_timers.js";
|
|
||||||
import { DOMException } from "./01_dom_exception.js";
|
import { DOMException } from "./01_dom_exception.js";
|
||||||
|
|
||||||
const illegalConstructorKey = Symbol("illegalConstructorKey");
|
const illegalConstructorKey = Symbol("illegalConstructorKey");
|
||||||
let performanceEntries = [];
|
let performanceEntries = [];
|
||||||
let timeOrigin;
|
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
|
webidl.converters["PerformanceMarkOptions"] = webidl
|
||||||
.createDictionaryConverter(
|
.createDictionaryConverter(
|
||||||
"PerformanceMarkOptions",
|
"PerformanceMarkOptions",
|
||||||
|
@ -90,10 +106,6 @@ webidl.converters["DOMString or PerformanceMeasureOptions"] = (
|
||||||
return webidl.converters.DOMString(V, prefix, context, opts);
|
return webidl.converters.DOMString(V, prefix, context, opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
function setTimeOrigin(origin) {
|
|
||||||
timeOrigin = origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findMostRecent(
|
function findMostRecent(
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
@ -135,8 +147,6 @@ function filterByNameType(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = opNow;
|
|
||||||
|
|
||||||
const _name = Symbol("[[name]]");
|
const _name = Symbol("[[name]]");
|
||||||
const _entryType = Symbol("[[entryType]]");
|
const _entryType = Symbol("[[entryType]]");
|
||||||
const _startTime = Symbol("[[startTime]]");
|
const _startTime = Symbol("[[startTime]]");
|
||||||
|
|
|
@ -52,7 +52,8 @@ pub use crate::message_port::Transferable;
|
||||||
|
|
||||||
use crate::timers::op_defer;
|
use crate::timers::op_defer;
|
||||||
use crate::timers::op_now;
|
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;
|
pub use crate::timers::TimersPermission;
|
||||||
|
|
||||||
deno_core::extension!(deno_web,
|
deno_core::extension!(deno_web,
|
||||||
|
@ -84,6 +85,7 @@ deno_core::extension!(deno_web,
|
||||||
compression::op_compression_write,
|
compression::op_compression_write,
|
||||||
compression::op_compression_finish,
|
compression::op_compression_finish,
|
||||||
op_now<P>,
|
op_now<P>,
|
||||||
|
op_time_origin<P>,
|
||||||
op_defer,
|
op_defer,
|
||||||
stream_resource::op_readable_stream_resource_allocate,
|
stream_resource::op_readable_stream_resource_allocate,
|
||||||
stream_resource::op_readable_stream_resource_allocate_sized,
|
stream_resource::op_readable_stream_resource_allocate_sized,
|
||||||
|
@ -123,7 +125,7 @@ deno_core::extension!(deno_web,
|
||||||
if let Some(location) = options.maybe_location {
|
if let Some(location) = options.maybe_location {
|
||||||
state.put(Location(location));
|
state.put(Location(location));
|
||||||
}
|
}
|
||||||
state.put(StartTime::now());
|
state.put(StartTime::default());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
|
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
use std::time::UNIX_EPOCH;
|
||||||
|
|
||||||
pub trait TimersPermission {
|
pub trait TimersPermission {
|
||||||
fn allow_hrtime(&mut self) -> bool;
|
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
|
impl Default for StartTime {
|
||||||
// since the start time of the deno runtime.
|
fn default() -> Self {
|
||||||
// If the High precision flag is not set, the
|
Self(Instant::now())
|
||||||
// nanoseconds are rounded on 2ms.
|
}
|
||||||
#[op2(fast)]
|
}
|
||||||
pub fn op_now<TP>(state: &mut OpState, #[buffer] buf: &mut [u8])
|
|
||||||
|
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
|
where
|
||||||
TP: TimersPermission + 'static,
|
TP: TimersPermission + 'static,
|
||||||
{
|
{
|
||||||
let start_time = state.borrow::<StartTime>();
|
let seconds = duration.as_secs() as u32;
|
||||||
let elapsed = start_time.elapsed();
|
let mut subsec_nanos = duration.subsec_nanos();
|
||||||
let seconds = elapsed.as_secs();
|
|
||||||
let mut subsec_nanos = elapsed.subsec_nanos();
|
|
||||||
|
|
||||||
// If the permission is not enabled
|
// If the permission is not enabled
|
||||||
// Round the nano result on 2 milliseconds
|
// Round the nano result on 2 milliseconds
|
||||||
|
@ -40,14 +50,33 @@ where
|
||||||
let reduced_time_precision = 2_000_000; // 2ms in nanoseconds
|
let reduced_time_precision = 2_000_000; // 2ms in nanoseconds
|
||||||
subsec_nanos -= subsec_nanos % reduced_time_precision;
|
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) };
|
#[op2(fast)]
|
||||||
buf[0] = seconds as u32;
|
pub fn op_now<TP>(state: &mut OpState, #[buffer] buf: &mut [u8])
|
||||||
buf[1] = subsec_nanos;
|
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)]
|
#[allow(clippy::unused_async)]
|
||||||
|
|
|
@ -27,7 +27,6 @@ const {
|
||||||
ArrayPrototypeForEach,
|
ArrayPrototypeForEach,
|
||||||
ArrayPrototypeIncludes,
|
ArrayPrototypeIncludes,
|
||||||
ArrayPrototypeMap,
|
ArrayPrototypeMap,
|
||||||
DateNow,
|
|
||||||
Error,
|
Error,
|
||||||
ErrorPrototype,
|
ErrorPrototype,
|
||||||
FunctionPrototypeBind,
|
FunctionPrototypeBind,
|
||||||
|
@ -642,7 +641,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
|
||||||
|
|
||||||
removeImportedOps();
|
removeImportedOps();
|
||||||
|
|
||||||
performance.setTimeOrigin(DateNow());
|
performance.setTimeOrigin();
|
||||||
globalThis_ = globalThis;
|
globalThis_ = globalThis;
|
||||||
|
|
||||||
// Remove bootstrapping data from the global scope
|
// Remove bootstrapping data from the global scope
|
||||||
|
@ -858,7 +857,7 @@ function bootstrapWorkerRuntime(
|
||||||
7: nodeDebug,
|
7: nodeDebug,
|
||||||
} = runtimeOptions;
|
} = runtimeOptions;
|
||||||
|
|
||||||
performance.setTimeOrigin(DateNow());
|
performance.setTimeOrigin();
|
||||||
globalThis_ = globalThis;
|
globalThis_ = globalThis;
|
||||||
|
|
||||||
// Remove bootstrapping data from the global scope
|
// Remove bootstrapping data from the global scope
|
||||||
|
|
Loading…
Reference in a new issue