234 lines
6.1 KiB
TypeScript
234 lines
6.1 KiB
TypeScript
|
|
/**
|
||
|
|
* System Info
|
||
|
|
*
|
||
|
|
* Gathers system information including CPU, RAM, OS, and Docker status.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { exec } from 'node:child_process';
|
||
|
|
import { promisify } from 'node:util';
|
||
|
|
import * as os from 'node:os';
|
||
|
|
import type { ISystemInfo } from '../interfaces/gpu.ts';
|
||
|
|
import { GpuDetector } from './gpu-detector.ts';
|
||
|
|
import { logger } from '../logger.ts';
|
||
|
|
|
||
|
|
const execAsync = promisify(exec);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* System Info class for gathering system information
|
||
|
|
*/
|
||
|
|
export class SystemInfo {
|
||
|
|
private gpuDetector: GpuDetector;
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.gpuDetector = new GpuDetector();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get complete system information
|
||
|
|
*/
|
||
|
|
public async getSystemInfo(): Promise<ISystemInfo> {
|
||
|
|
const [gpus, dockerVersion, nvidiaContainerVersion, kernelVersion] = await Promise.all([
|
||
|
|
this.gpuDetector.detectGpus(),
|
||
|
|
this.getDockerVersion(),
|
||
|
|
this.getNvidiaContainerVersion(),
|
||
|
|
this.getKernelVersion(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
return {
|
||
|
|
hostname: os.hostname(),
|
||
|
|
cpuModel: this.getCpuModel(),
|
||
|
|
cpuCores: os.cpus().length,
|
||
|
|
ramTotal: Math.round(os.totalmem() / (1024 * 1024)),
|
||
|
|
ramAvailable: Math.round(os.freemem() / (1024 * 1024)),
|
||
|
|
os: this.getOsInfo(),
|
||
|
|
kernelVersion,
|
||
|
|
gpus,
|
||
|
|
dockerVersion,
|
||
|
|
nvidiaContainerVersion,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get CPU model name
|
||
|
|
*/
|
||
|
|
private getCpuModel(): string {
|
||
|
|
const cpus = os.cpus();
|
||
|
|
if (cpus.length > 0) {
|
||
|
|
return cpus[0].model;
|
||
|
|
}
|
||
|
|
return 'Unknown CPU';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get OS information string
|
||
|
|
*/
|
||
|
|
private getOsInfo(): string {
|
||
|
|
const platform = os.platform();
|
||
|
|
const release = os.release();
|
||
|
|
|
||
|
|
if (platform === 'linux') {
|
||
|
|
return `Linux ${release}`;
|
||
|
|
} else if (platform === 'darwin') {
|
||
|
|
return `macOS ${release}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
return `${platform} ${release}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get kernel version
|
||
|
|
*/
|
||
|
|
private async getKernelVersion(): Promise<string> {
|
||
|
|
try {
|
||
|
|
const { stdout } = await execAsync('uname -r', { timeout: 5000 });
|
||
|
|
return stdout.trim();
|
||
|
|
} catch {
|
||
|
|
return os.release();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get Docker version
|
||
|
|
*/
|
||
|
|
private async getDockerVersion(): Promise<string | undefined> {
|
||
|
|
try {
|
||
|
|
const { stdout } = await execAsync('docker --version', { timeout: 5000 });
|
||
|
|
const match = stdout.match(/Docker version (\d+\.\d+\.\d+)/);
|
||
|
|
return match ? match[1] : stdout.trim();
|
||
|
|
} catch {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get NVIDIA Container Toolkit version
|
||
|
|
*/
|
||
|
|
private async getNvidiaContainerVersion(): Promise<string | undefined> {
|
||
|
|
try {
|
||
|
|
const { stdout } = await execAsync('nvidia-container-cli --version 2>&1 | head -1', { timeout: 5000 });
|
||
|
|
const match = stdout.match(/version (\d+\.\d+\.\d+)/);
|
||
|
|
return match ? match[1] : undefined;
|
||
|
|
} catch {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if Docker is running
|
||
|
|
*/
|
||
|
|
public async isDockerRunning(): Promise<boolean> {
|
||
|
|
try {
|
||
|
|
await execAsync('docker info', { timeout: 5000 });
|
||
|
|
return true;
|
||
|
|
} catch {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if NVIDIA Docker runtime is available
|
||
|
|
*/
|
||
|
|
public async isNvidiaRuntimeAvailable(): Promise<boolean> {
|
||
|
|
try {
|
||
|
|
const { stdout } = await execAsync('docker info --format "{{.Runtimes}}"', { timeout: 5000 });
|
||
|
|
return stdout.includes('nvidia');
|
||
|
|
} catch {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if Podman is available
|
||
|
|
*/
|
||
|
|
public async isPodmanAvailable(): Promise<boolean> {
|
||
|
|
try {
|
||
|
|
await execAsync('podman --version', { timeout: 5000 });
|
||
|
|
return true;
|
||
|
|
} catch {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get available disk space in the data directory
|
||
|
|
* @param path Directory to check
|
||
|
|
* @returns Available space in MB
|
||
|
|
*/
|
||
|
|
public async getAvailableDiskSpace(path: string = '/var/lib'): Promise<number> {
|
||
|
|
try {
|
||
|
|
const { stdout } = await execAsync(`df -m "${path}" | tail -1 | awk '{print $4}'`, { timeout: 5000 });
|
||
|
|
return parseInt(stdout.trim(), 10) || 0;
|
||
|
|
} catch {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get system memory usage
|
||
|
|
*/
|
||
|
|
public getMemoryUsage(): { total: number; used: number; available: number; percent: number } {
|
||
|
|
const total = Math.round(os.totalmem() / (1024 * 1024));
|
||
|
|
const available = Math.round(os.freemem() / (1024 * 1024));
|
||
|
|
const used = total - available;
|
||
|
|
const percent = Math.round((used / total) * 100);
|
||
|
|
|
||
|
|
return { total, used, available, percent };
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get system load average
|
||
|
|
*/
|
||
|
|
public getLoadAverage(): { load1: number; load5: number; load15: number } {
|
||
|
|
const [load1, load5, load15] = os.loadavg();
|
||
|
|
return {
|
||
|
|
load1: Math.round(load1 * 100) / 100,
|
||
|
|
load5: Math.round(load5 * 100) / 100,
|
||
|
|
load15: Math.round(load15 * 100) / 100,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Print system info summary to logger
|
||
|
|
*/
|
||
|
|
public async printSystemInfo(): Promise<void> {
|
||
|
|
const info = await this.getSystemInfo();
|
||
|
|
|
||
|
|
logger.logBoxTitle('System Information', 70, 'info');
|
||
|
|
logger.logBoxLine(`Hostname: ${info.hostname}`);
|
||
|
|
logger.logBoxLine(`OS: ${info.os}`);
|
||
|
|
logger.logBoxLine(`Kernel: ${info.kernelVersion}`);
|
||
|
|
logger.logBoxLine(`CPU: ${info.cpuModel} (${info.cpuCores} cores)`);
|
||
|
|
logger.logBoxLine(`RAM: ${Math.round(info.ramTotal / 1024)} GB total, ${Math.round(info.ramAvailable / 1024)} GB available`);
|
||
|
|
logger.logBoxLine('');
|
||
|
|
|
||
|
|
if (info.dockerVersion) {
|
||
|
|
logger.logBoxLine(`Docker: v${info.dockerVersion}`);
|
||
|
|
} else {
|
||
|
|
logger.logBoxLine('Docker: Not installed');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (info.nvidiaContainerVersion) {
|
||
|
|
logger.logBoxLine(`NVIDIA Container Toolkit: v${info.nvidiaContainerVersion}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.logBoxLine('');
|
||
|
|
logger.logBoxLine(`GPUs Detected: ${info.gpus.length}`);
|
||
|
|
|
||
|
|
for (const gpu of info.gpus) {
|
||
|
|
const vramGb = Math.round(gpu.vram / 1024 * 10) / 10;
|
||
|
|
logger.logBoxLine(` ${gpu.id}: ${gpu.model} (${vramGb} GB)`);
|
||
|
|
if (gpu.driverVersion) {
|
||
|
|
logger.logBoxLine(` Driver: ${gpu.driverVersion}`);
|
||
|
|
}
|
||
|
|
if (gpu.cudaVersion) {
|
||
|
|
logger.logBoxLine(` CUDA: ${gpu.cudaVersion}`);
|
||
|
|
}
|
||
|
|
if (gpu.rocmVersion) {
|
||
|
|
logger.logBoxLine(` ROCm: ${gpu.rocmVersion}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.logBoxEnd();
|
||
|
|
}
|
||
|
|
}
|