diff --git a/__LOCAL_LLMs/dashboard/src/app/api/system/route.ts b/__LOCAL_LLMs/dashboard/src/app/api/system/route.ts index d2786e98..82e74590 100644 --- a/__LOCAL_LLMs/dashboard/src/app/api/system/route.ts +++ b/__LOCAL_LLMs/dashboard/src/app/api/system/route.ts @@ -89,23 +89,66 @@ async function getStaticInfo() { return staticCache; } +// macOS vm_stat gives accurate memory breakdown (os.freemem() excludes reclaimable cache) +async function getAccurateMemory(): Promise<{ + total: number; + appMemory: number; + cached: number; + free: number; + pressure: string; +}> { + const totalMem = os.totalmem(); + try { + const { stdout } = await execAsync('vm_stat', { timeout: 2000 }); + const pageSize = 16384; // macOS Apple Silicon default + const parse = (label: string): number => { + const match = stdout.match(new RegExp(`${label}:\\s+(\\d+)`)); + return match ? parseInt(match[1]) * pageSize : 0; + }; + const active = parse('Pages active'); + const wired = parse('Pages wired down'); + const inactive = parse('Pages inactive'); + const purgeable = parse('Pages purgeable'); + const speculative = parse('Pages speculative'); + const free = parse('Pages free'); + const compressor = parse('Pages occupied by compressor'); + + const appMemory = active + wired + compressor; + const cached = inactive + purgeable + speculative; + const trueFree = free + cached; // macOS reclaims cached on demand + + const ratio = appMemory / totalMem; + const pressure = ratio > 0.85 ? 'critical' : ratio > 0.7 ? 'warning' : 'normal'; + + return { total: totalMem, appMemory, cached, free: trueFree, pressure }; + } catch { + // Fallback to Node.js (inaccurate on macOS but works everywhere) + const freeMem = os.freemem(); + return { + total: totalMem, + appMemory: totalMem - freeMem, + cached: 0, + free: freeMem, + pressure: 'unknown', + }; + } +} + export async function GET() { - const [staticInfo, disk, ollamaDisk] = await Promise.all([ + const [staticInfo, disk, ollamaDisk, memory] = await Promise.all([ getStaticInfo(), getDiskSpace(), getOllamaModelsDiskUsage(), + getAccurateMemory(), ]); - const totalMem = os.totalmem(); - const freeMem = os.freemem(); - const usedMem = totalMem - freeMem; const cpuCount = os.cpus().length; const uptime = os.uptime(); return NextResponse.json({ chip: staticInfo.chip, gpu: staticInfo.gpu, - memory: { total: totalMem, used: usedMem, free: freeMem }, + memory, disk, ollamaDiskUsage: ollamaDisk, cpuCores: cpuCount, diff --git a/__LOCAL_LLMs/dashboard/src/app/page.tsx b/__LOCAL_LLMs/dashboard/src/app/page.tsx index 891305cd..157c99e5 100644 --- a/__LOCAL_LLMs/dashboard/src/app/page.tsx +++ b/__LOCAL_LLMs/dashboard/src/app/page.tsx @@ -75,7 +75,7 @@ interface WhisperData { interface SystemData { chip: string; gpu: string; - memory: { total: number; used: number; free: number }; + memory: { total: number; appMemory: number; cached: number; free: number; pressure: string }; disk: { total: number; used: number; free: number }; ollamaDiskUsage: number; cpuCores: number; @@ -428,18 +428,21 @@ export default function Dashboard() { MEMORY - {formatBytes(system?.memory.used || 0)} + {formatBytes(system?.memory.appMemory || 0)} / {formatBytes(system?.memory.total || 0)} +
+ {formatBytes(system?.memory.cached || 0)} cached (reclaimable) +