UNPKG

@dnlup/doc

Version:

Get usage and health data about your Node.js process

193 lines (159 loc) 4.52 kB
'use strict' const EventEmitter = require('events') const diagnosticsChannel = require('diagnostics_channel') const EventLoopDelayMetric = require('./eventLoopDelay') const EventLoopUtilizationMetric = require('./eventLoopUtilization') const CpuMetric = require('./cpu') const ResourceUsageMetric = require('./resourceUsage') const { GCMetric } = require('./gc') const config = require('./config') const { hrtime2ns } = require('@dnlup/hrtime-utils') const { kOptions, kTimer, kStarted, kLastSampleTime, kEventLoopDelay, kEventLoopUtilization, kCpu, kResourceUsage, kGC, kActiveHandles, kMemory, kEmitSample, kSample, kReset, kStart, kStop } = require('./symbols') const { DOC_CHANNEL, DOC_SAMPLES_CHANNEL } = require('./constants') const samples = diagnosticsChannel.channel(DOC_SAMPLES_CHANNEL) const instances = diagnosticsChannel.channel(DOC_CHANNEL) class Sampler extends EventEmitter { constructor (options) { super() this[kOptions] = config(options) this[kLastSampleTime] = process.hrtime() if (this[kOptions].collect.eventLoopDelay) { this[kEventLoopDelay] = new EventLoopDelayMetric(this[kOptions].eventLoopDelayOptions) } if (this[kOptions].collect.eventLoopUtilization) { this[kEventLoopUtilization] = new EventLoopUtilizationMetric() } if (this[kOptions].collect.cpu) { this[kCpu] = new CpuMetric() } if (this[kOptions].collect.resourceUsage) { this[kResourceUsage] = new ResourceUsageMetric() } if (this[kOptions].collect.gc) { this[kGC] = new GCMetric(this[kOptions].gcOptions) } this[kSample]() if (this[kOptions].autoStart) { this.start() } if (instances.hasSubscribers) { process.nextTick(() => { try { // On Node 18 the `hasSubscribers` check is not enough so this call fails // with an uncaught exception if there are no subscribers. instances.publish(this) } catch (_) {} }) } } start () { if (this[kStarted]) return if (this[kOptions].collect.gc) { this[kGC][kStart]() } this[kTimer] = setInterval(this[kEmitSample].bind(this), this[kOptions].sampleInterval) if (this[kOptions].unref) { this[kTimer].unref() } this[kStarted] = true } stop () { clearInterval(this[kTimer]) this[kStarted] = false this[kReset]() if (this[kOptions].collect.gc) { this[kGC][kStop]() } } get cpu () { return this[kCpu] } get resourceUsage () { return this[kResourceUsage] } get eventLoopDelay () { return this[kEventLoopDelay] } get eventLoopUtilization () { return this[kEventLoopUtilization] } get gc () { return this[kGC] } get activeHandles () { return this[kActiveHandles] } get memory () { return this[kMemory] } [kEmitSample] () { this[kSample]() this.emit('sample') this[kReset]() if (samples.hasSubscribers) { try { // On Node 18 the `hasSubscribers` check is not enough so this call fails // with an uncaught exception if there are no subscribers. samples.publish(this) } catch (_) {} } } [kSample] () { const nextSampleTime = process.hrtime() const elapsedNs = hrtime2ns(nextSampleTime) - hrtime2ns(this[kLastSampleTime]) if (this[kOptions].collect.eventLoopDelay) { this[kEventLoopDelay][kSample](elapsedNs, this[kOptions].sampleInterval) } if (this[kOptions].collect.eventLoopUtilization) { this[kEventLoopUtilization][kSample]() } if (this[kOptions].collect.activeHandles) { this[kActiveHandles] = process._getActiveHandles().length } if (this[kOptions].collect.cpu) { this[kCpu][kSample](elapsedNs) } if (this[kOptions].collect.resourceUsage) { this[kResourceUsage][kSample](elapsedNs) } if (this[kOptions].collect.memory) { this[kMemory] = process.memoryUsage() } this[kLastSampleTime] = nextSampleTime } [kReset] () { if (this[kOptions].collect.eventLoopDelay) { this[kEventLoopDelay][kReset]() } if (this[kOptions].collect.eventLoopUtilization) { this[kEventLoopUtilization][kReset]() } if (this[kOptions].collect.cpu) { this[kCpu][kReset]() } if (this[kOptions].collect.resourceUsage) { this[kResourceUsage][kReset]() } if (this[kOptions].collect.gc) { this[kGC][kReset]() } } } module.exports = Sampler