Skip to content
Permalink
Browse files

perf,src: add HistogramBase and internal/histogram.js

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 authored and MylesBorins committed Feb 27, 2020
1 parent 4d5318c commit 6b44df24153aa9a6694c24a2fae2f30e0eff3129
Showing with 355 additions and 74 deletions.
  1. +94 −0 lib/internal/histogram.js
  2. +6 −43 lib/perf_hooks.js
  3. +2 −0 node.gyp
  4. +1 −0 src/env.h
  5. +45 −25 src/histogram-inl.h
  6. +141 −0 src/histogram.cc
  7. +65 −5 src/histogram.h
  8. +1 −1 src/node_perf.h
@@ -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,
};
@@ -3,7 +3,6 @@
const {
ArrayIsArray,
Boolean,
Map,
NumberIsSafeInteger,
ObjectDefineProperties,
ObjectDefineProperty,
@@ -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');
@@ -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 = {}) {
@@ -140,6 +140,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',
@@ -533,6 +534,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',
@@ -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) \
@@ -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

0 comments on commit 6b44df2

Please sign in to comment.
You can’t perform that action at this time.