mirror of
https://github.com/jarrodwatts/claude-hud.git
synced 2026-04-16 06:32:39 +00:00
fix: use vm_stat for accurate macOS memory percentage
This commit is contained in:
@@ -1,13 +1,50 @@
|
||||
import os from 'node:os';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import type { MemoryInfo } from './types.js';
|
||||
|
||||
type MemoryReader = () => { totalBytes: number; freeBytes: number };
|
||||
|
||||
let readMemory: MemoryReader = () => ({
|
||||
export function parseVmStat(
|
||||
output: string,
|
||||
): { pageSize: number; active: number; wired: number } | null {
|
||||
const pageSizeMatch = output.match(/page size of (\d+) bytes/);
|
||||
if (!pageSizeMatch) return null;
|
||||
|
||||
const activeMatch = output.match(/Pages active:\s+(\d+)/);
|
||||
const wiredMatch = output.match(/Pages wired down:\s+(\d+)/);
|
||||
if (!activeMatch || !wiredMatch) return null;
|
||||
|
||||
return {
|
||||
pageSize: Number(pageSizeMatch[1]),
|
||||
active: Number(activeMatch[1]),
|
||||
wired: Number(wiredMatch[1]),
|
||||
};
|
||||
}
|
||||
|
||||
const readDefaultMemory: MemoryReader = () => ({
|
||||
totalBytes: os.totalmem(),
|
||||
freeBytes: os.freemem(),
|
||||
});
|
||||
|
||||
const readMacOSMemory: MemoryReader = () => {
|
||||
try {
|
||||
const output = execFileSync('/usr/bin/vm_stat', {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
});
|
||||
const parsed = parseVmStat(output);
|
||||
if (!parsed) return readDefaultMemory();
|
||||
const totalBytes = os.totalmem();
|
||||
const usedBytes = (parsed.active + parsed.wired) * parsed.pageSize;
|
||||
return { totalBytes, freeBytes: totalBytes - usedBytes };
|
||||
} catch {
|
||||
return readDefaultMemory();
|
||||
}
|
||||
};
|
||||
|
||||
let readMemory: MemoryReader =
|
||||
process.platform === 'darwin' ? readMacOSMemory : readDefaultMemory;
|
||||
|
||||
export async function getMemoryUsage(): Promise<MemoryInfo | null> {
|
||||
try {
|
||||
const { totalBytes, freeBytes } = readMemory();
|
||||
@@ -51,8 +88,5 @@ export function formatBytes(bytes: number): string {
|
||||
}
|
||||
|
||||
export function _setMemoryReaderForTests(reader: MemoryReader | null): void {
|
||||
readMemory = reader ?? (() => ({
|
||||
totalBytes: os.totalmem(),
|
||||
freeBytes: os.freemem(),
|
||||
}));
|
||||
readMemory = reader ?? (process.platform === 'darwin' ? readMacOSMemory : readDefaultMemory);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { _setMemoryReaderForTests, formatBytes, getMemoryUsage } from '../dist/memory.js';
|
||||
import { _setMemoryReaderForTests, formatBytes, getMemoryUsage, parseVmStat } from '../dist/memory.js';
|
||||
|
||||
test('getMemoryUsage returns coarse system RAM usage with clamped values', async () => {
|
||||
_setMemoryReaderForTests(() => ({
|
||||
@@ -28,6 +28,55 @@ test('getMemoryUsage returns null when memory lookup fails', async () => {
|
||||
assert.equal(memoryUsage, null);
|
||||
});
|
||||
|
||||
test('parseVmStat parses vm_stat output correctly', () => {
|
||||
const output = `Mach Virtual Memory Statistics: (page size of 16384 bytes)
|
||||
Pages free: 2588951.
|
||||
Pages active: 253775.
|
||||
Pages inactive: 246498.
|
||||
Pages speculative: 35498.
|
||||
Pages throttled: 0.
|
||||
Pages wired down: 195488.
|
||||
Pages purgeable: 14994.
|
||||
"Translation faults": 894498389.
|
||||
Pages copy-on-write: 26696671.
|
||||
Pages zero filled: 416498538.
|
||||
Pages reactivated: 13299498.
|
||||
Pages purged: 5765498.
|
||||
File-backed pages: 156498.
|
||||
Anonymous pages: 343775.
|
||||
Pages stored in compressor: 498775.
|
||||
Pages occupied by compressor: 115488.
|
||||
Decompressions: 42498775.
|
||||
Compressions: 58498775.
|
||||
Pageins: 84498775.
|
||||
Pageouts: 1498775.
|
||||
Swapins: 0.
|
||||
Swapouts: 0.`;
|
||||
const result = parseVmStat(output);
|
||||
assert.deepEqual(result, { pageSize: 16384, active: 253775, wired: 195488 });
|
||||
});
|
||||
|
||||
test('parseVmStat returns null for empty string', () => {
|
||||
assert.equal(parseVmStat(''), null);
|
||||
});
|
||||
|
||||
test('parseVmStat returns null for malformed output', () => {
|
||||
assert.equal(parseVmStat('not valid vm_stat output'), null);
|
||||
});
|
||||
|
||||
test('macOS memory calculation: 16GB total, active+wired via vm_stat → 43%', async () => {
|
||||
const totalBytes = 16 * 1024 ** 3;
|
||||
const usedBytes = (253775 + 195488) * 16384;
|
||||
_setMemoryReaderForTests(() => ({
|
||||
totalBytes,
|
||||
freeBytes: totalBytes - usedBytes,
|
||||
}));
|
||||
|
||||
const memoryUsage = await getMemoryUsage();
|
||||
assert.equal(memoryUsage.usedPercent, 43);
|
||||
assert.equal(memoryUsage.usedBytes, usedBytes);
|
||||
});
|
||||
|
||||
test('formatBytes formats human-readable units for memory line display', () => {
|
||||
assert.equal(formatBytes(0), '0 B');
|
||||
assert.equal(formatBytes(512), '512 B');
|
||||
|
||||
Reference in New Issue
Block a user