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
31 changes: 27 additions & 4 deletions doc/api/perf_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -1879,6 +1879,9 @@
<!-- YAML
added: v11.10.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/00000

Check warning on line 1883 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 `lowest`, `highest`, and `figures` options.
- version:
- v26.5.0
- v24.19.0
Expand All @@ -1892,6 +1895,14 @@
* `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`.
* `lowest` {number|bigint} The lowest discernible delay, in nanoseconds. Must
be an integer value greater than `0`. **Default:** `1` when
`samplePerIteration` is `true`, otherwise `1000`.
* `highest` {number|bigint} The highest recordable delay, in nanoseconds.
Must be an integer value that is equal to or greater than two times
`lowest`. **Default:** `2n ** 63n - 1n`.
* `figures` {number} The number of accuracy digits. Must be an integer
between `1` and `5`. **Default:** `3`.
* Returns: {ELDHistogram}

_This property is an extension by Node.js. It is not available in Web browsers._
Expand All @@ -1907,6 +1918,16 @@
The two sampling modes produce significantly different results and should not
be compared directly.

The `lowest`, `highest`, and `figures` options configure the histogram as they
do for [`perf_hooks.createHistogram()`][]. `lowest` must be greater than `0`
because an event loop delay of zero is not possible: the event loop has a
minimal overhead, and the measurement itself depends on the event loop turning.
Delays greater than `highest` are not recorded, and are counted by
[`histogram.exceeds`][] instead. With interval-based sampling, every sample
includes the `resolution`, so `highest` should be well above
`resolution * 1e6`. The histogram's memory use depends on these options, not
on the number of samples.

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

Expand Down Expand Up @@ -2160,8 +2181,8 @@

* Type: {number}

The number of times the event loop delay exceeded the maximum 1 hour event
loop delay threshold.
The number of values that were not recorded because they exceeded the
histogram's highest recordable value.

### `histogram.exceedsBigInt`

Expand All @@ -2173,8 +2194,8 @@

* Type: {bigint}

The number of times the event loop delay exceeded the maximum 1 hour event
loop delay threshold.
The number of values that were not recorded because they exceeded the
histogram's highest recordable value.

### `histogram.export()`

