mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
199 lines
5.2 KiB
TypeScript
199 lines
5.2 KiB
TypeScript
import { promises } from "node:fs";
|
|
import type Dockerode from "dockerode";
|
|
import osUtils from "node-os-utils";
|
|
import { paths } from "../constants";
|
|
|
|
export const recordAdvancedStats = async (
|
|
stats: Dockerode.ContainerStats,
|
|
appName: string,
|
|
) => {
|
|
const { MONITORING_PATH } = paths();
|
|
const path = `${MONITORING_PATH}/${appName}`;
|
|
|
|
await promises.mkdir(path, { recursive: true });
|
|
|
|
const cpuPercent = calculateCpuUsagePercent(
|
|
stats.cpu_stats,
|
|
stats.precpu_stats,
|
|
);
|
|
const memoryStats = calculateMemoryStats(stats.memory_stats);
|
|
const blockIO = calculateBlockIO(stats.blkio_stats);
|
|
const networkUsage = calculateNetworkUsage(stats.networks);
|
|
|
|
await updateStatsFile(appName, "cpu", cpuPercent);
|
|
await updateStatsFile(appName, "memory", {
|
|
used: memoryStats.used,
|
|
free: memoryStats.free,
|
|
usedPercentage: memoryStats.usedPercentage,
|
|
total: memoryStats.total,
|
|
});
|
|
await updateStatsFile(appName, "block", {
|
|
readMb: blockIO.readMb,
|
|
writeMb: blockIO.writeMb,
|
|
});
|
|
|
|
await updateStatsFile(appName, "network", {
|
|
inputMb: networkUsage.inputMb,
|
|
outputMb: networkUsage.outputMb,
|
|
});
|
|
|
|
if (appName === "dokploy") {
|
|
const disk = await osUtils.drive.info("/");
|
|
|
|
const diskUsage = disk.usedGb;
|
|
const diskTotal = disk.totalGb;
|
|
const diskUsedPercentage = disk.usedPercentage;
|
|
const diskFree = disk.freeGb;
|
|
|
|
await updateStatsFile(appName, "disk", {
|
|
diskTotal: +diskTotal,
|
|
diskUsedPercentage: +diskUsedPercentage,
|
|
diskUsage: +diskUsage,
|
|
diskFree: +diskFree,
|
|
});
|
|
}
|
|
};
|
|
|
|
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 (error) {
|
|
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 (error) {
|
|
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"),
|
|
};
|
|
};
|
|
|
|
const calculateCpuUsagePercent = (
|
|
cpu_stats: Dockerode.ContainerStats["cpu_stats"],
|
|
precpu_stats: Dockerode.ContainerStats["precpu_stats"],
|
|
) => {
|
|
const cpuDelta =
|
|
cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage;
|
|
const systemDelta =
|
|
cpu_stats.system_cpu_usage - precpu_stats.system_cpu_usage;
|
|
|
|
const numberCpus =
|
|
cpu_stats.online_cpus ||
|
|
(cpu_stats.cpu_usage.percpu_usage
|
|
? cpu_stats.cpu_usage.percpu_usage.length
|
|
: 1);
|
|
|
|
if (systemDelta > 0 && cpuDelta > 0) {
|
|
return (cpuDelta / systemDelta) * numberCpus * 100.0;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
const calculateMemoryStats = (
|
|
memory_stats: Dockerode.ContainerStats["memory_stats"],
|
|
) => {
|
|
const usedMemory = memory_stats.usage - (memory_stats.stats.cache || 0);
|
|
const availableMemory = memory_stats.limit;
|
|
const memoryUsedPercentage = (usedMemory / availableMemory) * 100.0;
|
|
|
|
return {
|
|
used: usedMemory,
|
|
free: availableMemory - usedMemory,
|
|
usedPercentage: memoryUsedPercentage,
|
|
total: availableMemory,
|
|
};
|
|
};
|
|
const calculateBlockIO = (
|
|
blkio_stats: Dockerode.ContainerStats["blkio_stats"],
|
|
) => {
|
|
let readIO = 0;
|
|
let writeIO = 0;
|
|
if (blkio_stats?.io_service_bytes_recursive) {
|
|
for (const io of blkio_stats.io_service_bytes_recursive) {
|
|
if (io.op === "read") {
|
|
readIO += io.value;
|
|
} else if (io.op === "write") {
|
|
writeIO += io.value;
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
readMb: readIO / (1024 * 1024),
|
|
writeMb: writeIO / (1024 * 1024),
|
|
};
|
|
};
|
|
|
|
const calculateNetworkUsage = (
|
|
networks: Dockerode.ContainerStats["networks"],
|
|
) => {
|
|
let totalRx = 0;
|
|
let totalTx = 0;
|
|
|
|
const stats = Object.keys(networks);
|
|
|
|
for (const interfaceName of stats) {
|
|
const net = networks[interfaceName];
|
|
totalRx += net?.rx_bytes || 0;
|
|
totalTx += net?.tx_bytes || 0;
|
|
}
|
|
return {
|
|
inputMb: totalRx / (1024 * 1024),
|
|
outputMb: totalTx / (1024 * 1024),
|
|
};
|
|
};
|