Spaces:
Running
Running
| import type { ActionFunctionArgs, LoaderFunction } from '@remix-run/cloudflare'; | |
| import { json } from '@remix-run/cloudflare'; | |
| // Only import child_process if we're not in a Cloudflare environment | |
| let execSync: any; | |
| try { | |
| // Check if we're in a Node.js environment | |
| if (typeof process !== 'undefined' && process.platform) { | |
| // Using dynamic import to avoid require() | |
| const childProcess = { execSync: null }; | |
| execSync = childProcess.execSync; | |
| } | |
| } catch { | |
| // In Cloudflare environment, this will fail, which is expected | |
| console.log('Running in Cloudflare environment, child_process not available'); | |
| } | |
| // For development environments, we'll always provide mock data if real data isn't available | |
| const isDevelopment = process.env.NODE_ENV === 'development'; | |
| interface SystemMemoryInfo { | |
| total: number; | |
| free: number; | |
| used: number; | |
| percentage: number; | |
| swap?: { | |
| total: number; | |
| free: number; | |
| used: number; | |
| percentage: number; | |
| }; | |
| timestamp: string; | |
| error?: string; | |
| } | |
| const getSystemMemoryInfo = (): SystemMemoryInfo => { | |
| try { | |
| // Check if we're in a Cloudflare environment and not in development | |
| if (!execSync && !isDevelopment) { | |
| // Return error for Cloudflare production environment | |
| return { | |
| total: 0, | |
| free: 0, | |
| used: 0, | |
| percentage: 0, | |
| timestamp: new Date().toISOString(), | |
| error: 'System memory information is not available in this environment', | |
| }; | |
| } | |
| // If we're in development but not in Node environment, return mock data | |
| if (!execSync && isDevelopment) { | |
| // Return mock data for development | |
| const mockTotal = 16 * 1024 * 1024 * 1024; // 16GB | |
| const mockPercentage = Math.floor(30 + Math.random() * 20); // Random between 30-50% | |
| const mockUsed = Math.floor((mockTotal * mockPercentage) / 100); | |
| const mockFree = mockTotal - mockUsed; | |
| return { | |
| total: mockTotal, | |
| free: mockFree, | |
| used: mockUsed, | |
| percentage: mockPercentage, | |
| swap: { | |
| total: 8 * 1024 * 1024 * 1024, // 8GB | |
| free: 6 * 1024 * 1024 * 1024, // 6GB | |
| used: 2 * 1024 * 1024 * 1024, // 2GB | |
| percentage: 25, | |
| }, | |
| timestamp: new Date().toISOString(), | |
| }; | |
| } | |
| // Different commands for different operating systems | |
| let memInfo: { total: number; free: number; used: number; percentage: number; swap?: any } = { | |
| total: 0, | |
| free: 0, | |
| used: 0, | |
| percentage: 0, | |
| }; | |
| // Check the operating system | |
| const platform = process.platform; | |
| if (platform === 'darwin') { | |
| // macOS | |
| const totalMemory = parseInt(execSync('sysctl -n hw.memsize').toString().trim(), 10); | |
| // Get memory usage using vm_stat | |
| const vmStat = execSync('vm_stat').toString().trim(); | |
| const pageSize = 4096; // Default page size on macOS | |
| // Parse vm_stat output | |
| const matches = { | |
| free: /Pages free:\s+(\d+)/.exec(vmStat), | |
| active: /Pages active:\s+(\d+)/.exec(vmStat), | |
| inactive: /Pages inactive:\s+(\d+)/.exec(vmStat), | |
| speculative: /Pages speculative:\s+(\d+)/.exec(vmStat), | |
| wired: /Pages wired down:\s+(\d+)/.exec(vmStat), | |
| compressed: /Pages occupied by compressor:\s+(\d+)/.exec(vmStat), | |
| }; | |
| const freePages = parseInt(matches.free?.[1] || '0', 10); | |
| const activePages = parseInt(matches.active?.[1] || '0', 10); | |
| const inactivePages = parseInt(matches.inactive?.[1] || '0', 10); | |
| // Speculative pages are not currently used in calculations, but kept for future reference | |
| const wiredPages = parseInt(matches.wired?.[1] || '0', 10); | |
| const compressedPages = parseInt(matches.compressed?.[1] || '0', 10); | |
| const freeMemory = freePages * pageSize; | |
| const usedMemory = (activePages + inactivePages + wiredPages + compressedPages) * pageSize; | |
| memInfo = { | |
| total: totalMemory, | |
| free: freeMemory, | |
| used: usedMemory, | |
| percentage: Math.round((usedMemory / totalMemory) * 100), | |
| }; | |
| // Get swap information | |
| try { | |
| const swapInfo = execSync('sysctl -n vm.swapusage').toString().trim(); | |
| const swapMatches = { | |
| total: /total = (\d+\.\d+)M/.exec(swapInfo), | |
| used: /used = (\d+\.\d+)M/.exec(swapInfo), | |
| free: /free = (\d+\.\d+)M/.exec(swapInfo), | |
| }; | |
| const swapTotal = parseFloat(swapMatches.total?.[1] || '0') * 1024 * 1024; | |
| const swapUsed = parseFloat(swapMatches.used?.[1] || '0') * 1024 * 1024; | |
| const swapFree = parseFloat(swapMatches.free?.[1] || '0') * 1024 * 1024; | |
| memInfo.swap = { | |
| total: swapTotal, | |
| used: swapUsed, | |
| free: swapFree, | |
| percentage: swapTotal > 0 ? Math.round((swapUsed / swapTotal) * 100) : 0, | |
| }; | |
| } catch (swapError) { | |
| console.error('Failed to get swap info:', swapError); | |
| } | |
| } else if (platform === 'linux') { | |
| // Linux | |
| const meminfo = execSync('cat /proc/meminfo').toString().trim(); | |
| const memTotal = parseInt(/MemTotal:\s+(\d+)/.exec(meminfo)?.[1] || '0', 10) * 1024; | |
| // We use memAvailable instead of memFree for more accurate free memory calculation | |
| const memAvailable = parseInt(/MemAvailable:\s+(\d+)/.exec(meminfo)?.[1] || '0', 10) * 1024; | |
| /* | |
| * Buffers and cached memory are included in the available memory calculation by the kernel | |
| * so we don't need to calculate them separately | |
| */ | |
| const usedMemory = memTotal - memAvailable; | |
| memInfo = { | |
| total: memTotal, | |
| free: memAvailable, | |
| used: usedMemory, | |
| percentage: Math.round((usedMemory / memTotal) * 100), | |
| }; | |
| // Get swap information | |
| const swapTotal = parseInt(/SwapTotal:\s+(\d+)/.exec(meminfo)?.[1] || '0', 10) * 1024; | |
| const swapFree = parseInt(/SwapFree:\s+(\d+)/.exec(meminfo)?.[1] || '0', 10) * 1024; | |
| const swapUsed = swapTotal - swapFree; | |
| memInfo.swap = { | |
| total: swapTotal, | |
| free: swapFree, | |
| used: swapUsed, | |
| percentage: swapTotal > 0 ? Math.round((swapUsed / swapTotal) * 100) : 0, | |
| }; | |
| } else if (platform === 'win32') { | |
| /* | |
| * Windows | |
| * Using PowerShell to get memory information | |
| */ | |
| const memoryInfo = execSync( | |
| 'powershell "Get-CimInstance Win32_OperatingSystem | Select-Object TotalVisibleMemorySize, FreePhysicalMemory | ConvertTo-Json"', | |
| ) | |
| .toString() | |
| .trim(); | |
| const memData = JSON.parse(memoryInfo); | |
| const totalMemory = parseInt(memData.TotalVisibleMemorySize, 10) * 1024; | |
| const freeMemory = parseInt(memData.FreePhysicalMemory, 10) * 1024; | |
| const usedMemory = totalMemory - freeMemory; | |
| memInfo = { | |
| total: totalMemory, | |
| free: freeMemory, | |
| used: usedMemory, | |
| percentage: Math.round((usedMemory / totalMemory) * 100), | |
| }; | |
| // Get swap (page file) information | |
| try { | |
| const swapInfo = execSync( | |
| "powershell \"Get-CimInstance Win32_PageFileUsage | Measure-Object -Property CurrentUsage, AllocatedBaseSize -Sum | Select-Object @{Name='CurrentUsage';Expression={$_.Sum}}, @{Name='AllocatedBaseSize';Expression={$_.Sum}} | ConvertTo-Json\"", | |
| ) | |
| .toString() | |
| .trim(); | |
| const swapData = JSON.parse(swapInfo); | |
| const swapTotal = parseInt(swapData.AllocatedBaseSize, 10) * 1024 * 1024; | |
| const swapUsed = parseInt(swapData.CurrentUsage, 10) * 1024 * 1024; | |
| const swapFree = swapTotal - swapUsed; | |
| memInfo.swap = { | |
| total: swapTotal, | |
| free: swapFree, | |
| used: swapUsed, | |
| percentage: swapTotal > 0 ? Math.round((swapUsed / swapTotal) * 100) : 0, | |
| }; | |
| } catch (swapError) { | |
| console.error('Failed to get swap info:', swapError); | |
| } | |
| } else { | |
| throw new Error(`Unsupported platform: ${platform}`); | |
| } | |
| return { | |
| ...memInfo, | |
| timestamp: new Date().toISOString(), | |
| }; | |
| } catch (error) { | |
| console.error('Failed to get system memory info:', error); | |
| return { | |
| total: 0, | |
| free: 0, | |
| used: 0, | |
| percentage: 0, | |
| timestamp: new Date().toISOString(), | |
| error: error instanceof Error ? error.message : 'Unknown error', | |
| }; | |
| } | |
| }; | |
| export const loader: LoaderFunction = async ({ request: _request }) => { | |
| try { | |
| return json(getSystemMemoryInfo()); | |
| } catch (error) { | |
| console.error('Failed to get system memory info:', error); | |
| return json( | |
| { | |
| total: 0, | |
| free: 0, | |
| used: 0, | |
| percentage: 0, | |
| timestamp: new Date().toISOString(), | |
| error: error instanceof Error ? error.message : 'Unknown error', | |
| }, | |
| { status: 500 }, | |
| ); | |
| } | |
| }; | |
| export const action = async ({ request: _request }: ActionFunctionArgs) => { | |
| try { | |
| return json(getSystemMemoryInfo()); | |
| } catch (error) { | |
| console.error('Failed to get system memory info:', error); | |
| return json( | |
| { | |
| total: 0, | |
| free: 0, | |
| used: 0, | |
| percentage: 0, | |
| timestamp: new Date().toISOString(), | |
| error: error instanceof Error ? error.message : 'Unknown error', | |
| }, | |
| { status: 500 }, | |
| ); | |
| } | |
| }; | |