Expand Down Expand Up @@ -3257,7 +3278,9 @@
[Worker threads]: worker_threads.md#worker-threads
[`'exit'`]: process.md#event-exit
[`child_process.spawnSync()`]: child_process.md#child_processspawnsynccommand-args-options
[`histogram.exceeds`]: #histogramexceeds
[`histogram.export()`]: #histogramexport
[`perf_hooks.createHistogram()`]: #perf_hookscreatehistogramoptions
[`perf_hooks.createSlidingWindowHistogram()`]: #perf_hookscreateslidingwindowhistogramoptions
[`perf_hooks.eventLoopUtilization()`]: #perf_hookseventlooputilizationutilization1-utilization2
[`perf_hooks.importHistogram()`]: #perf_hooksimporthistogramdata
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/histogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -1003,8 +1003,10 @@ module.exports = {
isHistogram,
kDestroy,
kHandle,
kMaxInt64,
kSkipThrow,
createHistogram,
createSlidingWindowHistogram,
importHistogram,
validateHistogramOptions,
};
30 changes: 27 additions & 3 deletions lib/internal/perf/event_loop_delay.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';
const {
BigInt,
Symbol,
SymbolDispose,
} = primordials;
Expand All @@ -24,7 +25,9 @@ const {
const {
Histogram,
kHandle,
kMaxInt64,
kSkipThrow,
validateHistogramOptions,
} = require('internal/histogram');

const {
Expand All @@ -37,6 +40,12 @@ const {

const kEnabled = Symbol('kEnabled');

// Default histogram options. The lowest discernible delay is in nanoseconds,
// and its default depends on the sampling mode.
const kDefaultIntervalLowest = 1000;
const kDefaultIterationLowest = 1;
const kDefaultFigures = 3;

class ELDHistogram extends Histogram {
constructor(skipThrowSymbol = undefined) {
if (skipThrowSymbol !== kSkipThrow) {
Expand Down Expand Up @@ -76,8 +85,11 @@ class ELDHistogram extends Histogram {

/**
* @param {{
* samplePerIteration : boolean,
* resolution : number
* samplePerIteration? : boolean,
* resolution? : number,
* lowest? : number|bigint,
* highest? : number|bigint,
* figures? : number,
* }} [options]
* @returns {ELDHistogram}
*/
Expand All @@ -88,10 +100,22 @@ function monitorEventLoopDelay(options = kEmptyObject) {
validateBoolean(samplePerIteration, 'options.samplePerIteration');
validateInteger(resolution, 'options.resolution', 1);

const {
lowest = samplePerIteration ?
kDefaultIterationLowest : kDefaultIntervalLowest,
highest = kMaxInt64,
figures = kDefaultFigures,
} = options;
validateHistogramOptions(lowest, highest, figures);

// Throws if the native histogram cannot be created with these options.
const handle = createELDHistogram(
resolution, samplePerIteration, BigInt(lowest), BigInt(highest), figures);

const histogram = new ELDHistogram(kSkipThrow);
markTransferMode(histogram, true, false);
histogram[kEnabled] = false;
histogram[kHandle] = createELDHistogram(resolution, samplePerIteration);
histogram[kHandle] = handle;
return histogram;
}

Expand Down
21 changes: 11 additions & 10 deletions src/histogram.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2379,11 +2379,11 @@ void IntervalHistogram::RegisterExternalReferences(
IntervalHistogram::IntervalHistogram(Environment* env,
Local<Object> wrap,
AsyncWrap::ProviderType type,
int32_t interval,
uint64_t interval,
OnInterval on_interval,
const Histogram::Options& options)
std::shared_ptr<Histogram> histogram)
: HandleWrap(env, wrap, reinterpret_cast<uv_handle_t*>(&timer_), type),
HistogramImpl(options),
HistogramImpl(std::move(histogram)),
interval_(interval),
on_interval_(on_interval) {
MakeWeak();
Expand All @@ -2396,9 +2396,9 @@ IntervalHistogram::IntervalHistogram(Environment* env,

BaseObjectPtr<IntervalHistogram> IntervalHistogram::Create(
Environment* env,
int32_t interval,
uint64_t interval,
OnInterval on_interval,
const Histogram::Options& options,
std::shared_ptr<Histogram> histogram,
AsyncWrap::ProviderType type) {
Local<Object> obj;
if (!GetConstructorTemplate(env)
Expand All @@ -2409,7 +2409,7 @@ BaseObjectPtr<IntervalHistogram> IntervalHistogram::Create(
}

return MakeBaseObject<IntervalHistogram>(
env, obj, type, interval, on_interval, options);
env, obj, type, interval, on_interval, std::move(histogram));
}

void IntervalHistogram::TimerCB(uv_timer_t* handle) {
Expand Down Expand Up @@ -2475,10 +2475,10 @@ void IterationHistogram::RegisterExternalReferences(
IterationHistogram::IterationHistogram(Environment* env,
Local<Object> wrap,
AsyncWrap::ProviderType type,
const Histogram::Options& options)
std::shared_ptr<Histogram> histogram)
: HandleWrap(
env, wrap, reinterpret_cast<uv_handle_t*>(&check_handle_), type),
HistogramImpl(options) {
HistogramImpl(std::move(histogram)) {
MakeWeak();
wrap->SetAlignedPointerInInternalField(
HistogramImpl::InternalFields::kImplField,
Expand All @@ -2492,7 +2492,7 @@ IterationHistogram::IterationHistogram(Environment* env,

BaseObjectPtr<IterationHistogram> IterationHistogram::Create(
Environment* env,
const Histogram::Options& options,
std::shared_ptr<Histogram> histogram,
AsyncWrap::ProviderType type) {
Local<Object> obj;
if (!GetConstructorTemplate(env)
Expand All @@ -2502,7 +2502,8 @@ BaseObjectPtr<IterationHistogram> IterationHistogram::Create(
return nullptr;
}

return MakeBaseObject<IterationHistogram>(env, obj, type, options);
return MakeBaseObject<IterationHistogram>(
env, obj, type, std::move(histogram));
}

void IterationHistogram::PrepareCB(uv_prepare_t* handle) {
Expand Down
14 changes: 7 additions & 7 deletions src/histogram.h
Original file line number Diff line number Diff line change
Expand Up @@ -464,17 +464,17 @@ class IntervalHistogram final : public HandleWrap,

static BaseObjectPtr<IntervalHistogram> Create(
Environment* env,
int32_t interval,
uint64_t interval,
OnInterval on_interval,
const Histogram::Options& options,
std::shared_ptr<Histogram> histogram,
AsyncWrap::ProviderType type = AsyncWrap::PROVIDER_ELDHISTOGRAM);

IntervalHistogram(Environment* env,
v8::Local<v8::Object> wrap,
AsyncWrap::ProviderType type,
int32_t interval,
uint64_t interval,
OnInterval on_interval,
const Histogram::Options& options = Histogram::Options{});
std::shared_ptr<Histogram> histogram);

static void FastStart(v8::Local<v8::Value> receiver, bool reset);
static void FastStop(v8::Local<v8::Value> receiver);
Expand All @@ -499,7 +499,7 @@ class IntervalHistogram final : public HandleWrap,
template <typename T>
friend void StopHandleHistogram(v8::Local<v8::Value>);

int32_t interval_ = 0;
uint64_t interval_ = 0;
OnInterval on_interval_ = nullptr;
uv_timer_t timer_;

Expand All @@ -524,13 +524,13 @@ class IterationHistogram final

static BaseObjectPtr<IterationHistogram> Create(
Environment* env,
const Histogram::Options& options,
std::shared_ptr<Histogram> histogram,
AsyncWrap::ProviderType type = AsyncWrap::PROVIDER_ELDHISTOGRAM);

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

static void FastStart(v8::Local<v8::Value> receiver, bool reset);
static void FastStop(v8::Local<v8::Value> receiver);
Expand Down
38 changes: 31 additions & 7 deletions src/node_perf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "histogram-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_internals.h"
#include "node_process-inl.h"
Expand All @@ -14,6 +15,7 @@
namespace node {
namespace performance {

using v8::BigInt;
using v8::Context;
using v8::DontDelete;
using v8::Function;
Expand All @@ -28,6 +30,7 @@ using v8::Object;
using v8::ObjectTemplate;
using v8::PropertyAttribute;
using v8::ReadOnly;
using v8::Uint32;
using v8::Value;

// Microseconds in a millisecond, as a float.
Expand Down Expand Up @@ -290,14 +293,34 @@ void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
int64_t interval = args[0].As<Integer>()->Value();
CHECK_GT(interval, 0);
CHECK(args[2]->IsBigInt());
CHECK(args[3]->IsBigInt());
CHECK(args[4]->IsUint32());
bool lossless = true;
const int64_t lowest = args[2].As<BigInt>()->Int64Value(&lossless);
CHECK(lossless);
const int64_t highest = args[3].As<BigInt>()->Int64Value(&lossless);
CHECK(lossless);
const int figures = static_cast<int>(args[4].As<Uint32>()->Value());

// The options are validated in JS, but hdr_init() still rejects some
// combinations, such as a very large lowest value.
std::shared_ptr<Histogram> histogram =
Histogram::Create(Histogram::Options{lowest, highest, figures});
if (!histogram) {
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid histogram options");
}

if (args[1]->IsTrue()) {
BaseObjectPtr<IterationHistogram> histogram =
IterationHistogram::Create(env, Histogram::Options{1});
args.GetReturnValue().Set(histogram->object());
BaseObjectPtr<IterationHistogram> eld =
IterationHistogram::Create(env, std::move(histogram));
if (eld) args.GetReturnValue().Set(eld->object());
return;
}
BaseObjectPtr<IntervalHistogram> histogram =
IntervalHistogram::Create(env, interval, [](Histogram& histogram) {
BaseObjectPtr<IntervalHistogram> eld = IntervalHistogram::Create(
env,
interval,
[](Histogram& histogram) {
uint64_t delta = histogram.RecordDelta();
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"delay", delta);
Expand All @@ -309,8 +332,9 @@ void CreateELDHistogram(const FunctionCallbackInfo<Value>& args) {
"mean", histogram.Mean());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"stddev", histogram.Stddev());
}, Histogram::Options { 1000 });
args.GetReturnValue().Set(histogram->object());
},
std::move(histogram));
if (eld) args.GetReturnValue().Set(eld->object());
}

void MarkBootstrapComplete(const FunctionCallbackInfo<Value>& args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const assert = require('assert');
const { internalBinding } = require('internal/test/binding');
const { createELDHistogram } = internalBinding('performance');

const histogram = createELDHistogram(1, true);
const histogram = createELDHistogram(1, true, 1n, 2n ** 63n - 1n, 3);

function testFastMethods() {
histogram.start(true);
Expand Down
Loading
Loading