Skip to main content
Glama
health-monitor.jsโ€ข12.5 kB
/** * Health Monitor for MCP SSH Manager * Provides system health checks, service monitoring, and process management */ import { logger } from './logger.js'; // Health status levels export const HEALTH_STATUS = { HEALTHY: 'healthy', WARNING: 'warning', CRITICAL: 'critical', UNKNOWN: 'unknown' }; // Common services to monitor export const COMMON_SERVICES = { nginx: { systemd: 'nginx', sysv: 'nginx' }, apache: { systemd: 'apache2', sysv: 'apache2' }, mysql: { systemd: 'mysql', sysv: 'mysql' }, postgresql: { systemd: 'postgresql', sysv: 'postgresql' }, mongodb: { systemd: 'mongod', sysv: 'mongod' }, redis: { systemd: 'redis', sysv: 'redis-server' }, docker: { systemd: 'docker', sysv: 'docker' }, ssh: { systemd: 'sshd', sysv: 'ssh' } }; /** * Build command to check CPU usage */ export function buildCPUCheckCommand() { // Get CPU usage using top, show idle percentage, calculate used return 'top -bn1 | grep "Cpu(s)" | sed "s/.*, *\\([0-9.]*\\)%* id.*/\\1/" | awk \'{print 100 - $1}\''; } /** * Build command to check memory usage */ export function buildMemoryCheckCommand() { // Returns: total, used, free, available in MB and percentage return 'free -m | awk \'NR==2{printf "{\\"total\\":%s,\\"used\\":%s,\\"free\\":%s,\\"percent\\":%.2f}", $2,$3,$4,$3*100/$2}\''; } /** * Build command to check disk usage */ export function buildDiskCheckCommand(mountPoint = '/') { // Returns JSON with disk usage for specific mount point or all if (mountPoint === 'all') { return 'df -h | awk \'NR>1 {gsub(/%/,"",$5); printf "{\\"mount\\":\\"%s\\",\\"size\\":\\"%s\\",\\"used\\":\\"%s\\",\\"avail\\":\\"%s\\",\\"percent\\":%s}\\n", $6,$2,$3,$4,$5}\''; } return `df -h "${mountPoint}" | awk 'NR>1 {gsub(/%/,"",$5); printf "{\\"mount\\":\\"%s\\",\\"size\\":\\"%s\\",\\"used\\":\\"%s\\",\\"avail\\":\\"%s\\",\\"percent\\":%s}", $6,$2,$3,$4,$5}'`; } /** * Build command to check network statistics */ export function buildNetworkCheckCommand() { // Get basic network stats (RX/TX bytes) return 'cat /proc/net/dev | awk \'NR>2 {printf "{\\"interface\\":\\"%s\\",\\"rx_bytes\\":%s,\\"tx_bytes\\":%s}\\n", $1,$2,$10}\' | grep -v "lo:"'; } /** * Build command to check load average */ export function buildLoadAverageCommand() { return 'uptime | awk -F\'load average:\' \'{print $2}\' | sed \'s/^[ \\t]*//\''; } /** * Build command to check system uptime */ export function buildUptimeCommand() { return 'uptime -p 2>/dev/null || uptime | awk \'{print $3,$4}\' | sed \'s/,//\''; } /** * Parse CPU usage output */ export function parseCPUUsage(output) { const usage = parseFloat(output.trim()); return { usage: usage.toFixed(2), percent: usage, status: usage > 90 ? HEALTH_STATUS.CRITICAL : usage > 70 ? HEALTH_STATUS.WARNING : HEALTH_STATUS.HEALTHY }; } /** * Parse memory usage output */ export function parseMemoryUsage(output) { try { const mem = JSON.parse(output.trim()); return { total_mb: mem.total, used_mb: mem.used, free_mb: mem.free, percent: parseFloat(mem.percent), status: mem.percent > 90 ? HEALTH_STATUS.CRITICAL : mem.percent > 80 ? HEALTH_STATUS.WARNING : HEALTH_STATUS.HEALTHY }; } catch (error) { logger.warn('Failed to parse memory output', { error: error.message }); return { status: HEALTH_STATUS.UNKNOWN }; } } /** * Parse disk usage output */ export function parseDiskUsage(output) { const lines = output.trim().split('\n').filter(l => l); const disks = []; for (const line of lines) { try { const disk = JSON.parse(line); disk.status = disk.percent > 90 ? HEALTH_STATUS.CRITICAL : disk.percent > 80 ? HEALTH_STATUS.WARNING : HEALTH_STATUS.HEALTHY; disks.push(disk); } catch (error) { logger.warn('Failed to parse disk line', { line, error: error.message }); } } return disks; } /** * Parse network statistics */ export function parseNetworkStats(output) { const lines = output.trim().split('\n').filter(l => l); const interfaces = []; for (const line of lines) { try { const iface = JSON.parse(line); // Convert bytes to MB iface.rx_mb = (iface.rx_bytes / 1024 / 1024).toFixed(2); iface.tx_mb = (iface.tx_bytes / 1024 / 1024).toFixed(2); interfaces.push(iface); } catch (error) { logger.warn('Failed to parse network line', { line, error: error.message }); } } return interfaces; } /** * Determine overall health status */ export function determineOverallHealth(cpu, memory, disks) { const statuses = [cpu.status, memory.status, ...disks.map(d => d.status)]; if (statuses.includes(HEALTH_STATUS.CRITICAL)) { return HEALTH_STATUS.CRITICAL; } if (statuses.includes(HEALTH_STATUS.WARNING)) { return HEALTH_STATUS.WARNING; } if (statuses.includes(HEALTH_STATUS.UNKNOWN)) { return HEALTH_STATUS.UNKNOWN; } return HEALTH_STATUS.HEALTHY; } /** * Build command to check service status (systemd or sysv) */ export function buildServiceStatusCommand(serviceName) { // Try systemd first, fallback to sysv return ` if command -v systemctl >/dev/null 2>&1; then systemctl is-active ${serviceName} 2>/dev/null >/dev/null && echo "ACTIVE" || echo "INACTIVE" systemctl is-enabled ${serviceName} 2>/dev/null >/dev/null && echo "ENABLED" || echo "DISABLED" systemctl status ${serviceName} 2>/dev/null | grep "Main PID" | awk '{print $3}' | cut -d'(' -f1 systemctl status ${serviceName} 2>/dev/null | grep "Active:" | sed 's/.*Active: //' | awk '{print $1,$2,$3}' elif command -v service >/dev/null 2>&1; then service ${serviceName} status >/dev/null 2>&1 && echo "ACTIVE" || echo "INACTIVE" echo "UNKNOWN" pgrep -f ${serviceName} | head -1 || echo "" echo "sysv" else echo "UNKNOWN" echo "UNKNOWN" echo "" echo "no-init-system" fi `.trim(); } /** * Parse service status output */ export function parseServiceStatus(output, serviceName) { const lines = output.trim().split('\n'); const [status, enabled, pid, details] = lines; return { name: serviceName, status: status === 'ACTIVE' ? 'running' : 'stopped', enabled: enabled === 'ENABLED' ? 'yes' : enabled === 'DISABLED' ? 'no' : 'unknown', pid: pid && pid !== '' ? parseInt(pid) : null, details: details || 'unknown', health: status === 'ACTIVE' ? HEALTH_STATUS.HEALTHY : HEALTH_STATUS.CRITICAL }; } /** * Build command to list running processes */ export function buildProcessListCommand(options = {}) { const { sortBy = 'cpu', // cpu, memory, pid limit = 20, filter = null } = options; let sortFlag = sortBy === 'memory' ? '-m' : '-c'; // -c for CPU, -m for memory let command = `ps aux --sort=${sortFlag === '-c' ? '-pcpu' : '-pmem'} | head -n ${limit + 1}`; if (filter) { command += ` | grep -i "${filter}"`; } // Format output as JSON-like structure command += ' | awk \'NR>1 {printf "{\\"user\\":\\"%s\\",\\"pid\\":%s,\\"cpu\\":%.1f,\\"mem\\":%.1f,\\"vsz\\":%s,\\"rss\\":%s,\\"stat\\":\\"%s\\",\\"start\\":\\"%s\\",\\"time\\":\\"%s\\",\\"command\\":\\"%s\\"}\\n", $1,$2,$3,$4,$5,$6,$8,$9,$10,substr($0,index($0,$11))}\''; return command; } /** * Parse process list output */ export function parseProcessList(output) { const lines = output.trim().split('\n').filter(l => l); const processes = []; for (const line of lines) { try { const proc = JSON.parse(line); processes.push(proc); } catch (error) { logger.warn('Failed to parse process line', { line, error: error.message }); } } return processes; } /** * Build command to kill a process */ export function buildKillProcessCommand(pid, signal = 'TERM') { // Validate PID is numeric if (!Number.isInteger(pid) || pid <= 0) { throw new Error(`Invalid PID: ${pid}`); } const validSignals = ['TERM', 'KILL', 'HUP', 'INT', 'QUIT']; if (!validSignals.includes(signal)) { throw new Error(`Invalid signal: ${signal}. Valid signals: ${validSignals.join(', ')}`); } return `kill -${signal} ${pid}`; } /** * Build command to get process info */ export function buildProcessInfoCommand(pid) { return `ps -p ${pid} -o user,pid,pcpu,pmem,vsz,rss,stat,start,time,cmd --no-headers | awk '{printf "{\\"user\\":\\"%s\\",\\"pid\\":%s,\\"cpu\\":%.1f,\\"mem\\":%.1f,\\"vsz\\":%s,\\"rss\\":%s,\\"stat\\":\\"%s\\",\\"start\\":\\"%s\\",\\"time\\":\\"%s\\",\\"command\\":\\"%s\\"}", $1,$2,$3,$4,$5,$6,$7,$8,$9,substr($0,index($0,$10))}'`; } /** * Create alert configuration */ export function createAlertConfig(thresholds) { const defaults = { cpu: 80, memory: 90, disk: 85, enabled: true }; return { ...defaults, ...thresholds, created_at: new Date().toISOString() }; } /** * Build command to save alert config */ export function buildSaveAlertConfigCommand(config, configPath = '/etc/ssh-manager-alerts.json') { const jsonData = JSON.stringify(config, null, 2); const escapedJson = jsonData.replace(/'/g, '\'\\\'\''); return `echo '${escapedJson}' > "${configPath}"`; } /** * Build command to load alert config */ export function buildLoadAlertConfigCommand(configPath = '/etc/ssh-manager-alerts.json') { return `cat "${configPath}" 2>/dev/null || echo '{}'`; } /** * Check if thresholds are exceeded */ export function checkAlertThresholds(metrics, thresholds) { const alerts = []; if (thresholds.cpu && metrics.cpu && metrics.cpu.percent > thresholds.cpu) { alerts.push({ type: 'cpu', severity: 'warning', message: `CPU usage (${metrics.cpu.percent}%) exceeds threshold (${thresholds.cpu}%)`, value: metrics.cpu.percent, threshold: thresholds.cpu }); } if (thresholds.memory && metrics.memory && metrics.memory.percent > thresholds.memory) { alerts.push({ type: 'memory', severity: 'warning', message: `Memory usage (${metrics.memory.percent}%) exceeds threshold (${thresholds.memory}%)`, value: metrics.memory.percent, threshold: thresholds.memory }); } if (thresholds.disk && metrics.disks) { for (const disk of metrics.disks) { if (disk.percent > thresholds.disk) { alerts.push({ type: 'disk', severity: 'warning', message: `Disk usage on ${disk.mount} (${disk.percent}%) exceeds threshold (${thresholds.disk}%)`, mount: disk.mount, value: disk.percent, threshold: thresholds.disk }); } } } return alerts; } /** * Build comprehensive health check command */ export function buildComprehensiveHealthCheckCommand() { return ` echo "=== CPU ===" ${buildCPUCheckCommand()} echo "=== MEMORY ===" ${buildMemoryCheckCommand()} echo "=== DISK ===" ${buildDiskCheckCommand('all')} echo "=== LOAD ===" ${buildLoadAverageCommand()} echo "=== UPTIME ===" ${buildUptimeCommand()} echo "=== NETWORK ===" ${buildNetworkCheckCommand()} `.trim(); } /** * Parse comprehensive health check output */ export function parseComprehensiveHealthCheck(output) { const sections = output.split('=== ').filter(s => s); const result = {}; for (const section of sections) { const [name, ...content] = section.split('\n'); const data = content.join('\n').trim(); switch (name.toLowerCase().trim()) { case 'cpu ===': result.cpu = parseCPUUsage(data); break; case 'memory ===': result.memory = parseMemoryUsage(data); break; case 'disk ===': result.disks = parseDiskUsage(data); break; case 'load ===': result.load_average = data; break; case 'uptime ===': result.uptime = data; break; case 'network ===': result.network = parseNetworkStats(data); break; } } // Determine overall health if (result.cpu && result.memory && result.disks) { result.overall_status = determineOverallHealth(result.cpu, result.memory, result.disks); } return result; } /** * Get common service names for detection */ export function getCommonServices() { return Object.keys(COMMON_SERVICES); } /** * Resolve service name (handle both systemd and sysv names) */ export function resolveServiceName(shortName) { const service = COMMON_SERVICES[shortName.toLowerCase()]; return service ? service.systemd : shortName; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bvisible/mcp-ssh-manager'

If you have feedback or need assistance with the MCP directory API, please join our Discord server