mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
151 lines
4.4 KiB
TypeScript
151 lines
4.4 KiB
TypeScript
import {
|
|
execCommand,
|
|
execPowerShell,
|
|
type HostPlatform,
|
|
} from "@termix/plugin-sdk/host-commands";
|
|
import { toFixedNum, kibToGiB } from "../util.js";
|
|
import type { Client } from "ssh2";
|
|
|
|
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,
|
|
};
|
|
}
|