perf_hooks: introduce createHistogram

Adds a new `perf_hooks.createHistogram()` API for creating histogram
instances that allow user recording.

Makes Histogram instances cloneable via MessagePort. This allows, for
instance, an event loop delay monitor to be running on the main thread
while the histogram data can be monitored actively from a worker thread.

Signed-off-by: James M Snell <jasnell@gmail.com>

PR-URL: https://github.com/nodejs/node/pull/37155
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell 2021-01-30 09:26:15 -08:00
parent ce4798b555
commit 4a19cc8947
No known key found for this signature in database
GPG Key ID: 7341B15C070877AC
15 changed files with 781 additions and 320 deletions

View File

@ -653,6 +653,22 @@ performance.mark('test');
performance.mark('meow');
```
## `perf_hooks.createHistogram([options])`
<!-- YAML
added: REPLACEME
-->
* `options` {Object}
* `min` {number|bigint} The minimum recordable value. Must be an integer
value greater than 0. **Defaults**: `1`.
* `max` {number|bigint} The maximum recordable value. Must be an integer
value greater than `min`. **Defaults**: `Number.MAX_SAFE_INTEGER`.
* `figures` {number} The number of accuracy digits. Must be a number between
`1` and `5`. **Defaults**: `3`.
* Returns {RecordableHistogram}
Returns a {RecordableHistogram}.
## `perf_hooks.monitorEventLoopDelay([options])`
<!-- YAML
added: v11.10.0
@ -661,12 +677,12 @@ added: v11.10.0
* `options` {Object}
* `resolution` {number} The sampling rate in milliseconds. Must be greater
than zero. **Default:** `10`.
* Returns: {Histogram}
* Returns: {IntervalHistogram}
_This property is an extension by Node.js. It is not available in Web browsers._
Creates a `Histogram` object that samples and reports the event loop delay
over time. The delays will be reported in nanoseconds.
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
@ -689,36 +705,12 @@ console.log(h.percentile(50));
console.log(h.percentile(99));
```
### Class: `Histogram`
<!-- YAML
added: v11.10.0
-->
Tracks the event loop delay at a given sampling rate. The constructor of
this class not exposed to users.
_This property is an extension by Node.js. It is not available in Web browsers._
#### `histogram.disable()`
## Class: `Histogram`
<!-- YAML
added: v11.10.0
-->
* Returns: {boolean}
Disables the event loop delay sample timer. Returns `true` if the timer was
stopped, `false` if it was already stopped.
#### `histogram.enable()`
<!-- YAML
added: v11.10.0
-->
* Returns: {boolean}
Enables the event loop delay sample timer. Returns `true` if the timer was
started, `false` if it was already started.
#### `histogram.exceeds`
### `histogram.exceeds`
<!-- YAML
added: v11.10.0
-->
@ -728,7 +720,7 @@ added: v11.10.0
The number of times the event loop delay exceeded the maximum 1 hour event
loop delay threshold.
#### `histogram.max`
### `histogram.max`
<!-- YAML
added: v11.10.0
-->
@ -737,7 +729,7 @@ added: v11.10.0
The maximum recorded event loop delay.
#### `histogram.mean`
### `histogram.mean`
<!-- YAML
added: v11.10.0
-->
@ -746,7 +738,7 @@ added: v11.10.0
The mean of the recorded event loop delays.
#### `histogram.min`
### `histogram.min`
<!-- YAML
added: v11.10.0
-->
@ -755,7 +747,7 @@ added: v11.10.0
The minimum recorded event loop delay.
#### `histogram.percentile(percentile)`
### `histogram.percentile(percentile)`
<!-- YAML
added: v11.10.0
-->
@ -765,7 +757,7 @@ added: v11.10.0
Returns the value at the given percentile.
#### `histogram.percentiles`
### `histogram.percentiles`
<!-- YAML
added: v11.10.0
-->
@ -774,14 +766,14 @@ added: v11.10.0
Returns a `Map` object detailing the accumulated percentile distribution.
#### `histogram.reset()`
### `histogram.reset()`
<!-- YAML
added: v11.10.0
-->
Resets the collected histogram data.
#### `histogram.stddev`
### `histogram.stddev`
<!-- YAML
added: v11.10.0
-->
@ -790,6 +782,56 @@ added: v11.10.0
The standard deviation of the recorded event loop delays.
## Class: `IntervalHistogram extends Histogram`
A `Histogram` that is periodically updated on a given interval.
### `histogram.disable()`
<!-- YAML
added: v11.10.0
-->
* Returns: {boolean}
Disables the update interval timer. Returns `true` if the timer was
stopped, `false` if it was already stopped.
### `histogram.enable()`
<!-- YAML
added: v11.10.0
-->
* Returns: {boolean}
Enables the update interval timer. Returns `true` if the timer was
started, `false` if it was already started.
### Cloning an `IntervalHistogram`
{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.
## Class: `RecordableHistogram extends Histogram`
<!-- YAML
added: REPLACEME
-->
### `histogram.record(val)`
<!-- YAML
added: REPLACEME
-->
* `val` {number|bigint} The amount to record in the histogram.
### `histogram.recordDelta()`
<!-- YAML
added: REPLACEME
-->
Calculates the amount of time (in nanoseconds) that has passed since the
previous call to `recordDelta()` and records that amount in the histogram.
## Examples
### Measuring the duration of async operations

View File

@ -474,6 +474,9 @@ are part of the channel.
<!-- YAML
added: v10.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37155
description: Add 'Histogram' types to the list of cloneable types.
- version: v15.6.0
pr-url: https://github.com/nodejs/node/pull/36804
description: Added `X509Certificate` to the list of cloneable types.
@ -507,8 +510,13 @@ In particular, the significant differences to `JSON` are:
* `value` may contain typed arrays, both using `ArrayBuffer`s
and `SharedArrayBuffer`s.
* `value` may contain [`WebAssembly.Module`][] instances.
* `value` may not contain native (C++-backed) objects other than {MessagePort}s,
{FileHandle}s, {KeyObject}s, {CryptoKey}s, and {X509Certificate}s.
* `value` may not contain native (C++-backed) objects other than:
* {CryptoKey}s,
* {FileHandle}s,
* {Histogram}s,
* {KeyObject}s,
* {MessagePort}s,
* {X509Certificate}s.
```js
const { MessageChannel } = require('worker_threads');

View File

@ -1,38 +1,71 @@
'use strict';
const {
NumberIsNaN,
NumberIsInteger,
NumberMAX_SAFE_INTEGER,
ObjectSetPrototypeOf,
SafeMap,
Symbol,
TypeError,
} = primordials;
const {
Histogram: _Histogram
} = internalBinding('performance');
const {
customInspectSymbol: kInspect,
} = require('internal/util');
const { format } = require('util');
const { NumberIsNaN, SafeMap, Symbol } = primordials;
const { inspect } = require('util');
const { ERR_INVALID_ARG_VALUE } = require('internal/errors').codes;
const { validateNumber } = require('internal/validators');
const {
codes: {
ERR_INVALID_ARG_VALUE,
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE,
},
} = require('internal/errors');
const {
validateNumber,
} = require('internal/validators');
const kDestroy = Symbol('kDestroy');
const kHandle = Symbol('kHandle');
const kMap = Symbol('kMap');
// Histograms are created internally by Node.js and used to
// record various metrics. This Histogram class provides a
// generally read-only view of the internal histogram.
class Histogram {
#map = new SafeMap();
const {
kClone,
kDeserialize,
JSTransferable,
} = require('internal/worker/js_transferable');
class Histogram extends JSTransferable {
constructor(internal) {
super();
this[kHandle] = internal;
this[kMap] = new SafeMap();
}
[kInspect]() {
const obj = {
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1
};
return `Histogram ${inspect({
min: this.min,
max: this.max,
mean: this.mean,
exceeds: this.exceeds,
stddev: this.stddev,
percentiles: this.percentiles,
};
return `Histogram ${format(obj)}`;
}, opts)}`;
}
get min() {
@ -65,9 +98,9 @@ class Histogram {
}
get percentiles() {
this.#map.clear();
this[kHandle]?.percentiles(this.#map);
return this.#map;
this[kMap].clear();
this[kHandle]?.percentiles(this[kMap]);
return this[kMap];
}
reset() {
@ -77,10 +110,90 @@ class Histogram {
[kDestroy]() {
this[kHandle] = undefined;
}
[kClone]() {
const handle = this[kHandle];
return {
data: { handle },
deserializeInfo: 'internal/histogram:InternalHistogram'
};
}
[kDeserialize]({ handle }) {
this[kHandle] = handle;
}
}
class RecordableHistogram extends Histogram {
constructor() {
// eslint-disable-next-line no-restricted-syntax
throw new TypeError('illegal constructor');
}
record(val) {
if (typeof val === 'bigint') {
this[kHandle]?.record(val);
return;
}
if (!NumberIsInteger(val))
throw new ERR_INVALID_ARG_TYPE('val', ['integer', 'bigint'], val);
if (val < 1 || val > NumberMAX_SAFE_INTEGER)
throw new ERR_OUT_OF_RANGE('val', 'a safe integer greater than 0', val);
this[kHandle]?.record(val);
}
recordDelta() {
this[kHandle]?.recordDelta();
}
[kClone]() {
const handle = this[kHandle];
return {
data: { handle },
deserializeInfo: 'internal/histogram:InternalRecordableHistogram'
};
}
}
class InternalHistogram extends JSTransferable {
constructor(handle) {
super();
this[kHandle] = handle;
this[kMap] = new SafeMap();
}
}
class InternalRecordableHistogram extends JSTransferable {
constructor(handle) {
super();
this[kHandle] = handle;
this[kMap] = new SafeMap();
}
}
InternalHistogram.prototype.constructor = Histogram;
ObjectSetPrototypeOf(
InternalHistogram.prototype,
Histogram.prototype);
InternalRecordableHistogram.prototype.constructor = RecordableHistogram;
ObjectSetPrototypeOf(
InternalRecordableHistogram.prototype,
RecordableHistogram.prototype);
function createHistogram() {
return new InternalRecordableHistogram(new _Histogram());
}
module.exports = {
Histogram,
RecordableHistogram,
InternalHistogram,
InternalRecordableHistogram,
kDestroy,
kHandle,
createHistogram,
};

View File

@ -16,6 +16,7 @@ const {
ObjectKeys,
SafeSet,
Symbol,
TypeError,
} = primordials;
const {
@ -65,6 +66,7 @@ const {
const {
Histogram,
createHistogram,
kHandle,
} = require('internal/histogram');
@ -86,6 +88,7 @@ const kInsertEntry = Symbol('insert-entry');
const kGetEntries = Symbol('get-entries');
const kIndex = Symbol('index');
const kMarks = Symbol('marks');
const kEnabled = Symbol('kEnabled');
const observers = {};
const observerableTypes = [
@ -630,9 +633,26 @@ function sortedInsert(list, entry) {
}
class ELDHistogram extends Histogram {
constructor(i) { super(i); } // eslint-disable-line no-useless-constructor
enable() { return this[kHandle].enable(); }
disable() { return this[kHandle].disable(); }
constructor(i) {
if (!(i instanceof _ELDHistogram)) {
// eslint-disable-next-line no-restricted-syntax
throw new TypeError('illegal constructor');
}
super(i);
this[kEnabled] = false;
}
enable() {
if (this[kEnabled]) return false;
this[kEnabled] = true;
this[kHandle].start();
return true;
}
disable() {
if (!this[kEnabled]) return false;
this[kEnabled] = false;
this[kHandle].stop();
return true;
}
}
function monitorEventLoopDelay(options = {}) {
@ -651,7 +671,8 @@ function monitorEventLoopDelay(options = {}) {
module.exports = {
performance,
PerformanceObserver,
monitorEventLoopDelay
monitorEventLoopDelay,
createHistogram,
};
ObjectDefineProperty(module.exports, 'constants', {

View File

@ -449,11 +449,12 @@ constexpr size_t kFsStatsBufferLength =
V(filehandlereadwrap_template, v8::ObjectTemplate) \
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
V(handle_wrap_ctor_template, v8::FunctionTemplate) \
V(histogram_instance_template, v8::ObjectTemplate) \
V(histogram_ctor_template, v8::FunctionTemplate) \
V(http2settings_constructor_template, v8::ObjectTemplate) \
V(http2stream_constructor_template, v8::ObjectTemplate) \
V(http2ping_constructor_template, v8::ObjectTemplate) \
V(i18n_converter_template, v8::ObjectTemplate) \
V(intervalhistogram_constructor_template, v8::FunctionTemplate) \
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
V(message_port_constructor_template, v8::FunctionTemplate) \
V(microtask_queue_ctor_template, v8::FunctionTemplate) \

View File

@ -10,30 +10,34 @@
namespace node {
void Histogram::Reset() {
Mutex::ScopedLock lock(mutex_);
hdr_reset(histogram_.get());
}
bool Histogram::Record(int64_t value) {
return hdr_record_value(histogram_.get(), value);
exceeds_ = 0;
prev_ = 0;
}
int64_t Histogram::Min() {
Mutex::ScopedLock lock(mutex_);
return hdr_min(histogram_.get());
}
int64_t Histogram::Max() {
Mutex::ScopedLock lock(mutex_);
return hdr_max(histogram_.get());
}
double Histogram::Mean() {
Mutex::ScopedLock lock(mutex_);
return hdr_mean(histogram_.get());
}
double Histogram::Stddev() {
Mutex::ScopedLock lock(mutex_);
return hdr_stddev(histogram_.get());
}
double Histogram::Percentile(double percentile) {
Mutex::ScopedLock lock(mutex_);
CHECK_GT(percentile, 0);
CHECK_LE(percentile, 100);
return static_cast<double>(
@ -42,6 +46,7 @@ double Histogram::Percentile(double percentile) {
template <typename Iterator>
void Histogram::Percentiles(Iterator&& fn) {
Mutex::ScopedLock lock(mutex_);
hdr_iter iter;
hdr_iter_percentile_init(&iter, histogram_.get(), 1);
while (hdr_iter_next(&iter)) {
@ -51,29 +56,29 @@ void Histogram::Percentiles(Iterator&& fn) {
}
}
bool HistogramBase::RecordDelta() {
bool Histogram::Record(int64_t value) {
Mutex::ScopedLock lock(mutex_);
return hdr_record_value(histogram_.get(), value);
}
uint64_t Histogram::RecordDelta() {
Mutex::ScopedLock lock(mutex_);
uint64_t time = uv_hrtime();
bool ret = true;
uint64_t delta = 0;
if (prev_ > 0) {
int64_t delta = time - prev_;
delta = time - prev_;
if (delta > 0) {
ret = Record(delta);
TraceDelta(delta);
if (!ret) {
if (exceeds_ < 0xFFFFFFFF)
exceeds_++;
TraceExceeds(delta);
}
if (!hdr_record_value(histogram_.get(), delta) && exceeds_ < 0xFFFFFFFF)
exceeds_++;
}
}
prev_ = time;
return ret;
return delta;
}
void HistogramBase::ResetState() {
Reset();
exceeds_ = 0;
prev_ = 0;
size_t Histogram::GetMemorySize() const {
Mutex::ScopedLock lock(mutex_);
return hdr_get_memory_size(histogram_.get());
}
} // namespace node

View File

@ -1,15 +1,17 @@
#include "histogram.h" // NOLINT(build/include_inline)
#include "histogram-inl.h"
#include "base_object-inl.h"
#include "memory_tracker-inl.h"
#include "node_errors.h"
namespace node {
using v8::BigInt;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Local;
using v8::Map;
using v8::Number;
using v8::ObjectTemplate;
using v8::Object;
using v8::String;
using v8::Value;
@ -19,71 +21,88 @@ Histogram::Histogram(int64_t lowest, int64_t highest, int figures) {
histogram_.reset(histogram);
}
void Histogram::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("histogram", GetMemorySize());
}
HistogramImpl::HistogramImpl(int64_t lowest, int64_t highest, int figures)
: histogram_(new Histogram(lowest, highest, figures)) {}
HistogramImpl::HistogramImpl(std::shared_ptr<Histogram> histogram)
: histogram_(std::move(histogram)) {}
HistogramBase::HistogramBase(
Environment* env,
v8::Local<v8::Object> wrap,
Local<Object> wrap,
int64_t lowest,
int64_t highest,
int figures)
: BaseObject(env, wrap),
Histogram(lowest, highest, figures) {
HistogramImpl(lowest, highest, figures) {
MakeWeak();
}
HistogramBase::HistogramBase(
Environment* env,
Local<Object> wrap,
std::shared_ptr<Histogram> histogram)
: BaseObject(env, wrap),
HistogramImpl(std::move(histogram)) {
MakeWeak();
}
void HistogramBase::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackFieldWithSize("histogram", GetMemorySize());
tracker->TrackField("histogram", histogram());
}
void HistogramBase::GetMin(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>(histogram->Min());
double value = static_cast<double>((*histogram)->Min());
args.GetReturnValue().Set(value);
}
void HistogramBase::GetMax(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>(histogram->Max());
double value = static_cast<double>((*histogram)->Max());
args.GetReturnValue().Set(value);
}
void HistogramBase::GetMean(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Mean());
args.GetReturnValue().Set((*histogram)->Mean());
}
void HistogramBase::GetExceeds(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>(histogram->Exceeds());
double value = static_cast<double>((*histogram)->Exceeds());
args.GetReturnValue().Set(value);
}
void HistogramBase::GetStddev(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Stddev());
args.GetReturnValue().Set((*histogram)->Stddev());
}
void HistogramBase::GetPercentile(
const FunctionCallbackInfo<Value>& args) {
void HistogramBase::GetPercentile(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsNumber());
double percentile = args[0].As<Number>()->Value();
args.GetReturnValue().Set(histogram->Percentile(percentile));
args.GetReturnValue().Set((*histogram)->Percentile(percentile));
}
void HistogramBase::GetPercentiles(
const FunctionCallbackInfo<Value>& args) {
void HistogramBase::GetPercentiles(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsMap());
Local<Map> map = args[0].As<Map>();
histogram->Percentiles([map, env](double key, double value) {
(*histogram)->Percentiles([map, env](double key, double value) {
map->Set(
env->context(),
Number::New(env->isolate(), key),
@ -94,48 +113,254 @@ void HistogramBase::GetPercentiles(
void HistogramBase::DoReset(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
histogram->ResetState();
(*histogram)->Reset();
}
BaseObjectPtr<HistogramBase> HistogramBase::New(
void HistogramBase::RecordDelta(const FunctionCallbackInfo<Value>& args) {
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
(*histogram)->RecordDelta();
}
void HistogramBase::Record(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_IMPLIES(!args[0]->IsNumber(), args[0]->IsBigInt());
bool lossless = true;
int64_t value = args[0]->IsBigInt()
? args[0].As<BigInt>()->Int64Value(&lossless)
: static_cast<int64_t>(args[0].As<Number>()->Value());
if (!lossless || value < 1)
return THROW_ERR_OUT_OF_RANGE(env, "value is out of range");
HistogramBase* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
(*histogram)->Record(value);
}
BaseObjectPtr<HistogramBase> HistogramBase::Create(
Environment* env,
int64_t lowest,
int64_t highest,
int figures) {
CHECK_LE(lowest, highest);
CHECK_GT(figures, 0);
v8::Local<v8::Object> obj;
auto tmpl = env->histogram_instance_template();
if (!tmpl->NewInstance(env->context()).ToLocal(&obj))
return {};
Local<Object> obj;
if (!GetConstructorTemplate(env)
->InstanceTemplate()
->NewInstance(env->context()).ToLocal(&obj)) {
return BaseObjectPtr<HistogramBase>();
}
return MakeDetachedBaseObject<HistogramBase>(
return MakeBaseObject<HistogramBase>(
env, obj, lowest, highest, figures);
}
void HistogramBase::Initialize(Environment* env) {
// Guard against multiple initializations
if (!env->histogram_instance_template().IsEmpty())
return;
BaseObjectPtr<HistogramBase> HistogramBase::Create(
Environment* env,
std::shared_ptr<Histogram> histogram) {
Local<Object> obj;
if (!GetConstructorTemplate(env)
->InstanceTemplate()
->NewInstance(env->context()).ToLocal(&obj)) {
return BaseObjectPtr<HistogramBase>();
}
return MakeBaseObject<HistogramBase>(env, obj, std::move(histogram));
}
Local<FunctionTemplate> histogram = FunctionTemplate::New(env->isolate());
Local<String> classname = FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram");
histogram->SetClassName(classname);
void HistogramBase::New(const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
Environment* env = Environment::GetCurrent(args);
new HistogramBase(env, args.This());
}
Local<ObjectTemplate> histogramt =
histogram->InstanceTemplate();
Local<FunctionTemplate> HistogramBase::GetConstructorTemplate(
Environment* env) {
Local<FunctionTemplate> tmpl = env->histogram_ctor_template();
if (tmpl.IsEmpty()) {
tmpl = env->NewFunctionTemplate(New);
Local<String> classname =
FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram");
tmpl->SetClassName(classname);
tmpl->Inherit(BaseObject::GetConstructorTemplate(env));
histogramt->SetInternalFieldCount(1);
env->SetProtoMethod(histogram, "exceeds", HistogramBase::GetExceeds);
env->SetProtoMethod(histogram, "min", HistogramBase::GetMin);
env->SetProtoMethod(histogram, "max", HistogramBase::GetMax);
env->SetProtoMethod(histogram, "mean", HistogramBase::GetMean);
env->SetProtoMethod(histogram, "stddev", HistogramBase::GetStddev);
env->SetProtoMethod(histogram, "percentile", HistogramBase::GetPercentile);
env->SetProtoMethod(histogram, "percentiles", HistogramBase::GetPercentiles);
env->SetProtoMethod(histogram, "reset", HistogramBase::DoReset);
tmpl->InstanceTemplate()->SetInternalFieldCount(
HistogramBase::kInternalFieldCount);
env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds);
env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin);
env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax);
env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean);
env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev);
env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile);
env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles);
env->SetProtoMethod(tmpl, "reset", DoReset);
env->SetProtoMethod(tmpl, "record", Record);
env->SetProtoMethod(tmpl, "recordDelta", RecordDelta);
env->set_histogram_ctor_template(tmpl);
}
return tmpl;
}
env->set_histogram_instance_template(histogramt);
void HistogramBase::Initialize(Environment* env, Local<Object> target) {
env->SetConstructorFunction(target, "Histogram", GetConstructorTemplate(env));
}
BaseObjectPtr<BaseObject> HistogramBase::HistogramTransferData::Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) {
return Create(env, std::move(histogram_));
}
std::unique_ptr<worker::TransferData> HistogramBase::CloneForMessaging() const {
return std::make_unique<HistogramTransferData>(this);
}
void HistogramBase::HistogramTransferData::MemoryInfo(
MemoryTracker* tracker) const {
tracker->TrackField("histogram", histogram_);
}
Local<FunctionTemplate> IntervalHistogram::GetConstructorTemplate(
Environment* env) {
Local<FunctionTemplate> tmpl = env->intervalhistogram_constructor_template();
if (tmpl.IsEmpty()) {
tmpl = FunctionTemplate::New(env->isolate());
tmpl->Inherit(HandleWrap::GetConstructorTemplate(env));
tmpl->InstanceTemplate()->SetInternalFieldCount(
HistogramBase::kInternalFieldCount);
env->SetProtoMethodNoSideEffect(tmpl, "exceeds", GetExceeds);
env->SetProtoMethodNoSideEffect(tmpl, "min", GetMin);
env->SetProtoMethodNoSideEffect(tmpl, "max", GetMax);
env->SetProtoMethodNoSideEffect(tmpl, "mean", GetMean);
env->SetProtoMethodNoSideEffect(tmpl, "stddev", GetStddev);
env->SetProtoMethodNoSideEffect(tmpl, "percentile", GetPercentile);
env->SetProtoMethodNoSideEffect(tmpl, "percentiles", GetPercentiles);
env->SetProtoMethod(tmpl, "reset", DoReset);
env->SetProtoMethod(tmpl, "start", Start);
env->SetProtoMethod(tmpl, "stop", Stop);
env->set_intervalhistogram_constructor_template(tmpl);
}
return tmpl;
}
IntervalHistogram::IntervalHistogram(
Environment* env,
Local<Object> wrap,
AsyncWrap::ProviderType type,
int32_t interval,
int64_t lowest,
int64_t highest,
int figures)
: HandleWrap(
env,
wrap,
reinterpret_cast<uv_handle_t*>(&timer_),
type),
HistogramImpl(lowest, highest, figures),
interval_(interval) {
MakeWeak();
uv_timer_init(env->event_loop(), &timer_);
}
void IntervalHistogram::TimerCB(uv_timer_t* handle) {
IntervalHistogram* histogram =
ContainerOf(&IntervalHistogram::timer_, handle);
histogram->OnInterval();
}
void IntervalHistogram::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("histogram", histogram());
}
void IntervalHistogram::OnStart(StartFlags flags) {
if (enabled_ || IsHandleClosing()) return;
enabled_ = true;
if (flags == StartFlags::RESET)
histogram()->Reset();
uv_timer_start(&timer_, TimerCB, interval_, interval_);
uv_unref(reinterpret_cast<uv_handle_t*>(&timer_));
}
void IntervalHistogram::OnStop() {
if (!enabled_ || IsHandleClosing()) return;
enabled_ = false;
uv_timer_stop(&timer_);
}
void IntervalHistogram::Start(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
histogram->OnStart(args[0]->IsTrue() ? StartFlags::RESET : StartFlags::NONE);
}
void IntervalHistogram::Stop(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
histogram->OnStop();
}
void IntervalHistogram::GetMin(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>((*histogram)->Min());
args.GetReturnValue().Set(value);
}
void IntervalHistogram::GetMax(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>((*histogram)->Max());
args.GetReturnValue().Set(value);
}
void IntervalHistogram::GetMean(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set((*histogram)->Mean());
}
void IntervalHistogram::GetExceeds(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>((*histogram)->Exceeds());
args.GetReturnValue().Set(value);
}
void IntervalHistogram::GetStddev(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set((*histogram)->Stddev());
}
void IntervalHistogram::GetPercentile(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsNumber());
double percentile = args[0].As<Number>()->Value();
args.GetReturnValue().Set((*histogram)->Percentile(percentile));
}
void IntervalHistogram::GetPercentiles(
const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsMap());
Local<Map> map = args[0].As<Map>();
(*histogram)->Percentiles([map, env](double key, double value) {
map->Set(
env->context(),
Number::New(env->isolate(), key),
Number::New(env->isolate(), value)).IsEmpty();
});
}
void IntervalHistogram::DoReset(const FunctionCallbackInfo<Value>& args) {
IntervalHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
(*histogram)->Reset();
}
std::unique_ptr<worker::TransferData>
IntervalHistogram::CloneForMessaging() const {
return std::make_unique<HistogramBase::HistogramTransferData>(histogram());
}
} // namespace node

View File

@ -5,20 +5,25 @@
#include "hdr_histogram.h"
#include "base_object.h"
#include "memory_tracker.h"
#include "node_messaging.h"
#include "util.h"
#include "v8.h"
#include "uv.h"
#include <functional>
#include <limits>
#include <map>
#include <string>
namespace node {
constexpr int kDefaultHistogramFigures = 3;
class Histogram {
class Histogram : public MemoryRetainer {
public:
Histogram(
int64_t lowest = std::numeric_limits<int64_t>::min(),
int64_t lowest = 1,
int64_t highest = std::numeric_limits<int64_t>::max(),
int figures = kDefaultHistogramFigures);
virtual ~Histogram() = default;
@ -30,32 +35,61 @@ class Histogram {
inline double Mean();
inline double Stddev();
inline double Percentile(double percentile);
inline int64_t Exceeds() const { return exceeds_; }
inline uint64_t RecordDelta();
// Iterator is a function type that takes two doubles as argument, one for
// percentile and one for the value at that percentile.
template <typename Iterator>
inline void Percentiles(Iterator&& fn);
size_t GetMemorySize() const {
return hdr_get_memory_size(histogram_.get());
}
inline size_t GetMemorySize() const;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(Histogram)
SET_SELF_SIZE(Histogram)
private:
using HistogramPointer = DeleteFnPtr<hdr_histogram, hdr_close>;
HistogramPointer histogram_;
int64_t exceeds_ = 0;
uint64_t prev_ = 0;
Mutex mutex_;
};
class HistogramBase : public BaseObject, public Histogram {
class HistogramImpl {
public:
virtual ~HistogramBase() = default;
HistogramImpl(int64_t lowest, int64_t highest, int figures);
explicit HistogramImpl(std::shared_ptr<Histogram> histogram);
virtual void TraceDelta(int64_t delta) {}
virtual void TraceExceeds(int64_t delta) {}
Histogram* operator->() { return histogram_.get(); }
inline bool RecordDelta();
inline void ResetState();
protected:
const std::shared_ptr<Histogram>& histogram() const { return histogram_; }
int64_t Exceeds() const { return exceeds_; }
private:
std::shared_ptr<Histogram> histogram_;
};
class HistogramBase : public BaseObject, public HistogramImpl {
public:
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static BaseObjectPtr<HistogramBase> Create(
Environment* env,
int64_t lowest = 1,
int64_t highest = std::numeric_limits<int64_t>::max(),
int figures = kDefaultHistogramFigures);
static BaseObjectPtr<HistogramBase> Create(
Environment* env,
std::shared_ptr<Histogram> histogram);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(HistogramBase)
@ -71,24 +105,103 @@ class HistogramBase : public BaseObject, public Histogram {
static void GetPercentiles(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void DoReset(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Initialize(Environment* env);
static void Record(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RecordDelta(const v8::FunctionCallbackInfo<v8::Value>& args);
static BaseObjectPtr<HistogramBase> New(
HistogramBase(
Environment* env,
int64_t lowest = std::numeric_limits<int64_t>::min(),
v8::Local<v8::Object> wrap,
int64_t lowest = 1,
int64_t highest = std::numeric_limits<int64_t>::max(),
int figures = kDefaultHistogramFigures);
HistogramBase(
Environment* env,
v8::Local<v8::Object> wrap,
int64_t lowest = std::numeric_limits<int64_t>::min(),
std::shared_ptr<Histogram> histogram);
TransferMode GetTransferMode() const override {
return TransferMode::kCloneable;
}
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
class HistogramTransferData : public worker::TransferData {
public:
explicit HistogramTransferData(const HistogramBase* histogram)
: histogram_(histogram->histogram()) {}
explicit HistogramTransferData(std::shared_ptr<Histogram> histogram)
: histogram_(std::move(histogram)) {}
BaseObjectPtr<BaseObject> Deserialize(
Environment* env,
v8::Local<v8::Context> context,
std::unique_ptr<worker::TransferData> self) override;
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(HistogramTransferData)
SET_SELF_SIZE(HistogramTransferData)
private:
std::shared_ptr<Histogram> histogram_;
};
};
class IntervalHistogram : public HandleWrap, public HistogramImpl {
public:
enum class StartFlags {
NONE,
RESET
};
static v8::Local<v8::FunctionTemplate> GetConstructorTemplate(
Environment* env);
static BaseObjectPtr<IntervalHistogram> Create(
Environment* env,
int64_t lowest = 1,
int64_t highest = std::numeric_limits<int64_t>::max(),
int figures = kDefaultHistogramFigures);
virtual void OnInterval() = 0;
void MemoryInfo(MemoryTracker* tracker) const override;
IntervalHistogram(
Environment* env,
v8::Local<v8::Object> wrap,
AsyncWrap::ProviderType type,
int32_t interval,
int64_t lowest = 1,
int64_t highest = std::numeric_limits<int64_t>::max(),
int figures = kDefaultHistogramFigures);
static void GetMin(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMax(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetMean(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetExceeds(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetStddev(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPercentile(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetPercentiles(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void DoReset(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Stop(const v8::FunctionCallbackInfo<v8::Value>& args);
TransferMode GetTransferMode() const override {
return TransferMode::kCloneable;
}
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
private:
int64_t exceeds_ = 0;
uint64_t prev_ = 0;
static void TimerCB(uv_timer_t* handle);
void OnStart(StartFlags flags = StartFlags::RESET);
void OnStop();
bool enabled_ = false;
int32_t interval_ = 0;
uv_timer_t timer_;
};
} // namespace node

View File

@ -26,6 +26,7 @@
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "histogram-inl.h"
#include "node_binding.h"
#include "node_errors.h"
#include "node_internals.h"

View File

@ -2,6 +2,7 @@
#include "allocated_buffer-inl.h"
#include "aliased_struct-inl.h"
#include "debug_utils-inl.h"
#include "histogram-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
#include "node_buffer.h"

View File

@ -1,4 +1,5 @@
#include "aliased_buffer.h"
#include "histogram-inl.h"
#include "memory_tracker-inl.h"
#include "node_internals.h"
#include "node_perf.h"
@ -19,10 +20,10 @@ using v8::FunctionTemplate;
using v8::GCCallbackFlags;
using v8::GCType;
using v8::HandleScope;
using v8::Int32;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::Map;
using v8::MaybeLocal;
using v8::Number;
using v8::Object;
@ -446,156 +447,45 @@ void LoopIdleTime(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(1.0 * idle_time / 1e6);
}
// Event Loop Timing Histogram
namespace {
static void ELDHistogramMin(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>(histogram->Min());
args.GetReturnValue().Set(value);
}
static void ELDHistogramMax(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>(histogram->Max());
args.GetReturnValue().Set(value);
}
static void ELDHistogramMean(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Mean());
}
static void ELDHistogramExceeds(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>(histogram->Exceeds());
args.GetReturnValue().Set(value);
}
static void ELDHistogramStddev(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Stddev());
}
static void ELDHistogramPercentile(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsNumber());
double percentile = args[0].As<Number>()->Value();
args.GetReturnValue().Set(histogram->Percentile(percentile));
}
static void ELDHistogramPercentiles(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsMap());
Local<Map> map = args[0].As<Map>();
histogram->Percentiles([&](double key, double value) {
map->Set(env->context(),
Number::New(env->isolate(), key),
Number::New(env->isolate(), value)).IsEmpty();
});
}
static void ELDHistogramEnable(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Enable());
}
static void ELDHistogramDisable(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Disable());
}
static void ELDHistogramReset(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
histogram->ResetState();
}
static void ELDHistogramNew(const FunctionCallbackInfo<Value>& args) {
void ELDHistogram::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
int32_t resolution = args[0]->IntegerValue(env->context()).FromJust();
int32_t resolution = args[0].As<Int32>()->Value();
CHECK_GT(resolution, 0);
new ELDHistogram(env, args.This(), resolution);
}
} // namespace
void ELDHistogram::Initialize(Environment* env, Local<Object> target) {
Local<FunctionTemplate> tmpl = env->NewFunctionTemplate(New);
tmpl->Inherit(IntervalHistogram::GetConstructorTemplate(env));
tmpl->InstanceTemplate()->SetInternalFieldCount(
ELDHistogram::kInternalFieldCount);
env->SetConstructorFunction(target, "ELDHistogram", tmpl);
}
ELDHistogram::ELDHistogram(
Environment* env,
Local<Object> wrap,
int32_t resolution) : HandleWrap(env,
wrap,
reinterpret_cast<uv_handle_t*>(&timer_),
AsyncWrap::PROVIDER_ELDHISTOGRAM),
Histogram(1, 3.6e12),
resolution_(resolution) {
MakeWeak();
uv_timer_init(env->event_loop(), &timer_);
}
int32_t interval)
: IntervalHistogram(
env,
wrap,
AsyncWrap::PROVIDER_ELDHISTOGRAM,
interval, 1, 3.6e12, 3) {}
void ELDHistogram::DelayIntervalCallback(uv_timer_t* req) {
ELDHistogram* histogram = ContainerOf(&ELDHistogram::timer_, req);
histogram->RecordDelta();
void ELDHistogram::OnInterval() {
uint64_t delta = histogram()->RecordDelta();
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"min", histogram->Min());
"delay", delta);
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"max", histogram->Max());
"min", histogram()->Min());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"mean", histogram->Mean());
"max", histogram()->Max());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"stddev", histogram->Stddev());
}
bool ELDHistogram::RecordDelta() {
uint64_t time = uv_hrtime();
bool ret = true;
if (prev_ > 0) {
int64_t delta = time - prev_;
if (delta > 0) {
ret = Record(delta);
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"delay", delta);
if (!ret) {
if (exceeds_ < 0xFFFFFFFF)
exceeds_++;
ProcessEmitWarning(
env(),
"Event loop delay exceeded 1 hour: %" PRId64 " nanoseconds",
delta);
}
}
}
prev_ = time;
return ret;
}
bool ELDHistogram::Enable() {
if (enabled_ || IsHandleClosing()) return false;
enabled_ = true;
prev_ = 0;
uv_timer_start(&timer_,
DelayIntervalCallback,
resolution_,
resolution_);
uv_unref(reinterpret_cast<uv_handle_t*>(&timer_));
return true;
}
bool ELDHistogram::Disable() {
if (!enabled_ || IsHandleClosing()) return false;
enabled_ = false;
uv_timer_stop(&timer_);
return true;
"mean", histogram()->Mean());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"stddev", histogram()->Stddev());
}
void Initialize(Local<Object> target,
@ -688,24 +578,8 @@ void Initialize(Local<Object> target,
constants,
attr).ToChecked();
Local<String> eldh_classname = FIXED_ONE_BYTE_STRING(isolate, "ELDHistogram");
Local<FunctionTemplate> eldh =
env->NewFunctionTemplate(ELDHistogramNew);
eldh->SetClassName(eldh_classname);
eldh->InstanceTemplate()->SetInternalFieldCount(
ELDHistogram::kInternalFieldCount);
eldh->Inherit(BaseObject::GetConstructorTemplate(env));
env->SetProtoMethod(eldh, "exceeds", ELDHistogramExceeds);
env->SetProtoMethod(eldh, "min", ELDHistogramMin);
env->SetProtoMethod(eldh, "max", ELDHistogramMax);
env->SetProtoMethod(eldh, "mean", ELDHistogramMean);
env->SetProtoMethod(eldh, "stddev", ELDHistogramStddev);
env->SetProtoMethod(eldh, "percentile", ELDHistogramPercentile);
env->SetProtoMethod(eldh, "percentiles", ELDHistogramPercentiles);
env->SetProtoMethod(eldh, "enable", ELDHistogramEnable);
env->SetProtoMethod(eldh, "disable", ELDHistogramDisable);
env->SetProtoMethod(eldh, "reset", ELDHistogramReset);
env->SetConstructorFunction(target, eldh_classname, eldh);
HistogramBase::Initialize(env, target);
ELDHistogram::Initialize(env, target);
}
} // namespace performance

View File

@ -6,7 +6,7 @@
#include "node.h"
#include "node_perf_common.h"
#include "base_object-inl.h"
#include "histogram-inl.h"
#include "histogram.h"
#include "v8.h"
#include "uv.h"
@ -140,37 +140,20 @@ class GCPerformanceEntry : public PerformanceEntry {
PerformanceGCFlags gcflags_;
};
class ELDHistogram : public HandleWrap, public Histogram {
class ELDHistogram : public IntervalHistogram {
public:
ELDHistogram(Environment* env,
v8::Local<v8::Object> wrap,
int32_t resolution);
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
bool RecordDelta();
bool Enable();
bool Disable();
void ResetState() {
Reset();
exceeds_ = 0;
prev_ = 0;
}
int64_t Exceeds() const { return exceeds_; }
ELDHistogram(
Environment* env,
v8::Local<v8::Object> wrap,
int32_t interval);
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackFieldWithSize("histogram", GetMemorySize());
}
void OnInterval() override;
SET_MEMORY_INFO_NAME(ELDHistogram)
SET_SELF_SIZE(ELDHistogram)
private:
static void DelayIntervalCallback(uv_timer_t* req);
bool enabled_ = false;
int32_t resolution_ = 0;
int64_t exceeds_ = 0;
uint64_t prev_ = 0;
uv_timer_t timer_;
};
} // namespace performance

View File

@ -1,5 +1,6 @@
#include "node_worker.h"
#include "debug_utils-inl.h"
#include "histogram-inl.h"
#include "memory_tracker-inl.h"
#include "node_errors.h"
#include "node_external_reference.h"

View File

@ -0,0 +1,69 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const {
createHistogram,
monitorEventLoopDelay,
} = require('perf_hooks');
{
const h = createHistogram();
assert.strictEqual(h.min, 9223372036854776000);
assert.strictEqual(h.max, 0);
assert.strictEqual(h.exceeds, 0);
assert(Number.isNaN(h.mean));
assert(Number.isNaN(h.stddev));
h.record(1);
[false, '', {}, undefined, null].forEach((i) => {
assert.throws(() => h.record(i), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
assert.throws(() => h.record(0, Number.MAX_SAFE_INTEGER + 1), {
code: 'ERR_OUT_OF_RANGE'
});
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 1);
assert.strictEqual(h.exceeds, 0);
assert.strictEqual(h.mean, 1);
assert.strictEqual(h.stddev, 0);
assert.strictEqual(h.percentile(1), 1);
assert.strictEqual(h.percentile(100), 1);
const mc = new MessageChannel();
mc.port1.onmessage = common.mustCall(({ data }) => {
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 1);
assert.strictEqual(h.exceeds, 0);
assert.strictEqual(h.mean, 1);
assert.strictEqual(h.stddev, 0);
data.record(2n);
data.recordDelta();
assert.strictEqual(h.max, 2);
mc.port1.close();
});
mc.port2.postMessage(h);
}
{
const e = monitorEventLoopDelay();
e.enable();
const mc = new MessageChannel();
mc.port1.onmessage = common.mustCall(({ data }) => {
assert(typeof data.min, 'number');
assert(data.min > 0);
assert.strictEqual(data.disable, undefined);
assert.strictEqual(data.enable, undefined);
mc.port1.close();
});
setTimeout(() => mc.port2.postMessage(e), 100);
}

View File

@ -182,6 +182,10 @@ const customTypesMap = {
'os.constants.dlopen': 'os.html#os_dlopen_constants',
'Histogram': 'perf_hooks.html#perf_hooks_class_histogram',
'IntervalHistogram':
'perf_hooks.html#perf_hooks_class_intervalhistogram_extends_histogram',
'RecordableHistogram':
'perf_hooks.html#perf_hooks_class_recordablehistogram_extends_histogram',
'PerformanceEntry': 'perf_hooks.html#perf_hooks_class_performanceentry',
'PerformanceNodeTiming':
'perf_hooks.html#perf_hooks_class_performancenodetiming',