68

I'd like to format my Logger output to include the current memory usage, for part of a long-running process.

Is there anything built-in to Ruby for this, a bit like PHP's memory_get_usage()? Or do I have to execute some shell commands to get it from ps?

ahmet
  • 4,955
  • 11
  • 39
  • 64
d11wtq
  • 34,788
  • 19
  • 120
  • 195
  • 2
    Maybe look at this question: http://stackoverflow.com/questions/4132916/getting-memory-usage-of-my-process-from-osx-using-ruby – Michael Kohl Aug 28 '11 at 12:26
  • I think I heard once that Ruby 1.9.2 has a memory profiler. – Andrew Grimm May 15 '12 at 22:51
  • Note that adding this in your logging is probably not a good idea using a solution that relies on running external commands. The way ruby works when executing code inside \`backticks\` is that the current ruby process will be forked until the execution is finished. This doubles the memory consumption of your program every time the code is executed. You will most likely run into out of memory errors. The OS-gem does this also. – Kimmo Lehto Jun 26 '14 at 05:47

7 Answers7

42

The NewRelic gem provides simple RSS usage implementations for a number of operating systems and ruby runtimes with their MemorySampler class.

Include the newrelic_rpm gem in your Gemfile and invoke it thus:

NewRelic::Agent::Samplers::MemorySampler.new.sampler.get_sample

and it returns the number of megabytes of memory the current process holds as the RSS.

The implementation prefers in-process counters where available (jruby), use the /proc/#{$$}/status on Linux, and fall back to ps everywhere else.

rud
  • 1,012
  • 8
  • 22
  • 1
    feel like this should be the correct answer since it avoids the use of backticks and spawning of new processes. if you're already concerned about memory usage, thats a bad road to go down –  Dec 05 '18 at 19:09
  • @us except that it does spawn a new process - namely `ps`, and in quite a similar way to the accepted answer, with additional processing for different platforms :) (pls check out the "MemorySampler class" link above) – Halil Özgür Mar 17 '21 at 13:20
  • It spawns a `ps` process as the final fallback on non-Linux OSes. At the time when this was written I was unable to find any other way of getting this information from the running process. I think the [`os`](https://rubygems.org/gems/os) gem described elsewhere is probably a better choice these days. – rud Apr 12 '21 at 09:48
  • I take that back, actually, the `os` gem uses the same kind of algorithm, ending up spawning `ps` on Linux: https://github.com/rdp/os/blob/a7256aa1eebdbab545212b7330131126f973aa14/lib/os.rb#L139-L170 – rud Apr 12 '21 at 09:50
42

When trying to solve this problem a year ago, I did a lot of online research and API digging and was only able to solve it via a system call to ps.

In both OS X 10.7.2 and Red Hat 4.1.2-13 (on EC2):

pid, size = `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)

This fetches and places the resident memory size of the process in kilobytes into the size variable.

With a little effort this could be cleaned up, but most of the time is spend calling ps and capturing its output, so I don't think it is worth the time.

Paploo
  • 661
  • 6
  • 5
  • 11
    it would probably be easier if you simply `ps -o rss -p #{$$}.chomp.split("\n").last.to_i` instead – Philip C Oct 09 '13 at 11:32
  • 2
    And as noted in another answer, if your pid is 1234 and there's another process with pid 12345 this can/will give you wrong results. – Kimmo Lehto May 21 '14 at 16:35
  • should be noted that this solution is platform dependent. – amenthes Sep 13 '16 at 11:14
  • 1
    it seems `size = \`ps -o rss= -p #{$$}\`.to_i` or `puts "%.1fMB used" % [\`ps -o rss= -p #{$$}\`.to_f/1024]` is even better and shorter to get the job done. It works on the Mac and Ubuntu – nonopolarity Aug 19 '19 at 12:04
17

Using external commands on Ruby like ps through using backticks will fork the current process for the duration of running the command. This means that if your Ruby process consumes 300mb, you will need another 300mb just to run any of these `ps -o rss #{$$}`.strip.split.last.to_i solutions.

On linux based systems you can get process memory information by reading /proc/PID/statm. The second field is the Resident Set Size in number of kernel pages. Converting the RSS pages to bytes requires you to figure out the kernel page size (most likely 4096).

Here's sample code how to get the rss in kilobytes, works on Linux. I don't know how to do this on OSX or other systems.

module MemInfo
  # This uses backticks to figure out the pagesize, but only once
  # when loading this module.
  # You might want to move this into some kind of initializer
  # that is loaded when your app starts and not when autoload
  # loads this module.
  KERNEL_PAGE_SIZE = `getconf PAGESIZE`.chomp.to_i rescue 4096 
  STATM_PATH       = "/proc/#{Process.pid}/statm"
  STATM_FOUND      = File.exist?(STATM_PATH)

  def self.rss
    STATM_FOUND ? (File.read(STATM_PATH).split(' ')[1].to_i * KERNEL_PAGE_SIZE) / 1024 : 0
  end
end

# >> MemInfo.rss
# => 251944
Kimmo Lehto
  • 5,910
  • 1
  • 23
  • 32
  • 6
    This is true for older versions of Ruby, but Ruby is copy-on-write (COW) friendly from 2.0 onwards. – britishtea Dec 04 '14 at 16:40
  • Even in old versions of Ruby this answer is wrong. `fork()` is _always_ copy on write, and immediately after forking, ruby is going to call some flavor of `exec()`. So the process isn't around long enough for any of the "unfriendly" stuff to happen. – Phil Frost Jan 21 '21 at 21:04
11

The OS gem has an rss_bytes method.

require "os"
puts "#{OS.rss_bytes / 1_000_000} MB"
rogerdpack
  • 62,887
  • 36
  • 269
  • 388
  • 2
    the code from this gem is faster, `ps -o rss= -p #{Process.pid}`.to_i is about 6ms on my machine and the top answer is about 11ms – Kalendae Nov 30 '12 at 01:24
  • A heads-up: the OS gem actually spawns `ps` on linux, as of the current latest commit: https://github.com/rdp/os/blob/a7256aa1eebdbab545212b7330131126f973aa14/lib/os.rb#L139-L170 – rud Apr 12 '21 at 09:51
11

You can simple use this puts statement

puts 'RAM USAGE: ' + `pmap #{Process.pid} | tail -1`[10,40].strip
Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
Abhishek
  • 1,558
  • 16
  • 28
5

Time has moved on and there is now a gem for that: get_process_mem

require 'get_process_mem'
mem = GetProcessMem.new
puts "Memory used : #{mem.mb.round(0)} MB"
J Edward Ellis
  • 1,368
  • 2
  • 12
  • 21
0

Alluded to in other forms here, but I found this to be the simplest incantation, at least on Mac OS:

`ps -o rss #{Process.pid}`.lines.last.to_i

From man ps:

rss   the real memory (resident set) size of the process (in 1024 byte units).
Keith Bennett
  • 4,722
  • 1
  • 25
  • 35