mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
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>
This commit is contained in:
parent
0fac393d26
commit
eb2fe5ff90
94
lib/internal/histogram.js
Normal file
94
lib/internal/histogram.js
Normal file
@ -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 = {}) {
|
||||
|
2
node.gyp
2
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',
|
||||
|
@ -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
|
||||
|
141
src/histogram.cc
Normal file
141
src/histogram.cc
Normal file
@ -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
|
@ -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
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user