// src/services/host-monitor.ts // Copyright (C) 2025 DTP Technologies, LLC // All Rights Reserved import env from "../config/env.js"; import os from "node:os"; import v8 from "node:v8"; import { EventEmitter } from "node:events"; import { CronJob } from "cron"; import HostMonitor, { IHostMonitor } from "@/models/host-monitor.js"; import { DtpService } from "../lib/service.js"; export interface IHostMonitorStats { memoryUtilization: number; rss: number; heapTotal: number; heapUsed: number; heapExternal: number; osTotal: number; osFree: number; timestamp: Date; } class HostMonitorService extends DtpService { private cronJob: CronJob | undefined; private stats: IHostMonitor; private eventEmitter: EventEmitter; get name(): string { return "HostMonitorService"; } get slug(): string { return "search"; } constructor() { super(); this.stats = this.createMonitor(); this.stats.hostname = os.hostname(); this.eventEmitter = new EventEmitter(); } async start(): Promise { const { heap_size_limit } = v8.getHeapStatistics(); const limitMB = (heap_size_limit / 1024 / 1024).toFixed(2); this.log.info("starting host monitor cron job"); this.cronJob = new CronJob( "*/15 * * * * *", this.onStoreStats.bind(this), null, true, env.timezone, ); this.log.info("service started", { heapLimit: limitMB }); } async stop(): Promise { if (this.cronJob) { this.log.info("stopping host monitor cron job"); this.cronJob.stop(); delete this.cronJob; } this.log.info("service stopped"); } subagent(bytes: number): void { this.stats.memory.ai.subagents.count += 1; this.stats.memory.ai.subagents.bytes += bytes; } fileOperation(bytes: number): void { this.stats.memory.ai.fileOperations.count += 1; this.stats.memory.ai.fileOperations.bytes += bytes; } toolCall(bytes: number): void { this.stats.memory.ai.toolCalls.count += 1; this.stats.memory.ai.toolCalls.bytes += bytes; } on(event: string, listener: (...args: any[]) => void): void { this.eventEmitter.on(event, listener); } off(event: string, listener: (...args: any[]) => void): void { this.eventEmitter.off(event, listener); } async onStoreStats(): Promise { const NOW = new Date(); this.stats.timestamp = NOW; const usage: NodeJS.MemoryUsage = process.memoryUsage(); this.stats.memory.rss = usage.rss; this.stats.memory.v8.heapTotal = usage.heapTotal; this.stats.memory.v8.heapUsed = usage.heapUsed; this.stats.memory.v8.heapExternal = usage.external ?? 0; const osTotal = os.totalmem(); const osFree = os.freemem(); this.stats.memory.os.total = osTotal; this.stats.memory.os.free = osFree; // store stats to db await this.stats.save(); // Calculate memory utilization percentage const memoryUsed = osTotal - osFree; const memoryUtilization = Math.round((memoryUsed / osTotal) * 100); // Emit stats event for UI updates const statsData: IHostMonitorStats = { memoryUtilization, rss: usage.rss, heapTotal: usage.heapTotal, heapUsed: usage.heapUsed, heapExternal: usage.external ?? 0, osTotal, osFree, timestamp: NOW, }; this.eventEmitter.emit("stats", statsData); this.stats = this.createMonitor(); } createMonitor(): IHostMonitor { const monitor = new HostMonitor(); monitor.hostname = os.hostname(); const usage = process.memoryUsage(); monitor.memory = { rss: usage.rss, os: { total: os.totalmem(), free: os.freemem(), }, v8: { heapTotal: usage.heapTotal, heapUsed: usage.heapUsed, heapExternal: usage.external ?? 0, }, logs: { count: 0, bytes: 0, }, ai: { fileOperations: { count: 0, bytes: 0, }, subagents: { count: 0, bytes: 0, }, toolCalls: { count: 0, bytes: 0, }, }, }; return monitor; } } export default new HostMonitorService();