`
robbin
  • 浏览: 4825859 次
  • 性别: Icon_minigender_1
  • 来自: 上海
博客专栏
377a9ecd-1ea1-34ac-9530-9daa53bb2a7b
robbin谈管理
浏览量:137460
社区版块
存档分类
最新评论

监视Rails进程内存泄漏的技巧

    博客分类:
  • Ruby
阅读更多
Rails应用比较容易遇到的两类性能问题:一类是Rails执行很慢,CPU消耗过高;另一类是Rails进程内存泄漏。解决这两类问题都需要你首先能够精确定位出现问题的代码,然后才知道如何对症下药。

一、如何监控Rails进程的执行性能

定位消耗CPU高,执行速度缓慢的Rails代码,是相当容易的事情,仅仅需要你对production.log做一点统计分析,抽取出来执行时间最长的请求,问题就昭然若揭了。由于production.log对Rails请求的执行时间做了详细的统计,例如:

Completed in 0.00693 (144 reqs/sec) | Rendering: 0.00489 (70%) | DB: 0.00000 (0%) | 200 OK [http://www.iteye.com/]
Completed in 0.17238 (5 reqs/sec) | Rendering: 0.10011 (58%) | DB: 0.06244 (36%) | 200 OK [http://www.iteye.com/topic/49441?page=7]
Completed in 0.20508 (4 reqs/sec) | Rendering: 0.19373 (94%) | DB: 0.00645 (3%) | 200 OK [http://www.iteye.com/news/1586]


所以我们只需要写一行shell命令,就搞定了!他把最耗时的前500个请求筛选出来,保存到timing.log里面。

grep "200 OK" production.log | awk '{print "ALL: " $3 "  View: " $8 " DB: " $12 "  URL: " $17 }' \
| sort -r | head -n 500 > timing.log


排序好的结果例如:

ALL: 5.51774  View: 5.38277 DB: 0.13338  URL: [http://www.iteye.com/wiki/topic/131966]
ALL: 5.51316  View: 5.31300 DB: 0.19400  URL: [http://www.iteye.com/wiki/topic/145383]
ALL: 5.51311  View: 5.39321 DB: 0.11234  URL: [http://www.iteye.com/wiki/topic/160370]
ALL: 5.51135  View: 5.37604 DB: 0.12652  URL: [http://www.iteye.com/wiki/topic/233365]
ALL: 5.49881  View: 5.35998 DB: 0.10637  URL: [http://www.iteye.com/wiki/topic/265217]


哪些请求执行的慢,一目了然。 当然除此之外,我们还可以实时监控,在top监视窗口显示Rails当前正在执行的请求URL

二、如何监控Rails进程的内存泄漏

监控CPU是很容易的事情,但要监控Rails进程的内存泄漏,却非常困难,原因在于production.log里面并没有记录进程的内存变化状况,甚至你找不到任何ruby API可以用来直接查询到进程使用的物理内存。实际上,要获取一个进程的物理内存是一个平台相关的操作,每个操作系统都会自己特定的API,并不通用,即使用C语言来编码,也不是一件容易的事情。

不过对于Linux操作系统来说,我们有一个捷径可以获取进程的内存状况。Linux的/proc文件系统是内核的映象,/proc/进程pid/status 文件记录了这个进程的状态信息,例如:
Name:   dispatch.fcgi
State:  S (sleeping)
SleepAVG:       135%
Tgid:   26645
Pid:    26645
PPid:   1
TracerPid:      0
Uid:    1002    1002    1002    1002
Gid:    100     100     100     100
FDSize: 64
Groups: 14 16 17 33 100 
VmSize:   245680 kB
VmLck:         0 kB
VmRSS:    209104 kB
VmData:   205116 kB
VmStk:       824 kB
VmExe:       764 kB
VmLib:      4220 kB
Threads:        1
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000001000
SigCgt: 0000000002006e47
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000


注意第14行VmRSS,记录了该进程使用的常驻物理内存(Residence),这个就是该进程实际占用的物理内存了。因此只要我们读取该文件第14行,就可以得到内存信息。

所以我们的任务变成了:在Rails处理请求之前记录内存,等Rails处理完请求之后,再记录内存,计算内存的变化状况,写入到production.log里面去。完成这个工作,只需要我们在Rails应用的app/controllers/application.rb里面添加几行代码:

  around_filter :record_memory
  def record_memory
    process_status = File.open("/proc/#{Process.pid}/status")
    13.times { process_status.gets }
    rss_before_action = process_status.gets.split[1].to_i
    process_status.close
    yield
    process_status = File.open("/proc/#{Process.pid}/status")
    13.times { process_status.gets }
    rss_after_action = process_status.gets.split[1].to_i
    process_status.close
    logger.info("CONSUME MEMORY: #{rss_after_action - rss_before_action} \
KB\tNow: #{rss_after_action} KB\t#{request.url}")
  end


我们定义了一个AroundFilter,记录一下处理请求前后的内存变化。有了这个信息,我们接下来的事情就简单了,只需要从production.log里面抽取出来这行log,进行统计分析就可以了,这也仅仅只需要一行shell就搞定了:

grep "CONSUME MEMORY" production.log | grep -v "CONSUME MEMORY: 0" |  \
 grep -v "CONSUME MEMORY: -" |  awk '{print $3 "\t" $6 "\t" $8 }' | sort -r -n | \
 head -n 500 > memory.log 


抽取内存记录,去掉内存没有增加,去掉内存减少(发生了GC)的请求,然后对那些处理请求之后内存上升的记录进行排序,取出来前500条记录保存到memory.log里面,结果如下所示:

增加数 内存占用    请求URL
-----------------------------------------------
9528  175264  http://www.iteye.com/topic/304594
9524  129512  http://knityster.iteye.com/blog/172990
9496  147544  http://www.iteye.com/forums/
9492  197800  http://duyiwuer.iteye.com/rss
9452  146668  http://www.iteye.com/forums
9452  133844  http://wildlife.iteye.com/blog/47693
9440  157824  http://www.iteye.com/rss/blogs
9424  204664  http://www.iteye.com/wiki/topic/251964
9384  142200  http://towerhe.iteye.com/blog/93704
9380  165372  http://www.iteye.com/wiki/topic/77434
9368  207460  http://superleo.iteye.com/rss


第一列是访问了一个请求以后,Rails进程的内存上升了9MB多,第二列是处理完请求,Rails进程当前实际占了170多MB内存,第三列是处理了什么请求。

根据这个统计结果,你可以很容易找出那些造成你Rails进程内存泄漏的罪魁祸首,哪些请求一访问你的Rails进程内存就飚升已经是一目了然的事情了,这是不是很简单?事实上通过这个办法,JavaEye仅用了半个多小时,就解决了曾经困扰了半年多的内存泄漏问题,办法虽土,却很有效!
分享到:
评论
14 楼 鹤惊昆仑 2009-11-04  
2005年出版的《Python Cookbook, 2nd Edition》Recipe 8.2. Measuring Memory Usage on Linux 也是这个思路。
13 楼 QuakeWang 2009-10-10  
今天发现一个更简单的获取内存使用的方法:
memory_usage = `ps -o rss= -p #{$$}`.to_i

