获取当前 Ruby 进程的内存使用情况

68
我想要格式化我的Logger输出,以包含当前内存使用情况,用于长时间运行的过程的一部分。
Ruby中是否有内置的功能可以实现这一点,有点像PHP的memory_get_usage()?还是我需要执行一些shell命令来从ps获取它?

2
也许可以看一下这个问题:https://dev59.com/xG855IYBdhLWcg3wz3zo - Michael Kohl
我记得曾经听说过 Ruby 1.9.2 有一个内存分析器。 - Andrew Grimm
请注意,在记录日志时使用依赖于运行外部命令的解决方案可能不是一个好主意。当在 `反引号` 中执行代码时,Ruby 的工作方式是当前 Ruby 进程将被分叉,直到执行完成。每次执行代码时,这会使程序的内存消耗翻倍。您很可能会遇到内存不足错误。OS-gem 也是如此。 - Kimmo Lehto
7个回答

42

一年前,我尝试解决此问题时,进行了大量的在线研究和API挖掘,只能通过调用ps系统调用来解决它。

在OS X 10.7.2和Red Hat 4.1.2-13(在EC2上)中都适用:

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

这个代码会获取当前进程的驻留内存大小(以千字节为单位),并将其放入size变量中。

虽然可以稍加修改来改进代码,但大部分时间都花在调用ps命令和捕获其输出上,因此我认为这样做不值得。


11
如果你直接运行 ps -o rss -p #{$$}.chomp.split("\n").last.to_i,可能会更容易些。这句话的意思是获取当前进程的内存使用情况。 - Philip C
2
正如另一个答案中所指出的,如果您的pid是1234,而另一个进程的pid是12345,这可能会导致错误的结果。 - Kimmo Lehto
应该注意到这个解决方案依赖于平台。 - amenthes
1
似乎使用 size = \ps -o rss= -p #{$$}`.to_i或者puts "%.1fMB used" % [`ps -o rss= -p #{$$}`.to_f/1024]` 更好更简洁,可以完成工作。它适用于Mac和Ubuntu。 - nonopolarity

42

NewRelic gem提供了一些针对操作系统和ruby运行时的简单RSS使用实现,使用它们的MemorySampler类。(参见此处)

在您的Gemfile中包含newrelic_rpm gem,并这样调用:

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

返回当前进程占用的内存大小(以RSS表示),单位为兆字节。

实现时优先使用进程内计数器(例如jruby),在Linux上使用/proc/#{$$}/status,其他情况下则回退到ps


1
感觉这应该是正确的答案,因为它避免了使用反引号和生成新进程。如果你已经担心内存使用,那么这是一条不好走的路。 - user4468000
除了它会生成一个新的进程(即ps),并且以与被接受的答案相似的方式进行处理,还有针对不同平台的额外处理 :) (请查看上面的“MemorySampler类”链接) - Halil Özgür
它在非Linux操作系统上作为最后的备选方案生成一个ps进程。在编写此文时,我无法找到任何其他获取正在运行进程信息的方法。我认为,在当今这个时代,其他地方描述的os gem可能是更好的选择。 - rud
我收回之前的说法,实际上 os gem 使用了相同类型的算法,在 Linux 上最终会生成 ps 进程:https://github.com/rdp/os/blob/a7256aa1eebdbab545212b7330131126f973aa14/lib/os.rb#L139-L170 - rud

17

使用Ruby的外部命令(例如通过回引号使用ps)将在运行命令期间分叉当前进程。这意味着,如果您的Ruby进程消耗了300MB,则需要另外300MB才能运行任何这些`ps -o rss #{$$}`.strip.split.last.to_i解决方案。

在基于Linux的系统上,您可以通过读取/proc/PID/statm来获取进程内存信息。第二个字段是以内核页面数为单位的常驻集大小(RSS)。将RSS页面转换为字节需要确定内核页大小(最有可能是4096)。

以下是一段示例代码,用于获取以千字节为单位的rss,在Linux上有效。我不知道如何在OSX或其他系统上执行此操作。

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

6
这适用于较早版本的Ruby,但自2.0版本以来,Ruby支持写时复制(COW)。 - britishtea
即使在旧版本的Ruby中,这个答案也是错误的。fork()始终是写时复制,并且在分叉之后,Ruby将立即调用某种形式的exec()。因此,进程不会存在足够长的时间来发生任何“不友好”的事情。 - Phil Frost

12

您可以简单地使用此puts语句

puts 'RAM USAGE: ' + `pmap #{Process.pid} | tail -1`[10,40].strip

11

OS gem有一个rss_bytes方法。

require "os"
puts "#{OS.rss_bytes / 1_000_000} MB"

2
这个宝石的代码更快,ps -o rss= -p #{Process.pid}.to_i 在我的机器上大约为6毫秒,而最佳答案大约为11毫秒。 - Kalendae
提醒一下:OS gem实际上在Linux上生成了ps,截至当前最新提交:https://github.com/rdp/os/blob/a7256aa1eebdbab545212b7330131126f973aa14/lib/os.rb#L139-L170 - rud

5

时光流转,现在有一个针对此功能的宝石:get_process_mem

require 'get_process_mem'
mem = GetProcessMem.new
puts "Memory used : #{mem.mb.round(0)} MB"

你能演示一下如何使用它吗? - Dharman

0

在其他形式中已提及,但我发现这是最简单的咒语,至少在Mac OS上:

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

来自man ps

rss   the real memory (resident set) size of the process (in 1024 byte units).

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接