perf_hooks: implement histogram based api

Add a sampling-based event loop delay monitor.

```js
const { monitorEventLoopDelay } = require('perf_hooks');

const h = monitorEventLoopDelay();

h.enable();

h.disable();

console.log(h.percentiles);
console.log(h.min);
console.log(h.max);
console.log(h.mean);
console.log(h.stddev);
console.log(h.percentile(50));
```

PR-URL: https://github.com/nodejs/node/pull/25378
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
James M Snell 2019-01-07 11:36:35 -08:00 committed by jasnell
parent 679c23f2ae
commit bcdd228f90
No known key found for this signature in database
GPG Key ID: 7341B15C070877AC
18 changed files with 2207 additions and 3 deletions

45
LICENSE
View File

@ -1371,3 +1371,48 @@ The externally maintained libraries used by Node.js are:
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- HdrHistogram, located at deps/histogram, is licensed as follows:
"""
The code in this repository code was Written by Gil Tene, Michael Barker,
and Matt Warren, and released to the public domain, as explained at
http://creativecommons.org/publicdomain/zero/1.0/
For users of this code who wish to consume it under the "BSD" license
rather than under the public domain or CC0 contribution text mentioned
above, the code found under this directory is *also* provided under the
following license (commonly referred to as the BSD 2-Clause License). This
license does not detract from the above stated release of the code into
the public domain, and simply represents an additional license granted by
the Author.
-----------------------------------------------------------------------------
** Beginning of "BSD 2-Clause License" text. **
Copyright (c) 2012, 2013, 2014 Gil Tene
Copyright (c) 2014 Michael Barker
Copyright (c) 2014 Matt Warren
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
"""

41
deps/histogram/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,41 @@
The code in this repository code was Written by Gil Tene, Michael Barker,
and Matt Warren, and released to the public domain, as explained at
http://creativecommons.org/publicdomain/zero/1.0/
For users of this code who wish to consume it under the "BSD" license
rather than under the public domain or CC0 contribution text mentioned
above, the code found under this directory is *also* provided under the
following license (commonly referred to as the BSD 2-Clause License). This
license does not detract from the above stated release of the code into
the public domain, and simply represents an additional license granted by
the Author.
-----------------------------------------------------------------------------
** Beginning of "BSD 2-Clause License" text. **
Copyright (c) 2012, 2013, 2014 Gil Tene
Copyright (c) 2014 Michael Barker
Copyright (c) 2014 Matt Warren
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.

3
deps/histogram/README.md vendored Normal file
View File

@ -0,0 +1,3 @@
# HdrHistogram_c
From: https://github.com/HdrHistogram/HdrHistogram_c

15
deps/histogram/histogram.gyp vendored Normal file
View File

@ -0,0 +1,15 @@
{
'targets': [
{
'target_name': 'histogram',
'type': 'static_library',
'include_dirs': ['src'],
'direct_dependent_settings': {
'include_dirs': [ 'src' ]
},
'sources': [
'src/hdr_histogram.c',
]
}
]
}

1032
deps/histogram/src/hdr_histogram.c vendored Normal file

File diff suppressed because it is too large Load Diff

434
deps/histogram/src/hdr_histogram.h vendored Normal file
View File

