Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ available on Linux.
- `register` to which metrics should be registered. Default: the global default registry.
- `gcDurationBuckets` with custom buckets for GC duration histogram. Default buckets of GC duration histogram are `[0.001, 0.01, 0.1, 1, 2, 5]` (in seconds).
- `eventLoopMonitoringPrecision` with sampling rate in milliseconds. Must be greater than zero. Default: 10.
- `eventLoopUtilizationTimeout` measurement duration in milliseconds. Must be greater than zero. Default: 100.

To register metrics to another registry, pass it in as `register`:

Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@ export interface DefaultMetricsCollectorConfiguration {
prefix?: string;
gcDurationBuckets?: number[];
eventLoopMonitoringPrecision?: number;
eventLoopUtilizationTimeout?: number;
labels?: Object;
}

Expand Down
52 changes: 52 additions & 0 deletions lib/metrics/eventLoopUtilization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';

const Gauge = require('../gauge');

// Check if perf_hooks module is available
let perf_hooks;
try {
/* eslint-disable node/no-unsupported-features/node-builtins */
perf_hooks = require('perf_hooks');
} catch {
// node version is too old
}

// Reported always.
const NODEJS_EVENTLOOP_UTILIZATION = 'nodejs_eventloop_utilization';

module.exports = (registry, config = {}) => {
if (
!perf_hooks ||
!perf_hooks.performance ||
!perf_hooks.performance.eventLoopUtilization
) {
return;
}

const eventLoopUtilization = perf_hooks.performance.eventLoopUtilization;

const namePrefix = config.prefix ? config.prefix : '';
const labels = config.labels ? config.labels : {};
const labelNames = Object.keys(labels);
const registers = registry ? [registry] : undefined;

new Gauge({
name: namePrefix + NODEJS_EVENTLOOP_UTILIZATION,
help: 'Ratio of time the event loop is not idling in the event provider to the total time the event loop is running.',
registers,
labelNames,
async collect() {
const start = eventLoopUtilization();

return new Promise(resolve => {
setTimeout(() => {
const end = eventLoopUtilization();
this.set(labels, eventLoopUtilization(end, start).utilization);
resolve();
}, config.eventLoopUtilizationTimeout || 100);
});
},
});
};

module.exports.metricNames = [NODEJS_EVENTLOOP_UTILIZATION];
55 changes: 55 additions & 0 deletions test/metrics/eventLoopUtilizationTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';

describe('eventLoopUtilization', () => {
const register = require('../../index').register;
const elu = require('../../lib/metrics/eventLoopUtilization');
const { eventLoopUtilization } = require('perf_hooks').performance;

beforeAll(() => {
register.clear();
});

afterEach(() => {
register.clear();
});

it('should add metric to the registry', async () => {
expect(await register.getMetricsAsJSON()).toHaveLength(0);

elu(register, { eventLoopUtilizationTimeout: 1000 });

const eluStart = eventLoopUtilization();
const metricsPromise = register.getMetricsAsJSON();

setImmediate(() => blockEventLoop(500));

const metrics = await metricsPromise;
const eluEnd = eventLoopUtilization();

expect(metrics).toHaveLength(1);

const eluMetric = metrics[0];
expect(eluMetric.help).toEqual(
'Ratio of time the event loop is not idling in the event provider to the total time the event loop is running.',
);
expect(eluMetric.type).toEqual('gauge');
expect(eluMetric.name).toEqual('nodejs_eventloop_utilization');
expect(eluMetric.values).toHaveLength(1);

const eluValue = eluMetric.values[0].value;
expect(eluValue).toBeGreaterThanOrEqual(0);
expect(eluValue).toBeLessThanOrEqual(1);

const expectedELU = eventLoopUtilization(eluEnd, eluStart).utilization;
expect(eluValue).toBeCloseTo(expectedELU, 2);

console.log(eluValue, eventLoopUtilization(eluEnd, eluStart));
});
});

function blockEventLoop(ms) {
const start = Date.now();
while (Date.now() - start < ms) {
// heavy operations
}
}