Skip to content

Commit

Permalink
feat: add notification when disk is getting full (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael1011 authored Sep 2, 2019
1 parent 6fdc4d4 commit 47843ba
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 5 deletions.
4 changes: 4 additions & 0 deletions lib/notifications/DiscordClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ class DiscordClient extends EventEmitter {
}
}

public sendAlert = async (alert: string) => {
await this.sendMessage(`:rotating_light: **Alert** :rotating_light:\n\n${alert}`);
}

private listenForMessages = async () => {
if (this.channel) {
this.client.on('message', (message: Message) => {
Expand Down
36 changes: 36 additions & 0 deletions lib/notifications/DiskUsageChecker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { platform } from 'os';
import { check } from 'diskusage';
import Logger from '../Logger';
import DiscordClient from './DiscordClient';

class DiskUsageChecker {
private static warningThreshold = 0.9;
private static rootDir = platform() !== 'win32' ? '/' : 'C:';

constructor(private logger: Logger, private discord: DiscordClient) {}

public checkUsage = async () => {
const { available, total } = await check(DiskUsageChecker.rootDir);

const used = total - available;
const usedPercentage = used / total;

if (usedPercentage >= DiskUsageChecker.warningThreshold) {
const message = `Disk usage is **${this.formatNumber(usedPercentage * 100)}%**: ` +
`**${this.formatNumber(this.convertToGb(available))} GB** of **${this.formatNumber(this.convertToGb(total))} GB** available`;

this.logger.warn(message);
await this.discord.sendAlert(message);
}
}

private formatNumber = (toFormat: number) => {
return Number(toFormat.toFixed(2));
}

private convertToGb = (bytes: number) => {
return bytes / 1073741824;
}
}

export default DiskUsageChecker;
17 changes: 12 additions & 5 deletions lib/notifications/NotificationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Swap from '../db/models/Swap';
import Service from '../service/Service';
import DiscordClient from './DiscordClient';
import CommandHandler from './CommandHandler';
import DiskUsageChecker from './DiskUsageChecker';
import ReverseSwap from '../db/models/ReverseSwap';
import BackupScheduler from '../backup/BackupScheduler';
import { satoshisToCoins } from '../DenominationConverter';
Expand All @@ -17,10 +18,12 @@ import {
getLightningCurrency,
} from '../Utils';

// TODO: test balance and service alerts
// TODO: use events instead of intervals to check connections and balances
class NotificationProvider {
private timer!: any;
private discord: DiscordClient;
private diskUsageChecker: DiskUsageChecker;

// These Sets contain the symbols for which an alert notification was sent
private walletAlerts = new Set<string>();
Expand Down Expand Up @@ -56,6 +59,8 @@ class NotificationProvider {
this.service,
this.backup,
);

this.diskUsageChecker = new DiskUsageChecker(this.logger, this.discord);
}

public init = async () => {
Expand All @@ -66,8 +71,11 @@ class NotificationProvider {
this.logger.verbose('Connected to Discord');

const check = async () => {
await this.checkConnections();
await this.checkBalances();
await Promise.all([
this.checkBalances(),
this.checkConnections(),
this.diskUsageChecker.checkUsage(),
]);
};

await check();
Expand Down Expand Up @@ -241,16 +249,15 @@ class NotificationProvider {
this.logger.warn(`${currency} ${balanceName} balance is less than ${threshold}: ${balance}`);

// tslint:disable-next-line:prefer-template
let message = ':rotating_light: **Alert** :rotating_light:\n\n' +
`The ${currency} ${balanceName} balance of ${actual} ${currency} is less than expected ${expected} ${currency}\n\n` +
let message = `The ${currency} ${balanceName} balance of ${actual} ${currency} is less than expected ${expected} ${currency}\n\n` +
`Funds missing: **${missing} ${currency}**`;

if (isWallet) {
const address = await this.service.newAddress(currency, OutputType.COMPATIBILITY);
message += `\nDeposit address: **${address}**`;
}

await this.discord.sendMessage(message);
await this.discord.sendAlert(message);
}

private sendRelief = async (currency: string, balance: number, threshold: number, isWallet: boolean, isLocal?: boolean) => {
Expand Down
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"cors": "^2.8.5",
"cross-os": "^1.3.0",
"discord.js": "^11.5.1",
"diskusage": "^1.1.3",
"express": "^4.17.1",
"grpc": "1.23.3",
"node-forge": "^0.8.5",
Expand Down
95 changes: 95 additions & 0 deletions test/unit/notifications/DiskUsageChecker.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { platform } from 'os';
import { DiskUsage } from 'diskusage';
import Logger from '../../../lib/Logger';
import DiscordClient from '../../../lib/notifications/DiscordClient';
import DiskUsageChecker from '../../../lib/notifications/DiskUsageChecker';

const mockSendAlert = jest.fn().mockImplementation(() => Promise.resolve());

jest.mock('../../../lib/notifications/DiscordClient', () => {
return jest.fn().mockImplementation(() => ({
sendAlert: mockSendAlert,
}));
});

const mockedDiscordClient = <jest.Mock<DiscordClient>><any>DiscordClient;

const gigabyte = 1073741824;

let diskUsage: DiskUsage = {
free: 2 * gigabyte,
total: 10 * gigabyte,
available: 1 * gigabyte,
};

const mockCheck = jest.fn().mockImplementation(() => diskUsage);

jest.mock('diskusage', () => {
return {
check: async () => {
return mockCheck();
},
};
});

describe('DiskUsageChecker', () => {
const discordClient = mockedDiscordClient();
const checker = new DiskUsageChecker(Logger.disabledLogger, discordClient);

beforeEach(() => {
mockSendAlert.mockClear();
});

test('should get correct root path', () => {
if (platform() !== 'win32') {
expect(DiskUsageChecker['rootDir']).toEqual('/');
} else {
expect(DiskUsageChecker['rootDir']).toEqual('C:');
}
});

test('should format numbers', () => {
const formatNumber = checker['formatNumber'];

expect(formatNumber(100.123)).toEqual(100.12);
expect(formatNumber(100.0000123)).toEqual(100);
});

test('should convert bytes to gigabytes', () => {
const convertToGb = checker['convertToGb'];

expect(convertToGb(536870912)).toEqual(0.5);
expect(convertToGb(1073741824)).toEqual(1);
expect(convertToGb(2147483648)).toEqual(2);
});

test('should send a warning if disk usage is 90% or greater', async () => {
await checker.checkUsage();

expect(mockSendAlert).toHaveBeenCalledTimes(1);
expect(mockSendAlert).toHaveBeenCalledWith('Disk usage is **90%**: **1 GB** of **10 GB** available');

diskUsage = {
available: 0,
free: 5 * gigabyte,
total: 20 * gigabyte,
};

await checker.checkUsage();

expect(mockSendAlert).toHaveBeenCalledTimes(2);
expect(mockSendAlert).toHaveBeenCalledWith('Disk usage is **100%**: **0 GB** of **20 GB** available');
});

test('should not send a warning if disk usage is less than 90%', async () => {
diskUsage = {
free: 5.05 * gigabyte,
available: 5 * gigabyte,
total: 10 * gigabyte,
};

await checker.checkUsage();

expect(mockSendAlert).toHaveBeenCalledTimes(0);
});
});

0 comments on commit 47843ba

Please sign in to comment.