@ -0,0 +1,434 @@
/**
* hdr_histogram.h
* Written by Michael Barker and released to the public domain,
* as explained at http://creativecommons.org/publicdomain/zero/1.0/
*
* The source for the hdr_histogram utilises a few C99 constructs, specifically
* the use of stdint/stdbool and inline variable declaration.
*/
#ifndef HDR_HISTOGRAM_H
#define HDR_HISTOGRAM_H 1
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
struct hdr_histogram
{
int64_t lowest_trackable_value;
int64_t highest_trackable_value;
int32_t unit_magnitude;
int32_t significant_figures;
int32_t sub_bucket_half_count_magnitude;
int32_t sub_bucket_half_count;
int64_t sub_bucket_mask;
int32_t sub_bucket_count;
int32_t bucket_count;
int64_t min_value;
int64_t max_value;
int32_t normalizing_index_offset;
double conversion_ratio;
int32_t counts_len;
int64_t total_count;
int64_t* counts;
};
#ifdef __cplusplus
extern "C" {
#endif
/**
* Allocate the memory and initialise the hdr_histogram.
*
* Due to the size of the histogram being the result of some reasonably
* involved math on the input parameters this function it is tricky to stack allocate.
* The histogram should be released with hdr_close
*
* @param lowest_trackable_value The smallest possible value to be put into the
* histogram.
* @param highest_trackable_value The largest possible value to be put into the
* histogram.
* @param significant_figures The level of precision for this histogram, i.e. the number
* of figures in a decimal number that will be maintained. E.g. a value of 3 will mean
* the results from the histogram will be accurate up to the first three digits. Must
* be a value between 1 and 5 (inclusive).
* @param result Output parameter to capture allocated histogram.
* @return 0 on success, EINVAL if lowest_trackable_value is < 1 or the
* significant_figure value is outside of the allowed range, ENOMEM if malloc
* failed.
*/
int hdr_init(
int64_t lowest_trackable_value,
int64_t highest_trackable_value,
int significant_figures,
struct hdr_histogram** result);
/**
* Free the memory and close the hdr_histogram.
*
* @param h The histogram you want to close.
*/
void hdr_close(struct hdr_histogram* h);
/**
* Allocate the memory and initialise the hdr_histogram. This is the equivalent of calling
* hdr_init(1, highest_trackable_value, significant_figures, result);
*
* @deprecated use hdr_init.
*/
int hdr_alloc(int64_t highest_trackable_value, int significant_figures, struct hdr_histogram** result);
/**
* Reset a histogram to zero - empty out a histogram and re-initialise it
*
* If you want to re-use an existing histogram, but reset everything back to zero, this
* is the routine to use.
*
* @param h The histogram you want to reset to empty.
*
*/
void hdr_reset(struct hdr_histogram* h);
/**
* Get the memory size of the hdr_histogram.
*
* @param h "This" pointer
* @return The amount of memory used by the hdr_histogram in bytes
*/
size_t hdr_get_memory_size(struct hdr_histogram* h);
/**
* Records a value in the histogram, will round this value of to a precision at or better
* than the significant_figure specified at construction time.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @return false if the value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_value(struct hdr_histogram* h, int64_t value);
/**
* Records count values in the histogram, will round this value of to a
* precision at or better than the significant_figure specified at construction
* time.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @param count Number of 'value's to add to the histogram
* @return false if any value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_values(struct hdr_histogram* h, int64_t value, int64_t count);
/**
* Record a value in the histogram and backfill based on an expected interval.
*
* Records a value in the histogram, will round this value of to a precision at or better
* than the significant_figure specified at contruction time. This is specifically used
* for recording latency. If the value is larger than the expected_interval then the
* latency recording system has experienced co-ordinated omission. This method fills in the
* values that would have occured had the client providing the load not been blocked.
* @param h "This" pointer
* @param value Value to add to the histogram
* @param expected_interval The delay between recording values.
* @return false if the value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_corrected_value(struct hdr_histogram* h, int64_t value, int64_t expexcted_interval);
/**
* Record a value in the histogram 'count' times. Applies the same correcting logic
* as 'hdr_record_corrected_value'.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @param count Number of 'value's to add to the histogram
* @param expected_interval The delay between recording values.
* @return false if the value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_corrected_values(struct hdr_histogram* h, int64_t value, int64_t count, int64_t expected_interval);
/**
* Adds all of the values from 'from' to 'this' histogram. Will return the
* number of values that are dropped when copying. Values will be dropped
* if they around outside of h.lowest_trackable_value and
* h.highest_trackable_value.
*
* @param h "This" pointer
* @param from Histogram to copy values from.
* @return The number of values dropped when copying.
*/
int64_t hdr_add(struct hdr_histogram* h, const struct hdr_histogram* from);
/**
* Adds all of the values from 'from' to 'this' histogram. Will return the
* number of values that are dropped when copying. Values will be dropped
* if they around outside of h.lowest_trackable_value and
* h.highest_trackable_value.
*
* @param h "This" pointer
* @param from Histogram to copy values from.
* @return The number of values dropped when copying.
*/
int64_t hdr_add_while_correcting_for_coordinated_omission(
struct hdr_histogram* h, struct hdr_histogram* from, int64_t expected_interval);
/**
* Get minimum value from the histogram. Will return 2^63-1 if the histogram
* is empty.
*
* @param h "This" pointer
*/
int64_t hdr_min(const struct hdr_histogram* h);
/**
* Get maximum value from the histogram. Will return 0 if the histogram
* is empty.
*
* @param h "This" pointer
*/
int64_t hdr_max(const struct hdr_histogram* h);
/**
* Get the value at a specific percentile.
*
* @param h "This" pointer.
* @param percentile The percentile to get the value for
*/
int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile);
/**
* Gets the standard deviation for the values in the histogram.
*
* @param h "This" pointer
* @return The standard deviation
*/
double hdr_stddev(const struct hdr_histogram* h);
/**
* Gets the mean for the values in the histogram.
*
* @param h "This" pointer
* @return The mean
*/
double hdr_mean(const struct hdr_histogram* h);
/**
* Determine if two values are equivalent with the histogram's resolution.
* Where "equivalent" means that value samples recorded for any two
* equivalent values are counted in a common total count.
*
* @param h "This" pointer
* @param a first value to compare
* @param b second value to compare
* @return 'true' if values are equivalent with the histogram's resolution.
*/
bool hdr_values_are_equivalent(const struct hdr_histogram* h, int64_t a, int64_t b);
/**
* Get the lowest value that is equivalent to the given value within the histogram's resolution.
* Where "equivalent" means that value samples recorded for any two
* equivalent values are counted in a common total count.
*
* @param h "This" pointer
* @param value The given value
* @return The lowest value that is equivalent to the given value within the histogram's resolution.
*/
int64_t hdr_lowest_equivalent_value(const struct hdr_histogram* h, int64_t value);
/**
* Get the count of recorded values at a specific value
* (to within the histogram resolution at the value level).
*
* @param h "This" pointer
* @param value The value for which to provide the recorded count
* @return The total count of values recorded in the histogram within the value range that is
* {@literal >=} lowestEquivalentValue(<i>value</i>) and {@literal <=} highestEquivalentValue(<i>value</i>)
*/
int64_t hdr_count_at_value(const struct hdr_histogram* h, int64_t value);
int64_t hdr_count_at_index(const struct hdr_histogram* h, int32_t index);
int64_t hdr_value_at_index(const struct hdr_histogram* h, int32_t index);
struct hdr_iter_percentiles
{
bool seen_last_value;
int32_t ticks_per_half_distance;
double percentile_to_iterate_to;
double percentile;
};
struct hdr_iter_recorded
{
int64_t count_added_in_this_iteration_step;
};
struct hdr_iter_linear
{
int64_t value_units_per_bucket;
int64_t count_added_in_this_iteration_step;
int64_t next_value_reporting_level;
int64_t next_value_reporting_level_lowest_equivalent;
};
struct hdr_iter_log
{
double log_base;
int64_t count_added_in_this_iteration_step;
int64_t next_value_reporting_level;
int64_t next_value_reporting_level_lowest_equivalent;
};
/**
* The basic iterator. This is a generic structure
* that supports all of the types of iteration. Use
* the appropriate initialiser to get the desired
* iteration.
*
* @
*/
struct hdr_iter
{
const struct hdr_histogram* h;
/** raw index into the counts array */
int32_t counts_index;
/** snapshot of the length at the time the iterator is created */
int32_t total_count;
/** value directly from array for the current counts_index */
int64_t count;
/** sum of all of the counts up to and including the count at this index */
int64_t cumulative_count;
/** The current value based on counts_index */
int64_t value;
int64_t highest_equivalent_value;
int64_t lowest_equivalent_value;
int64_t median_equivalent_value;
int64_t value_iterated_from;
int64_t value_iterated_to;
union
{
struct hdr_iter_percentiles percentiles;
struct hdr_iter_recorded recorded;
struct hdr_iter_linear linear;
struct hdr_iter_log log;
} specifics;
bool (* _next_fp)(struct hdr_iter* iter);
};
/**
* Initalises the basic iterator.
*
* @param itr 'This' pointer
* @param h The histogram to iterate over
*/
void hdr_iter_init(struct hdr_iter* iter, const struct hdr_histogram* h);
/**
* Initialise the iterator for use with percentiles.
*/
void hdr_iter_percentile_init(struct hdr_iter* iter, const struct hdr_histogram* h, int32_t ticks_per_half_distance);
/**
* Initialise the iterator for use with recorded values.
*/
void hdr_iter_recorded_init(struct hdr_iter* iter, const struct hdr_histogram* h);
/**
* Initialise the iterator for use with linear values.
*/
void hdr_iter_linear_init(
struct hdr_iter* iter,
const struct hdr_histogram* h,
int64_t value_units_per_bucket);
/**
* Initialise the iterator for use with logarithmic values
*/
void hdr_iter_log_init(
struct hdr_iter* iter,
const struct hdr_histogram* h,
int64_t value_units_first_bucket,
double log_base);
/**
* Iterate to the next value for the iterator. If there are no more values
* available return faluse.
*
* @param itr 'This' pointer
* @return 'false' if there are no values remaining for this iterator.
*/
bool hdr_iter_next(struct hdr_iter* iter);
typedef enum
{
CLASSIC,
CSV
} format_type;
/**
* Print out a percentile based histogram to the supplied stream. Note that
* this call will not flush the FILE, this is left up to the user.
*
* @param h 'This' pointer
* @param stream The FILE to write the output to
* @param ticks_per_half_distance The number of iteration steps per half-distance to 100%
* @param value_scale Scale the output values by this amount
* @param format_type Format to use, e.g. CSV.
* @return 0 on success, error code on failure. EIO if an error occurs writing
* the output.
*/
int hdr_percentiles_print(
struct hdr_histogram* h, FILE* stream, int32_t ticks_per_half_distance,
double value_scale, format_type format);
/**
* Internal allocation methods, used by hdr_dbl_histogram.
*/
struct hdr_histogram_bucket_config
{
int64_t lowest_trackable_value;
int64_t highest_trackable_value;
int64_t unit_magnitude;
int64_t significant_figures;
int32_t sub_bucket_half_count_magnitude;
int32_t sub_bucket_half_count;
int64_t sub_bucket_mask;
int32_t sub_bucket_count;
int32_t bucket_count;
int32_t counts_len;
};
int hdr_calculate_bucket_config(
int64_t lowest_trackable_value,
int64_t highest_trackable_value,
int significant_figures,
struct hdr_histogram_bucket_config* cfg);
void hdr_init_preallocated(struct hdr_histogram* h, struct hdr_histogram_bucket_config* cfg);
int64_t hdr_size_of_equivalent_value_range(const struct hdr_histogram* h, int64_t value);
int64_t hdr_next_non_equivalent_value(const struct hdr_histogram* h, int64_t value);
int64_t hdr_median_equivalent_value(const struct hdr_histogram* h, int64_t value);
/**
* Used to reset counters after importing data manuallying into the histogram, used by the logging code
* and other custom serialisation tools.
*/
void hdr_reset_internal_counters(struct hdr_histogram* h);
#ifdef __cplusplus
}
#endif
#endif

