Skip to content

Commit 7d66cea

Browse files
jasnelltargos
authored andcommittedApr 20, 2020
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>
1 parent 6e68d98 commit 7d66cea

File tree

8 files changed

+355
-74
lines changed

8 files changed

+355
-74
lines changed
 

‎lib/internal/histogram.js

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
'use strict';
2+
3+
const {
4+
customInspectSymbol: kInspect,
5+
} = require('internal/util');
6+
7+
const { format } = require('util');
8+
const { Map, Symbol } = primordials;
9+
10+
const {
11+
ERR_INVALID_ARG_TYPE,
12+
ERR_INVALID_ARG_VALUE,
13+
} = require('internal/errors').codes;
14+
15+
const kDestroy = Symbol('kDestroy');
16+
const kHandle = Symbol('kHandle');
17+
18+
// Histograms are created internally by Node.js and used to
19+
// record various metrics. This Histogram class provides a
20+
// generally read-only view of the internal histogram.
21+
class Histogram {
22+
#handle = undefined;
23+
#map = new Map();
24+
25+
constructor(internal) {
26+
this.#handle = internal;
27+
}
28+
29+
[kInspect]() {
30+
const obj = {
31+
min: this.min,
32+
max: this.max,
33+
mean: this.mean,
34+
exceeds: this.exceeds,
35+
stddev: this.stddev,
36+
percentiles: this.percentiles,
37+
};
38+
return `Histogram ${format(obj)}`;
39+
}
40+
41+
get min() {
42+
return this.#handle ? this.#handle.min() : undefined;
43+
}
44+
45+
get max() {
46+
return this.#handle ? this.#handle.max() : undefined;
47+
}
48+
49+
get mean() {
50+
return this.#handle ? this.#handle.mean() : undefined;
51+
}
52+
53+
get exceeds() {
54+
return this.#handle ? this.#handle.exceeds() : undefined;
55+
}
56+
57+
get stddev() {
58+
return this.#handle ? this.#handle.stddev() : undefined;
59+
}
60+
61+
percentile(percentile) {
62+
if (typeof percentile !== 'number')
63+
throw new ERR_INVALID_ARG_TYPE('percentile', 'number', percentile);
64+
65+
if (percentile <= 0 || percentile > 100)
66+
throw new ERR_INVALID_ARG_VALUE.RangeError('percentile', percentile);
67+
68+
return this.#handle ? this.#handle.percentile(percentile) : undefined;
69+
}
70+
71+
get percentiles() {
72+
this.#map.clear();
73+
if (this.#handle)
74+
this.#handle.percentiles(this.#map);
75+
return this.#map;
76+
}
77+
78+
reset() {
79+
if (this.#handle)
80+
this.#handle.reset();
81+
}
82+
83+
[kDestroy]() {
84+
this.#handle = undefined;
85+
}
86+
87+
get [kHandle]() { return this.#handle; }
88+
}
89+
90+
module.exports = {
91+
Histogram,
92+
kDestroy,
93+
kHandle,
94+
};

‎lib/perf_hooks.js

+6-43
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
const {
44
ArrayIsArray,
55
Boolean,
6-
Map,
76
NumberIsSafeInteger,
87
ObjectDefineProperties,
98
ObjectDefineProperty,
@@ -52,16 +51,18 @@ const kInspect = require('internal/util').customInspectSymbol;
5251

5352
const {
5453
ERR_INVALID_CALLBACK,
55-
ERR_INVALID_ARG_VALUE,
5654
ERR_INVALID_ARG_TYPE,
5755
ERR_INVALID_OPT_VALUE,
5856
ERR_VALID_PERFORMANCE_ENTRY_TYPE,
5957
ERR_INVALID_PERFORMANCE_MARK
6058
} = require('internal/errors').codes;
6159

60+
const {
61+
Histogram,
62+
kHandle,
63+
} = require('internal/histogram');
64+
6265
const { setImmediate } = require('timers');
63-
const kHandle = Symbol('handle');
64-
const kMap = Symbol('map');
6566
const kCallback = Symbol('callback');
6667
const kTypes = Symbol('types');
6768
const kEntries = Symbol('entries');
@@ -557,47 +558,9 @@ function sortedInsert(list, entry) {
557558
list.splice(location, 0, entry);
558559
}
559560

560-
class ELDHistogram {
561-
constructor(handle) {
562-
this[kHandle] = handle;
563-
this[kMap] = new Map();
564-
}
565-
566-
reset() { this[kHandle].reset(); }
561+
class ELDHistogram extends Histogram {
567562
enable() { return this[kHandle].enable(); }
568563
disable() { return this[kHandle].disable(); }
569-
570-
get exceeds() { return this[kHandle].exceeds(); }
571-
get min() { return this[kHandle].min(); }
572-
get max() { return this[kHandle].max(); }
573-
get mean() { return this[kHandle].mean(); }
574-
get stddev() { return this[kHandle].stddev(); }
575-
percentile(percentile) {
576-
if (typeof percentile !== 'number') {
577-
throw new ERR_INVALID_ARG_TYPE('percentile', 'number', percentile);
578-
}
579-
if (percentile <= 0 || percentile > 100) {
580-
throw new ERR_INVALID_ARG_VALUE.RangeError('percentile',
581-
percentile);
582-
}
583-
return this[kHandle].percentile(percentile);
584-
}
585-
get percentiles() {
586-
this[kMap].clear();
587-
this[kHandle].percentiles(this[kMap]);
588-
return this[kMap];
589-
}
590-
591-
[kInspect]() {
592-
return {
593-
min: this.min,
594-
max: this.max,
595-
mean: this.mean,
596-
stddev: this.stddev,
597-
percentiles: this.percentiles,
598-
exceeds: this.exceeds
599-
};
600-
}
601564
}
602565

603566
function monitorEventLoopDelay(options = {}) {

‎node.gyp

+2
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
'lib/internal/fs/utils.js',
138138
'lib/internal/fs/watchers.js',
139139
'lib/internal/http.js',
140+
'lib/internal/histogram.js',
140141
'lib/internal/idna.js',
141142
'lib/internal/inspector_async_hook.js',
142143
'lib/internal/js_stream_socket.js',
@@ -529,6 +530,7 @@
529530
'src/fs_event_wrap.cc',
530531
'src/handle_wrap.cc',
531532
'src/heap_utils.cc',
533+
'src/histogram.cc',
532534
'src/js_native_api.h',
533535
'src/js_native_api_types.h',
534536
'src/js_native_api_v8.cc',

‎src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ constexpr size_t kFsStatsBufferLength =
409409
V(filehandlereadwrap_template, v8::ObjectTemplate) \
410410
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
411411
V(handle_wrap_ctor_template, v8::FunctionTemplate) \
412+
V(histogram_instance_template, v8::ObjectTemplate) \
412413
V(http2settings_constructor_template, v8::ObjectTemplate) \
413414
V(http2stream_constructor_template, v8::ObjectTemplate) \
414415
V(http2ping_constructor_template, v8::ObjectTemplate) \

‎src/histogram-inl.h

+45-25
Original file line numberDiff line numberDiff line change
@@ -4,58 +4,78 @@
44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

66
#include "histogram.h"
7+
#include "base_object-inl.h"
78
#include "node_internals.h"
89

910
namespace node {
1011

11-
inline Histogram::Histogram(int64_t lowest, int64_t highest, int figures) {
12-
CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram_));
12+
void Histogram::Reset() {
13+
hdr_reset(histogram_.get());
1314
}
1415

15-
inline Histogram::~Histogram() {
16-
hdr_close(histogram_);
16+
bool Histogram::Record(int64_t value) {
17+
return hdr_record_value(histogram_.get(), value);
1718
}
1819

19-
inline void Histogram::Reset() {
20-
hdr_reset(histogram_);
20+
int64_t Histogram::Min() {
21+
return hdr_min(histogram_.get());
2122
}
2223

23-
inline bool Histogram::Record(int64_t value) {
24-
return hdr_record_value(histogram_, value);
24+
int64_t Histogram::Max() {
25+
return hdr_max(histogram_.get());
2526
}
2627

27-
inline int64_t Histogram::Min() {
28-
return hdr_min(histogram_);
28+
double Histogram::Mean() {
29+
return hdr_mean(histogram_.get());
2930
}
3031

31-
inline int64_t Histogram::Max() {
32-
return hdr_max(histogram_);
32+
double Histogram::Stddev() {
33+
return hdr_stddev(histogram_.get());
3334
}
3435

35-
inline double Histogram::Mean() {
36-
return hdr_mean(histogram_);
37-
}
38-
39-
inline double Histogram::Stddev() {
40-
return hdr_stddev(histogram_);
41-
}
42-
43-
inline double Histogram::Percentile(double percentile) {
36+
double Histogram::Percentile(double percentile) {
4437
CHECK_GT(percentile, 0);
4538
CHECK_LE(percentile, 100);
46-
return hdr_value_at_percentile(histogram_, percentile);
39+
return static_cast<double>(
40+
hdr_value_at_percentile(histogram_.get(), percentile));
4741
}
4842

49-
inline void Histogram::Percentiles(std::function<void(double, double)> fn) {
43+
template <typename Iterator>
44+
void Histogram::Percentiles(Iterator&& fn) {
5045
hdr_iter iter;
51-
hdr_iter_percentile_init(&iter, histogram_, 1);
46+
hdr_iter_percentile_init(&iter, histogram_.get(), 1);
5247
while (hdr_iter_next(&iter)) {
5348
double key = iter.specifics.percentiles.percentile;
54-
double value = iter.value;
49+
double value = static_cast<double>(iter.value);
5550
fn(key, value);
5651
}
5752
}
5853

54+
bool HistogramBase::RecordDelta() {
55+
uint64_t time = uv_hrtime();
56+
bool ret = true;
57+
if (prev_ > 0) {
58+
int64_t delta = time - prev_;
59+
if (delta > 0) {
60+
ret = Record(delta);
61+
TraceDelta(delta);
62+
if (!ret) {
63+
if (exceeds_ < 0xFFFFFFFF)
64+
exceeds_++;
65+
TraceExceeds(delta);
66+
}
67+
}
68+
}
69+
prev_ = time;
70+
return ret;
71+
}
72+
73+
void HistogramBase::ResetState() {
74+
Reset();
75+
exceeds_ = 0;
76+
prev_ = 0;
77+
}
78+
5979
} // namespace node
6080

6181
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

‎src/histogram.cc

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#include "histogram.h" // NOLINT(build/include_inline)
2+
#include "histogram-inl.h"
3+
#include "memory_tracker-inl.h"
4+
5+
namespace node {
6+
7+
using v8::FunctionCallbackInfo;
8+
using v8::FunctionTemplate;
9+
using v8::Local;
10+
using v8::Map;
11+
using v8::Number;
12+
using v8::ObjectTemplate;
13+
using v8::String;
14+
using v8::Value;
15+
16+
Histogram::Histogram(int64_t lowest, int64_t highest, int figures) {
17+
hdr_histogram* histogram;
18+
CHECK_EQ(0, hdr_init(lowest, highest, figures, &histogram));
19+
histogram_.reset(histogram);
20+
}
21+
22+
HistogramBase::HistogramBase(
23+
Environment* env,
24+
v8::Local<v8::Object> wrap,
25+
int64_t lowest,
26+
int64_t highest,
27+
int figures)
28+
: BaseObject(env, wrap),
29+
Histogram(lowest, highest, figures) {
30+
MakeWeak();
31+
}
32+
33+
void HistogramBase::MemoryInfo(MemoryTracker* tracker) const {
34+
tracker->TrackFieldWithSize("histogram", GetMemorySize());
35+
}
36+
37+
void HistogramBase::GetMin(const FunctionCallbackInfo<Value>& args) {
38+
HistogramBase* histogram;
39+
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
40+
double value = static_cast<double>(histogram->Min());
41+
args.GetReturnValue().Set(value);
42+
}
43+
44+
void HistogramBase::GetMax(const FunctionCallbackInfo<Value>& args) {
45+
HistogramBase* histogram;
46+
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
47+
double value = static_cast<double>(histogram->Max());
48+
args.GetReturnValue().Set(value);
49+
}
50+
51+
void HistogramBase::GetMean(const FunctionCallbackInfo<Value>& args) {
52+
HistogramBase* histogram;
53+
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
54+
args.GetReturnValue().Set(histogram->Mean());
55+
}
56+
57+
void HistogramBase::GetExceeds(const FunctionCallbackInfo<Value>& args) {
58+
HistogramBase* histogram;
59+
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
60+
double value = static_cast<double>(histogram->Exceeds());
61+
args.GetReturnValue().Set(value);
62+
}
63+
64+
void HistogramBase::GetStddev(const FunctionCallbackInfo<Value>& args) {
65+
HistogramBase* histogram;
66+
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
67+
args.GetReturnValue().Set(histogram->Stddev());
68+
}
69+
70+
void HistogramBase::GetPercentile(
71+
const FunctionCallbackInfo<Value>& args) {
72+
HistogramBase* histogram;
73+
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
74+
CHECK(args[0]->IsNumber());
75+
double percentile = args[0].As<Number>()->Value();
76+
args.GetReturnValue().Set(histogram->Percentile(percentile));
77+
}
78+
79+
void HistogramBase::GetPercentiles(
80+
const FunctionCallbackInfo<Value>& args) {
81+
Environment* env = Environment::GetCurrent(args);
82+
HistogramBase* histogram;
83+
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
84+
CHECK(args[0]->IsMap());
85+
Local<Map> map = args[0].As<Map>();
86+
histogram->Percentiles([map, env](double key, double value) {
87+
map->Set(
88+
env->context(),
89+
Number::New(env->isolate(), key),
90+
Number::New(env->isolate(), value)).IsEmpty();
91+
});
92+
}
93+
94+
void HistogramBase::DoReset(const FunctionCallbackInfo<Value>& args) {
95+
HistogramBase* histogram;
96+
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
97+
histogram->ResetState();
98+
}
99+
100+
BaseObjectPtr<HistogramBase> HistogramBase::New(
101+
Environment* env,
102+
int64_t lowest,
103+
int64_t highest,
104+
int figures) {
105+
CHECK_LE(lowest, highest);
106+
CHECK_GT(figures, 0);
107+
v8::Local<v8::Object> obj;
108+
auto tmpl = env->histogram_instance_template();
109+
if (!tmpl->NewInstance(env->context()).ToLocal(&obj))
110+
return {};
111+
112+
return MakeDetachedBaseObject<HistogramBase>(
113+
env, obj, lowest, highest, figures);
114+
}
115+
116+
void HistogramBase::Initialize(Environment* env) {
117+
// Guard against multiple initializations
118+
if (!env->histogram_instance_template().IsEmpty())
119+
return;
120+
121+
Local<FunctionTemplate> histogram = FunctionTemplate::New(env->isolate());
122+
Local<String> classname = FIXED_ONE_BYTE_STRING(env->isolate(), "Histogram");
123+
histogram->SetClassName(classname);
124+
125+
Local<ObjectTemplate> histogramt =
126+
histogram->InstanceTemplate();
127+
128+
histogramt->SetInternalFieldCount(1);
129+
env->SetProtoMethod(histogram, "exceeds", HistogramBase::GetExceeds);
130+
env->SetProtoMethod(histogram, "min", HistogramBase::GetMin);
131+
env->SetProtoMethod(histogram, "max", HistogramBase::GetMax);
132+
env->SetProtoMethod(histogram, "mean", HistogramBase::GetMean);
133+
env->SetProtoMethod(histogram, "stddev", HistogramBase::GetStddev);
134+
env->SetProtoMethod(histogram, "percentile", HistogramBase::GetPercentile);
135+
env->SetProtoMethod(histogram, "percentiles", HistogramBase::GetPercentiles);
136+
env->SetProtoMethod(histogram, "reset", HistogramBase::DoReset);
137+
138+
env->set_histogram_instance_template(histogramt);
139+
}
140+
141+
} // namespace node

‎src/histogram.h

+65-5
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,24 @@
44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

66
#include "hdr_histogram.h"
7+
#include "base_object.h"
8+
#include "util.h"
9+
710
#include <functional>
11+
#include <limits>
812
#include <map>
913

1014
namespace node {
1115

16+
constexpr int kDefaultHistogramFigures = 3;
17+
1218
class Histogram {
1319
public:
14-
inline Histogram(int64_t lowest, int64_t highest, int figures = 3);
15-
inline virtual ~Histogram();
20+
Histogram(
21+
int64_t lowest = std::numeric_limits<int64_t>::min(),
22+
int64_t highest = std::numeric_limits<int64_t>::max(),
23+
int figures = kDefaultHistogramFigures);
24+
virtual ~Histogram() = default;
1625

1726
inline bool Record(int64_t value);
1827
inline void Reset();
@@ -21,14 +30,65 @@ class Histogram {
2130
inline double Mean();
2231
inline double Stddev();
2332
inline double Percentile(double percentile);
24-
inline void Percentiles(std::function<void(double, double)> fn);
33+
34+
// Iterator is a function type that takes two doubles as argument, one for
35+
// percentile and one for the value at that percentile.
36+
template <typename Iterator>
37+
inline void Percentiles(Iterator&& fn);
2538

2639
size_t GetMemorySize() const {
27-
return hdr_get_memory_size(histogram_);
40+
return hdr_get_memory_size(histogram_.get());
2841
}
2942

3043
private:
31-
hdr_histogram* histogram_;
44+
using HistogramPointer = DeleteFnPtr<hdr_histogram, hdr_close>;
45+
HistogramPointer histogram_;
46+
};
47+
48+
class HistogramBase : public BaseObject, public Histogram {
49+
public:
50+
virtual ~HistogramBase() = default;
51+
52+
virtual void TraceDelta(int64_t delta) {}
53+
virtual void TraceExceeds(int64_t delta) {}
54+
55+
inline bool RecordDelta();
56+
inline void ResetState();
57+
58+
int64_t Exceeds() const { return exceeds_; }
59+
60+
void MemoryInfo(MemoryTracker* tracker) const override;
61+
SET_MEMORY_INFO_NAME(HistogramBase)
62+
SET_SELF_SIZE(HistogramBase)
63+
64+
static void GetMin(const v8::FunctionCallbackInfo<v8::Value>& args);
65+
static void GetMax(const v8::FunctionCallbackInfo<v8::Value>& args);
66+
static void GetMean(const v8::FunctionCallbackInfo<v8::Value>& args);
67+
static void GetExceeds(const v8::FunctionCallbackInfo<v8::Value>& args);
68+
static void GetStddev(const v8::FunctionCallbackInfo<v8::Value>& args);
69+
static void GetPercentile(
70+
const v8::FunctionCallbackInfo<v8::Value>& args);
71+
static void GetPercentiles(
72+
const v8::FunctionCallbackInfo<v8::Value>& args);
73+
static void DoReset(const v8::FunctionCallbackInfo<v8::Value>& args);
74+
static void Initialize(Environment* env);
75+
76+
static BaseObjectPtr<HistogramBase> New(
77+
Environment* env,
78+
int64_t lowest = std::numeric_limits<int64_t>::min(),
79+
int64_t highest = std::numeric_limits<int64_t>::max(),
80+
int figures = kDefaultHistogramFigures);
81+
82+
HistogramBase(
83+
Environment* env,
84+
v8::Local<v8::Object> wrap,
85+
int64_t lowest = std::numeric_limits<int64_t>::min(),
86+
int64_t highest = std::numeric_limits<int64_t>::max(),
87+
int figures = kDefaultHistogramFigures);
88+
89+
private:
90+
int64_t exceeds_ = 0;
91+
uint64_t prev_ = 0;
3292
};
3393

3494
} // namespace node

‎src/node_perf.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ class ELDHistogram : public HandleWrap, public Histogram {
139139
exceeds_ = 0;
140140
prev_ = 0;
141141
}
142-
int64_t Exceeds() { return exceeds_; }
142+
int64_t Exceeds() const { return exceeds_; }
143143

144144
void MemoryInfo(MemoryTracker* tracker) const override {
145145
tracker->TrackFieldWithSize("histogram", GetMemorySize());

0 commit comments

Comments
 (0)
Please sign in to comment.