summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames M Snell <jasnell@gmail.com>2020-02-27 13:14:38 -0800
committerJames M Snell <jasnell@gmail.com>2020-03-02 11:01:00 -0800
commiteb2fe5ff90d68520b148fdc86362c07561c1c635 (patch)
tree6ad5259a9423f6de5d414abe938576627b9ea432
parent0fac393d263fc7e2f4f054c9d4aab0c1c3cf00c8 (diff)
downloadnode-new-eb2fe5ff90d68520b148fdc86362c07561c1c635.tar.gz
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: https://github.com/nodejs/node/pull/31988 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
-rw-r--r--lib/internal/histogram.js94
-rw-r--r--lib/perf_hooks.js49
-rw-r--r--node.gyp2
-rw-r--r--src/env.h1
-rw-r--r--src/histogram-inl.h70
-rw-r--r--src/histogram.cc141
-rw-r--r--src/histogram.h70
-rw-r--r--src/node_perf.h2
8 files changed, 355 insertions, 74 deletions
diff --git a/lib/internal/histogram.js b/lib/internal/histogram.js
new file mode 100644
index 0000000000..6deb8314a4
--- /dev/null
+++ b/lib/internal/histogram.js
@@ -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,
+};
diff --git a/lib/perf_hooks.js b/lib/perf_hooks.js
index 267b4577ff..a141705728 100644
--- a/lib/perf_hooks.js
+++ b/lib/perf_hooks.js
@@ -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 = {}) {
diff --git a/node.gyp b/node.gyp
index 9c632d3838..b941eff565 100644
--- a/node.gyp
+++ b/node.gyp
@@ -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',
@@ -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',
diff --git a/src/env.h b/src/env.h
index 3b577e4030..f02c8e9775 100644
--- a/src/env.h
+++ b/src/env.h
@@ -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) \
diff --git a/src/histogram-inl.h b/src/histogram-inl.h
index 3135041f73..58911dae8f 100644
--- a/src/histogram-inl.h
+++ b/src/histogram-inl.h
@@ -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
diff --git a/src/histogram.cc b/src/histogram.cc
new file mode 100644
index 0000000000..8d1eb77b1b
--- /dev/null
+++ b/src/histogram.cc
@@ -0,0 +1,141 @@
+#include "histogram.h" // NOLINT(build/include_inline)
+#include "histogram-inl.h"
+#include "memory_tracker-inl.h"
+
+namespace node {
+
+using v8::FunctionCallbackInfo;
+using v8::FunctionTemplate;
+using v8::Local;
+using v8::Map;
+using v8::Number;
+using v8::ObjectTemplate;
+using v8::String;
+using v8::Value;
+
+Histogram::Histogram(int64_t lowest, int64_t highest, int figures) {
+ hdr_histogram* histogram;
+ CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram));
+ histogram_.reset(histogram);
+}
+
+HistogramBase::HistogramBase(
+ Environment* env,
+ v8::Local<v8::Object> wrap,
+ int64_t lowest,
+ int64_t highest,
+ int figures)
+ : BaseObject(env, wrap),
+ Histogram(lowest, highest, figures) {
+ MakeWeak();
+}
+
+void HistogramBase::MemoryInfo(MemoryTracker* tracker) const {
+ tracker->TrackFieldWithSize("histogram", GetMemorySize());
+}
+
+void HistogramBase::GetMin(const FunctionCallbackInfo<Value>& args) {
+ HistogramBase* histogram;
+ ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
+ 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());
+ 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());
+}
+
+void HistogramBase::GetExceeds(const FunctionCallbackInfo<Value>& args) {
+ HistogramBase* histogram;
+ ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
+ 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());
+}
+
+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));
+}
+
+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) {
+ map->Set(
+ env->context(),
+ Number::New(env->isolate(), key),
+ Number::New(env->isolate(), value)).IsEmpty();
+ });
+}
+
+void HistogramBase::DoReset(const FunctionCallbackInfo<Value>& args) {
+ HistogramBase* histogram;
+ ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
+ histogram->ResetState();
+}
+
+BaseObjectPtr<HistogramBase> HistogramBase::New(
+ 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 {};
+
+ return MakeDetachedBaseObject<HistogramBase>(
+ env, obj, lowest, highest, figures);
+}
+
+void HistogramBase::Initialize(Environment* env) {
+ // Guard against multiple initializations
+ if (!env->histogram_instance_template().IsEmpty())
+ return;
+
+ Local<FunctionTemplate> histogram = FunctionTemplate::New(env->isolate());
+ Local<String> classname = FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram");
+ histogram->SetClassName(classname);
+
+ Local<ObjectTemplate> histogramt =
+ histogram->InstanceTemplate();
+
+ 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);
+
+ env->set_histogram_instance_template(histogramt);
+}
+
+} // namespace node
diff --git a/src/histogram.h b/src/histogram.h
index eb94af5da2..e92c31c472 100644
--- a/src/histogram.h
+++ b/src/histogram.h
@@ -4,15 +4,24 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "hdr_histogram.h"
+#include "base_object.h"
+#include "util.h"
+
#include <functional>
+#include <limits>
#include <map>
namespace node {
+constexpr int kDefaultHistogramFigures = 3;
+
class Histogram {
public:
- inline Histogram(int64_t lowest, int64_t highest, int figures = 3);
- inline virtual ~Histogram();
+ Histogram(
+ int64_t lowest = std::numeric_limits<int64_t>::min(),
+ int64_t highest = std::numeric_limits<int64_t>::max(),
+ int figures = kDefaultHistogramFigures);
+ virtual ~Histogram() = default;
inline bool Record(int64_t value);
inline void Reset();
@@ -21,14 +30,65 @@ class Histogram {
inline double Mean();
inline double Stddev();
inline double Percentile(double percentile);
- inline void Percentiles(std::function<void(double, double)> fn);
+
+ // 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_);
+ return hdr_get_memory_size(histogram_.get());
}
private:
- hdr_histogram* histogram_;
+ using HistogramPointer = DeleteFnPtr<hdr_histogram, hdr_close>;
+ HistogramPointer histogram_;
+};
+
+class HistogramBase : public BaseObject, public Histogram {
+ public:
+ virtual ~HistogramBase() = default;
+
+ virtual void TraceDelta(int64_t delta) {}
+ virtual void TraceExceeds(int64_t delta) {}
+
+ inline bool RecordDelta();
+ inline void ResetState();
+
+ int64_t Exceeds() const { return exceeds_; }
+
+ void MemoryInfo(MemoryTracker* tracker) const override;
+ SET_MEMORY_INFO_NAME(HistogramBase)
+ SET_SELF_SIZE(HistogramBase)
+
+ 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 Initialize(Environment* env);
+
+ static BaseObjectPtr<HistogramBase> New(
+ Environment* env,
+ int64_t lowest = std::numeric_limits<int64_t>::min(),
+ 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(),
+ int64_t highest = std::numeric_limits<int64_t>::max(),
+ int figures = kDefaultHistogramFigures);
+
+ private:
+ int64_t exceeds_ = 0;
+ uint64_t prev_ = 0;
};
} // namespace node
diff --git a/src/node_perf.h b/src/node_perf.h
index 4f5ca93f22..ac65533a77 100644
--- a/src/node_perf.h
+++ b/src/node_perf.h
@@ -161,7 +161,7 @@ class ELDHistogram : public HandleWrap, public Histogram {
exceeds_ = 0;
prev_ = 0;
}
- int64_t Exceeds() { return exceeds_; }
+ int64_t Exceeds() const { return exceeds_; }
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackFieldWithSize("histogram", GetMemorySize());