mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
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:
parent
ce4798b555
commit
4a19cc8947
@ -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
|
||||
|
@ -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');
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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', {
|
||||
|
@ -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) \
|
||||
|
@ -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
|
||||
|
313
src/histogram.cc
313
src/histogram.cc
@ -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
|
||||
|
149
src/histogram.h
149
src/histogram.h
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
182
src/node_perf.cc
182
src/node_perf.cc
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
69
test/parallel/test-perf-hooks-histogram.js
Normal file
69
test/parallel/test-perf-hooks-histogram.js
Normal 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);
|
||||
}
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user