Skip to content
Closed
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
18 changes: 18 additions & 0 deletions doc/api/perf_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,23 @@ console.log(h.percentile(50));
console.log(h.percentile(99));
```

## `perf_hooks.monitorEventLoopIdleness([options])`
<!-- YAML
added: REPLACEME
-->

* `options` {Object}
* `resolution` {number} The sampling rate in milliseconds. Must be greater
than zero. **Default:** `10`.
* Returns: {Histogram}

Creates a `Histogram` object that samples and reports the event loop idleness
over time (time difference between IO poll start and poll end). The idleness
will be reported in nanoseconds.

The behavior of `monitorEventLoopIdleness` is identical to
[`monitorEventLoopDelay`][] above.

### Class: `Histogram`
<!-- YAML
added: v11.10.0
Expand Down Expand Up @@ -643,3 +660,4 @@ require('some-module');
[`timeOrigin`]: https://w3c.github.io/hr-time/#dom-performance-timeorigin
[Async Hooks]: async_hooks.html
[W3C Performance Timeline]: https://w3c.github.io/performance-timeline/
[`monitorEventLoopDelay`]: #perf_hooks_perf_hooks_monitoreventloopdelay_options
94 changes: 94 additions & 0 deletions lib/internal/histogram.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
'use strict';

const {
customInspectSymbol: kInspect,
} = require('internal/util');

const { format } = require('util');
const { Map, Symbol } = primordials;

const {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
} = require('internal/errors').codes;

const kDestroy = Symbol('kDestroy');
const kHandle = Symbol('kHandle');

// 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 {
#handle = undefined;
#map = new Map();

constructor(internal) {
this.#handle = internal;
}

[kInspect]() {
const obj = {
min: this.min,
max: this.max,
mean: this.mean,
exceeds: this.exceeds,
stddev: this.stddev,
percentiles: this.percentiles,
};
return `Histogram ${format(obj)}`;
}

get min() {
return this.#handle ? this.#handle.min() : undefined;
}

get max() {
return this.#handle ? this.#handle.max() : undefined;
}

get mean() {
return this.#handle ? this.#handle.mean() : undefined;
}

get exceeds() {
return this.#handle ? this.#handle.exceeds() : undefined;
}

get stddev() {
return this.#handle ? this.#handle.stddev() : undefined;
}

percentile(percentile) {
if (typeof percentile !== 'number')
throw new ERR_INVALID_ARG_TYPE('percentile', 'number', percentile);

if (percentile <= 0 || percentile > 100)
throw new ERR_INVALID_ARG_VALUE.RangeError('percentile', percentile);

return this.#handle ? this.#handle.percentile(percentile) : undefined;
}

get percentiles() {
this.#map.clear();
if (this.#handle)
this.#handle.percentiles(this.#map);
return this.#map;
}

reset() {
if (this.#handle)
this.#handle.reset();
}

[kDestroy]() {
this.#handle = undefined;
}

get [kHandle]() { return this.#handle; }
}

module.exports = {
Histogram,
kDestroy,
kHandle,
};
66 changes: 23 additions & 43 deletions lib/perf_hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const {
ArrayIsArray,
Boolean,
Map,
NumberIsSafeInteger,
ObjectDefineProperties,
ObjectDefineProperty,
Expand All @@ -13,7 +12,8 @@ const {
} = primordials;

const {
ELDHistogram: _ELDHistogram,
ELDHistogram,
ELIHistogram,
PerformanceEntry,
mark: _mark,
clearMark: _clearMark,
Expand Down Expand Up @@ -52,16 +52,18 @@ const kInspect = require('internal/util').customInspectSymbol;

const {
ERR_INVALID_CALLBACK,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_OPT_VALUE,
ERR_VALID_PERFORMANCE_ENTRY_TYPE,
ERR_INVALID_PERFORMANCE_MARK
} = require('internal/errors').codes;

const {
Histogram,
kHandle,
} = require('internal/histogram');

const { setImmediate } = require('timers');
const kHandle = Symbol('handle');
const kMap = Symbol('map');
const kCallback = Symbol('callback');
const kTypes = Symbol('types');
const kEntries = Symbol('entries');
Expand Down Expand Up @@ -557,50 +559,27 @@ function sortedInsert(list, entry) {
list.splice(location, 0, entry);
}

class ELDHistogram {
constructor(handle) {
this[kHandle] = handle;
this[kMap] = new Map();
}

reset() { this[kHandle].reset(); }
class EventLoopHistogram extends Histogram {
enable() { return this[kHandle].enable(); }
disable() { return this[kHandle].disable(); }
}

get exceeds() { return this[kHandle].exceeds(); }
get min() { return this[kHandle].min(); }
get max() { return this[kHandle].max(); }
get mean() { return this[kHandle].mean(); }
get stddev() { return this[kHandle].stddev(); }
percentile(percentile) {
if (typeof percentile !== 'number') {
throw new ERR_INVALID_ARG_TYPE('percentile', 'number', percentile);
}
if (percentile <= 0 || percentile > 100) {
throw new ERR_INVALID_ARG_VALUE.RangeError('percentile',
percentile);
}
return this[kHandle].percentile(percentile);
function monitorEventLoopDelay(options = {}) {
if (typeof options !== 'object' || options === null) {
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
}
get percentiles() {
this[kMap].clear();
this[kHandle].percentiles(this[kMap]);
return this[kMap];
const { resolution = 10 } = options;
if (typeof resolution !== 'number') {
throw new ERR_INVALID_ARG_TYPE('options.resolution',
'number', resolution);
}

[kInspect]() {
return {
min: this.min,
max: this.max,
mean: this.mean,
stddev: this.stddev,
percentiles: this.percentiles,
exceeds: this.exceeds
};
if (resolution <= 0 || !NumberIsSafeInteger(resolution)) {
throw new ERR_INVALID_OPT_VALUE.RangeError('resolution', resolution);
}
return new EventLoopHistogram(new ELDHistogram(resolution));
}

function monitorEventLoopDelay(options = {}) {
function monitorEventLoopIdleness(options = {}) {
if (typeof options !== 'object' || options === null) {
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
}
Expand All @@ -612,13 +591,14 @@ function monitorEventLoopDelay(options = {}) {
if (resolution <= 0 || !NumberIsSafeInteger(resolution)) {
throw new ERR_INVALID_OPT_VALUE.RangeError('resolution', resolution);
}
return new ELDHistogram(new _ELDHistogram(resolution));
return new EventLoopHistogram(new ELIHistogram(resolution));
}

module.exports = {
performance,
PerformanceObserver,
monitorEventLoopDelay
monitorEventLoopDelay,
monitorEventLoopIdleness
};

ObjectDefineProperty(module.exports, 'constants', {
Expand Down
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
'lib/internal/fs/watchers.js',
'lib/internal/http.js',
'lib/internal/heap_utils.js',
'lib/internal/histogram.js',
'lib/internal/idna.js',
'lib/internal/inspector_async_hook.js',
'lib/internal/js_stream_socket.js',
Expand Down Expand Up @@ -534,6 +535,7 @@
'src/fs_event_wrap.cc',
'src/handle_wrap.cc',
'src/heap_utils.cc',
'src/histogram.cc',
'src/js_native_api.h',
'src/js_native_api_types.h',
'src/js_native_api_v8.cc',
Expand Down
1 change: 1 addition & 0 deletions src/async_wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace node {
V(DIRHANDLE) \
V(DNSCHANNEL) \
V(ELDHISTOGRAM) \
V(ELIHISTOGRAM) \
V(FILEHANDLE) \
V(FILEHANDLECLOSEREQ) \
V(FSEVENTWRAP) \
Expand Down
1 change: 1 addition & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ constexpr size_t kFsStatsBufferLength =
V(filehandlereadwrap_template, v8::ObjectTemplate) \
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
V(handle_wrap_ctor_template, v8::FunctionTemplate) \
V(histogram_ctor_template, v8::ObjectTemplate) \
V(http2settings_constructor_template, v8::ObjectTemplate) \
V(http2stream_constructor_template, v8::ObjectTemplate) \
V(http2ping_constructor_template, v8::ObjectTemplate) \
Expand Down
70 changes: 45 additions & 25 deletions src/histogram-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,78 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "histogram.h"
#include "base_object-inl.h"
#include "node_internals.h"

namespace node {

inline Histogram::Histogram(int64_t lowest, int64_t highest, int figures) {
CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram_));
void Histogram::Reset() {
hdr_reset(histogram_.get());
}

inline Histogram::~Histogram() {
hdr_close(histogram_);
bool Histogram::Record(int64_t value) {
return hdr_record_value(histogram_.get(), value);
}

inline void Histogram::Reset() {
hdr_reset(histogram_);
int64_t Histogram::Min() {
return hdr_min(histogram_.get());
}

inline bool Histogram::Record(int64_t value) {
return hdr_record_value(histogram_, value);
int64_t Histogram::Max() {
return hdr_max(histogram_.get());
}

inline int64_t Histogram::Min() {
return hdr_min(histogram_);
double Histogram::Mean() {
return hdr_mean(histogram_.get());
}

inline int64_t Histogram::Max() {
return hdr_max(histogram_);
double Histogram::Stddev() {
return hdr_stddev(histogram_.get());
}

inline double Histogram::Mean() {
return hdr_mean(histogram_);
}

inline double Histogram::Stddev() {
return hdr_stddev(histogram_);
}

inline double Histogram::Percentile(double percentile) {
double Histogram::Percentile(double percentile) {
CHECK_GT(percentile, 0);
CHECK_LE(percentile, 100);
return hdr_value_at_percentile(histogram_, percentile);
return static_cast<double>(
hdr_value_at_percentile(histogram_.get(), percentile));
}

inline void Histogram::Percentiles(std::function<void(double, double)> fn) {
template <typename Iterator>
void Histogram::Percentiles(Iterator&& fn) {
hdr_iter iter;
hdr_iter_percentile_init(&iter, histogram_, 1);
hdr_iter_percentile_init(&iter, histogram_.get(), 1);
while (hdr_iter_next(&iter)) {
double key = iter.specifics.percentiles.percentile;
double value = iter.value;
double value = static_cast<double>(iter.value);
fn(key, value);
}
}

bool HistogramBase::RecordDelta() {
uint64_t time = uv_hrtime();
bool ret = true;
if (prev_ > 0) {
int64_t delta = time - prev_;
if (delta > 0) {
ret = Record(delta);
TraceDelta(delta);
if (!ret) {
if (exceeds_ < 0xFFFFFFFF)
exceeds_++;
TraceExceeds(delta);
}
}
}
prev_ = time;
return ret;
}

void HistogramBase::ResetState() {
Reset();
exceeds_ = 0;
prev_ = 0;
}

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Expand Down
Loading