Skip to main content

Command Palette

Search for a command to run...

Monitoring Ruby Process Memory Without the Bloat: A Lightweight DIY Approach

Updated
3 min read

The Problem: When Heavyweight Tools Are Too Heavy

Recently, I found myself needing to monitor memory usage in a long-running Ruby process. Like any good developer, I first reached for popular tools like memory_profiler and get_process_mem. But there was a problem: these tools themselves consumed significant memory, which felt ironic when trying to monitor a memory-sensitive process.

I realized I didn't need detailed object allocation traces or complex statistics – I just wanted a simple answer: "How much memory is my process using right now?"

The Discovery: Linux's Proc Filesystem

The solution came from Linux's /proc filesystem, specifically /proc/self/statm. This special file contains memory usage information for the current process in a simple space-separated format.

Key fields:

  • Field 1: Total program size (virtual memory)

  • Field 2: Resident Set Size (RSS) - physical memory being used

  • Field 3: Shared pages

Since we care about actual physical memory usage, RSS (field 2) is our golden number.

The Solution: A Minimal Memory Logger

Here's the core implementation I landed on:

def log_memory_usage(interval: 5, log_path: "memory.log")
  Thread.new do
    loop do
      begin
        # Get RSS from proc in pages (1 page = 4KB)
        rss_pages = File.read('/proc/self/statm').split[1].to_i
        rss_kb = rss_pages * 4

        # Append to log file with timestamp
        File.open(log_path, 'a') do |f|
          f.puts("[#{Time.now.utc.iso8601}] Memory: #{rss_kb} KB")
          f.flush
        end
      rescue => e
        $stderr.puts "Memory logging failed: #{e}"
      end

      sleep(interval)
    end
  end
end

Why This Works:

  1. Lightweight: Adds negligible overhead compared to gems like memory_profiler

  2. Simple: Just a few lines of core logic

  3. Efficient: File.flush ensures writes survive process crashes

  4. Thread-safe: Runs in background without blocking main process


Key Benefits I Discovered

  1. Zero Dependencies
    No need to add gems to your Gemfile or worry about version conflicts

  2. Customizable Logging
    Easily adjust:

    • Log frequency (from seconds to hours)

    • Output format (JSON, CSV, etc.)

    • Destination (file, STDOUT, remote service)

  3. Low Maintenance
    The /proc interface has been stable for decades – unlikely to break

  4. Surprisingly Portable
    Works on:

    • Any Linux distribution

    • Docker containers

    • WSL (Windows Subsystem for Linux)

Caveats and Alternatives

  1. Linux-only
    For macOS, consider using ps command parsing:
rss_kb = `ps -o rss= -p #{Process.pid}`.to_i
  1. Not for Detailed Profiling
    Use memory_profiler if you need object-level granularity

  2. Container-Aware
    In Docker, remember this shows container memory, not host memory

The Surprising Lesson

Sometimes the simplest solutions are the best. While sophisticated tools have their place, there's value in understanding your platform's fundamentals. By leveraging Linux's built-in instrumentation, I achieved:

  • Negligible memory footprint compared to gem-based solutions

  • Custom logging perfectly tailored to my needs

  • Deeper understanding of process memory management

Try It Yourself!

Next time you need basic memory monitoring, consider this approach. You might be surprised how much you can achieve with:

  • A few lines of Ruby

  • Linux fundamentals

  • A curious mindset