这里的$$是ruby进程的pid
12 楼 wakuwaku 2009-01-12  
好像GOD就是干这个事情得

11 楼 jiachengxi38 2009-01-08  
是的,那日也来尝试一下,我的centos也是第16行,自己也小写了一个循环的shell,定期检测一下。
10 楼 QuakeWang 2009-01-07  
magicgod 写道
在debian系下的status,第16行才是RSS

所以要改为这样:
15.times { process_status.gets }  


可能其他linux也有不同,事先人工查看一下。


可以用正则匹配VmRSS这一行 , Robbin这个16.times也太偷懒了,呵呵
9 楼 magicgod 2009-01-06  
另外如果自己改了production.log的格式,那么要注意数好列数,以空格为分隔,不要完全照抄这个shell,里面的列数是写死的,要照实际列数来改一下。

缺省情况下可以照抄。

附上我写的几个简单的shell, 只是略微包装了一下。另外我的日志里的列数是不同的,不要照抄。
没有写死日志名,运行的时候要跟一个参数<log filename>。

memory.sh:
if [ -z $1 ] ;then
  echo "usage: memory.sh <log filename>"
else
  grep "CONSUME MEMORY" $1 | grep -v "CONSUME MEMORY: 0" |  grep -v "CONSUME MEMORY: -" |  awk '{print $10 "\t" $13 "\t" $15 }' | sort -r -n |  head -n 500 > memory.log   

  head memory.log
