Skip to content

Commit 6b44df2

Browse files
jasnellMylesBorins
authored andcommitted
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 <[email protected]> PR-URL: #31988 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 4d5318c commit 6b44df2

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
@@ -140,6 +140,7 @@
140140
'lib/internal/fs/watchers.js',
141141
'lib/internal/http.js',
142142
'lib/internal/heap_utils.js',
143+
'lib/internal/histogram.js',
143144
'lib/internal/idna.js',
144145
'lib/internal/inspector_async_hook.js',
145146
'lib/internal/js_stream_socket.js',
@@ -533,6 +534,7 @@
533534
'src/fs_event_wrap.cc',
534535
'src/handle_wrap.cc',
535536
'src/heap_utils.cc',
537+
'src/histogram.cc',
536538
'src/js_native_api.h',
537539
'src/js_native_api_types.h',
538540
'src/js_native_api_v8.cc',

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ constexpr size_t kFsStatsBufferLength =
406406
V(filehandlereadwrap_template, v8::ObjectTemplate) \
407407
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
408408
V(handle_wrap_ctor_template, v8::FunctionTemplate) \
409+
V(histogram_instance_template, v8::ObjectTemplate) \
409410
V(http2settings_constructor_template, v8::ObjectTemplate) \
410411
V(http2stream_constructor_template, v8::ObjectTemplate) \
411412
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

0 commit comments

Comments
 (0)