top-tty.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { IPtyForkOptions, IWindowsPtyForkOptions, spawn } from 'node-pty';
  2. import { Logger, LogLevel } from '../../common/util/logger.class';
  3. const LOG_LEVEL: LogLevel = (process.env.LOG_LEVEL as LogLevel) || 'INFO';
  4. Logger.logLevel = LOG_LEVEL;
  5. export type MemoryUsage = { used: number; avail: number; unit: string };
  6. export class TopTTY {
  7. private cpuSubscriber?: (usage: number) => void;
  8. private ramSubscriber?: (usage: MemoryUsage) => void;
  9. public subscribe = {
  10. cpu: (subscriber: (usage: number) => void) => {
  11. this.cpuSubscriber = subscriber;
  12. },
  13. ram: (subscriber: (usage: MemoryUsage) => void) => {
  14. this.ramSubscriber = subscriber;
  15. }
  16. };
  17. public async runloop() {
  18. while (true) {
  19. try {
  20. await execTTY('top -d 0.5', { cols: 100, rows: 10, env: { LC_ALL: 'C' } }, (line: string) => {
  21. if (line.includes('Cpu')) {
  22. line = line.replace(/\u001b(\[|\()([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGKB]/g, ''); // get rid of tty color format codes
  23. line = line.replace(/:/g, ' ');
  24. const tokens = line.split(/\s+|,/g).filter(v => !!v); // split by whitespace and colon, omit emties
  25. const usage = Number(tokens[tokens.length - 14]) + Number(tokens[tokens.length - 16]);
  26. if (this.cpuSubscriber) this.cpuSubscriber(usage);
  27. } else if (/^([KMGPT]i)?B\s+Mem\s*:/.test(line)) {
  28. line = line.replace(/\u001b(\[|\()([0-9]{1,3}(;[0-9]{1,2};?)?)?[mGKB]/g, ''); // get rid of tty color format codes
  29. line = line.replace(/:/g, ' ');
  30. const tokens = line.split(/\s+/g).filter(v => !!v); // split by whitespace, omit emties
  31. const unit = tokens[0];
  32. const avail = Number(tokens[2]);
  33. const used = Number(tokens[6]);
  34. if (this.ramSubscriber) this.ramSubscriber({ unit, avail, used });
  35. }
  36. });
  37. } catch (err) {
  38. Logger.error('top runloop crashed:', err);
  39. Logger.info('Restaring top runloop...');
  40. }
  41. await new Promise<void>(res => setTimeout(res, 1000));
  42. }
  43. }
  44. }
  45. function execTTY(command: string, options: IPtyForkOptions | IWindowsPtyForkOptions, stdout?: (...args: any[]) => void) {
  46. return new Promise<void>((resolve, reject) => {
  47. try {
  48. let stdoutbuf = '';
  49. // SPAWN PTY PROCESS
  50. const p = spawn(command.split(' ')[0], command.split(' ').slice(1), options);
  51. // PIPE STDOUT
  52. if (typeof stdout === 'function') {
  53. p.onData(chunk => {
  54. stdoutbuf += chunk;
  55. let i = -1;
  56. while ((i = stdoutbuf.indexOf('\n')) >= 0) {
  57. const line = stdoutbuf.substring(0, i);
  58. stdoutbuf = stdoutbuf.substring(i + 1);
  59. if (typeof stdout === 'function') {
  60. stdout(line);
  61. }
  62. }
  63. });
  64. p.onExit(() => resolve());
  65. }
  66. } catch (err) {
  67. reject(err);
  68. }
  69. });
  70. }