fi



timing.sh:
if [ -z $1 ] ;then
  echo "usage: timing.sh <log filename>"
else

  grep "200 OK" $1 | awk '{print "ALL: " $10 "  View: " $15 " DB: " $19 "  URL: " $24 }'  | sort -r | head -n 500 > timing.log  

  head timing.log
fi

8 楼 magicgod 2009-01-06  
在debian系下的status,第16行才是RSS

所以要改为这样:
15.times { process_status.gets }  


可能其他linux也有不同,事先人工查看一下。
7 楼 robbin 2009-01-05  
frozentree 写道
一个CAP任务,做类似的事情。好处是可以在多个Server上跑。

task :fdr, :roles=>:app do
    # ignore lines like CACHE (0.000000) - vermeer
    run <<-CMD
      cd #{deploy_to}/current;
      echo -----------------------------------------------------;
      echo TOP TWENTY UUID WITH REQUEST;
      echo -----------------------------------------------------;
      cat log/production.log | grep UUID | sed '/CACHE (0.000000)/d' | sed 's/.[0-9]* [a-z]* .*/\1/' | awk -F ' UID:.* user_id:' '{print $1 $2}' > tmp.uuid;
      uniq -w 42 -dc tmp.uuid dangers.uuid;
      sort -n -r dangers.uuid | head -20;
      rm -f tmp.uuid;
      rm -f dangers.uuid;
      echo -----------------------------------------------------;
      echo TOP TWENTY COMPLETED LONG TIMES REQUEST;
      echo -----------------------------------------------------;
      cat log/production.log | grep 'Completed in ' | awk -F 'Completed in ' '{print $2}' | cut -d ' ' -f1 > tmp.times;
      sort -n -r tmp.times | head -20 > dangers.times;
      while read line;
      do
        cat log/production.log | grep \"Completed in $line\" | awk -F '(' '{print $1}' | sed 's/ UID:.* user_id://g';
      done < dangers.times;
      rm -f tmp.times;
      rm -f dangers.times;
      cd #{deploy_to}/current;
      echo -----------------------------------------------------;
      echo COUNT OF LINE WITHOUT UUID;
      echo -----------------------------------------------------;
      cat log/production.log | grep "UUID: UID: user_id: " > uuid.times;
      echo "Count:"`wc -l uuid.times | cut -d ' ' -f1`;
      rm -f uuid.times;
    CMD
  end


你这个根本就监控不了内存。 要在多个Server上面跑,用Shell照应可以,而且比你这个脚本还要简单的多,还是一行shell搞定,用不着这么四五十行代码。
6 楼 seemoon 2009-01-05  
轻巧地解决,很草根,很强
5 楼 frozentree 2009-01-05  
一个CAP任务,做类似的事情。好处是可以在多个Server上跑。

