Skip to content

Instantly share code, notes, and snippets.

@telatin
Created June 10, 2025 12:10
Show Gist options
  • Save telatin/2dd3c6294aad2b5fe77ac9ba75ef1a59 to your computer and use it in GitHub Desktop.
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.
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