22
deps/histogram/src/hdr_tests.h vendored Normal file
View File

@ -0,0 +1,22 @@
#ifndef HDR_TESTS_H
#define HDR_TESTS_H
/* These are functions used in tests and are not intended for normal usage. */
#include "hdr_histogram.h"
#ifdef __cplusplus
extern "C" {
#endif
int32_t counts_index_for(const struct hdr_histogram* h, int64_t value);
int hdr_encode_compressed(struct hdr_histogram* h, uint8_t** compressed_histogram, size_t* compressed_len);
int hdr_decode_compressed(uint8_t* buffer, size_t length, struct hdr_histogram** histogram);
void hdr_base64_decode_block(const char* input, uint8_t* output);
void hdr_base64_encode_block(const uint8_t* input, char* output);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -398,6 +398,119 @@ Returns a list of `PerformanceEntry` objects in chronological order
with respect to `performanceEntry.startTime` whose `performanceEntry.entryType`
is equal to `type`.
## monitorEventLoopDelay([options])
<!-- YAML
added: REPLACEME
-->
* `options` {Object}
* `resolution` {number} The sampling rate in milliseconds. Must be greater
than zero. Defaults to `10`.
* Returns: {Histogram}
Creates a `Histogram` object that samples and reports the event loop delay
over time.
Using a timer to detect approximate event loop delay works because the
execution of timers is tied specifically to the lifecycle of the libuv
event loop. That is, a delay in the loop will cause a delay in the execution
of the timer, and those delays are specifically what this API is intended to
detect.
```js
const { monitorEventLoopDelay } = require('perf_hooks');
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
// Do something
h.disable();
console.log(h.min);
console.log(h.max);
console.log(h.mean);
console.log(h.stddev);
console.log(h.percentiles);
console.log(h.percentile(50));
console.log(h.percentile(99));
```
### Class: Histogram
<!-- YAML
added: REPLACEME
-->
Tracks the event loop delay at a given sampling rate.
#### histogram.disable()
<!-- YAML
added: REPLACEME
-->
* Returns: {boolean}
Disables the event loop delay sample timer. Returns `true` if the timer was
stopped, `false` if it was already stopped.
#### histogram.enable()
<!-- YAML
added: REPLACEME
-->
* Returns: {boolean}
Enables the event loop delay sample timer. Returns `true` if the timer was
started, `false` if it was already started.
#### histogram.exceeds
* Value: {number}
The number of times the event loop delay exceeded the maximum 1 hour event
loop delay threshold.
#### histogram.max
* Value: {number}
The maximum recorded event loop delay.
#### histogram.mean
* Value: {number}
The mean of the recorded event loop delays.
#### histogram.min
<!-- YAML
added: REPLACEME
-->
* Value: {number}
The minimum recorded event loop delay.
#### histogram.percentile(percentile)
* `percentile` {number} A percentile value between 1 and 100.
Returns the value at the given percentile.
#### histogram.percentiles
* Value: {Map}
Returns a `Map` object detailing the accumulated percentile distribution.
#### histogram.reset()
<!-- YAML
added: REPLACEME
-->
Resets the collected histogram data.
#### histogram.stddev
* Value: {number}
The standard deviation of the recorded event loop delays.
## Examples
### Measuring the duration of async operations

View File

@ -1,6 +1,7 @@
'use strict';
const {
ELDHistogram: _ELDHistogram,
PerformanceEntry,
mark: _mark,
clearMark: _clearMark,
@ -35,6 +36,8 @@ const { AsyncResource } = require('async_hooks');
const L = require('internal/linkedlist');
const kInspect = require('internal/util').customInspectSymbol;
const kHandle = Symbol('handle');
const kMap = Symbol('map');
const kCallback = Symbol('callback');
const kTypes = Symbol('types');
const kEntries = Symbol('entries');
@ -545,9 +548,73 @@ function sortedInsert(list, entry) {
list.splice(location, 0, entry);
}
class ELDHistogram {
constructor(handle) {
this[kHandle] = handle;
this[kMap] = new Map();
}
reset() { this[kHandle].reset(); }
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') {
const errors = lazyErrors();
throw new errors.ERR_INVALID_ARG_TYPE('percentile', 'number', percentile);
}
if (percentile <= 0 || percentile > 100) {
const errors = lazyErrors();
throw new errors.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 = {}) {
if (typeof options !== 'object' || options === null) {
const errors = lazyErrors();
throw new errors.ERR_INVALID_ARG_TYPE('options', 'Object', options);
}
const { resolution = 10 } = options;
if (typeof resolution !== 'number') {
const errors = lazyErrors();
throw new errors.ERR_INVALID_ARG_TYPE('options.resolution',
'number', resolution);
}
if (resolution <= 0 || !Number.isSafeInteger(resolution)) {
const errors = lazyErrors();
throw new errors.ERR_INVALID_OPT_VALUE.RangeError('resolution', resolution);
}
return new ELDHistogram(new _ELDHistogram(resolution));
}
module.exports = {
performance,
PerformanceObserver
PerformanceObserver,
monitorEventLoopDelay
};
Object.defineProperty(module.exports, 'constants', {

View File

@ -261,8 +261,9 @@
],
'include_dirs': [
'src',
'deps/v8/include',
'deps/v8/include'
],
'dependencies': [ 'deps/histogram/histogram.gyp:histogram' ],
# - "C4244: conversion from 'type1' to 'type2', possible loss of data"
# Ususaly safe. Disable for `dep`, enable for `src`
@ -360,6 +361,7 @@
'src',
'<(SHARED_INTERMEDIATE_DIR)' # for node_natives.h
],
'dependencies': [ 'deps/histogram/histogram.gyp:histogram' ],
'sources': [
'src/api/callback.cc',
@ -458,6 +460,8 @@
'src/env.h',
'src/env-inl.h',
'src/handle_wrap.h',
'src/histogram.h',
'src/histogram-inl.h',
'src/http_parser_adaptor.h',
'src/js_stream.h',
'src/memory_tracker.h',
@ -966,6 +970,7 @@
'<(node_lib_target_name)',
'rename_node_bin_win',
'deps/gtest/gtest.gyp:gtest',
'deps/histogram/histogram.gyp:histogram',
'node_dtrace_header',
'node_dtrace_ustack',
'node_dtrace_provider',

View File

@ -233,6 +233,7 @@
[ 'OS=="aix"', {
'defines': [
'_LINUX_SOURCE_COMPAT',
'__STDC_FORMAT_MACROS'
],
'conditions': [
[ 'force_load=="true"', {

63
src/histogram-inl.h Normal file
View File

@ -0,0 +1,63 @@
#ifndef SRC_HISTOGRAM_INL_H_
#define SRC_HISTOGRAM_INL_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "histogram.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_));
}
inline Histogram::~Histogram() {
hdr_close(histogram_);
}
inline void Histogram::Reset() {
hdr_reset(histogram_);
}
inline bool Histogram::Record(int64_t value) {
return hdr_record_value(histogram_, value);
}
inline int64_t Histogram::Min() {
return hdr_min(histogram_);
}
inline int64_t Histogram::Max() {
return hdr_max(histogram_);
}
inline double Histogram::Mean() {
return hdr_mean(histogram_);
}
inline double Histogram::Stddev() {
return hdr_stddev(histogram_);
}
inline double Histogram::Percentile(double percentile) {
CHECK_GT(percentile, 0);
CHECK_LE(percentile, 100);
return hdr_value_at_percentile(histogram_, percentile);
}
inline void Histogram::Percentiles(std::function<void(double, double)> fn) {
hdr_iter iter;
hdr_iter_percentile_init(&iter, histogram_, 1);
while (hdr_iter_next(&iter)) {
double key = iter.specifics.percentiles.percentile;
double value = iter.value;
fn(key, value);
}
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_HISTOGRAM_INL_H_

38
src/histogram.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef SRC_HISTOGRAM_H_
#define SRC_HISTOGRAM_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "hdr_histogram.h"
#include <functional>
#include <map>
namespace node {
class Histogram {
public:
inline Histogram(int64_t lowest, int64_t highest, int figures = 3);
inline virtual ~Histogram();
inline bool Record(int64_t value);
inline void Reset();
inline int64_t Min();
inline int64_t Max();
inline double Mean();
inline double Stddev();
inline double Percentile(double percentile);
inline void Percentiles(std::function<void(double, double)> fn);
size_t GetMemorySize() const {
return hdr_get_memory_size(histogram_);
}
private:
hdr_histogram* histogram_;
};
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_HISTOGRAM_H_

View File

@ -1,5 +1,10 @@
#include "aliased_buffer.h"
#include "node_internals.h"
#include "node_perf.h"
#include "node_buffer.h"
#include "node_process.h"
#include <cinttypes>
#ifdef __POSIX__
#include <sys/time.h> // gettimeofday
@ -20,6 +25,7 @@ using v8::HandleScope;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::Map;
using v8::MaybeLocal;
using v8::Name;
using v8::NewStringType;
@ -387,6 +393,168 @@ void Timerify(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(wrap);
}
// Event Loop Timing Histogram
namespace {
static void ELDHistogramMin(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>(histogram->Min());
args.GetReturnValue().Set(value);
}
static void ELDHistogramMax(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>(histogram->Max());
args.GetReturnValue().Set(value);
}
static void ELDHistogramMean(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Mean());
}
static void ELDHistogramExceeds(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
double value = static_cast<double>(histogram->Exceeds());
args.GetReturnValue().Set(value);
}
static void ELDHistogramStddev(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Stddev());
}
static void ELDHistogramPercentile(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* 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));
}
static void ELDHistogramPercentiles(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
CHECK(args[0]->IsMap());
Local<Map> map = args[0].As<Map>();
histogram->Percentiles([&](double key, double value) {
map->Set(env->context(),
Number::New(env->isolate(), key),
Number::New(env->isolate(), value)).IsEmpty();
});
}
static void ELDHistogramEnable(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Enable());
}
static void ELDHistogramDisable(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
args.GetReturnValue().Set(histogram->Disable());
}
static void ELDHistogramReset(const FunctionCallbackInfo<Value>& args) {
ELDHistogram* histogram;
ASSIGN_OR_RETURN_UNWRAP(&histogram, args.Holder());
histogram->ResetState();
}
static void ELDHistogramNew(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args.IsConstructCall());
int32_t resolution = args[0]->IntegerValue(env->context()).FromJust();
CHECK_GT(resolution, 0);
new ELDHistogram(env, args.This(), resolution);
}
} // namespace
ELDHistogram::ELDHistogram(
Environment* env,
Local<Object> wrap,
int32_t resolution) : BaseObject(env, wrap),
Histogram(1, 3.6e12),
resolution_(resolution) {
MakeWeak();
timer_ = new uv_timer_t();
uv_timer_init(env->event_loop(), timer_);
timer_->data = this;
}
void ELDHistogram::CloseTimer() {
if (timer_ == nullptr)
return;
env()->CloseHandle(timer_, [](uv_timer_t* handle) { delete handle; });
timer_ = nullptr;
}
ELDHistogram::~ELDHistogram() {
Disable();
CloseTimer();
}
void ELDHistogramDelayInterval(uv_timer_t* req) {
ELDHistogram* histogram =
reinterpret_cast<ELDHistogram*>(req->data);
histogram->RecordDelta();
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"min", histogram->Min());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"max", histogram->Max());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"mean", histogram->Mean());
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"stddev", histogram->Stddev());
}
bool ELDHistogram::RecordDelta() {
uint64_t time = uv_hrtime();
bool ret = true;
if (prev_ > 0) {
int64_t delta = time - prev_;
if (delta > 0) {
ret = Record(delta);
TRACE_COUNTER1(TRACING_CATEGORY_NODE2(perf, event_loop),
"delay", delta);
if (!ret) {
if (exceeds_ < 0xFFFFFFFF)
exceeds_++;
ProcessEmitWarning(
env(),
"Event loop delay exceeded 1 hour: %" PRId64 " nanoseconds",
delta);
}
}
}
prev_ = time;
return ret;
}
bool ELDHistogram::Enable() {
if (enabled_) return false;
enabled_ = true;
uv_timer_start(timer_,
ELDHistogramDelayInterval,
resolution_,
resolution_);
uv_unref(reinterpret_cast<uv_handle_t*>(timer_));
return true;
}
bool ELDHistogram::Disable() {
if (!enabled_) return false;
enabled_ = false;
uv_timer_stop(timer_);
return true;
}
void Initialize(Local<Object> target,
Local<Value> unused,
@ -456,6 +624,24 @@ void Initialize(Local<Object> target,
env->constants_string(),
constants,
attr).ToChecked();
Local<String> eldh_classname = FIXED_ONE_BYTE_STRING(isolate, "ELDHistogram");
Local<FunctionTemplate> eldh =
env->NewFunctionTemplate(ELDHistogramNew);
eldh->SetClassName(eldh_classname);
eldh->InstanceTemplate()->SetInternalFieldCount(1);
env->SetProtoMethod(eldh, "exceeds", ELDHistogramExceeds);
env->SetProtoMethod(eldh, "min", ELDHistogramMin);
env->SetProtoMethod(eldh, "max", ELDHistogramMax);
env->SetProtoMethod(eldh, "mean", ELDHistogramMean);
env->SetProtoMethod(eldh, "stddev", ELDHistogramStddev);
env->SetProtoMethod(eldh, "percentile", ELDHistogramPercentile);
env->SetProtoMethod(eldh, "percentiles", ELDHistogramPercentiles);
env->SetProtoMethod(eldh, "enable", ELDHistogramEnable);
env->SetProtoMethod(eldh, "disable", ELDHistogramDisable);
env->SetProtoMethod(eldh, "reset", ELDHistogramReset);
target->Set(context, eldh_classname,
eldh->GetFunction(env->context()).ToLocalChecked()).FromJust();
}
} // namespace performance

