Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ext/node): perf_hooks.monitorEventLoopDelay() #26905

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -335,3 +335,6 @@ opt-level = 3
opt-level = 3
[profile.release.package.zstd-sys]
opt-level = 3

[patch.crates-io]
deno_core = { git = "https://github.com/littledivy/deno_core", branch = "object_wrap_camelcase" }
11 changes: 8 additions & 3 deletions ext/ffi/dlfcn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use dlopen2::raw::Library;
use serde::Deserialize;
use serde_value::ValueDeserializer;
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::c_void;
use std::rc::Rc;
Expand Down Expand Up @@ -126,14 +127,17 @@ pub struct FfiLoadArgs {
#[op2]
pub fn op_ffi_load<'scope, FP>(
scope: &mut v8::HandleScope<'scope>,
state: &mut OpState,
state: Rc<RefCell<OpState>>,
#[serde] args: FfiLoadArgs,
) -> Result<v8::Local<'scope, v8::Value>, DlfcnError>
where
FP: FfiPermissions + 'static,
{
let permissions = state.borrow_mut::<FP>();
let path = permissions.check_partial_with_path(&args.path)?;
let path = {
let mut state = state.borrow_mut();
let permissions = state.borrow_mut::<FP>();
permissions.check_partial_with_path(&args.path)?
};

let lib = Library::open(&path).map_err(|e| {
dlopen2::Error::OpeningLibraryError(std::io::Error::new(
Expand Down Expand Up @@ -215,6 +219,7 @@ where
}
}

let mut state = state.borrow_mut();
let out = v8::Array::new(scope, 2);
let rid = state.resource_table.add(resource);
let rid_v8 = v8::Integer::new_from_unsigned(scope, rid);
Expand Down
1 change: 1 addition & 0 deletions ext/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ spki.workspace = true
stable_deref_trait = "1.2.0"
thiserror.workspace = true
tokio.workspace = true
tokio-eld = "0.2"
url.workspace = true
webpki-root-certs.workspace = true
winapi.workspace = true
Expand Down
3 changes: 3 additions & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,9 @@ deno_core::extension!(deno_node,
ops::inspector::op_inspector_emit_protocol_event,
ops::inspector::op_inspector_enabled,
],
objects = [
ops::perf_hooks::EldHistogram
],
esm_entry_point = "ext:deno_node/02_init.js",
esm = [
dir "polyfills",
Expand Down
1 change: 1 addition & 0 deletions ext/node/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod idna;
pub mod inspector;
pub mod ipc;
pub mod os;
pub mod perf_hooks;
pub mod process;
pub mod require;
pub mod tls;
Expand Down
135 changes: 135 additions & 0 deletions ext/node/ops/perf_hooks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use deno_core::op2;
use deno_core::GarbageCollected;

use std::cell::Cell;

#[derive(Debug, thiserror::Error)]
pub enum PerfHooksError {
#[error(transparent)]
TokioEld(#[from] tokio_eld::Error),
}

pub struct EldHistogram {
eld: tokio_eld::EldHistogram<u64>,
started: Cell<bool>,
}

impl GarbageCollected for EldHistogram {}

#[op2]
impl EldHistogram {
// Creates an interval EldHistogram object that samples and reports the event
// loop delay over time.
//
// The delays will be reported in nanoseconds.
#[constructor]
#[cppgc]
pub fn new(#[smi] resolution: u32) -> Result<EldHistogram, PerfHooksError> {
Ok(EldHistogram {
eld: tokio_eld::EldHistogram::new(resolution as usize)?,
started: Cell::new(false),
})
}

// Disables the update interval timer.
//
// Returns true if the timer was stopped, false if it was already stopped.
#[fast]
fn enable(&self) -> bool {
if self.started.get() {
return false;
}

self.eld.start();
self.started.set(true);

true
}

// Enables the update interval timer.
//
// Returns true if the timer was started, false if it was already started.
#[fast]
fn disable(&self) -> bool {
if !self.started.get() {
return false;
}

self.eld.stop();
self.started.set(false);

true
}

// Returns the value at the given percentile.
//
// `percentile` ∈ (0, 100]
#[fast]
#[number]
fn percentile(&self, percentile: f64) -> u64 {
self.eld.value_at_percentile(percentile)
}

// Returns the value at the given percentile as a bigint.
#[fast]
#[bigint]
fn percentile_big_int(&self, percentile: f64) -> u64 {
self.eld.value_at_percentile(percentile)
}

// The number of samples recorded by the histogram.
#[getter]
#[number]
fn count(&self) -> u64 {
self.eld.len()
}

// The number of samples recorded by the histogram as a bigint.
#[getter]
#[bigint]
fn count_big_int(&self) -> u64 {
self.eld.len()
}

// The maximum recorded event loop delay.
#[getter]
#[number]
fn max(&self) -> u64 {
self.eld.max()
}

// The maximum recorded event loop delay as a bigint.
#[getter]
#[bigint]
fn max_big_int(&self) -> u64 {
self.eld.max()
}

// The mean of the recorded event loop delays.
#[getter]
fn mean(&self) -> f64 {
self.eld.mean()
}

// The minimum recorded event loop delay.
#[getter]
#[number]
fn min(&self) -> u64 {
self.eld.min()
}

// The minimum recorded event loop delay as a bigint.
#[getter]
#[bigint]
fn min_big_int(&self) -> u64 {
self.eld.min()
}

// The standard deviation of the recorded event loop delays.
#[getter]
fn stddev(&self) -> f64 {
self.eld.stdev()
}
}
10 changes: 6 additions & 4 deletions ext/node/polyfills/perf_hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
performance as shimPerformance,
PerformanceEntry,
} from "ext:deno_web/15_performance.js";
import { EldHistogram } from "ext:core/ops";

class PerformanceObserver {
static supportedEntryTypes: string[] = [];
Expand Down Expand Up @@ -89,10 +90,11 @@ const performance:
) => shimPerformance.dispatchEvent(...args),
};

const monitorEventLoopDelay = () =>
notImplemented(
"monitorEventLoopDelay from performance",
);
function monitorEventLoopDelay(options = {}) {
const { resolution = 10 } = options;

return new EldHistogram(resolution);
}

export default {
performance,
Expand Down
19 changes: 12 additions & 7 deletions tests/unit_node/perf_hooks_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
performance,
PerformanceObserver,
} from "node:perf_hooks";
import { assertEquals, assertThrows } from "@std/assert";
import { assert, assertEquals, assertThrows } from "@std/assert";

Deno.test({
name: "[perf_hooks] performance",
Expand Down Expand Up @@ -73,11 +73,16 @@ Deno.test("[perf_hooks]: eventLoopUtilization", () => {
assertEquals(typeof obj.utilization, "number");
});

Deno.test("[perf_hooks]: monitorEventLoopDelay", () => {
const e = assertThrows(() => {
monitorEventLoopDelay({ resolution: 1 });
});
Deno.test("[perf_hooks]: monitorEventLoopDelay", async () => {
const e = monitorEventLoopDelay();
assertEquals(e.count, 0);
e.enable();

// deno-lint-ignore no-explicit-any
assertEquals((e as any).code, "ERR_NOT_IMPLEMENTED");
await new Promise((resolve) => setTimeout(resolve, 100));

assert(e.min > 0);
assert(e.minBigInt > 0n);
assert(e.count > 0);

e.disable();
});
Loading