224 lines · 6.4 KB · TypeScript
RawDownload
import { promises } from "node:fs";import { OSUtils } from "node-os-utils";import { paths } from "../constants"; export interface Container {	BlockIO: string;	CPUPerc: string;	Container: string;	ID: string;	MemPerc: string;	MemUsage: string;	Name: string;	NetIO: string;}export const recordAdvancedStats = async (	stats: Container,	appName: string,) => {	const { MONITORING_PATH } = paths();	const path = `${MONITORING_PATH}/${appName}`; 	await promises.mkdir(path, { recursive: true }); 	await updateStatsFile(appName, "cpu", stats.CPUPerc);	await updateStatsFile(appName, "memory", {		used: stats.MemUsage.split(" ")[0],		total: stats.MemUsage.split(" ")[2],	}); 	await updateStatsFile(appName, "block", {		readMb: stats.BlockIO.split(" ")[0],		writeMb: stats.BlockIO.split(" ")[2],	}); 	await updateStatsFile(appName, "network", {		inputMb: stats.NetIO.split(" ")[0],		outputMb: stats.NetIO.split(" ")[2],	}); 	if (appName === "dokploy") {		const osutils = new OSUtils();		const diskResult = await osutils.disk.usageByMountPoint("/"); 		if (diskResult.success && diskResult.data) {			const disk = diskResult.data;			const diskUsage = disk.used.toGB().toFixed(2);			const diskTotal = disk.total.toGB().toFixed(2);			const diskUsedPercentage = disk.usagePercentage;			const diskFree = disk.available.toGB().toFixed(2); 			await updateStatsFile(appName, "disk", {				diskTotal: +diskTotal,				diskUsedPercentage: +diskUsedPercentage,				diskUsage: +diskUsage,				diskFree: +diskFree,			});		}	}}; /** * Get host system statistics using node-os-utils * This is used when monitoring "dokploy" to show host stats instead of container stats */export const getHostSystemStats = async (): Promise<Container> => {	const osutils = new OSUtils({		disk: {			includeStats: true, // Enable disk I/O statistics		},	}); 	// Get CPU usage	const cpuResult = await osutils.cpu.usage();	const cpuUsage = cpuResult.success ? cpuResult.data : 0; 	// Get memory info	const memResult = await osutils.memory.info();	let memUsedGB = 0;	let memTotalGB = 0;	let memUsedPercent = 0;	if (memResult.success) {		memTotalGB = memResult.data.total.toGB();		memUsedGB = memResult.data.used.toGB();		memUsedPercent = memResult.data.usagePercentage;	} 	// Get network stats from network.overview()	let netInputBytes = 0;	let netOutputBytes = 0;	const networkOverview = await osutils.network.overview();	if (networkOverview.success) {		netInputBytes = networkOverview.data.totalRxBytes.toBytes();		netOutputBytes = networkOverview.data.totalTxBytes.toBytes();	} 	// Get Block I/O from disk.stats()	let blockReadBytes = 0;	let blockWriteBytes = 0;	const diskStats = await osutils.disk.stats();	if (diskStats.success && diskStats.data.length > 0) {		// Filter out virtual devices (loop, ram, sr, etc.) - only include real disk devices		const excludePatterns = [/^loop/, /^ram/, /^sr\d+$/, /^fd\d+$/];		for (const stat of diskStats.data) {			// Skip virtual devices			if (				stat.device &&				excludePatterns.some((pattern) => pattern.test(stat.device))			) {				continue;			}			// readBytes and writeBytes are DataSize objects with .toBytes() method			blockReadBytes += stat.readBytes.toBytes();			blockWriteBytes += stat.writeBytes.toBytes();		}	} 	// Format values similar to docker stats	const formatBytes = (bytes: number): string => {		if (bytes >= 1024 * 1024 * 1024) {			return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GiB`;		}		if (bytes >= 1024 * 1024) {			return `${(bytes / (1024 * 1024)).toFixed(2)}MiB`;		}		if (bytes >= 1024) {			return `${(bytes / 1024).toFixed(2)}KiB`;		}		return `${bytes}B`;	}; 	// Format memory usage similar to docker stats format: "used / total"	const memUsedFormatted = `${memUsedGB.toFixed(2)}GiB`;	const memTotalFormatted = `${memTotalGB.toFixed(2)}GiB`;	const memUsageFormatted = `${memUsedFormatted} / ${memTotalFormatted}`; 	// Format network I/O	const netInputMb = netInputBytes / (1024 * 1024);	const netOutputMb = netOutputBytes / (1024 * 1024);	const netIOFormatted = `${netInputMb.toFixed(2)}MB / ${netOutputMb.toFixed(2)}MB`; 	// Format Block I/O	const blockIOFormatted = `${formatBytes(blockReadBytes)} / ${formatBytes(blockWriteBytes)}`; 	// Create a stat object compatible with recordAdvancedStats	return {		CPUPerc: `${cpuUsage.toFixed(2)}%`,		MemPerc: `${memUsedPercent.toFixed(2)}%`,		MemUsage: memUsageFormatted,		BlockIO: blockIOFormatted,		NetIO: netIOFormatted,		Container: "dokploy",		ID: "host-system",		Name: "dokploy",	};}; export const getAdvancedStats = async (appName: string) => {	return {		cpu: await readStatsFile(appName, "cpu"),		memory: await readStatsFile(appName, "memory"),		disk: await readStatsFile(appName, "disk"),		network: await readStatsFile(appName, "network"),		block: await readStatsFile(appName, "block"),	};}; export const readStatsFile = async (	appName: string,	statType: "cpu" | "memory" | "disk" | "network" | "block",) => {	try {		const { MONITORING_PATH } = paths();		const filePath = `${MONITORING_PATH}/${appName}/${statType}.json`;		const data = await promises.readFile(filePath, "utf-8");		return JSON.parse(data);	} catch {		return [];	}}; export const updateStatsFile = async (	appName: string,	statType: "cpu" | "memory" | "disk" | "network" | "block",	value: number | string | unknown,) => {	const { MONITORING_PATH } = paths();	const stats = await readStatsFile(appName, statType);	stats.push({ value, time: new Date() }); 	if (stats.length > 288) {		stats.shift();	} 	const content = JSON.stringify(stats);	await promises.writeFile(		`${MONITORING_PATH}/${appName}/${statType}.json`,		content,	);}; export const readLastValueStatsFile = async (	appName: string,	statType: "cpu" | "memory" | "disk" | "network" | "block",) => {	try {		const { MONITORING_PATH } = paths();		const filePath = `${MONITORING_PATH}/${appName}/${statType}.json`;		const data = await promises.readFile(filePath, "utf-8");		const stats = JSON.parse(data);		return stats[stats.length - 1] || null;	} catch {		return null;	}}; export const getLastAdvancedStatsFile = async (appName: string) => {	return {		cpu: await readLastValueStatsFile(appName, "cpu"),		memory: await readLastValueStatsFile(appName, "memory"),		disk: await readLastValueStatsFile(appName, "disk"),		network: await readLastValueStatsFile(appName, "network"),		block: await readLastValueStatsFile(appName, "block"),	};};