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
25 changes: 14 additions & 11 deletions doc/api/perf_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -1708,20 +1708,23 @@ added: v11.10.0
-->

* `options` {Object}
* `resolution` {number} The sampling rate in milliseconds. Must be greater
than zero. **Default:** `10`.
* `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: {IntervalHistogram}

_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.

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.

```mjs
import { monitorEventLoopDelay } from 'node:perf_hooks';
Expand Down Expand Up @@ -2000,7 +2003,7 @@ The standard deviation of the recorded event loop delays.

## Class: `IntervalHistogram extends Histogram`

A `Histogram` that is periodically updated on a given interval.
A `Histogram` that records event loop delay.

### `histogram.disable()`

Expand All @@ -2010,7 +2013,7 @@ added: v11.10.0

* 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 @@ -2021,7 +2024,7 @@ added: v11.10.0

* 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 @@ -2030,7 +2033,7 @@ started, `false` if it was already started.
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 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 @@ -429,6 +429,7 @@
V(http2ping_constructor_template, v8::ObjectTemplate) \
V(i18n_converter_template, v8::ObjectTemplate) \
V(intervalhistogram_constructor_template, v8::FunctionTemplate) \
V(eldhistogram_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
176 changes: 176 additions & 0 deletions src/histogram.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ CFunction IntervalHistogram::fast_start_(
CFunction::Make(&IntervalHistogram::FastStart));
CFunction IntervalHistogram::fast_stop_(
CFunction::Make(&IntervalHistogram::FastStop));
CFunction ELDHistogram::fast_start_(
CFunction::Make(&ELDHistogram::FastStart));
CFunction ELDHistogram::fast_stop_(
CFunction::Make(&ELDHistogram::FastStop));

void HistogramImpl::AddMethods(Isolate* isolate, Local<FunctionTemplate> tmpl) {
// TODO(@jasnell): The bigint API variations do not yet support fast
Expand Down Expand Up @@ -444,6 +448,173 @@ void IntervalHistogram::FastStop(Local<Value> receiver) {
histogram->OnStop();
}

Local<FunctionTemplate> ELDHistogram::GetConstructorTemplate(
Environment* env) {
Local<FunctionTemplate> tmpl = env->eldhistogram_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(ELDHistogram::kInternalFieldCount);
HistogramImpl::AddMethods(isolate, tmpl);
SetFastMethod(isolate, instance, "start", Start, &fast_start_);
SetFastMethod(isolate, instance, "stop", Stop, &fast_stop_);
env->set_eldhistogram_constructor_template(tmpl);
}
return tmpl;
}

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

ELDHistogram::ELDHistogram(
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),
EmbedderDataTag::kDefault);
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<ELDHistogram> ELDHistogram::Create(
Environment* env,
const Histogram::Options& options) {
Local<Object> obj;
if (!GetConstructorTemplate(env)
->InstanceTemplate()
->NewInstance(env->context()).ToLocal(&obj)) {
return nullptr;
}

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

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

void ELDHistogram::CheckCB(uv_check_t* handle) {
ELDHistogram* self =
ContainerOf(&ELDHistogram::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 ELDHistogram::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("histogram", histogram());
}

void ELDHistogram::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 ELDHistogram::OnStop() {
if (!enabled_ || IsHandleClosing()) return;
enabled_ = false;
uv_check_stop(&check_handle_);
uv_prepare_stop(&prepare_handle_);
}

void ELDHistogram::PrepareCloseCB(uv_handle_t* handle) {
ELDHistogram* self = static_cast<ELDHistogram*>(handle->data);
uv_close(reinterpret_cast<uv_handle_t*>(&self->check_handle_),
HandleWrap::OnClose);
}

void ELDHistogram::Close(Local<Value> close_callback) {
if (IsHandleClosing()) return;
OnStop();
state_ = kClosing;

if (!close_callback.IsEmpty() && close_callback->IsFunction() &&
!persistent().IsEmpty()) {
object()->Set(env()->context(),
env()->handle_onclose_symbol(),
close_callback).Check();
}

uv_close(reinterpret_cast<uv_handle_t*>(&prepare_handle_),
PrepareCloseCB);
}

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

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

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

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

void HistogramImpl::GetCount(const FunctionCallbackInfo<Value>& args) {
HistogramImpl* histogram = HistogramImpl::FromJSObject(args.This());
double value = static_cast<double>((*histogram)->Count());
Expand Down Expand Up @@ -607,6 +778,11 @@ HistogramImpl* HistogramImpl::FromJSObject(Local<Value> value) {
HistogramImpl::kImplField, EmbedderDataTag::kDefault));
}

std::unique_ptr<worker::TransferData>
ELDHistogram::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
63 changes: 63 additions & 0 deletions src/histogram.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,69 @@ class IntervalHistogram final : public HandleWrap, public HistogramImpl {
static v8::CFunction fast_stop_;
};

class ELDHistogram final : public HandleWrap, public HistogramImpl {
public:
enum InternalFields {
kInternalFieldCount = std::max<uint32_t>(
HandleWrap::kInternalFieldCount, HistogramImpl::kInternalFieldCount),
};

enum class StartFlags {
NONE,
RESET
};

static void RegisterExternalReferences(ExternalReferenceRegistry* registry);

static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);

static BaseObjectPtr<ELDHistogram> Create(
Environment* env,
const Histogram::Options& options);

ELDHistogram(
Environment* env,
v8::Local<v8::Object> wrap,
AsyncWrap::ProviderType type,
const Histogram::Options& options = Histogram::Options {});

static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Stop(const v8::FunctionCallbackInfo<v8::Value>& args);

static void FastStart(v8::Local<v8::Value> receiver, bool reset);
static void FastStop(v8::Local<v8::Value> receiver);

BaseObject::TransferMode GetTransferMode() const override {
return TransferMode::kCloneable;
}
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;

void Close(v8::Local<v8::Value> close_callback =
v8::Local<v8::Value>()) override;

void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ELDHistogram)
SET_SELF_SIZE(ELDHistogram)

private:
static void PrepareCB(uv_prepare_t* handle);
static void CheckCB(uv_check_t* handle);
static void PrepareCloseCB(uv_handle_t* handle);
void OnStart(StartFlags flags = StartFlags::RESET);
void OnStop();

bool enabled_ = false;
uv_prepare_t prepare_handle_;
uv_check_t check_handle_;
uint64_t prepare_time_ = 0;
uint64_t check_time_ = 0;
int64_t timeout_ = 0;

static v8::CFunction fast_start_;
static v8::CFunction fast_stop_;
};

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Expand Down
7 changes: 7 additions & 0 deletions src/node_perf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,12 @@ void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
int64_t interval = args[0].As<Integer>()->Value();
CHECK_GT(interval, 0);
if (args[1]->IsTrue()) {
BaseObjectPtr<ELDHistogram> histogram =
ELDHistogram::Create(env, Histogram::Options { 1 });
args.GetReturnValue().Set(histogram->object());
return;
}
BaseObjectPtr<IntervalHistogram> histogram =
IntervalHistogram::Create(env, interval, [](Histogram& histogram) {
uint64_t delta = histogram.RecordDelta();
Expand Down Expand Up @@ -413,6 +419,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(fast_performance_now);
HistogramBase::RegisterExternalReferences(registry);
IntervalHistogram::RegisterExternalReferences(registry);
ELDHistogram::RegisterExternalReferences(registry);
}
} // namespace performance
} // namespace node
Expand Down
Loading