Skip to content

Commit c836052

Browse files
committed
initial work to add event loop based metrics
1 parent 6909ea6 commit c836052

5 files changed

Lines changed: 83 additions & 49 deletions

File tree

doc/api/perf_hooks.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,7 +1708,10 @@ added: v11.10.0
17081708
-->
17091709

17101710
* `options` {Object}
1711-
* `resolution` {number} The sampling rate in milliseconds. Must be greater
1711+
* `resolution` {number} Deprecated. Previously the sampling rate in
1712+
milliseconds; sampling is now driven directly by the libuv event loop via
1713+
`uv_prepare_t`/`uv_check_t` hooks, so this value is ignored. It is still
1714+
validated to preserve backward compatible error behavior. Must be greater
17121715
than zero. **Default:** `10`.
17131716
* Returns: {IntervalHistogram}
17141717

@@ -1717,11 +1720,10 @@ _This property is an extension by Node.js. It is not available in Web browsers._
17171720
Creates an `IntervalHistogram` object that samples and reports the event loop
17181721
delay over time. The delays will be reported in nanoseconds.
17191722

1720-
Using a timer to detect approximate event loop delay works because the
1721-
execution of timers is tied specifically to the lifecycle of the libuv
1722-
event loop. That is, a delay in the loop will cause a delay in the execution
1723-
of the timer, and those delays are specifically what this API is intended to
1724-
detect.
1723+
Samples are taken directly from the event loop using `uv_prepare_t` and
1724+
`uv_check_t` hooks, which only fire while the loop is iterating. As a result,
1725+
the histogram does not keep the loop alive or force additional iterations when
1726+
the application is idle.
17251727

