Skip to content

Commit 334af98

Browse files
committed
perf_hooks: precise mode for monitorEventLoopDelay
Introduce precise mode of `monitorEventLoopDelay` by using `uv_check_t` and `uv_prepare_t` instead of `uv_timer_t`.
1 parent 331d636 commit 334af98

File tree

5 files changed

+136
-34
lines changed

5 files changed

+136
-34
lines changed

doc/api/perf_hooks.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -432,21 +432,31 @@ is equal to `type`.
432432
## `perf_hooks.monitorEventLoopDelay([options])`
433433
<!-- YAML
434434
added: v11.10.0
435+
changes:
436+
- version: REPLACEME
437+
pr-url: https://github.com/nodejs/node/pull/32018
438+
description: introduce precise mode (`resoltuion` equal to `0`). Change
439+
the default `resolution` to `0`.
435440
-->
436441

437442
* `options` {Object}
438-
* `resolution` {number} The sampling rate in milliseconds. Must be greater
439-
than zero. **Default:** `10`.
443+
* `resolution` {number} Optional sampling rate in milliseconds. Must be
444+
greater or equal to zero. **Default:** `0`.
440445
* Returns: {Histogram}
441446

442447
Creates a `Histogram` object that samples and reports the event loop delay
443448
over time. The delays will be reported in nanoseconds.
444449

445-
Using a timer to detect approximate event loop delay works because the
446-
execution of timers is tied specifically to the lifecycle of the libuv
447-
event loop. That is, a delay in the loop will cause a delay in the execution
448-
of the timer, and those delays are specifically what this API is intended to
449-
detect.
450+
When `resolution` is zero precise time difference between IO poll end and IO
451+
poll start is entered into the histogram on every event-loop iteration. During
452+
standby (i.e., no event-loop activity) - no data is added to the histogram.
453+
454+
When `resolution` is non-zero a timer is used to detect approximate event loop
455+
delay. This works because the execution of timers is tied specifically to the
456+
lifecycle of the libuv event loop. That is, a delay in the loop will cause a
457+
delay in the execution of the timer, and those delays are specifically what this
458+
API is intended to detect. Timer-based monitoring happens continuously and adds
459+
delay statistics to the histogram even during standby.
450460

