Skip to content

Commit

Permalink
Add health monitor
Browse files Browse the repository at this point in the history
  • Loading branch information
Americas committed May 10, 2022
1 parent aaa53b7 commit a5fee1e
Show file tree
Hide file tree
Showing 4 changed files with 418 additions and 1 deletion.
115 changes: 115 additions & 0 deletions src/health-monitor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use strict';

/**
* Module dependencies.
*/

const log = require('debugnyan')('process-manager:health-monitor');
const utils = require('./utils');

/**
* `HealthMonitor`.
*/

class HealthMonitor {
/**
* Constructor.
*/

constructor() {
this.checks = {};
this.globalState = HealthMonitor.states.UNKNOWN;
this.states = {};
}

/**
* Add health check.
*/

addCheck({ handler, id, interval = 5000 }) {
if (this.states[id]) {
throw new Error('Cannot add handler since it would overwrite an existing one');
}

this.states[id] = HealthMonitor.states.UNKNOWN;

const check = async () => {
let state;

try {
state = (await Promise.race([handler(), utils.timeout(5000, false)]))
? HealthMonitor.states.HEALTHY
: HealthMonitor.states.UNHEALTHY;
} catch (e) {
state = HealthMonitor.states.UNHEALTHY;
}

this.updateState({ id, state });

this.checks[id] = setTimeout(check, interval);
};

this.checks[id] = setTimeout(check, 0);

log.info(`New health monitor check added with id '${id}'`);
}

/**
* Cleanup health monitor by clearing all timers and resetting the internal state.
*/

cleanup() {
Object.values(this.checks).forEach(clearTimeout);

this.checks = {};
this.globalState = HealthMonitor.states.UNKNOWN;
this.states = {};
}

/**
* Handles state changes.
*/

updateState({ id, state }) {
if (this.states[id] === state) {
return;
}

log.info({ id, newState: state, oldState: this.states[id] }, 'Component health status has changed');

this.states[id] = state;

// The sorted states array makes it so that the state at the end of the array is the relevant one.
// The global state is:
// - UNKNOWN if one exists.
// - UNHEALTHY if one exists and there are no UNKNOWN states.
// - HEALTHY if there are no UNKNOWN and UNHEALTHY states.
const [globalState] = Object.values(this.states).sort((left, right) => {
return left < right ? 1 : -1;
});

if (this.globalState === globalState) {
return;
}

log.info({ newState: globalState, oldState: this.globalState }, 'Global health status has changed');

this.globalState = globalState;
}
}

/**
* Health states.
*/

HealthMonitor.states = {
HEALTHY: 'healthy',
UNHEALTHY: 'unhealthy',
UNKNOWN: 'unknown'
};

/**
* Export `HealthMonitor` class.
*/

module.exports = HealthMonitor;
22 changes: 22 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Module dependencies.
*/

const HealthMonitor = require('./health-monitor');
const log = require('debugnyan')('process-manager');
const utils = require('./utils');

Expand All @@ -25,12 +26,21 @@ class ProcessManager {
constructor() {
this.errors = [];
this.forceShutdown = utils.deferred();
this.healthMonitor = new HealthMonitor();
this.hooks = [];
this.running = [];
this.terminating = false;
this.timeout = 30000;
}

/**
* Add health monitor check.
*/

addHealthCheck(...args) {
this.healthMonitor.addCheck(...args);
}

/**
* Add hook.
*/
Expand Down Expand Up @@ -75,6 +85,17 @@ class ProcessManager {
process.exit();
}

/**
* Get health monitor status.
*/

getHealthStatus() {
return {
global: this.healthMonitor.globalState,
individual: this.healthMonitor.states
};
}

/**
* Call all handlers for a hook.
*/
Expand Down Expand Up @@ -186,6 +207,7 @@ class ProcessManager {
.then(() => log.info('All running instances have stopped'))
.then(() => this.hook('drain'))
.then(() => log.info(`${this.hooks.filter(hook => hook.type === 'drain').length} server(s) drained`))
.then(() => this.healthMonitor.cleanup())
.then(() => this.hook('disconnect'))
.then(() => log.info(`${this.hooks.filter(hook => hook.type === 'disconnect').length} service(s) disconnected`))
.then(() => this.hook('exit', this.errors));
Expand Down
Loading

0 comments on commit a5fee1e

Please sign in to comment.