Skip to content

Commit 069bc15

Browse files
authored
feat(ext/node): perf_hooks.monitorEventLoopDelay() (#26905)
Fixes #20961 Depends on denoland/deno_core#965 and denoland/deno_core#966
1 parent 0e2f6e3 commit 069bc15

File tree

9 files changed

+205
-21
lines changed

9 files changed

+205
-21
lines changed

Cargo.lock

+38-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ repository = "https://github.com/denoland/deno"
4646

4747
[workspace.dependencies]
4848
deno_ast = { version = "=0.43.3", features = ["transpiling"] }
49-
deno_core = { version = "0.319.0" }
49+
deno_core = { version = "0.320.0" }
5050

5151
deno_bench_util = { version = "0.171.0", path = "./bench_util" }
5252
deno_config = { version = "=0.39.1", features = ["workspace", "sync"] }

ext/ffi/dlfcn.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use dlopen2::raw::Library;
1515
use serde::Deserialize;
1616
use serde_value::ValueDeserializer;
1717
use std::borrow::Cow;
18+
use std::cell::RefCell;
1819
use std::collections::HashMap;
1920
use std::ffi::c_void;
2021
use std::rc::Rc;
@@ -126,14 +127,17 @@ pub struct FfiLoadArgs {
126127
#[op2]
127128
pub fn op_ffi_load<'scope, FP>(
128129
scope: &mut v8::HandleScope<'scope>,
129-
state: &mut OpState,
130+
state: Rc<RefCell<OpState>>,
130131
#[serde] args: FfiLoadArgs,
131132
) -> Result<v8::Local<'scope, v8::Value>, DlfcnError>
132133
where
133134
FP: FfiPermissions + 'static,
134135
{
135-
let permissions = state.borrow_mut::<FP>();
136-
let path = permissions.check_partial_with_path(&args.path)?;
136+
let path = {
137+
let mut state = state.borrow_mut();
138+
let permissions = state.borrow_mut::<FP>();
139+
permissions.check_partial_with_path(&args.path)?
140+
};
137141

138142
let lib = Library::open(&path).map_err(|e| {
139143
dlopen2::Error::OpeningLibraryError(std::io::Error::new(
@@ -215,6 +219,7 @@ where
215219
}
216220
}
217221

222+
let mut state = state.borrow_mut();
218223
let out = v8::Array::new(scope, 2);
219224
let rid = state.resource_table.add(resource);
220225
let rid_v8 = v8::Integer::new_from_unsigned(scope, rid);

ext/node/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ spki.workspace = true
9595
stable_deref_trait = "1.2.0"
9696
thiserror.workspace = true
9797
tokio.workspace = true
98+
tokio-eld = "0.2"
9899
url.workspace = true
99100
webpki-root-certs.workspace = true
100101
winapi.workspace = true

ext/node/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,9 @@ deno_core::extension!(deno_node,
427427
ops::inspector::op_inspector_emit_protocol_event,
428428
ops::inspector::op_inspector_enabled,
429429
],
430+
objects = [
431+
ops::perf_hooks::EldHistogram
432+
],
430433
esm_entry_point = "ext:deno_node/02_init.js",
431434
esm = [
432435
dir "polyfills",

ext/node/ops/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod idna;
1010
pub mod inspector;
1111
pub mod ipc;
1212
pub mod os;
13+
pub mod perf_hooks;
1314
pub mod process;
1415
pub mod require;
1516
pub mod tls;

ext/node/ops/perf_hooks.rs

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
3+
use deno_core::op2;
4+
use deno_core::GarbageCollected;
5+
6+
use std::cell::Cell;
7+
8+
#[derive(Debug, thiserror::Error)]
9+
pub enum PerfHooksError {
10+
#[error(transparent)]
11+
TokioEld(#[from] tokio_eld::Error),
12+
}
13+
14+
pub struct EldHistogram {
15+
eld: tokio_eld::EldHistogram<u64>,
16+
started: Cell<bool>,
17+
}
18+
19+
impl GarbageCollected for EldHistogram {}
20+
21+
#[op2]
22+
impl EldHistogram {
23+
// Creates an interval EldHistogram object that samples and reports the event
24+
// loop delay over time.
25+
//
26+
// The delays will be reported in nanoseconds.
27+
#[constructor]
28+
#[cppgc]
29+
pub fn new(#[smi] resolution: u32) -> Result<EldHistogram, PerfHooksError> {
30+
Ok(EldHistogram {
31+
eld: tokio_eld::EldHistogram::new(resolution as usize)?,
32+
started: Cell::new(false),
33+
})
34+
}
35+
36+
// Disables the update interval timer.
37+
//
38+
// Returns true if the timer was stopped, false if it was already stopped.
39+
#[fast]
40+
fn enable(&self) -> bool {
41+
if self.started.get() {
42+
return false;
43+
}
44+
45+
self.eld.start();
46+
self.started.set(true);
47+
48+
true
49+
}
50+
51+
// Enables the update interval timer.
52+
//
53+
// Returns true if the timer was started, false if it was already started.
54+
#[fast]
55+
fn disable(&self) -> bool {
56+
if !self.started.get() {
57+
return false;
58+
}
59+
60+
self.eld.stop();
61+
self.started.set(false);
62+
63+
true
64+
}
65+
66+
// Returns the value at the given percentile.
67+
//
68+
// `percentile` ∈ (0, 100]
69+
#[fast]
70+
#[number]
71+
fn percentile(&self, percentile: f64) -> u64 {
72+
self.eld.value_at_percentile(percentile)
73+
}
74+
75+
// Returns the value at the given percentile as a bigint.
76+
#[fast]
77+
#[bigint]
78+
fn percentile_big_int(&self, percentile: f64) -> u64 {
79+
self.eld.value_at_percentile(percentile)
80+
}
81+
82+
// The number of samples recorded by the histogram.
83+
#[getter]
84+
#[number]
85+
fn count(&self) -> u64 {
86+
self.eld.len()
87+
}
88+
89+
// The number of samples recorded by the histogram as a bigint.
90+
#[getter]
91+
#[bigint]
92+
fn count_big_int(&self) -> u64 {
93+
self.eld.len()
94+
}
95+
96+
// The maximum recorded event loop delay.
97+
#[getter]
98+
#[number]
99+
fn max(&self) -> u64 {
100+
self.eld.max()
101+
}
102+
103+
// The maximum recorded event loop delay as a bigint.
104+
#[getter]
105+
#[bigint]
106+
fn max_big_int(&self) -> u64 {
107+
self.eld.max()
108+
}
109+
110+
// The mean of the recorded event loop delays.
111+
#[getter]
112+
fn mean(&self) -> f64 {
113+
self.eld.mean()
114+
}
115+
116+
// The minimum recorded event loop delay.
117+
#[getter]
118+
#[number]
119+
fn min(&self) -> u64 {
120+
self.eld.min()
121+
}
122+
123+
// The minimum recorded event loop delay as a bigint.
124+
#[getter]
125+
#[bigint]
126+
fn min_big_int(&self) -> u64 {
127+
self.eld.min()
128+
}
129+
130+
// The standard deviation of the recorded event loop delays.
131+
#[getter]
132+
fn stddev(&self) -> f64 {
133+
self.eld.stdev()
134+
}
135+
}

ext/node/polyfills/perf_hooks.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
performance as shimPerformance,
99
PerformanceEntry,
1010
} from "ext:deno_web/15_performance.js";
11+
import { EldHistogram } from "ext:core/ops";
1112

1213
class PerformanceObserver {
1314
static supportedEntryTypes: string[] = [];
@@ -89,10 +90,11 @@ const performance:
8990
) => shimPerformance.dispatchEvent(...args),
9091
};
9192

92-
const monitorEventLoopDelay = () =>
93-
notImplemented(
94-
"monitorEventLoopDelay from performance",
95-
);
93+
function monitorEventLoopDelay(options = {}) {
94+
const { resolution = 10 } = options;
95+
96+
return new EldHistogram(resolution);
97+
}
9698

9799
export default {
98100
performance,

tests/unit_node/perf_hooks_test.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
performance,
66
PerformanceObserver,
77
} from "node:perf_hooks";
8-
import { assertEquals, assertThrows } from "@std/assert";
8+
import { assert, assertEquals, assertThrows } from "@std/assert";
99

1010
Deno.test({
1111
name: "[perf_hooks] performance",
@@ -73,11 +73,16 @@ Deno.test("[perf_hooks]: eventLoopUtilization", () => {
7373
assertEquals(typeof obj.utilization, "number");
7474
});
7575

76-
Deno.test("[perf_hooks]: monitorEventLoopDelay", () => {
77-
const e = assertThrows(() => {
78-
monitorEventLoopDelay({ resolution: 1 });
79-
});
76+
Deno.test("[perf_hooks]: monitorEventLoopDelay", async () => {
77+
const e = monitorEventLoopDelay();
78+
assertEquals(e.count, 0);
79+
e.enable();
8080

81-
// deno-lint-ignore no-explicit-any
82-
assertEquals((e as any).code, "ERR_NOT_IMPLEMENTED");
81+
await new Promise((resolve) => setTimeout(resolve, 100));
82+
83+
assert(e.min > 0);
84+
assert(e.minBigInt > 0n);
85+
assert(e.count > 0);
86+
87+
e.disable();
8388
});

0 commit comments

Comments
 (0)