Skip to content

Commit

Permalink
feat: metric for event loop utilization
Browse files Browse the repository at this point in the history
  • Loading branch information
sastan committed Nov 2, 2020
1 parent 9954bfa commit 1afd1ee
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 3 deletions.
44 changes: 44 additions & 0 deletions src/event-loop-utilization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { performance } from 'perf_hooks'

import { Telemetry } from '@carv/telemetry'

import { ProcessEventLoopUtilizationOptions } from './types'

export const isEventLoopUtilizationSupported = Boolean(performance.eventLoopUtilization)

export function processEventLoopUtilization(
telemetry: Telemetry,
{
prefix = 'process',
name = 'event_loop_utilization',
description = `ELU is similar to CPU utilization, except that it only measures event loop statistics and not CPU usage. It represents the percentage of time the event loop has spent outside the event loop's event provider (e.g. epoll_wait).`,
labels,
}: ProcessEventLoopUtilizationOptions,
done: () => void,
) {
if (!isEventLoopUtilizationSupported) {
telemetry.log.warn(
'[%s] Monitoring the event loop utilization is not supported on Node.js %s',
telemetry.makeName(prefix, name),
process.version,
)
done()
return
}

const { eventLoopUtilization } = performance

let last = eventLoopUtilization()

telemetry.createValueObserver({ prefix, name, description, labels }, () => {
const current = eventLoopUtilization()

const { utilization } = eventLoopUtilization(last, current)

last = current

return utilization
})

done()
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './process'

export * from './cpu-usage'
export * from './event-loop-delay'
export * from './event-loop-utilization'
export * from './gc'
export * from './heap-space'
export * from './memory-usage'
Expand Down
9 changes: 9 additions & 0 deletions src/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { ProcessOptions, ProcessCommonOptions } from './types'

import { processCpuUsage } from './cpu-usage'
import { processEventLoopDelay, isMonitorEventLoopDelaySupported } from './event-loop-delay'
import {
isEventLoopUtilizationSupported,
processEventLoopUtilization,
} from './event-loop-utilization'
import { processGcDuration } from './gc'
import { processHeapSpace } from './heap-space'
import { processMemoryUsage } from './memory-usage'
Expand All @@ -25,6 +29,7 @@ export function processMetrics(
heapSpace = true,
gc = true,
eventLoopDelay = isMonitorEventLoopDelaySupported,
eventLoopUtilization = isEventLoopUtilizationSupported,
}: ProcessOptions,
done: () => void,
) {
Expand Down Expand Up @@ -68,6 +73,10 @@ export function processMetrics(
telemetry.use(processGcDuration, mergeOptions(gc))
}

if (eventLoopUtilization !== false) {
telemetry.use(processEventLoopUtilization, mergeOptions(eventLoopUtilization))
}

if (eventLoopDelay !== false) {
telemetry.use(processEventLoopDelay, mergeOptions(eventLoopDelay))
}
Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface ProcessCommonOptions {

export interface ProcessCpuUsageOptions extends ProcessCommonOptions {}

export interface ProcessEventLoopUtilizationOptions extends ProcessCommonOptions {}

export interface ProcessEventLoopDelayOptions extends ProcessCommonOptions {
/**
* The sampling rate in milliseconds (default: `10`)
Expand Down Expand Up @@ -44,6 +46,7 @@ export interface ProcessOptions {
labels?: Labels

cpu?: boolean | ProcessCpuUsageOptions
eventLoopUtilization?: boolean | ProcessEventLoopUtilizationOptions
eventLoopDelay?: boolean | ProcessEventLoopDelayOptions
gc?: boolean | ProcessGcDurationOptions
heapSpace?: boolean | ProcessHeapSpaceOptions
Expand Down
61 changes: 61 additions & 0 deletions test/event-loop-utilization.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @jest-environment node
*/
import { promisify } from 'util'
import { performance } from 'perf_hooks'

import { Telemetry, TestLogger } from '@carv/telemetry'

import { processEventLoopUtilization, isEventLoopUtilizationSupported } from '../src'

jest.useFakeTimers()

const nextTick = promisify(setImmediate)

let telemetry: Telemetry

beforeEach(() => {
telemetry = new Telemetry({
logger: new TestLogger(),
})
})

afterEach(() => telemetry.shutdown())

if (isEventLoopUtilizationSupported) {
test('processEventLoopUtilization', async () => {
telemetry.use(processEventLoopUtilization)

await telemetry.ready()

// Do something
const finishedTimestamp = Date.now() + 23
while (finishedTimestamp > Date.now()) {
const endTimestamp = performance.now() + Math.random()
while (endTimestamp > performance.now()) {}
await nextTick()
}

const metrics = await telemetry.collect()

expect(metrics).toMatch(
'# HELP process_event_loop_utilization ELU is similar to CPU utilization, except that it only measures event loop statistics and not CPU usage.',
)
expect(metrics).toMatch('# TYPE process_event_loop_utilization gauge')
expect(metrics).toMatch(/^process_event_loop_utilization \d+(?:\.\d+)? \d{13}$/m)
})
} else {
test('processEventLoopUtilization', async () => {
telemetry.use(processEventLoopUtilization)

telemetry.log.warn = jest.fn()

await telemetry.ready()

expect(telemetry.log.warn).toHaveBeenCalledWith(
'[%s] Monitoring the event loop utilization is not supported on Node.js %s',
'process_event_loop_utilization',
process.version,
)
})
}
25 changes: 22 additions & 3 deletions test/process.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { performance } from 'perf_hooks'

import { Telemetry, TestLogger } from '@carv/telemetry'

import processMetrics, { isMonitorEventLoopDelaySupported } from '../src'
import processMetrics, {
isMonitorEventLoopDelaySupported,
isEventLoopUtilizationSupported,
} from '../src'

jest.useFakeTimers()

Expand All @@ -23,15 +26,15 @@ beforeEach(() => {
afterEach(() => telemetry.shutdown())

test('processMetrics', async () => {
if (!isMonitorEventLoopDelaySupported) {
if (!(isMonitorEventLoopDelaySupported || isEventLoopUtilizationSupported)) {
telemetry.log.warn = jest.fn()
}

telemetry.use(processMetrics)

await telemetry.ready()

if (!isMonitorEventLoopDelaySupported) {
if (!(isMonitorEventLoopDelaySupported || isEventLoopUtilizationSupported)) {
expect(telemetry.log.warn).not.toHaveBeenCalled()
}

Expand Down Expand Up @@ -78,3 +81,19 @@ if (!isMonitorEventLoopDelaySupported) {
)
})
}

if (!isEventLoopUtilizationSupported) {
test('processMetrics (warn missing event loop utilization support)', async () => {
telemetry.log.warn = jest.fn()

telemetry.use(processMetrics, { eventLoopUtilization: true })

await telemetry.ready()

expect(telemetry.log.warn).toHaveBeenCalledWith(
'[%s] Monitoring the event loop utilization is not supported on Node.js %s',
'process_event_loop_utilization',
process.version,
)
})
}

0 comments on commit 1afd1ee

Please sign in to comment.