1
0
Fork 0
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:
snek 2024-11-08 23:20:24 +01:00 committed by GitHub
parent d4f1bd3dac
commit 73fbd61bd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 73 additions and 44 deletions

View file

@ -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)
} }

View file

@ -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,

View file

@ -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]]");

View file

@ -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());
} }
); );

View file

@ -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)]

View file

@ -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