Skip to content

Commit

Permalink
feat: finish container and add system checks
Browse files Browse the repository at this point in the history
  • Loading branch information
shumkov committed Sep 4, 2024
1 parent e9727be commit e2691b2
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 73 deletions.
8 changes: 8 additions & 0 deletions packages/dashmate/src/createDIContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ import verifySystemRequirementsTaskFactory
import analyseSamplesTaskFactory from './listr/tasks/doctor/analyseSamplesTaskFactory.js';
import collectSamplesTaskFactory from './listr/tasks/doctor/collectSamplesTaskFactory.js';
import prescriptionTaskFactory from './listr/tasks/doctor/prescriptionTaskFactory.js';
import verifySystemRequirementsFactory from './doctor/verifySystemRequirementsFactory.js';

/**
* @param {Object} [options]
Expand Down Expand Up @@ -314,6 +315,13 @@ export default async function createDIContainer(options = {}) {
prescriptionTask: asFunction(prescriptionTaskFactory).singleton(),
});

/**
* Doctor
*/
container.register({
verifySystemRequirements: asFunction(verifySystemRequirementsFactory),
});

/**
* Helper
*/
Expand Down
94 changes: 94 additions & 0 deletions packages/dashmate/src/doctor/verifySystemRequirementsFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* @return {verifySystemRequirements}
*/
function verifySystemRequirementsFactory() {
/**
* @typedef {Function} verifySystemRequirements
* @param {Object} systemInfo
* @param {Object} systemInfo.dockerSystemInfo
* @param {Object} systemInfo.cpu
* @param {Object} systemInfo.memory
* @param {Object} systemInfo.diskSpace
* @param {boolean} isHP
* @param {Object} [overrideRequirements]
* @param {Number} [overrideRequirements.diskSpace]
* @returns {Object}
*/
function verifySystemRequirements(
{
dockerSystemInfo,
cpu,
memory,
diskSpace,
},
isHP,
overrideRequirements = {},
) {
const MINIMUM_CPU_CORES = isHP ? 4 : 2;
const MINIMUM_CPU_FREQUENCY = 2.4; // GHz
const MINIMUM_RAM = isHP ? 8 : 4; // GB
const MINIMUM_DISK_SPACE = overrideRequirements.diskSpace ?? (isHP ? 200 : 100); // GB

const warnings = {};

// CPU cores
const cpuCores = dockerSystemInfo?.NCPU ?? cpu?.cores;

if (cpuCores) {
if (cpuCores < MINIMUM_CPU_CORES) {
warnings.cpuCores = `${cpuCores} CPU cores detected. At least ${MINIMUM_CPU_CORES} are required`;
}
} else if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.warn('Can\'t get CPU cores information');
}

// Memory
const totalMemory = dockerSystemInfo?.MemTotal ?? memory?.total;

if (totalMemory) {
const totalMemoryGb = totalMemory / (1024 ** 3); // Convert to GB

if (totalMemoryGb < MINIMUM_RAM) {
warnings.memory = `${totalMemoryGb.toFixed(2)}GB RAM detected. At least ${MINIMUM_RAM}GB is required`;
}
} else if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.warn('Can\'t get memory information');
}

// CPU speed
if (cpu && cpu.speed !== 0) {
if (cpu.speed < MINIMUM_CPU_FREQUENCY) {
warnings.cpuSpeed = `${cpu.speed.toFixed(1)}GHz CPU frequency detected. At least ${MINIMUM_CPU_FREQUENCY}GHz is required`;
}
} else if (process.env.DEBUG) {
// eslint-disable-next-line no-console
console.warn('Can\'t get CPU frequency');
}

// Check swap information
if (memory) {
const swapTotalGb = (memory.swaptotal / (1024 ** 3)); // Convert bytes to GB

if (swapTotalGb < 2) {
warnings.swap = `Swap space is ${swapTotalGb.toFixed(2)}GB. 2GB is recommended`;
}
}

// Get disk usage info
if (diskSpace) {
const availableDiskSpace = diskSpace.available / (1024 ** 3); // Convert to GB

if (availableDiskSpace < MINIMUM_DISK_SPACE) {
warnings.diskSpace = `${availableDiskSpace.toFixed(2)}GB available disk space detected. At least ${MINIMUM_DISK_SPACE}GB is required`;
}
}

return warnings;
}

return verifySystemRequirements;
}

