Files
Termix/plugins/host-metrics/src/backend/widgets/memory-collector.ts
T

152 lines
4.4 KiB
TypeScript

import type { Client } from "ssh2";
import {
execCommand,
execPowerShell,
toFixedNum,
kibToGiB,
type HostPlatform,
} from "../../../../../src/backend/hosts/metrics-shared/common-utils.js";
async function collectDarwinMemoryMetrics(client: Client): Promise<{
percent: number | null;
usedGiB: number | null;
totalGiB: number | null;
}> {
let memPercent: number | null = null;
let usedGiB: number | null = null;
let totalGiB: number | null = null;
try {
const [vmStatOut, memSizeOut] = await Promise.all([
execCommand(client, "vm_stat"),
execCommand(client, "sysctl -n hw.memsize"),
]);
const totalBytes = Number((memSizeOut.stdout || "").trim());
const pageSizeMatch = vmStatOut.stdout.match(/page size of (\d+) bytes/);
const pageSize = pageSizeMatch ? Number(pageSizeMatch[1]) : 4096;
const getPages = (key: string): number | null => {
const line = vmStatOut.stdout
.split("\n")
.find((l) => l.trim().startsWith(key));
if (!line) return null;
const m = line.match(/(\d+)\./);
return m ? Number(m[1]) : null;
};
const active = getPages("Pages active");
const wired = getPages("Pages wired down");
const compressed = getPages("Pages occupied by compressor");
if (
Number.isFinite(totalBytes) &&
totalBytes > 0 &&
active !== null &&
wired !== null
) {
const usedPages = active + wired + (compressed ?? 0);
const usedBytes = usedPages * pageSize;
memPercent = Math.max(0, Math.min(100, (usedBytes / totalBytes) * 100));
usedGiB = kibToGiB(usedBytes / 1024);
totalGiB = kibToGiB(totalBytes / 1024);
}
} catch {
memPercent = null;
usedGiB = null;
totalGiB = null;
}
return {
percent: toFixedNum(memPercent, 0),
usedGiB: usedGiB ? toFixedNum(usedGiB, 2) : null,
totalGiB: totalGiB ? toFixedNum(totalGiB, 2) : null,
};
}
// Win32_OperatingSystem reports TotalVisibleMemorySize/FreePhysicalMemory in KB.
async function collectWindowsMemoryMetrics(client: Client): Promise<{
percent: number | null;
usedGiB: number | null;
totalGiB: number | null;
}> {
let memPercent: number | null = null;
let usedGiB: number | null = null;
let totalGiB: number | null = null;
try {
const { stdout } = await execPowerShell(
client,
"$os=Get-CimInstance Win32_OperatingSystem; [PSCustomObject]@{total=$os.TotalVisibleMemorySize; free=$os.FreePhysicalMemory} | ConvertTo-Json -Compress",
);
const parsed = JSON.parse(stdout.trim());
const totalKb = Number(parsed?.total);
const freeKb = Number(parsed?.free);
if (Number.isFinite(totalKb) && Number.isFinite(freeKb) && totalKb > 0) {
const usedKb = totalKb - freeKb;
memPercent = Math.max(0, Math.min(100, (usedKb / totalKb) * 100));
usedGiB = kibToGiB(usedKb);
totalGiB = kibToGiB(totalKb);
}
} catch {
memPercent = null;
usedGiB = null;
totalGiB = null;
}
return {
percent: toFixedNum(memPercent, 0),
usedGiB: usedGiB ? toFixedNum(usedGiB, 2) : null,
totalGiB: totalGiB ? toFixedNum(totalGiB, 2) : null,
};
}
export async function collectMemoryMetrics(
client: Client,
platform?: HostPlatform,
): Promise<{
percent: number | null;
usedGiB: number | null;
totalGiB: number | null;
}> {
if (platform === "darwin") {
return collectDarwinMemoryMetrics(client);
}
if (platform === "windows") {
return collectWindowsMemoryMetrics(client);
}
let memPercent: number | null = null;
let usedGiB: number | null = null;
let totalGiB: number | null = null;
try {
const memInfo = await execCommand(client, "cat /proc/meminfo");
const lines = memInfo.stdout.split("\n");
const getVal = (key: string) => {
const line = lines.find((l) => l.startsWith(key));
if (!line) return null;
const m = line.match(/\d+/);
return m ? Number(m[0]) : null;
};
const totalKb = getVal("MemTotal:");
const availKb = getVal("MemAvailable:");
if (totalKb && availKb && totalKb > 0) {
const usedKb = totalKb - availKb;
memPercent = Math.max(0, Math.min(100, (usedKb / totalKb) * 100));
usedGiB = kibToGiB(usedKb);
totalGiB = kibToGiB(totalKb);
}
} catch {
memPercent = null;
usedGiB = null;
totalGiB = null;
}
return {
percent: toFixedNum(memPercent, 0),
usedGiB: usedGiB ? toFixedNum(usedGiB, 2) : null,
totalGiB: totalGiB ? toFixedNum(totalGiB, 2) : null,
};
}