Skip to content

Commit

Permalink
perf,src: add HistogramBase and internal/histogram.js
Browse files Browse the repository at this point in the history
Separating this out from the QUIC PR to allow it to be separately
reviewed. The QUIC implementation makes use of the hdr_histogram
for dynamic performance monitoring. This introduces a BaseObject
class that allows the internal histograms to be accessed on the
JavaScript side and adds a generic Histogram class that will be
used by both QUIC and perf_hooks (for the event loop delay
monitoring).

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

PR-URL: #31988
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
  • Loading branch information
jasnell committed Mar 2, 2020
1 parent 0fac393 commit eb2fe5f
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 74 deletions.
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,
};
49 changes: 6 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 Down Expand Up @@ -52,16 +51,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,47 +558,9 @@ 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 ELDHistogram 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);
}
get percentiles() {
this[kMap].clear();
this[kHandle].percentiles(this[kMap]);
return this[kMap];
}

[kInspect]() {
return {
min: this.min,
max: this.max,
mean: this.mean,
stddev: this.stddev,
percentiles: this.percentiles,
exceeds: this.exceeds
};
}
}

function monitorEventLoopDelay(options = {}) {
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/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_instance_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

0 comments on commit eb2fe5f

Please sign in to comment.