Skip to content

Commit

Permalink
Adds support for event loop utilization to the core metrics service (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
lukeelmers authored Mar 29, 2023
1 parent 0d6ad68 commit 9517d06
Show file tree
Hide file tree
Showing 18 changed files with 305 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ const mockedResponse: StatusResponse = {
},
event_loop_delay: 1,
event_loop_delay_histogram: mocked.createHistogram(),
event_loop_utilization: {
active: 1,
idle: 1,
utilization: 1,
},
uptime_in_millis: 1,
},
processes: [
Expand All @@ -93,6 +98,11 @@ const mockedResponse: StatusResponse = {
},
event_loop_delay: 1,
event_loop_delay_histogram: mocked.createHistogram(),
event_loop_utilization: {
active: 1,
idle: 1,
utilization: 1,
},
uptime_in_millis: 1,
},
],
Expand Down Expand Up @@ -232,15 +242,15 @@ describe('response processing', () => {
const data = await loadStatus({ http, notifications });
const names = data.metrics.map((m) => m.name);
expect(names).toEqual([
'Heap total',
'Heap used',
'Heap used out of 976.56 KB',
'Requests per second',
'Utilization (active: 1.00 / idle: 1.00)',
'Load',
'Delay',
'Response time avg',
]);
const values = data.metrics.map((m) => m.value);
expect(values).toEqual([1000000, 100, 400, [4.1, 2.1, 0.1], 1, 4000]);
expect(values).toEqual([100, 400, 1, [4.1, 2.1, 0.1], 1, 4000]);
});

test('adds meta details to Load, Delay and Response time', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import numeral from '@elastic/numeral';
import { i18n } from '@kbn/i18n';
import type { HttpSetup } from '@kbn/core-http-browser';
import type { NotificationsSetup } from '@kbn/core-notifications-browser';
Expand Down Expand Up @@ -57,16 +58,10 @@ function formatMetrics({ metrics }: StatusResponse): Metric[] {
}

return [
{
name: i18n.translate('core.statusPage.metricsTiles.columns.heapTotalHeader', {
defaultMessage: 'Heap total',
}),
value: metrics.process.memory.heap.size_limit,
type: 'byte',
},
{
name: i18n.translate('core.statusPage.metricsTiles.columns.heapUsedHeader', {
defaultMessage: 'Heap used',
defaultMessage: 'Heap used out of {heapTotal}',
values: { heapTotal: numeral(metrics.process.memory.heap.size_limit).format('0.00 b') },
}),
value: metrics.process.memory.heap.used_in_bytes,
type: 'byte',
Expand All @@ -78,6 +73,17 @@ function formatMetrics({ metrics }: StatusResponse): Metric[] {
value: (metrics.requests.total * 1000) / metrics.collection_interval_in_millis,
type: 'float',
},
{
name: i18n.translate('core.statusPage.metricsTiles.columns.utilizationHeader', {
defaultMessage: 'Utilization (active: {active} / idle: {idle})',
values: {
active: numeral(metrics.process.event_loop_utilization.active).format('0.00'),
idle: numeral(metrics.process.event_loop_utilization.idle).format('0.00'),
},
}),
value: metrics.process.event_loop_utilization.utilization,
type: 'float',
},
{
name: i18n.translate('core.statusPage.metricsTiles.columns.loadHeader', {
defaultMessage: 'Load',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export const eventLoopUtilizationMock = jest.fn().mockImplementation(() => ({
active: 1,
idle: 1,
utilization: 1,
}));

jest.doMock('perf_hooks', () => ({
performance: {
eventLoopUtilization: eventLoopUtilizationMock,
},
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { eventLoopUtilizationMock } from './event_loop_utilization_monitor.test.mocks';
import { EventLoopUtilizationMonitor } from './event_loop_utilization_monitor';

describe('EventLoopUtilizationMonitor', () => {
afterEach(() => jest.clearAllMocks());

describe('#constructor', () => {
test('#constructor collects utilization', () => {
new EventLoopUtilizationMonitor();
expect(eventLoopUtilizationMock).toHaveBeenCalledTimes(1);
});
});

describe('#reset', () => {
test('collects utilization', () => {
const monitor = new EventLoopUtilizationMonitor();
monitor.reset();
expect(eventLoopUtilizationMock).toHaveBeenCalledTimes(2);
});
});

describe('#collect', () => {
test('collects utilization', () => {
const monitor = new EventLoopUtilizationMonitor();
monitor.collect();
expect(eventLoopUtilizationMock).toHaveBeenCalledTimes(2);
});

test('returns values from call to performance.eventLoopUtilization', () => {
const monitor = new EventLoopUtilizationMonitor();
expect(monitor.collect()).toMatchInlineSnapshot(`
Object {
"active": 1,
"idle": 1,
"utilization": 1,
}
`);
});

test('passes last ELU value from constructor to calculate diff', () => {
const mockInitialData = {
active: 0,
idle: 0,
utilization: 0,
};
eventLoopUtilizationMock.mockImplementationOnce(() => mockInitialData);

const monitor = new EventLoopUtilizationMonitor();
monitor.collect();

expect(eventLoopUtilizationMock).toHaveBeenCalledTimes(2);
expect(eventLoopUtilizationMock.mock.calls[1][0]).toEqual(mockInitialData);
});

test('passes last ELU value from reset to calculate diff', () => {
const monitor = new EventLoopUtilizationMonitor();
const mockInitialData = {
active: 0,
idle: 0,
utilization: 0,
};
eventLoopUtilizationMock.mockImplementationOnce(() => mockInitialData);

monitor.reset();
monitor.collect();

expect(eventLoopUtilizationMock).toHaveBeenCalledTimes(3);
expect(eventLoopUtilizationMock.mock.calls[2][0]).toEqual(mockInitialData);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { EventLoopUtilization } from 'perf_hooks';
import { performance } from 'perf_hooks';

export class EventLoopUtilizationMonitor {
private elu: EventLoopUtilization;

/**
* Creating a new instance of EventLoopUtilizationMonitor will capture the
* current ELU to use as a point of comparison against the first call to
* `collect`.
*/
constructor() {
this.elu = performance.eventLoopUtilization();
}

/**
* Get ELU between now and last time the ELU was reset.
*/
public collect(): EventLoopUtilization {
const { active, idle, utilization } = performance.eventLoopUtilization(this.elu);

return {
active,
idle,
utilization,
};
}

/**
* Resets the ELU to now. Will be used to calculate the diff on the next call to `collect`.
*/
public reset() {
this.elu = performance.eventLoopUtilization();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ function createMockOpsProcessMetrics(): OpsProcessMetrics {
},
event_loop_delay: 1,
event_loop_delay_histogram: histogram,
event_loop_utilization: {
active: 1,
idle: 1,
utilization: 1,
},
pid: 1,
uptime_in_millis: 1,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { collectorMock } from './mocks_internal';

export const mockEventLoopDelayMonitor = collectorMock.create();
jest.doMock('./event_loop_delays_monitor', () => ({
EventLoopDelaysMonitor: jest.fn().mockImplementation(() => mockEventLoopDelayMonitor),
}));

export const mockEventLoopUtilizationMonitor = collectorMock.create();
jest.doMock('./event_loop_utilization_monitor', () => ({
EventLoopUtilizationMonitor: jest.fn().mockImplementation(() => mockEventLoopUtilizationMonitor),
}));
Loading

0 comments on commit 9517d06

Please sign in to comment.