View File

@ -7,6 +7,7 @@
#include "node_perf_common.h"
#include "env.h"
#include "base_object-inl.h"
#include "histogram-inl.h"
#include "v8.h"
#include "uv.h"
@ -124,6 +125,41 @@ class GCPerformanceEntry : public PerformanceEntry {
PerformanceGCKind gckind_;
};
class ELDHistogram : public BaseObject, public Histogram {
public:
ELDHistogram(Environment* env,
Local<Object> wrap,
int32_t resolution);
~ELDHistogram() override;
bool RecordDelta();
bool Enable();
bool Disable();
void ResetState() {
Reset();
exceeds_ = 0;
prev_ = 0;
}
int64_t Exceeds() { return exceeds_; }
void MemoryInfo(MemoryTracker* tracker) const override {
tracker->TrackFieldWithSize("histogram", GetMemorySize());
}
SET_MEMORY_INFO_NAME(ELDHistogram)
SET_SELF_SIZE(ELDHistogram)
private:
void CloseTimer();
bool enabled_ = false;
int32_t resolution_ = 0;
int64_t exceeds_ = 0;
uint64_t prev_ = 0;
uv_timer_t* timer_;
};
} // namespace performance
} // namespace node

View File

@ -0,0 +1,99 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const {
monitorEventLoopDelay
} = require('perf_hooks');
{
const histogram = monitorEventLoopDelay();
assert(histogram);
assert(histogram.enable());
assert(!histogram.enable());
histogram.reset();
assert(histogram.disable());
assert(!histogram.disable());
}
{
[null, 'a', 1, false, Infinity].forEach((i) => {
common.expectsError(
() => monitorEventLoopDelay(i),
{
type: TypeError,
code: 'ERR_INVALID_ARG_TYPE'
}
);
});
[null, 'a', false, {}, []].forEach((i) => {
common.expectsError(
() => monitorEventLoopDelay({ resolution: i }),
{
type: TypeError,
code: 'ERR_INVALID_ARG_TYPE'
}
);
});
[-1, 0, Infinity].forEach((i) => {
common.expectsError(
() => monitorEventLoopDelay({ resolution: i }),
{
type: RangeError,
code: 'ERR_INVALID_OPT_VALUE'
}
);
});
}
{
const histogram = monitorEventLoopDelay({ resolution: 1 });
histogram.enable();
let m = 5;
function spinAWhile() {
common.busyLoop(1000);
if (--m > 0) {
setTimeout(spinAWhile, common.platformTimeout(500));
} else {
histogram.disable();
// The values are non-deterministic, so we just check that a value is
// present, as opposed to a specific value.
assert(histogram.min > 0);
assert(histogram.max > 0);
assert(histogram.stddev > 0);
assert(histogram.mean > 0);
assert(histogram.percentiles.size > 0);
for (let n = 1; n < 100; n = n + 0.1) {
assert(histogram.percentile(n) >= 0);
}
histogram.reset();
assert.strictEqual(histogram.min, 9223372036854776000);
assert.strictEqual(histogram.max, 0);
assert(Number.isNaN(histogram.stddev));
assert(Number.isNaN(histogram.mean));
assert.strictEqual(histogram.percentiles.size, 1);
['a', false, {}, []].forEach((i) => {
common.expectsError(
() => histogram.percentile(i),
{
type: TypeError,
code: 'ERR_INVALID_ARG_TYPE'
}
);
});
[-1, 0, 101].forEach((i) => {
common.expectsError(
() => histogram.percentile(i),
{
type: RangeError,
code: 'ERR_INVALID_ARG_VALUE'
}
);
});
}
}
spinAWhile();
}

