import { IPtyForkOptions, IWindowsPtyForkOptions, spawn } from 'node-pty'; import { Logger, LogLevel } from '../../common/util/logger.class'; const LOG_LEVEL: LogLevel = (process.env.LOG_LEVEL as LogLevel) || 'INFO'; Logger.logLevel = LOG_LEVEL; export type MemoryUsage = { used: number; avail: number; unit: string }; export class TopTTY { private cpuSubscriber?: (usage: number) => void; private ramSubscriber?: (usage: MemoryUsage) => void; public subscribe = { cpu: (subscriber: (usage: number) => void) => { this.cpuSubscriber = subscriber; }, ram: (subscriber: (usage: MemoryUsage) => void) => { this.ramSubscriber = subscriber; } }; public async runloop() { while (true) { try { await execTTY('top -d 0.5', { cols: 100, rows: 10, env: { LC_ALL: 'C' } }, (line: string) => { if (line.includes('Cpu')) { line = line.replace(/\u001b(\[|\()([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGKB]/g, ''); // get rid of tty color format codes line = line.replace(/:/g, ' '); const tokens = line.split(/\s+|,/g).filter(v => !!v); // split by whitespace and colon, omit emties const usage = Number(tokens[tokens.length - 14]) + Number(tokens[tokens.length - 16]); if (this.cpuSubscriber) this.cpuSubscriber(usage); } else if (/^([KMGPT]i)?B\s+Mem\s*:/.test(line)) { line = line.replace(/\u001b(\[|\()([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGKB]/g, ''); // get rid of tty color format codes line = line.replace(/:/g, ' '); const tokens = line.split(/\s+/g).filter(v => !!v); // split by whitespace, omit emties const unit = tokens[0]; const avail = Number(tokens[2]); const used = Number(tokens[6]); if (this.ramSubscriber) this.ramSubscriber({ unit, avail, used }); } }); } catch (err) { Logger.error('top runloop crashed:', err); Logger.info('Restaring top runloop...'); } await new Promise(res => setTimeout(res, 1000)); } } } function execTTY(command: string, options: IPtyForkOptions | IWindowsPtyForkOptions, stdout?: (...args: any[]) => void) { return new Promise((resolve, reject) => { try { let stdoutbuf = ''; // SPAWN PTY PROCESS const p = spawn(command.split(' ')[0], command.split(' ').slice(1), options); // PIPE STDOUT if (typeof stdout === 'function') { p.onData(chunk => { stdoutbuf += chunk; let i = -1; while ((i = stdoutbuf.indexOf('\n')) >= 0) { const line = stdoutbuf.substring(0, i); stdoutbuf = stdoutbuf.substring(i + 1); if (typeof stdout === 'function') { stdout(line); } } }); p.onExit(() => resolve()); } } catch (err) { reject(err); } }); }