Created
June 10, 2025 12:10
-
-
Save telatin/2dd3c6294aad2b5fe77ac9ba75ef1a59 to your computer and use it in GitHub Desktop.
A cross-platform Nim program that tracks process and system memory usage before and after allocating a user-specified amount of RAM to test memory consumption.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os, strutils, parseopt, times, osproc | |
# Structure to hold memory information | |
type | |
MemInfo = object | |
processRamMB: float # Process RAM usage in MB | |
freeRamMB: float # System free RAM in MB | |
totalRamMB: float # Total system RAM in MB | |
# Get current process RAM usage in MB (cross-platform) | |
proc getProcessRamUsage(): float = | |
when defined(linux): | |
try: | |
# Linux: Read from /proc/self/status | |
let statusContent = readFile("/proc/self/status") | |
for line in statusContent.splitLines(): | |
if line.startsWith("VmRSS:"): | |
let parts = line.split() | |
if parts.len >= 2: | |
return parseFloat(parts[1]) / 1024.0 # Convert KB to MB | |
except: | |
discard | |
elif defined(macosx): | |
try: | |
# macOS: Use ps command to get memory usage | |
let output = execProcess("ps -o rss= -p " & $getCurrentProcessId()) | |
let rssKB = parseFloat(output.strip()) | |
return rssKB / 1024.0 # Convert KB to MB | |
except: | |
discard | |
elif defined(windows): | |
try: | |
# Windows: Use tasklist command | |
let pid = getCurrentProcessId() | |
let output = execProcess("tasklist /fi \"PID eq " & $pid & "\" /fo csv") | |
let lines = output.splitLines() | |
if lines.len > 1: | |
let fields = lines[1].split(",") | |
if fields.len >= 5: | |
# Memory usage is typically in the 5th field, remove quotes and commas | |
var memStr = fields[4].replace("\"", "").replace(",", "").replace(" K", "") | |
let memKB = parseFloat(memStr) | |
return memKB / 1024.0 # Convert KB to MB | |
except: | |
discard | |
# Fallback: Try generic approach using external tools | |
try: | |
when defined(posix): | |
# Try ps command as fallback for Unix-like systems | |
let output = execProcess("ps -o rss= -p " & $getCurrentProcessId()) | |
let rssKB = parseFloat(output.strip()) | |
return rssKB / 1024.0 | |
except: | |
discard | |
return -1.0 # Unable to determine | |
# Get system memory info (cross-platform) | |
proc getSystemMemInfo(): tuple[free, total: float] = | |
when defined(linux): | |
try: | |
# Linux: Read from /proc/meminfo | |
let meminfoContent = readFile("/proc/meminfo") | |
var memTotal, memAvailable: float = 0.0 | |
for line in meminfoContent.splitLines(): | |
if line.startsWith("MemTotal:"): | |
let parts = line.split() | |
if parts.len >= 2: | |
memTotal = parseFloat(parts[1]) / 1024.0 # Convert KB to MB | |
elif line.startsWith("MemAvailable:"): | |
let parts = line.split() | |
if parts.len >= 2: | |
memAvailable = parseFloat(parts[1]) / 1024.0 # Convert KB to MB | |
return (memAvailable, memTotal) | |
except: | |
discard | |
elif defined(macosx): | |
try: | |
# macOS: Use vm_stat and sysctl commands | |
let vmOutput = execProcess("vm_stat") | |
let sysctlOutput = execProcess("sysctl hw.memsize") | |
# Parse total memory from sysctl | |
var totalBytes: float = 0.0 | |
for line in sysctlOutput.splitLines(): | |
if "hw.memsize:" in line: | |
let parts = line.split(":") | |
if parts.len >= 2: | |
totalBytes = parseFloat(parts[1].strip()) | |
break | |
# Parse memory statistics from vm_stat | |
var freePages, inactivePages, speculativePages: float = 0.0 | |
for line in vmOutput.splitLines(): | |
let cleanLine = line.strip() | |
if cleanLine.startsWith("Pages free:"): | |
let numStr = cleanLine.split(":")[1].strip().replace(".", "") | |
try: | |
freePages = parseFloat(numStr) | |
except: | |
discard | |
elif cleanLine.startsWith("Pages inactive:"): | |
let numStr = cleanLine.split(":")[1].strip().replace(".", "") | |
try: | |
inactivePages = parseFloat(numStr) | |
except: | |
discard | |
elif cleanLine.startsWith("Pages speculative:"): | |
let numStr = cleanLine.split(":")[1].strip().replace(".", "") | |
try: | |
speculativePages = parseFloat(numStr) | |
except: | |
discard | |
# Get page size dynamically | |
let pageSizeOutput = execProcess("sysctl hw.pagesize") | |
var pageSize: float = 4096.0 # Default fallback | |
for line in pageSizeOutput.splitLines(): | |
if "hw.pagesize:" in line: | |
let parts = line.split(":") | |
if parts.len >= 2: | |
try: | |
pageSize = parseFloat(parts[1].strip()) | |
except: | |
discard | |
break | |
let totalMB = totalBytes / (1024.0 * 1024.0) | |
# Available memory includes free + inactive + speculative pages | |
let availableMB = (freePages + inactivePages + speculativePages) * pageSize / (1024.0 * 1024.0) | |
return (availableMB, totalMB) | |
except Exception as e: | |
# Debug: show what went wrong | |
echo "macOS memory detection error: ", e.msg | |
discard | |
elif defined(windows): | |
try: | |
# Windows: Use wmic command | |
let totalOutput = execProcess("wmic computersystem get TotalPhysicalMemory /value") | |
let availOutput = execProcess("wmic OS get FreePhysicalMemory /value") | |
var totalBytes, freeKB: float = 0.0 | |
# Parse total memory | |
for line in totalOutput.splitLines(): | |
if "TotalPhysicalMemory=" in line: | |
let value = line.split("=")[1].strip() | |
if value.len > 0: | |
totalBytes = parseFloat(value) | |
break | |
# Parse free memory | |
for line in availOutput.splitLines(): | |
if "FreePhysicalMemory=" in line: | |
let value = line.split("=")[1].strip() | |
if value.len > 0: | |
freeKB = parseFloat(value) | |
break | |
let totalMB = totalBytes / (1024.0 * 1024.0) | |
let freeMB = freeKB / 1024.0 | |
return (freeMB, totalMB) | |
except: | |
discard | |
return (-1.0, -1.0) # Unable to determine | |
# Get complete memory information | |
proc getMemInfo(): MemInfo = | |
let processRam = getProcessRamUsage() | |
let (freeRam, totalRam) = getSystemMemInfo() | |
return MemInfo( | |
processRamMB: processRam, | |
freeRamMB: freeRam, | |
totalRamMB: totalRam | |
) | |
# Display memory information in a formatted way | |
proc displayMemInfo(info: MemInfo, label: string) = | |
echo "\n=== ", label, " ===" | |
if info.processRamMB >= 0: | |
echo "Process RAM usage: ", info.processRamMB.formatFloat(ffDecimal, 2), " MB" | |
else: | |
echo "Process RAM usage: Unable to determine" | |
if info.freeRamMB >= 0: | |
echo "Free system RAM: ", info.freeRamMB.formatFloat(ffDecimal, 2), " MB" | |
else: | |
echo "Free system RAM: Unable to determine" | |
if info.totalRamMB >= 0: | |
echo "Total system RAM: ", info.totalRamMB.formatFloat(ffDecimal, 2), " MB" | |
if info.freeRamMB >= 0: | |
let usedPercent = ((info.totalRamMB - info.freeRamMB) / info.totalRamMB) * 100 | |
echo "System RAM usage: ", usedPercent.formatFloat(ffDecimal, 1), "%" | |
# Parse command line arguments | |
proc parseArgs(): int = | |
var megabytes = 10 # Default value | |
for kind, key, val in getopt(): | |
case kind | |
of cmdArgument: | |
discard | |
of cmdLongOption, cmdShortOption: | |
if key == "megabytes" or key == "m": | |
try: | |
megabytes = parseInt(val) | |
if megabytes <= 0: | |
echo "Error: megabytes must be positive" | |
quit(1) | |
except ValueError: | |
echo "Error: Invalid number for megabytes" | |
quit(1) | |
of cmdEnd: | |
break | |
return megabytes | |
# Alternative memory monitoring using Nim's memory info | |
proc getNimMemInfo(): tuple[used, free: int] = | |
# Get Nim's internal memory statistics if available | |
when declared(getTotalMem) and declared(getFreeMem): | |
return (getTotalMem(), getFreeMem()) | |
else: | |
return (-1, -1) | |
# Display system information for debugging | |
proc displaySystemInfo() = | |
echo "System Information:" | |
echo "- OS: ", hostOS | |
echo "- CPU: ", hostCPU | |
echo "- Process ID: ", getCurrentProcessId() | |
# Try to show available memory monitoring methods | |
when defined(linux): | |
echo "- Memory monitoring: Linux /proc filesystem" | |
elif defined(macosx): | |
echo "- Memory monitoring: macOS vm_stat/sysctl" | |
elif defined(windows): | |
echo "- Memory monitoring: Windows wmic" | |
else: | |
echo "- Memory monitoring: Generic fallback" | |
# Main program | |
proc main() = | |
echo "RAM Usage Monitor (Cross-Platform)" | |
echo "==================================" | |
displaySystemInfo() | |
# Parse command line arguments | |
let megabytesToAllocate = parseArgs() | |
echo "\nWill allocate ", megabytesToAllocate, " MB of memory" | |
# Get initial memory info | |
let initialMem = getMemInfo() | |
displayMemInfo(initialMem, "BEFORE allocation") | |
# Show Nim's internal memory info if available | |
let (nimUsedBefore, nimFreeBefore) = getNimMemInfo() | |
if nimUsedBefore >= 0: | |
echo "Nim internal - Used: ", nimUsedBefore, " bytes, Free: ", nimFreeBefore, " bytes" | |
# Calculate number of integers needed for specified MB | |
let bytesPerInt = sizeof(int) | |
let totalBytes = megabytesToAllocate * 1024 * 1024 | |
let numInts = totalBytes div bytesPerInt | |
echo "\nAllocating ", numInts, " integers (", bytesPerInt, " bytes each)..." | |
echo "Expected memory usage: ~", totalBytes, " bytes (", megabytesToAllocate, " MB)" | |
# Allocate memory by creating a large sequence and filling it | |
var largeData = newSeq[int](numInts) | |
# Fill the sequence with data to ensure memory is actually allocated | |
echo "Filling data structure..." | |
let startTime = cpuTime() | |
for i in 0..<numInts: | |
largeData[i] = i mod 1000 # Fill with some pattern to prevent optimization | |
let endTime = cpuTime() | |
echo "Filled ", numInts, " integers in ", (endTime - startTime).formatFloat(ffDecimal, 3), " seconds" | |
# Force garbage collection to get accurate measurements | |
when declared(GC_fullCollect): | |
GC_fullCollect() | |
# Get memory info after allocation | |
let finalMem = getMemInfo() | |
displayMemInfo(finalMem, "AFTER allocation") | |
# Show Nim's internal memory info after allocation | |
let (nimUsedAfter, nimFreeAfter) = getNimMemInfo() | |
if nimUsedAfter >= 0: | |
echo "Nim internal - Used: ", nimUsedAfter, " bytes, Free: ", nimFreeAfter, " bytes" | |
if nimUsedBefore >= 0: | |
echo "Nim memory increase: ", nimUsedAfter - nimUsedBefore, " bytes" | |
# Calculate and display differences | |
if initialMem.processRamMB >= 0 and finalMem.processRamMB >= 0: | |
let ramIncrease = finalMem.processRamMB - initialMem.processRamMB | |
echo "\n=== MEMORY CHANGE ===" | |
echo "Process RAM increase: ", ramIncrease.formatFloat(ffDecimal, 2), " MB" | |
echo "Expected increase: ~", megabytesToAllocate, " MB" | |
if initialMem.freeRamMB >= 0 and finalMem.freeRamMB >= 0: | |
let freeDecrease = initialMem.freeRamMB - finalMem.freeRamMB | |
echo "System free RAM decrease: ", freeDecrease.formatFloat(ffDecimal, 2), " MB" | |
else: | |
echo "\n=== MEMORY VERIFICATION ===" | |
echo "Unable to measure process RAM directly on this system." | |
echo "However, allocated data structure contains:" | |
echo "- ", largeData.len, " integers" | |
echo "- Total size: ", largeData.len * sizeof(int), " bytes" | |
echo "- Expected size: ~", megabytesToAllocate, " MB" | |
# Keep the data in memory for a moment to see the effect | |
echo "\nPress Enter to exit and free memory..." | |
discard readLine(stdin) | |
# Data will be automatically freed when largeData goes out of scope | |
# Entry point | |
when isMainModule: | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment