Skip to content
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
49 changes: 30 additions & 19 deletions doc/api/perf_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -1701,23 +1701,32 @@

<!-- YAML
added: v11.10.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/62935

Check warning on line 1706 in doc/api/perf_hooks.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added the `samplePerIteration` option.
-->

* `options` {Object}
* `resolution` {number} The sampling rate in milliseconds. Must be greater
than zero. **Default:** `10`.
* Returns: {IntervalHistogram}
* `samplePerIteration` {boolean} When `true`, samples are taken once per
event loop iteration. **Default:** `false`.
* `resolution` {number} The sampling rate in milliseconds for interval-based
sampling. Must be greater than zero. This option is ignored when
`samplePerIteration` is `true`. **Default:** `10`.
* Returns: {ELDHistogram}

_This property is an extension by Node.js. It is not available in Web browsers._

Creates an `IntervalHistogram` object that samples and reports the event loop
delay over time. The delays will be reported in nanoseconds.
Creates a histogram object that samples and reports the event loop delay over
time. The delays will be reported in nanoseconds.

Using a timer to detect approximate event loop delay works because the
execution of timers is tied specifically to the lifecycle of the libuv
event loop. That is, a delay in the loop will cause a delay in the execution
of the timer, and those delays are specifically what this API is intended to
detect.
By default, the histogram is updated by a timer using the configured
`resolution`. When `samplePerIteration` is `true`, samples are taken once per
event loop iteration using `uv_prepare_t` and `uv_check_t` hooks. In that mode,
the histogram does not keep the loop alive or force additional iterations when
the application is idle.
The two sampling modes produce significantly different results and should not
be compared directly.

