Skip to content

Commit

Permalink
Merge pull request #110 from nestjs/feature/memory-health-indicator
Browse files Browse the repository at this point in the history
Feature/memory health indicator
  • Loading branch information
BrunnerLivio authored Apr 7, 2019
2 parents a8478f5 + 085e088 commit 67a2400
Show file tree
Hide file tree
Showing 19 changed files with 6,530 additions and 53 deletions.
9 changes: 6 additions & 3 deletions e2e/health-checks/disk.health.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ describe('Disk Health', () => {
{
url: '/health',
healthIndicators: [
async () => disk.check('disk', { path: '/', threshold: free + 1 }),
async () =>
disk.checkStorage('disk', { path: '/', threshold: free + 1 }),
],
},
],
Expand Down Expand Up @@ -46,7 +47,8 @@ describe('Disk Health', () => {
{
url: '/health',
healthIndicators: [
async () => disk.check('disk', { path: '/', threshold: 0 }),
async () =>
disk.checkStorage('disk', { path: '/', threshold: 0 }),
],
},
],
Expand Down Expand Up @@ -74,7 +76,8 @@ describe('Disk Health', () => {
{
url: '/health',
healthIndicators: [
async () => disk.check('disk', { path: '/', thresholdPercent }),
async () =>
disk.checkStorage('disk', { path: '/', thresholdPercent }),
],
},
],
Expand Down
103 changes: 103 additions & 0 deletions e2e/health-checks/memory.health.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { INestApplication } from '@nestjs/common';

import Axios from 'axios';
import { MemoryHealthIndicator, TerminusModuleOptions } from '../../lib';
import { bootstrapModule } from '../helper/bootstrap-module';

describe('Memory Health', () => {
let app: INestApplication;
let port: number;

describe('checkRss', () => {
let getTerminusOptions: (
memory: MemoryHealthIndicator,
) => TerminusModuleOptions;
beforeEach(async () => {
getTerminusOptions = (
memory: MemoryHealthIndicator,
): TerminusModuleOptions => ({
endpoints: [
{
url: '/health',
healthIndicators: [
async () => {
const { rss } = process.memoryUsage();
return memory.checkHeap('memory_rss', rss + 1);
},
],
},
],
});
});

it('should check if the rss threshold is not exceeded', async () => {
[app, port] = await bootstrapModule({
inject: [MemoryHealthIndicator],
useFactory: getTerminusOptions,
});
const response = await Axios.get(`http://0.0.0.0:${port}/health`);
expect(response.status).toBe(200);
expect(response.data).toEqual({
status: 'ok',
info: { memory_rss: { status: 'up' } },
});
});
});

describe('checkHeap', () => {
it('should check if the heap threshold is not exceeded', async () => {
const getTerminusOptions = (
memory: MemoryHealthIndicator,
): TerminusModuleOptions => ({
endpoints: [
{
url: '/health',
healthIndicators: [
async () =>
memory.checkHeap('memory_heap', 1 * 1024 * 1024 * 1024 * 1024),
],
},
],
});

[app, port] = await bootstrapModule({
inject: [MemoryHealthIndicator],
useFactory: getTerminusOptions,
});
const response = await Axios.get(`http://0.0.0.0:${port}/health`);
expect(response.status).toBe(200);
expect(response.data).toEqual({
status: 'ok',
info: { memory_heap: { status: 'up' } },
});
});

it('should check if correctly displays a heap exceeded error', async () => {
[app, port] = await bootstrapModule({
inject: [MemoryHealthIndicator],
useFactory: (disk: MemoryHealthIndicator): TerminusModuleOptions => ({
endpoints: [
{
url: '/health',
healthIndicators: [async () => disk.checkHeap('memory_heap', 0)],
},
],
}),
});

try {
await Axios.get(`http://0.0.0.0:${port}/health`);
} catch (error) {
expect(error.response.status).toBe(503);
expect(error.response.data).toEqual({
status: 'error',
error: {
memory_heap: { status: 'down', message: expect.any(String) },
},
});
}
});
});

afterEach(async () => await app.close());
});
16 changes: 0 additions & 16 deletions lib/errors/disk-threshold.error.ts

This file was deleted.

1 change: 1 addition & 0 deletions lib/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './connection-not-found.error';
export * from './timeout-error';
export * from './storage-exceeded.error';
4 changes: 2 additions & 2 deletions lib/errors/messages.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ export const CONNECTION_NOT_FOUND =
export const TIMEOUT_EXCEEDED = (timeout: number) =>
`timeout of ${timeout.toString()}ms exceeded`;

export const DISK_STORAGE_EXCEEDED =
'Available disk storage exceeded the set threshold';
export const STORAGE_EXCEEDED = (keyword: string) =>
`Used ${keyword} exceeded the set threshold`;
18 changes: 18 additions & 0 deletions lib/errors/storage-exceeded.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { HealthCheckError } from '@godaddy/terminus';
import { STORAGE_EXCEEDED } from './messages.constant';

/**
* Error which gets thrown when the given storage threshold
* has exceeded.
*/
export class StorageExceededError extends HealthCheckError {
/**
* Initializes the error
*
* @param {string} keyword The keyword (heap, rss, disk e.g.)
* @param {unknown} cause The cause of the health check error
*/
constructor(keyword: string, cause: unknown) {
super(STORAGE_EXCEEDED(keyword), cause);
}
}
50 changes: 18 additions & 32 deletions lib/health-indicators/disk/disk.health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import * as checkdiskspace from 'check-disk-space';
import { HealthIndicatorResult } from '../../interfaces';
import { HealthIndicator } from '../health-indicator';
import { CHECKDISKSPACE_LIB } from '../../terminus.constants';
import { DiskStorageExceededError } from '../../errors/disk-threshold.error';
import { DISK_STORAGE_EXCEEDED } from '../../errors/messages.constant';
import { StorageExceededError } from '../../errors';
import { STORAGE_EXCEEDED } from '../../errors/messages.constant';
import {
DiskHealthIndicatorOptions,
DiskOptionsWithThresholdPercent,
Expand Down Expand Up @@ -54,17 +54,24 @@ export class DiskHealthIndicator extends HealthIndicator {
}

/**
* Checks the storage space and returns the status
* @param key The key which will be used for the result object
* @param options The options of the `DiskHealthIndicator`
* Checks if the size of the given size has exceeded the
* given threshold
*
* @throws {DiskStorageExceededError} In case the disk storage has exceeded the given threshold
* @param key The key which will be used for the result object
*
* @private
* @throws {HealthCheckError} In case the health indicator failed
* @throws {StorageExceededError} In case the disk storage has exceeded the given threshold
*
* @returns {Promise<HealthIndicatorResult>} The result of the health indicator check
*
* @example
* // The used disk storage should not exceed 250 GB
* diskHealthIndicator.checkStorage('storage', { threshold: 250 * 1024 * 1024 * 1024, path: '/' });
* @example
* // The used disk storage should not exceed 50% of the full disk size
* diskHealthIndicator.checkStorage('storage', { thresholdPercent: 0.5, path: 'C:\\' });
*/
private async checkStorage(
public async checkStorage(
key: string,
options: DiskHealthIndicatorOptions,
): Promise<HealthIndicatorResult> {
Expand All @@ -79,34 +86,13 @@ export class DiskHealthIndicator extends HealthIndicator {
}

if (!isHealthy) {
throw new DiskStorageExceededError(
throw new StorageExceededError(
'disk storage',
this.getStatus(key, false, {
message: DISK_STORAGE_EXCEEDED,
message: STORAGE_EXCEEDED('disk storage'),
}),
);
}
return this.getStatus(key, true);
}

/**
* Checks if the given url respons in the given timeout
* and returns a result object corresponding to the result
* @param key The key which will be used for the result object
*
* @throws {HealthCheckError} In case the health indicator failed
* @throws {DiskStorageExceededError} In case the disk storage has exceeded the given threshold
*
* @returns {Promise<HealthIndicatorResult>} The result of the health indicator check
*
* @example
* diskHealthIndicator.checkStorage('storage', { threshold: 120000000000, path: '/' });
* @example
* diskHealthIndicator.checkSotrage('storage', { thresholdPercent: 0.5, path: 'C:\\' });
*/
async check(
key: string,
options: DiskHealthIndicatorOptions,
): Promise<HealthIndicatorResult> {
return await this.checkStorage(key, options);
}
}
1 change: 1 addition & 0 deletions lib/health-indicators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export * from './database/mongoose.health';
export * from './database/typeorm.health';
export * from './microservice/microservice.health';
export * from './disk';
export * from './memory';

export * from './health-indicator';
1 change: 1 addition & 0 deletions lib/health-indicators/memory/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './memory.health';
83 changes: 83 additions & 0 deletions lib/health-indicators/memory/memory.health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Injectable } from '@nestjs/common';

import { HealthIndicatorResult } from '../../interfaces';
import { HealthIndicator } from '../health-indicator';
import { STORAGE_EXCEEDED } from '../../errors/messages.constant';
import { StorageExceededError } from '../../errors';

/**
* The MemoryHealthIndicator contains checks which are related
* to the memory storage of the current running machine
*
* @public
*/
@Injectable()
export class MemoryHealthIndicator extends HealthIndicator {
/**
* Checks the heap space and returns the status
*
* @param key The key which will be used for the result object
* @param options The options of the `MemoryHealthIndicator`
*
* @throws {StorageExceededError} In case the heap has exceeded the given threshold
*
* @public
*
* @returns {Promise<HealthIndicatorResult>} The result of the health indicator check
*
* @example
* // The process should not use more than 150MB memory
* memoryHealthIndicator.checkRSS('memory_heap', 150 * 1024 * 1024);
*/
public async checkHeap(
key: string,
heapUsedThreshold: number,
): Promise<HealthIndicatorResult> {
const { heapUsed } = process.memoryUsage();

if (heapUsedThreshold < heapUsed) {
throw new StorageExceededError(
'heap',
this.getStatus(key, false, {
message: STORAGE_EXCEEDED('heap'),
}),
);
}

return this.getStatus(key, true);
}

/**
* Checks the rss space and returns the status
*
* @param key The key which will be used for the result object
* @param options The options of the `MemoryHealthIndicator`
*
* @throws {StorageExceededError} In case the rss has exceeded the given threshold
*
* @public
*
* @returns {Promise<HealthIndicatorResult>} The result of the health indicator check
*
* @example
* // The process should not have more than 150MB allocated
* memoryHealthIndicator.checkRSS('memory_rss', 150 * 1024 * 1024);
*/
public async checkRSS(
key: string,
rssThreshold: number,
): Promise<HealthIndicatorResult> {
const { rss } = process.memoryUsage();

if (rssThreshold < rss) {
throw new StorageExceededError(
'rss',
this.getStatus(key, false, {
message: STORAGE_EXCEEDED('rss'),
}),
);
}

return this.getStatus(key, true);
}
}
5 changes: 5 additions & 0 deletions lib/terminus-core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
DNSHealthIndicator,
MicroserviceHealthIndicator,
DiskHealthIndicator,
MemoryHealthIndicator,
} from './health-indicators';
import { DiskusageLibProvider } from './health-indicators/disk/diskusage-lib.provider';

Expand Down Expand Up @@ -55,13 +56,15 @@ export class TerminusCoreModule {
MicroserviceHealthIndicator,
DiskusageLibProvider,
DiskHealthIndicator,
MemoryHealthIndicator,
],
exports: [
TypeOrmHealthIndicator,
MongooseHealthIndicator,
DNSHealthIndicator,
MicroserviceHealthIndicator,
DiskHealthIndicator,
MemoryHealthIndicator,
],
};
}
Expand All @@ -86,13 +89,15 @@ export class TerminusCoreModule {
MicroserviceHealthIndicator,
DiskusageLibProvider,
DiskHealthIndicator,
MemoryHealthIndicator,
],
exports: [
TypeOrmHealthIndicator,
DNSHealthIndicator,
MongooseHealthIndicator,
MicroserviceHealthIndicator,
DiskHealthIndicator,
MemoryHealthIndicator,
],
};
}
Expand Down
Loading

0 comments on commit 67a2400

Please sign in to comment.