451461
```js
452462
const { monitorEventLoopDelay } = require('perf_hooks');

lib/perf_hooks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ function monitorEventLoopDelay(options = {}) {
609609
throw new ERR_INVALID_ARG_TYPE('options.resolution',
610610
'number', resolution);
611611
}
612-
if (resolution <= 0 || !NumberIsSafeInteger(resolution)) {
612+
if (resolution < 0 || !NumberIsSafeInteger(resolution)) {
613613
throw new ERR_INVALID_OPT_VALUE.RangeError('resolution', resolution);
614614
}
615615
return new ELDHistogram(new _ELDHistogram(resolution));

src/node_perf.cc

Lines changed: 101 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ static void ELDHistogramNew(const FunctionCallbackInfo<Value>& args) {
474474
Environment* env = Environment::GetCurrent(args);
475475
CHECK(args.IsConstructCall());
476476
int32_t resolution = args[0]->IntegerValue(env->context()).FromJust();
477-
CHECK_GT(resolution, 0);
477+
CHECK_GE(resolution, 0);
478478
new ELDHistogram(env, args.This(), resolution);
479479
}
480480
} // namespace
@@ -489,31 +489,59 @@ ELDHistogram::ELDHistogram(
489489
Histogram(1, 3.6e12),
490490
resolution_(resolution) {
491491
MakeWeak();
492-
uv_timer_init(env->event_loop(), &timer_);
492+
CHECK_EQ(uv_timer_init(env->event_loop(), &timer_), 0);
493+
494+
if (is_precise()) {
495+
CHECK_EQ(uv_prepare_init(env->event_loop(), &prepare_), 0);
496+
CHECK_EQ(uv_check_init(env->event_loop(), &check_), 0);
497+
}
498+
}
499+
500+
void ELDHistogram::Close(Local<Value> close_callback) {
501+
// HandleWrap::Close will call `uv_close()` on `timer_` and
502+
// deallocate `ELDHistogram` in `HandleWrap::OnClose`.
503+
// Therefore, it is safe to call `uv_close` with `nullptr` here since
504+
// both `prepare_` and `check_`.
505+
if (is_precise() && !IsHandleClosing()) {
506+
uv_close(reinterpret_cast<uv_handle_t*>(&prepare_), nullptr);
507+
uv_close(reinterpret_cast<uv_handle_t*>(&check_), nullptr);
508+
}
509+
510+
HandleWrap::Close(close_callback);
511+
}
512+
513+
void ELDHistogram::TraceHistogram() {
514+
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), "min", Min());
515+
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), "max", Max());
516+
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), "mean", Mean());
517+
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop), "stddev", Stddev());
493518
}
494519

495520
void ELDHistogram::DelayIntervalCallback(uv_timer_t* req) {
496521
ELDHistogram* histogram = ContainerOf(&ELDHistogram::timer_, req);
497-
histogram->RecordDelta();
498-
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
499-
"min", histogram->Min());
500-
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
501-
"max", histogram->Max());
502-
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
503-
"mean", histogram->Mean());
504-
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
505-
"stddev", histogram->Stddev());
522+
histogram->RecordCoarseDelta();
523+
histogram->TraceHistogram();
524+
}
525+
526+
void ELDHistogram::CheckCallback(uv_check_t* handle) {
527+
ELDHistogram* histogram = ContainerOf(&ELDHistogram::check_, handle);
528+
histogram->RecordPreciseDelayStart();
506529
}
507530

508-
bool ELDHistogram::RecordDelta() {
531+
void ELDHistogram::PrepareCallback(uv_prepare_t* handle) {
532+
ELDHistogram* histogram = ContainerOf(&ELDHistogram::prepare_, handle);
533+
histogram->RecordPreciseDelayStop();
534+
histogram->TraceHistogram();
535+
}
536+
537+
void ELDHistogram::RecordCoarseDelta() {
509538
uint64_t time = uv_hrtime();
510-
bool ret = true;
511539
if (prev_ > 0) {
512540
int64_t delta = time - prev_;
513541
if (delta > 0) {
514-
ret = Record(delta);
542+
bool ret = Record(delta);
515543
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
516-
"delay", delta);
544+
"delay", delta);
517545
if (!ret) {
518546
if (exceeds_ < 0xFFFFFFFF)
519547
exceeds_++;
@@ -525,25 +553,76 @@ bool ELDHistogram::RecordDelta() {
525553
}
526554
}
527555
prev_ = time;
528-
return ret;
556+
}
557+
558+
void ELDHistogram::RecordPreciseDelayStart() {
559+
prev_ = uv_hrtime();
560+
}
561+
562+
void ELDHistogram::RecordPreciseDelayStop() {
563+
if (prev_ == 0) {
564+
return;
565+
}
566+
567+
uint64_t time = uv_hrtime();
568+
int64_t delta = time - prev_;
569+
if (delta <= 0) {
570+
return;
571+
}
572+
bool ret = Record(delta);
573+
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
574+
"delay", delta);
575+
if (ret) {
576+
return;
577+
}
578+
if (exceeds_ < 0xFFFFFFFF) {
579+
exceeds_++;
580+
}
581+
ProcessEmitWarning(
582+
env(),
583+
"Event loop delay exceeded 1 hour: %" PRId64 " nanoseconds",
584+
delta);
529585
}
530586

531587
bool ELDHistogram::Enable() {
532588
if (enabled_ || IsHandleClosing()) return false;
533589
enabled_ = true;
534590
prev_ = 0;
535-
uv_timer_start(&timer_,
536-
DelayIntervalCallback,
537-
resolution_,
538-
resolution_);
539-
uv_unref(reinterpret_cast<uv_handle_t*>(&timer_));
591+
592+
if (is_precise()) {
593+
// `uv_prepare_start`/`uv_check_start` prepend the handles to the queue.
594+
// Newly started handles will have their callbacks invoked **before** the
595+
// old handles' callbacks.
596+
//
597+
// This is important because of `immediate_check_handle` in `src/env.cc`.
598+
// `immediate_check_handle` is used for invoking user callbacks queued with
599+
// `setImmediate()` API call. `ELDHistogram` has to record the delay
600+
// induced by `setImmediate()` callbacks too so the order of handles is
601+
// important.
602+
CHECK_EQ(uv_prepare_start(&prepare_, PrepareCallback), 0);
603+
CHECK_EQ(uv_check_start(&check_, CheckCallback), 0);
604+
uv_unref(reinterpret_cast<uv_handle_t*>(&prepare_));
605+
uv_unref(reinterpret_cast<uv_handle_t*>(&check_));
606+
} else {
607+
CHECK_EQ(uv_timer_start(&timer_,
608+
DelayIntervalCallback,
609+
resolution_,
610+
resolution_),
611+
0);
612+
uv_unref(reinterpret_cast<uv_handle_t*>(&timer_));
613+
}
540614
return true;
541615
}
542616

543617
bool ELDHistogram::Disable() {
544618
if (!enabled_ || IsHandleClosing()) return false;
545619
enabled_ = false;
546-
uv_timer_stop(&timer_);
620+
if (is_precise()) {
621+
CHECK_EQ(uv_prepare_stop(&prepare_), 0);
622+
CHECK_EQ(uv_check_stop(&check_), 0);
623+
} else {
624+
CHECK_EQ(uv_timer_stop(&timer_), 0);
625+
}
547626
return true;
548627
}
549628

src/node_perf.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@ class ELDHistogram : public HandleWrap, public Histogram {
153153
Local<Object> wrap,
154154
int32_t resolution);
155155

156-
bool RecordDelta();
156+
void RecordCoarseDelta();
157+
void RecordPreciseDelayStart();
158+
void RecordPreciseDelayStop();
159+
void TraceHistogram();
157160
bool Enable();
158161
bool Disable();
159162
void ResetState() {
@@ -163,6 +166,8 @@ class ELDHistogram : public HandleWrap, public Histogram {
163166
}
164167
int64_t Exceeds() { return exceeds_; }
165168

169+
void Close(Local<Value> close_callback = Local<Value>()) override;
170+
166171
void MemoryInfo(MemoryTracker* tracker) const override {
167172
tracker->TrackFieldWithSize("histogram", GetMemorySize());
168173
}
@@ -172,12 +177,20 @@ class ELDHistogram : public HandleWrap, public Histogram {
172177

173178
private:
174179
static void DelayIntervalCallback(uv_timer_t* req);
180+
static void PrepareCallback(uv_prepare_t* handle);
181+
static void CheckCallback(uv_check_t* handle);
182+
183+
inline bool is_precise() const {
184+
return resolution_ == 0;
185+
}
175186

176187
bool enabled_ = false;
177188
int32_t resolution_ = 0;
178189
int64_t exceeds_ = 0;
179190
uint64_t prev_ = 0;
180191
uv_timer_t timer_;
192+
uv_prepare_t prepare_;
193+
uv_check_t check_;
181194
};
182195

183196
} // namespace performance

test/sequential/test-performance-eventloopdelay.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const { sleep } = require('internal/util');
3939
);
4040
});
4141

42-
[-1, 0, Infinity].forEach((i) => {
42+
[-1, Infinity].forEach((i) => {
4343
assert.throws(
4444
() => monitorEventLoopDelay({ resolution: i }),
4545
{
@@ -50,8 +50,8 @@ const { sleep } = require('internal/util');
5050
});
5151
}
5252

53-
{
54-
const histogram = monitorEventLoopDelay({ resolution: 1 });
53+
for (const resolution of [ 0, 1 ]) {
54+
const histogram = monitorEventLoopDelay({ resolution });
5555
histogram.enable();
5656
let m = 5;
5757
function spinAWhile() {

0 commit comments

Comments
 (0)