```mjs
import { monitorEventLoopDelay } from 'node:perf_hooks';
Expand Down Expand Up @@ -1992,9 +2001,10 @@

The standard deviation of the recorded event loop delays.

## Class: `IntervalHistogram extends Histogram`
## Class: `ELDHistogram extends Histogram`

A `Histogram` that is periodically updated on a given interval.
A `Histogram` that records event loop delay, returned by
[`perf_hooks.monitorEventLoopDelay()`][].

### `histogram.disable()`

Expand All @@ -2004,7 +2014,7 @@

* Returns: {boolean}

Disables the update interval timer. Returns `true` if the timer was
Disables event loop delay sampling. Returns `true` if sampling was
stopped, `false` if it was already stopped.

### `histogram.enable()`
Expand All @@ -2015,7 +2025,7 @@

* Returns: {boolean}

Enables the update interval timer. Returns `true` if the timer was
Enables event loop delay sampling. Returns `true` if sampling was
started, `false` if it was already started.

### `histogram[Symbol.dispose]()`
Expand All @@ -2024,7 +2034,7 @@
added: v24.2.0
-->

Disables the update interval timer when the histogram is disposed.
Disables event loop delay sampling when the histogram is disposed.

```js
const { monitorEventLoopDelay } = require('node:perf_hooks');
Expand All @@ -2035,11 +2045,11 @@
}
```

### Cloning an `IntervalHistogram`
### Cloning an `ELDHistogram`

{IntervalHistogram} instances can be cloned via {MessagePort}. On the receiving
end, the histogram is cloned as a plain {Histogram} object that does not
implement the `enable()` and `disable()` methods.
{ELDHistogram} instances can be cloned via {MessagePort}. On the receiving end,
the histogram is cloned as a plain {Histogram} object that does not implement
the `enable()` and `disable()` methods.

## Class: `RecordableHistogram extends Histogram`

Expand Down Expand Up @@ -2352,6 +2362,7 @@
[`'exit'`]: process.md#event-exit
[`child_process.spawnSync()`]: child_process.md#child_processspawnsynccommand-args-options
[`perf_hooks.eventLoopUtilization()`]: #perf_hookseventlooputilizationutilization1-utilization2
[`perf_hooks.monitorEventLoopDelay()`]: #perf_hooksmonitoreventloopdelayoptions
[`perf_hooks.timerify()`]: #perf_hookstimerifyfn-options
[`process.hrtime()`]: process.md#processhrtimetime
[`timeOrigin`]: https://w3c.github.io/hr-time/#dom-performance-timeorigin
Expand Down
7 changes: 5 additions & 2 deletions lib/internal/perf/event_loop_delay.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {
} = internalBinding('performance');

const {
validateBoolean,
validateInteger,
validateObject,
} = require('internal/validators');
Expand Down Expand Up @@ -74,21 +75,23 @@ class ELDHistogram extends Histogram {

/**
* @param {{
* samplePerIteration : boolean,
* resolution : number
* }} [options]
* @returns {ELDHistogram}
*/
function monitorEventLoopDelay(options = kEmptyObject) {
validateObject(options, 'options');

const { resolution = 10 } = options;
const { samplePerIteration = false, resolution = 10 } = options;
validateBoolean(samplePerIteration, 'options.samplePerIteration');
validateInteger(resolution, 'options.resolution', 1);

return ReflectConstruct(
function() {
markTransferMode(this, true, false);
this[kEnabled] = false;
this[kHandle] = createELDHistogram(resolution);
this[kHandle] = createELDHistogram(resolution, samplePerIteration);
this[kMap] = new SafeMap();
}, [], ELDHistogram);
}
Expand Down
1 change: 1 addition & 0 deletions src/env_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@
V(http2ping_constructor_template, v8::ObjectTemplate) \
V(i18n_converter_template, v8::ObjectTemplate) \
V(intervalhistogram_constructor_template, v8::FunctionTemplate) \
V(iterationhistogram_constructor_template, v8::FunctionTemplate) \
V(iter_template, v8::DictionaryTemplate) \
V(js_transferable_constructor_template, v8::FunctionTemplate) \
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
Expand Down
174 changes: 162 additions & 12 deletions src/histogram.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@ using v8::String;
using v8::Uint32;
using v8::Value;

template <typename T>
void StartHandleHistogram(Local<Value> receiver, bool reset) {
T* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, receiver);
histogram->OnStart(reset ? T::StartFlags::RESET : T::StartFlags::NONE);
}

template <typename T>
void StopHandleHistogram(Local<Value> receiver) {
T* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, receiver);
histogram->OnStop();
}

Histogram::Histogram(const Options& options) {
hdr_histogram* histogram;
CHECK_EQ(0, hdr_init(options.lowest,
Expand Down Expand Up @@ -68,6 +82,10 @@ CFunction IntervalHistogram::fast_start_(
CFunction::Make(&IntervalHistogram::FastStart));
CFunction IntervalHistogram::fast_stop_(
CFunction::Make(&IntervalHistogram::FastStop));
CFunction IterationHistogram::fast_start_(
CFunction::Make(&IterationHistogram::FastStart));
CFunction IterationHistogram::fast_stop_(
CFunction::Make(&IterationHistogram::FastStop));

void HistogramImpl::AddMethods(Isolate* isolate, Local<FunctionTemplate> tmpl) {
// TODO(@jasnell): The bigint API variations do not yet support fast
Expand Down Expand Up @@ -416,29 +434,156 @@ void IntervalHistogram::OnStop() {
}

void IntervalHistogram::Start(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.This());
histogram->OnStart(args[0]->IsTrue() ? StartFlags::RESET : StartFlags::NONE);
StartHandleHistogram<IntervalHistogram>(args.This(), args[0]->IsTrue());
}

void IntervalHistogram::FastStart(Local<Value> receiver, bool reset) {
TRACK_V8_FAST_API_CALL("histogram.start");
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, receiver);
histogram->OnStart(reset ? StartFlags::RESET : StartFlags::NONE);
StartHandleHistogram<IntervalHistogram>(receiver, reset);
}

void IntervalHistogram::Stop(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.This());
histogram->OnStop();
StopHandleHistogram<IntervalHistogram>(args.This());
}

void IntervalHistogram::FastStop(Local<Value> receiver) {
TRACK_V8_FAST_API_CALL("histogram.stop");
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, receiver);
histogram->OnStop();
StopHandleHistogram<IntervalHistogram>(receiver);
}

Local<FunctionTemplate> IterationHistogram::GetConstructorTemplate(
Environment* env) {
Local<FunctionTemplate> tmpl = env->iterationhistogram_constructor_template();
if (tmpl.IsEmpty()) {
Isolate* isolate = env->isolate();
tmpl = NewFunctionTemplate(isolate, nullptr);
tmpl->Inherit(HandleWrap::GetConstructorTemplate(env));
tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "Histogram"));
auto instance = tmpl->InstanceTemplate();
instance->SetInternalFieldCount(IterationHistogram::kInternalFieldCount);
HistogramImpl::AddMethods(isolate, tmpl);
SetFastMethod(isolate, instance, "start", Start, &fast_start_);
SetFastMethod(isolate, instance, "stop", Stop, &fast_stop_);
env->set_iterationhistogram_constructor_template(tmpl);
}
return tmpl;
}

void IterationHistogram::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(Start);
registry->Register(Stop);
registry->Register(fast_start_);
registry->Register(fast_stop_);
HistogramImpl::RegisterExternalReferences(registry);
}

IterationHistogram::IterationHistogram(Environment* env,
Local<Object> wrap,
AsyncWrap::ProviderType type,
const Histogram::Options& options)
: HandleWrap(
env, wrap, reinterpret_cast<uv_handle_t*>(&check_handle_), type),
HistogramImpl(options) {
MakeWeak();
wrap->SetAlignedPointerInInternalField(
HistogramImpl::InternalFields::kImplField,
static_cast<HistogramImpl*>(this));
uv_check_init(env->event_loop(), &check_handle_);
uv_prepare_init(env->event_loop(), &prepare_handle_);
uv_unref(reinterpret_cast<uv_handle_t*>(&check_handle_));
uv_unref(reinterpret_cast<uv_handle_t*>(&prepare_handle_));
prepare_handle_.data = this;
}

BaseObjectPtr<IterationHistogram> IterationHistogram::Create(
Environment* env, const Histogram::Options& options) {
Local<Object> obj;
if (!GetConstructorTemplate(env)
->InstanceTemplate()
->NewInstance(env->context())
.ToLocal(&obj)) {
return nullptr;
}

return MakeBaseObject<IterationHistogram>(
env, obj, AsyncWrap::PROVIDER_ELDHISTOGRAM, options);
}

void IterationHistogram::PrepareCB(uv_prepare_t* handle) {
IterationHistogram* self = static_cast<IterationHistogram*>(handle->data);
if (!self->enabled_) return;
self->prepare_time_ = uv_hrtime();
self->timeout_ = uv_backend_timeout(handle->loop);
}

void IterationHistogram::CheckCB(uv_check_t* handle) {
IterationHistogram* self =
ContainerOf(&IterationHistogram::check_handle_, handle);
if (!self->enabled_) return;

uint64_t check_time = uv_hrtime();
uint64_t poll_time = check_time - self->prepare_time_;
uint64_t latency = self->prepare_time_ - self->check_time_;

if (self->timeout_ >= 0) {
uint64_t timeout_ns = static_cast<uint64_t>(self->timeout_) * 1000 * 1000;
if (poll_time > timeout_ns) {
latency += poll_time - timeout_ns;
}
}

self->histogram()->Record(latency == 0 ? 1 : latency);
self->check_time_ = check_time;
}

void IterationHistogram::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("histogram", histogram());
}

void IterationHistogram::OnStart(StartFlags flags) {
if (enabled_ || IsHandleClosing()) return;
enabled_ = true;
if (flags == StartFlags::RESET) histogram()->Reset();
check_time_ = uv_hrtime();
prepare_time_ = check_time_;
timeout_ = 0;
uv_check_start(&check_handle_, CheckCB);
uv_prepare_start(&prepare_handle_, PrepareCB);
uv_unref(reinterpret_cast<uv_handle_t*>(&check_handle_));
uv_unref(reinterpret_cast<uv_handle_t*>(&prepare_handle_));
}

void IterationHistogram::OnStop() {
if (!enabled_ || IsHandleClosing()) return;
enabled_ = false;
uv_check_stop(&check_handle_);
uv_prepare_stop(&prepare_handle_);
}

void IterationHistogram::Close(Local<Value> close_callback) {
if (IsHandleClosing()) return;
OnStop();
HandleWrap::Close(close_callback);
uv_close(reinterpret_cast<uv_handle_t*>(&prepare_handle_), nullptr);
}

void IterationHistogram::Start(const FunctionCallbackInfo<Value>& args) {
StartHandleHistogram<IterationHistogram>(args.This(), args[0]->IsTrue());
}

void IterationHistogram::FastStart(Local<Value> receiver, bool reset) {
TRACK_V8_FAST_API_CALL("histogram.eventLoopDelay.start");
StartHandleHistogram<IterationHistogram>(receiver, reset);
}

void IterationHistogram::Stop(const FunctionCallbackInfo<Value>& args) {
StopHandleHistogram<IterationHistogram>(args.This());
}

void IterationHistogram::FastStop(Local<Value> receiver) {
TRACK_V8_FAST_API_CALL("histogram.eventLoopDelay.stop");
StopHandleHistogram<IterationHistogram>(receiver);
}

void HistogramImpl::GetCount(const FunctionCallbackInfo<Value>& args) {
Expand Down Expand Up @@ -604,6 +749,11 @@ HistogramImpl* HistogramImpl::FromJSObject(Local<Value> value) {
obj->GetAlignedPointerFromInternalField(HistogramImpl::kImplField));
}

std::unique_ptr<worker::TransferData> IterationHistogram::CloneForMessaging()
const {
return std::make_unique<HistogramBase::HistogramTransferData>(histogram());
}

std::unique_ptr<worker::TransferData>
IntervalHistogram::CloneForMessaging() const {
return std::make_unique<HistogramBase::HistogramTransferData>(histogram());
Expand Down
Loading
Loading