task :fdr, :roles=>:app do
    # ignore lines like CACHE (0.000000) - vermeer
    run <<-CMD
      cd #{deploy_to}/current;
      echo -----------------------------------------------------;
      echo TOP TWENTY UUID WITH REQUEST;
      echo -----------------------------------------------------;
      cat log/production.log | grep UUID | sed '/CACHE (0.000000)/d' | sed 's/.[0-9]* [a-z]* .*/\1/' | awk -F ' UID:.* user_id:' '{print $1 $2}' > tmp.uuid;
      uniq -w 42 -dc tmp.uuid dangers.uuid;
      sort -n -r dangers.uuid | head -20;
      rm -f tmp.uuid;
      rm -f dangers.uuid;
      echo -----------------------------------------------------;
      echo TOP TWENTY COMPLETED LONG TIMES REQUEST;
      echo -----------------------------------------------------;
      cat log/production.log | grep 'Completed in ' | awk -F 'Completed in ' '{print $2}' | cut -d ' ' -f1 > tmp.times;
      sort -n -r tmp.times | head -20 > dangers.times;
      while read line;
      do
        cat log/production.log | grep \"Completed in $line\" | awk -F '(' '{print $1}' | sed 's/ UID:.* user_id://g';
      done < dangers.times;
      rm -f tmp.times;
      rm -f dangers.times;
      cd #{deploy_to}/current;
      echo -----------------------------------------------------;
      echo COUNT OF LINE WITHOUT UUID;
      echo -----------------------------------------------------;
      cat log/production.log | grep "UUID: UID: user_id: " > uuid.times;
      echo "Count:"`wc -l uuid.times | cut -d ' ' -f1`;
      rm -f uuid.times;
    CMD
  end
4 楼 xhanxhanxhan 2009-01-04  
学习了。LINUX 监控内存确实方便。
3 楼 rain2005 2008-12-31  
robbin对Linux好精通啊,尤其是shell.
2 楼 holin 2008-12-31  
赞。
也要尝试一下
1 楼 yb31 2008-12-30  
  好实用.收藏先
如果最后的javaeye内存泄漏的bug点能够详细一点或者另开一篇详解就好了.