View File

@ -16,7 +16,7 @@ const jsPrimitives = {
const jsGlobalObjectsUrl = `${jsDocPrefix}Reference/Global_Objects/`;
const jsGlobalTypes = [
'Array', 'ArrayBuffer', 'DataView', 'Date', 'Error', 'EvalError', 'Function',
'Object', 'Promise', 'RangeError', 'ReferenceError', 'RegExp', 'Set',
'Map', 'Object', 'Promise', 'RangeError', 'ReferenceError', 'RegExp', 'Set',
'SharedArrayBuffer', 'SyntaxError', 'TypeError', 'TypedArray', 'URIError',
'Uint8Array',
];
@ -75,6 +75,8 @@ const customTypesMap = {
'fs.Stats': 'fs.html#fs_class_fs_stats',
'fs.WriteStream': 'fs.html#fs_class_fs_writestream',
'Histogram': 'perf_hooks.html#perf_hooks_class_histogram',
'http.Agent': 'http.html#http_class_http_agent',
'http.ClientRequest': 'http.html#http_class_http_clientrequest',
'http.IncomingMessage': 'http.html#http_class_http_incomingmessage',

View File

@ -95,4 +95,6 @@ addlicense "large_pages" "src/large_pages" "$(sed -e '/SPDX-License-Identifier/,
# brotli
addlicense "brotli" "deps/brotli" "$(cat ${rootdir}/deps/brotli/LICENSE)"
addlicense "HdrHistogram" "deps/histogram" "$(cat ${rootdir}/deps/histogram/LICENSE.txt)"
mv $tmplicense $licensefile