module.exports = verifySystemRequirementsFactory;
186 changes: 178 additions & 8 deletions packages/dashmate/src/listr/tasks/doctor/analyseSamplesTaskFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,126 @@ import { Listr } from 'listr2';
*
* @param {DockerCompose} dockerCompose
* @param {getServiceList} getServiceList
* @param {verifySystemRequirements} verifySystemRequirements
* @return {analyseSamplesTask}
*/
export default function analyseSamplesTaskFactory(dockerCompose, getServiceList) {
export default function analyseSamplesTaskFactory(
dockerCompose,
getServiceList,
verifySystemRequirements,
) {
/**
* @typedef {function} analyseSamplesTask
* @param config
* @return {Listr}
*/
function analyseSamplesTask(config) {
return new Listr([
{
title: 'System resources',
task: async (ctx) => {
const {
cpu,
dockerSystemInfo,
currentLoad,
diskSpace,
fsOpenFiles,
memory,
} = ctx.samples.getSystemInfo();

ctx.systemResourceProblems = verifySystemRequirements(
{
dockerSystemInfo,
cpu,
memory,
diskSpace,
},
config.get('platform.enabled'),
{
diskSpace: 5,
},
);

return new Listr([
{
title: 'CPU cores',
task: (_ctx, task) => {
if (ctx.systemResourceProblems.cpuCores) {
throw new Error(task.title);
}
},
},
{
title: 'CPU speed',
task: (_ctx, task) => {
if (ctx.systemResourceProblems.cpuSpeed) {
throw new Error(task.title);
}
},
},
{
title: 'CPU load',
task: (_ctx, task) => {
if (currentLoad.avgLoad > 0.8) {
ctx.systemResourceProblems.avgLoad = `Average system load ${currentLoad.avgLoad.toFixed(2)} is higher than normal. Consider to upgrade CPU.`;
throw new Error(task.title);
}
},
},
{
title: 'Total RAM',
task: (_ctx, task) => {
if (ctx.systemResourceProblems.memory) {
throw new Error(task.title);
}
},
},
{
title: 'Free RAM',
enabled: Number.isInteger(memory.free),
task: (_ctx, task) => {
if (memory.free) {
const memoryGb = memory.free / (1024 ** 3);
if (memoryGb < 0.5) {
ctx.systemResourceProblems.freeMemory = `Only ${memoryGb.toFixed(1)}GB RAM is available. Consider to upgrade RAM.`;
throw new Error(task.title);
}
}
},
},
{
title: 'File descriptors',
enabled: fsOpenFiles?.allocated && fsOpenFiles?.max,
task: (_ctx, task) => {
const available = fsOpenFiles.max - fsOpenFiles.allocated;
if (available < 1000) {
ctx.systemResourceProblems.fsOpenFiles = `${available} available file descriptors left. Consider to increase max limit.`;
throw new Error(task.title);
}
},
},
{
title: 'Swap',
task: (_ctx, task) => {
if (ctx.systemResourceProblems.swap) {
throw new Error(task.title);
}
},
},
{
title: 'Disk space',
task: (_ctx, task) => {
if (ctx.systemResourceProblems.diskSpace) {
throw new Error(task.title);
}
},
},
// TODO: Disk IO
], {
exitOnError: false,
});
},
},
{
title: 'Docker is started',
task: async (ctx, task) => {
Expand All @@ -39,8 +149,8 @@ export default function analyseSamplesTaskFactory(dockerCompose, getServiceList)
ctx.servicesOOMKilled = [];

return new Listr(
services.map((service) => {
return {
services.map((service) => (
{
title: service.title,
task: () => {
const dockerInspect = ctx.samples.getServiceInfo(service.name, 'dockerInspect');
Expand All @@ -50,17 +160,27 @@ export default function analyseSamplesTaskFactory(dockerCompose, getServiceList)
service,
message: dockerInspect.message,
});
} else if (dockerInspect.State.Restarting) {
// TODO: ctx.servicesFailed
//dockerInspect.State.Started = dockerInspect.State.Started ?? false;
} else if (
dockerInspect.State.Restarting === true
&& dockerInspect.State.ExitCode !== 0
) {
ctx.servicesFailed.push({
service,
message: dockerInspect.State.Error,
code: dockerInspect.State.ExitCode,
});
} else if (dockerInspect.State.OOMKilled === true) {
ctx.servicesOOMKilled.push({
service,
});
} else {
return;
}

throw new Error(service.title);
},
};
}),
}
)),
{
exitOnError: false,
},
Expand All @@ -82,6 +202,56 @@ export default function analyseSamplesTaskFactory(dockerCompose, getServiceList)

ctx.problems.push(problem);
}

if (ctx.servicesFailed.length > 0) {
let problem;
if (ctx.servicesFailed.length === 1) {
const failedService = ctx.servicesFailed[0];

problem = chalk`Service ${failedService.service.title} failed with an error code ${failedService.code}`;

if (failedService.message?.length > 0) {
problem += `and message: ${failedService.message}`;
}

problem += '.';
} else {
problem = chalk`${ctx.servicesFailed.length} services failed:`;

ctx.servicesFailed.map((failedService) => {
let output = chalk` ${failedService.service.title} failed with an error code ${failedService.code}`;

if (failedService.message?.length > 0) {
output += `and message: ${failedService.message}`;
}

output += '.';

return output;
}).join('\n');
}

problem += chalk`\n\nPlease check corresponding logs or share them with Dash Core Group`;

ctx.problems.push(problem);
}

if (ctx.servicesOOMKilled.length > 0) {
let problem;
if (ctx.servicesNotStarted.length === 1) {
problem = chalk`Service ${ctx.servicesNotStarted[0].service.title} is killed due to lack of memory.`;
} else {
problem = chalk`Services ${ctx.servicesNotStarted.map((e) => e.service.title).join(', ')} aren't killed due to lack of memory.`;
}

problem += chalk`\n\nMake sure you have enough memory to run the node.`;

ctx.problems.push(problem);
}

if (ctx.systemResourceProblems.length > 0) {

}
},
},
// TODO: dont have priavate ky to sign
Expand Down
Loading

0 comments on commit e2691b2

Please sign in to comment.