相关推荐

    Rails3 使用rake启动后台任务

    Rails3 是 Ruby on Rails 框架的一个版本,它在2010年发布,引入了许多新特性并改进了框架的性能。Rake 是 Ruby 的一个构建工具,类似于 Java 的 Ant 或者 Python 的 setup.py,它允许开发者用自然语言定义任务,并...

    Rails 101 入门电子书

    ### Rails 101 入门电子书知识点详解 #### 一、简介 《Rails 101 入门电子书》是一本非常适合初学者直接入门的书籍,它由xdite编写并出版于2014年6月10日。本书主要针对的是希望学习Ruby on Rails框架的读者,特别...

    Rails

    描述中提到的博文链接指向了一个ITEYE博客文章,尽管具体内容未提供,但通常这样的博客可能会包含Rails的使用技巧、最佳实践、新版本更新或者特定问题的解决方案。 标签 "源码" 暗示了可能涉及Rails的源代码分析或...

    Rails101_by_rails4.0

    《Rails101_by_rails4.0》是一本专注于Rails 4.0.0版本和Ruby 2.0.0版本的自学教程书籍,它定位于中文读者,旨在成为学习Rails框架的参考教材。Rails(Ruby on Rails)是一个采用Ruby语言编写的开源Web应用框架,它...

    Rails recipes

    Rails Recipes是一本针对Ruby on Rails框架的实用书籍,它收集了一系列高效解决问题的技巧和方法,也被称为“Rails开发者的宝典”。作者们通过分享自己的经验和见解,为Rails程序员提供了一本既有实际操作指导又有...

    Rails项目源代码

    Ruby on Rails,通常简称为Rails,是一个基于Ruby编程语言的开源Web应用框架,遵循MVC(Model-View-Controller)架构模式。这个“Rails项目源代码”是一个使用Rails构建的图片分享网站的完整源代码,它揭示了如何...

    Ruby on Rails Guides v2 - Ruby on Rails 4.2.5

    ### Ruby on Rails Guides v2 - Ruby on Rails 4.2.5 #### 一、重要概念及基础假设 - **重要概念**:本指南旨在帮助读者深入理解Ruby on Rails(以下简称Rails)4.2.5版本的核心功能与最佳实践。 - **基础假设**:...

    关于rails 3.1 cucumber-rails 1.2.0

    Rails 3.1 和 Cucumber-Rails 1.2.0 是两个在Web开发领域非常重要的工具,尤其对于Ruby on Rails框架的测试和自动化流程。本文将深入探讨这两个组件,以及它们如何协同工作来增强软件开发的效率和质量。 首先,...

    rails2-sample

    从给定的文件信息来看,我们正在探讨的是一本关于Ruby on Rails的书籍,书名为《Simply Rails2》,作者是Patrick Lenz。本书旨在为初学者提供深入理解Ruby on Rails框架的指南,从基础概念到高级主题均有涵盖,是...

    rails指南 中文版

    Rails指南中文版是针对Ruby on Rails框架的一份详尽教程,旨在帮助开发者深入理解并熟练掌握这个强大的Web应用开发工具。Ruby on Rails(简称Rails)是一个基于Ruby语言的开源Web应用框架,它遵循MVC(Model-View-...

    使用Aptana+Rails开发Rails Web应用(中文)

    在开发Web应用时,Ruby on Rails(简称Rails)框架因其高效、简洁的代码风格和强大的社区支持而备受青睐。Aptana是一款强大的集成开发环境(IDE),尤其适用于Rails项目的开发,它提供了丰富的特性来提升开发效率。...

    rails api(文档)

    2. **更好的性能**:由于API通常处理的是JSON数据而非HTML,所以Rails API优化了对JSON格式的支持,降低了内存占用和处理时间。 3. **路由优化**:Rails API的路由系统更侧重于资源操作,简化了API路由的定义,方便...

    Ruby on Rails实例开发

    Ruby on Rails,简称Rails,是基于Ruby编程语言的一个开源Web应用程序框架,它遵循MVC(模型-视图-控制器)架构模式,旨在提高开发效率和可读性,同时强调“约定优于配置”的原则。在本实例开发中,我们将探讨如何...

    Advanced Rails

    9. **Rails最佳实践**:了解并遵循Rails社区的最佳实践,如代码风格、设计模式和重构技巧,有助于写出更高质量的代码。 10. **Rails社区与工具**:熟悉Rails社区的资源,如Stack Overflow、GitHub上的开源项目,...

    rails 2.3.2离线安装rails 2.3.2离线安装

    rails 2.3.2离线安装rails 2.3.2离线安装rails 2.3.2离线安装rails 2.3.2离线安装rails 2.3.2离线安装rails 2.3.2离线安装rails 2.3.2离线安装rails 2.3.2离线安装rails 2.3.2离线安装rails 2.3.2离线安装rails ...

    rails2.3.2

    标题 "rails2.3.2" 指的是 Ruby on Rails 框架的一个特定版本,即 2.3.2。Ruby on Rails(通常简称为 Rails)是一个基于 Ruby 语言的开源 Web 应用程序框架,它遵循 Model-View-Controller (MVC) 设计模式,用于构建...

    rails本地安装包完整版

    Rails是Ruby编程语言的一个著名框架,用于开发Web应用程序。它以MVC(模型-视图-控制器)架构模式为基础,提供了许多内置功能,使开发者能够更高效地编写代码。在这个"rails本地安装包完整版"中,包含了Rails 2.1.0...

    Rails相关电子书汇总

    标题 "Rails相关电子书汇总" 暗示了这个压缩包包含了关于Ruby on Rails框架的电子书籍资源。Ruby on Rails,通常简称为Rails,是一个基于Ruby语言的开源Web应用程序框架,它遵循MVC(模型-视图-控制器)架构模式,以...

    component base rails applications

    本书《Component-Based Rails Applications》主要介绍了如何使用Rails引擎(Rails Engine)进行基于组件的Rails应用开发,以及如何对应用程序的大型模块进行拆分和模块化。以下是书中一些核心知识点的详细说明: 1....

Global site tag (gtag.js) - Google Analytics