17261728
```mjs
17271729
import { monitorEventLoopDelay } from 'node:perf_hooks';

lib/internal/perf/event_loop_delay.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class ELDHistogram extends Histogram {
7979
* @returns {ELDHistogram}
8080
*/
8181
function monitorEventLoopDelay(options = kEmptyObject) {
82+
// TODO: remove this once backward compatibility is removed
8283
validateObject(options, 'options');
8384

8485
const { resolution = 10 } = options;
@@ -88,7 +89,7 @@ function monitorEventLoopDelay(options = kEmptyObject) {
8889
function() {
8990
markTransferMode(this, true, false);
9091
this[kEnabled] = false;
91-
this[kHandle] = createELDHistogram(resolution);
92+
this[kHandle] = createELDHistogram();
9293
this[kMap] = new SafeMap();
9394
}, [], ELDHistogram);
9495
}

src/histogram.cc

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -350,29 +350,27 @@ IntervalHistogram::IntervalHistogram(
350350
Environment* env,
351351
Local<Object> wrap,
352352
AsyncWrap::ProviderType type,
353-
int32_t interval,
354-
std::function<void(Histogram&)> on_interval,
355353
const Histogram::Options& options)
356354
: HandleWrap(
357355
env,
358356
wrap,
359-
reinterpret_cast<uv_handle_t*>(&timer_),
357+
reinterpret_cast<uv_handle_t*>(&check_handle_),
360358
type),
361-
HistogramImpl(options),
362-
interval_(interval),
363-
on_interval_(std::move(on_interval)) {
359+
HistogramImpl(options) {
364360
MakeWeak();
365361
wrap->SetAlignedPointerInInternalField(
366362
HistogramImpl::InternalFields::kImplField,
367363
static_cast<HistogramImpl*>(this),
368364
EmbedderDataTag::kDefault);
369-
uv_timer_init(env->event_loop(), &timer_);
365+
uv_check_init(env->event_loop(), &check_handle_);
366+
uv_prepare_init(env->event_loop(), &prepare_handle_);
367+
uv_unref(reinterpret_cast<uv_handle_t*>(&check_handle_));
368+
uv_unref(reinterpret_cast<uv_handle_t*>(&prepare_handle_));
369+
prepare_handle_.data = this;
370370
}
371371

372372
BaseObjectPtr<IntervalHistogram> IntervalHistogram::Create(
373373
Environment* env,
374-
int32_t interval,
375-
std::function<void(Histogram&)> on_interval,
376374
const Histogram::Options& options) {
377375
Local<Object> obj;
378376
if (!GetConstructorTemplate(env)
@@ -385,18 +383,34 @@ BaseObjectPtr<IntervalHistogram> IntervalHistogram::Create(
385383
env,
386384
obj,
387385
AsyncWrap::PROVIDER_ELDHISTOGRAM,
388-
interval,
389-
std::move(on_interval),
390386
options);
391387
}
392388

393-
void IntervalHistogram::TimerCB(uv_timer_t* handle) {
394-
IntervalHistogram* histogram =
395-
ContainerOf(&IntervalHistogram::timer_, handle);
389+
void IntervalHistogram::PrepareCB(uv_prepare_t* handle) {
390+
IntervalHistogram* self = static_cast<IntervalHistogram*>(handle->data);
391+
if (!self->enabled_) return;
392+
self->prepare_time_ = uv_hrtime();
393+
self->timeout_ = uv_backend_timeout(handle->loop);
394+
}
395+
396+
void IntervalHistogram::CheckCB(uv_check_t* handle) {
397+
IntervalHistogram* self =
398+
ContainerOf(&IntervalHistogram::check_handle_, handle);
399+
if (!self->enabled_) return;
396400

397-
Histogram* h = histogram->histogram().get();
401+
uint64_t check_time = uv_hrtime();
402+
uint64_t poll_time = check_time - self->prepare_time_;
403+
uint64_t latency = self->prepare_time_ - self->check_time_;
398404

399-
histogram->on_interval_(*h);
405+
if (self->timeout_ >= 0) {
406+
uint64_t timeout_ns = static_cast<uint64_t>(self->timeout_) * 1000 * 1000;
407+
if (poll_time > timeout_ns) {
408+
latency += poll_time - timeout_ns;
409+
}
410+
}
411+
412+
self->histogram()->Record(latency == 0 ? 1 : latency);
413+
self->check_time_ = check_time;
400414
}
401415

402416
void IntervalHistogram::MemoryInfo(MemoryTracker* tracker) const {
@@ -408,14 +422,42 @@ void IntervalHistogram::OnStart(StartFlags flags) {
408422
enabled_ = true;
409423
if (flags == StartFlags::RESET)
410424
histogram()->Reset();
411-
uv_timer_start(&timer_, TimerCB, interval_, interval_);
412-
uv_unref(reinterpret_cast<uv_handle_t*>(&timer_));
425+
check_time_ = uv_hrtime();
426+
prepare_time_ = check_time_;
427+
timeout_ = 0;
428+
uv_check_start(&check_handle_, CheckCB);
429+
uv_prepare_start(&prepare_handle_, PrepareCB);
430+
uv_unref(reinterpret_cast<uv_handle_t*>(&check_handle_));
431+
uv_unref(reinterpret_cast<uv_handle_t*>(&prepare_handle_));
413432
}
414433

415434
void IntervalHistogram::OnStop() {
416435
if (!enabled_ || IsHandleClosing()) return;
417436
enabled_ = false;
418-
uv_timer_stop(&timer_);
437+
uv_check_stop(&check_handle_);
438+
uv_prepare_stop(&prepare_handle_);
439+
}
440+
441+
void IntervalHistogram::PrepareCloseCB(uv_handle_t* handle) {
442+
IntervalHistogram* self = static_cast<IntervalHistogram*>(handle->data);
443+
uv_close(reinterpret_cast<uv_handle_t*>(&self->check_handle_),
444+
HandleWrap::OnClose);
445+
}
446+
447+
void IntervalHistogram::Close(Local<Value> close_callback) {
448+
if (IsHandleClosing()) return;
449+
OnStop();
450+
state_ = kClosing;
451+
452+
if (!close_callback.IsEmpty() && close_callback->IsFunction() &&
453+
!persistent().IsEmpty()) {
454+
object()->Set(env()->context(),
455+
env()->handle_onclose_symbol(),
456+
close_callback).Check();
457+
}
458+
459+
uv_close(reinterpret_cast<uv_handle_t*>(&prepare_handle_),
460+
PrepareCloseCB);
419461
}
420462

421463
void IntervalHistogram::Start(const FunctionCallbackInfo<Value>& args) {

src/histogram.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,16 +225,12 @@ class IntervalHistogram final : public HandleWrap, public HistogramImpl {
225225

226226
static BaseObjectPtr<IntervalHistogram> Create(
227227
Environment* env,
228-
int32_t interval,
229-
std::function<void(Histogram&)> on_interval,
230228
const Histogram::Options& options);
231229

232230
IntervalHistogram(
233231
Environment* env,
234232
v8::Local<v8::Object> wrap,
235233
AsyncWrap::ProviderType type,
236-
int32_t interval,
237-
std::function<void(Histogram&)> on_interval,
238234
const Histogram::Options& options = Histogram::Options {});
239235

240236
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
@@ -248,19 +244,26 @@ class IntervalHistogram final : public HandleWrap, public HistogramImpl {
248244
}
249245
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
250246

247+
void Close(v8::Local<v8::Value> close_callback =
248+
v8::Local<v8::Value>()) override;
249+
251250
void MemoryInfo(MemoryTracker* tracker) const override;
252251
SET_MEMORY_INFO_NAME(IntervalHistogram)
253252
SET_SELF_SIZE(IntervalHistogram)
254253

255254
private:
256-
static void TimerCB(uv_timer_t* handle);
255+
static void PrepareCB(uv_prepare_t* handle);
256+
static void CheckCB(uv_check_t* handle);
257+
static void PrepareCloseCB(uv_handle_t* handle);
257258
void OnStart(StartFlags flags = StartFlags::RESET);
258259
void OnStop();
259260

260261
bool enabled_ = false;
261-
int32_t interval_ = 0;
262-
std::function<void(Histogram&)> on_interval_;
263-
uv_timer_t timer_;
262+
uv_prepare_t prepare_handle_;
263+
uv_check_t check_handle_;
264+
uint64_t prepare_time_ = 0;
265+
uint64_t check_time_ = 0;
266+
int64_t timeout_ = 0;
264267

265268
static v8::CFunction fast_start_;
266269
static v8::CFunction fast_stop_;

src/node_perf.cc

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -280,22 +280,8 @@ void UvMetricsInfo(const FunctionCallbackInfo<Value>& args) {
280280

281281
void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) {
282282
Environment* env = Environment::GetCurrent(args);
283-
int64_t interval = args[0].As<Integer>()->Value();
284-
CHECK_GT(interval, 0);
285283
BaseObjectPtr<IntervalHistogram> histogram =
286-
IntervalHistogram::Create(env, interval, [](Histogram& histogram) {
287-
uint64_t delta = histogram.RecordDelta();
288-
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
289-
"delay", delta);
290-
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
291-
"min", histogram.Min());
292-
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
293-
"max", histogram.Max());
294-
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
295-
"mean", histogram.Mean());
296-
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
297-
"stddev", histogram.Stddev());
298-
}, Histogram::Options { 1000 });
284+
IntervalHistogram::Create(env, Histogram::Options { 1 });
299285
args.GetReturnValue().Set(histogram->object());
300286
}
301287

0 commit comments

Comments
 (0)