Rails 内存泄露检测:实践一
Posted by devon on December 30th, 2007 filed in 部署与优化在服务器运行在development模式下式,发现mongrel进程所占用的内存迅速上升,怀疑文件上传引起内存泄露(项目中有大文件上传的应用),以下是测试过程。
服务器环境:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | rails2@server:~$ cat /proc/version Linux version 2.6.16.29-xen (shand@endor) (gcc version 3.4.4 20050314 (prerelease) (Debian 3.4.3-13)) #3 SMP Sun Oct 15 13:15:34 BST 2006 rails2@server:~$ ruby -v ruby 1.8.6 (2007-03-13 patchlevel 0) [x86_64-linux] rails2@server:~$ gem -v 0.9.4 rails2@server:~$ rails -v Rails 2.0.2 |
Step 1: use valgrind to check the mongrel
安装
1 2 3 4 5 6 7 8 9 10 11 12 | wget http://valgrind.org/downloads/valgrind-3.3.0.tar.bz2 (http://valgrind.org/downloads/current.html#current)tar jxf valgrind-3.3.0.tar.bz2 cd valgrind-3.3.0 ./configure make sudo make install |
使用
1 2 3 4 | valgrind –tool=memcheck program_namecd project_path valgrind –tool=memcheck mongrel_rails start -p 3000 -P log/mongrel.3000.pid |
一系列操作后,中断mongrel,获取到如下结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | ** INT signal received. ==13134== ==13134== ERROR SUMMARY: 2130785 errors From 130 contexts (suppressed: 2 From 1) ==13134== malloc/free: in use at exit: 42,952,786 bytes in 256,684 blocks. ==13134== malloc/free: 3,271,446 allocs, 3,014,762 frees, 195,599,435 bytes allocated. ==13134== For counts of detected errors, rerun with: -v ==13134== searching for pointers to 256,684 not-freed blocks. ==13134== checked 47,374,288 bytes. ==13134== ==13134== LEAK SUMMARY: ==13134== definitely lost: 173,745 bytes in 2,595 blocks. ==13134== possibly lost: 41,456 bytes in 674 blocks. ==13134== still reachable: 42,737,585 bytes in 253,415 blocks. ==13134== suppressed: 0 bytes in 0 blocks. ==13134== Rerun with –leak-check=full to see details of leaked memory. |
Step 2: memory_profiler
在项目的 lib 下增加 memory_profiler.rb,文件内容为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | # This is a memory profiler for Ruby. Once started, it runs in a thread in the # background, periodically inspecting Ruby's ObjectSpace to look for new # objects and printing a count of objects added and removed since the previous # cycle. # # To use the profiler, do something like this: # # require 'memory_profiler' # # MemoryProfiler.start # # The profiler will write logs to ./log/memory_profiler.log. # # If you start MemoryProfiler with the ':string_debug => true' option, then it # will dump a list of all strings in the app into the log/ directory after # each cycle. You can then use 'diff' to spot which strings were added # between runs. class MemoryProfiler DEFAULTS = {:delay => 10, :string_debug => false} def self.start(opt={}) opt = DEFAULTS.dup.merge(opt) Thread.new do prev = Hash.new(0) curr = Hash.new(0) curr_strings = [] delta = Hash.new(0) file = File.open('log/memory_profiler.log','w') loop do begin GC.start curr.clear curr_strings = [] if opt[:string_debug] ObjectSpace.each_object do |o| curr[o.class] += 1 #Marshal.dump(o).size rescue 1 if opt[:string_debug] and o.class == String curr_strings.push o end end if opt[:string_debug] File.open("log/memory_profiler_strings.log.#{Time.now.to_i}",'w') do |f| curr_strings.sort.each do |s| f.puts s end end curr_strings.clear end delta.clear (curr.keys + delta.keys).uniq.each do |k,v| delta[k] = curr[k]-prev[k] end file.puts "Top 20" delta.sort_by { |k,v| -v.abs }[0..19].sort_by { |k,v| -v}.each do |k,v| file.printf "%+5d: %s (%d)\n", v, k.name, curr[k] unless v == 0 end file.flush delta.clear prev.clear prev.update curr GC.start rescue Exception => err STDERR.puts "** memory_profiler error: #{err}" end sleep opt[:delay] end end end end |
在 config/enviroment.rb 的未尾加上
require ‘memory_profiler’
MemoryProfiler.start
在 development 模式下重新启动项目,等待一会到分配与回收新的对象已趋于稳定,然后开始测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | cd project_path mongrel_rails start -p 3000 -P log/mongrel.3000.pidTop 20 -3: String (71128) Top 20 +3: String (71131) Top 20 -2: String (71129) Top 20 +2: String (71131) |
第一次测试
请求一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | Top 20 +1940: String (73071) +203: Regexp (1327) +65: Hash (669) +32: Class (1149) +32: Module (435) +11: Time (199) +8: ActionController::Filters::ClassMethods::SymbolFilter (8) +6: Set (23) +6: StringIO (6) +5: ActionController::Filters::ClassMethods::BeforeFilterProxy (5) +5: ActiveRecord::ConnectionAdapters::MysqlColumn (5) +3: ActionController::Filters::ClassMethods::AfterFilterProxy (3) +3: Proc (390) +3: GetText::TextDomainManager (3) +3: HashWithIndifferentAccess (3) +3: Float (54) +2: Mongrel::HeaderOut (2) +2: Mongrel::HttpRequest (2) +2: CGI::Cookie (2) -25: Array (6051) Top 20 +101: Array (6152) -1: Mongrel::HeaderOut (1) -1: Time (198) -1: Mongrel::HttpParams (1) -1: Mongrel::HttpRequest (1) -1: Mongrel::HttpResponse (1) -1: TCPSocket (1) -2: Float (52) -3: StringIO (3) -21: Hash (648) -193: String (72878) Top 20 -1: Range (173) -1: Set (22) -2: ActionController::Filters::ClassMethods::AfterFilterProxy (1) -2: Regexp (1325) -3: Proc (387) -4: ActionController::Filters::ClassMethods::BeforeFilterProxy (1) -5: Class (1144) -6: ActionController::Filters::ClassMethods::SymbolFilter (2) -9: Module (426) -13: Hash (635) -31: Array (6121) -293: String (72585) Top 20 -3: String (72582) Top 20 +3: String (72585) Top 20 +2: String (72585) Top 20 -2: String (72583) |
请求二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | Top 20 +682: String (73267) +37: Hash (672) +24: Array (6145) +9: Module (435) +8: Regexp (1333) +7: Proc (394) +6: ActionController::Filters::ClassMethods::SymbolFilter (8) +5: Class (1149) +4: ActionController::Filters::ClassMethods::BeforeFilterProxy (5) +3: HashWithIndifferentAccess (3) +2: CGI::Cookie (2) +2: ActionController::Filters::ClassMethods::AfterFilterProxy (3) +2: Float (54) +1: OriginalFile (1) +1: ActionController::Dispatcher (1) +1: Set (23) +1: CGI::Session::ActiveRecordStore (1) +1: Mongrel::CGIWrapper (1) +1: ActionController::Flash::FlashHash (1) +1: ActionView::Base (1) Top 20 +16: Array (6161) -4: String (73263) Top 20 -1: Array (6160) Top 20 Top 20 -3: String (73260) Top 20 +3: String (73263) Top 20 -2: String (73261) |
稳定后,开始文件上传(1.6M的文件):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | Top 20 -2: String (73261) Top 20 +2: String (73263) Top 20 -2: String (73261) Top 20 +2: String (73263) Top 20 -2: String (73261) Top 20 +2: String (73263) Top 20 -2: String (73261) Top 20 +2: String (73263) Top 20 -2: String (73261) Top 20 +2: String (73263) Top 20 -2: String (73261) Top 20 +2: String (73263) |
上传完成,重新 render 页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | Top 20 +674: String (73937) +38: Hash (710) +9: Module (444) +6: ActionController::Filters::ClassMethods::SymbolFilter (14) +6: StringIO (9) +5: Class (1154) +4: ActionController::Filters::ClassMethods::BeforeFilterProxy (9) +3: Time (200) +3: Proc (397) +3: HashWithIndifferentAccess (6) +3: OriginalFile (3) +2: CGI::Cookie (4) +2: Mongrel::HttpParams (3) +2: Mongrel::HttpRequest (3) +2: ActionController::Filters::ClassMethods::AfterFilterProxy (5) +2: Range (176) +2: Mongrel::HeaderOut (3) +2: Regexp (1335) +2: TCPSocket (3) -84: Array (6076) Top 20 +108: Array (6184) -1: File (7) -1: Time (199) -1: Mongrel::CGIWrapper (1) -1: ActionController::CgiResponse (1) -1: CGI::Session (1) -1: Range (175) -1: Mongrel::HttpRequest (2) -1: Mongrel::HeaderOut (2) -1: CGI::Session::ActiveRecordStore (1) -1: ActionController::Flash::FlashHash (1) -1: ActionView::Base (1) -1: TCPSocket (2) -1: Mongrel::HttpParams (2) -2: CGI::Cookie (2) -2: Float (54) -3: HashWithIndifferentAccess (3) -3: StringIO (6) -23: Hash (687) -241: String (73696) Top 20 -1: Range (174) -1: Set (23) -2: ActionController::Filters::ClassMethods::AfterFilterProxy (3) -2: Regexp (1333) -3: Proc (394) -4: ActionController::Filters::ClassMethods::BeforeFilterProxy (5) -5: Class (1149) -6: ActionController::Filters::ClassMethods::SymbolFilter (8) -9: Module (435) -13: Hash (674) -21: Array (6163) -294: String (73402) Top 20 Top 20 -3: String (73399) Top 20 +3: String (73402) Top 20 -2: String (73400) Top 20 +2: String (73402) |
第二次测试:
请求一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | Top 20 +594: String (73996) +31: Hash (705) +24: Array (6187) +9: Module (444) +6: ActionController::Filters::ClassMethods::SymbolFilter (14) +5: Class (1154) +4: Proc (398) +4: ActionController::Filters::ClassMethods::BeforeFilterProxy (9) +3: HashWithIndifferentAccess (6) +2: CGI::Cookie (4) +2: ActionController::Filters::ClassMethods::AfterFilterProxy (5) +2: Regexp (1335) +2: Float (56) +1: CGI::Session::ActiveRecordStore (2) +1: OriginalFile (1) +1: ActionController::Flash::FlashHash (2) +1: Mongrel::CGIWrapper (2) +1: VideosController (1) +1: Set (24) -2: Time (197) Top 20 +2: Array (6189) -1: CGI::Session (1) -1: CGI::Session::ActiveRecordStore (1) -1: ActiveRecord::Errors (1) -1: Proc (397) -1: ActionController::UrlRewriter (1) -1: ActionController::Flash::FlashHash (1) -1: ActionController::CgiRequest (1) -1: ActionView::Base (1) -1: ActionController::CgiResponse (1) -2: Float (54) -3: HashWithIndifferentAccess (3) -14: Hash (691) -83: String (73913) Top 20 -1: Range (174) -1: Set (23) -2: ActionController::Filters::ClassMethods::AfterFilterProxy (3) -2: Regexp (1333) -3: Proc (394) -4: ActionController::Filters::ClassMethods::BeforeFilterProxy (5) -5: Class (1149) -6: ActionController::Filters::ClassMethods::SymbolFilter (8) -9: Module (435) -13: Hash (678) -21: Array (6168) -295: String (73618) Top 20 Top 20 -3: String (73615) Top 20 +3: String (73618) |
请求二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | Top 20 +668: String (74284) +34: Hash (712) +9: Module (444) +6: ActionController::Filters::ClassMethods::SymbolFilter (14) +5: Class (1154) +4: ActionController::Filters::ClassMethods::BeforeFilterProxy (9) +4: OriginalFile (4) +3: Proc (397) +3: Time (200) +3: StringIO (9) +3: HashWithIndifferentAccess (6) +2: Regexp (1335) +2: Float (56) +2: ActionController::Filters::ClassMethods::AfterFilterProxy (5) +2: Range (176) +1: CGI::Session::ActiveRecordStore (2) +1: Mongrel::HttpParams (3) +1: ActionController::UrlRewriter (2) +1: Set (24) -92: Array (6076) Top 20 +108: Array (6184) -1: File (7) -1: Range (175) -1: Mongrel::CGIWrapper (1) -1: ActionController::CgiResponse (1) -1: Mongrel::HeaderOut (2) -1: ActionController::UrlRewriter (1) -1: CGI::Session::ActiveRecordStore (1) -1: CGI::Session (1) -1: Mongrel::HttpParams (2) -1: ActionController::Flash::FlashHash (1) -1: Mongrel::HttpResponse (2) -1: TCPSocket (2) -1: Mongrel::HttpRequest (2) -2: CGI::Cookie (2) -2: Float (54) -3: HashWithIndifferentAccess (3) -3: StringIO (6) -25: Hash (687) -272: String (74012) Top 20 -1: Range (174) -1: Set (23) -1: Time (199) -2: ActionController::Filters::ClassMethods::AfterFilterProxy (3) -2: Regexp (1333) -3: Proc (394) -4: ActionController::Filters::ClassMethods::BeforeFilterProxy (5) -5: Class (1149) -6: ActionController::Filters::ClassMethods::SymbolFilter (8) -9: Module (435) -13: Hash (674) -21: Array (6163) -299: String (73713) Top 20 Top 20 -3: String (73710) Top 20 +3: String (73713) |
结果中的每一行 表示每个对象在内存中的个数(后面的数字)和增减变化(前面的数字),每10秒计算一次,排出内存中的数量前20。
在整个过程中:
String约增加了2580个,Regexp约增加了200个。
重复4次以后(总共6次)
开始
1 2 3 4 | PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 6590 rails2 16 0 101m 52m 2852 S 0 5.2 0:58.65 ruby |
结束
1 2 | 6590 rails2 16 0 101m 53m 2852 S 0 5.2 1:29.63 ruby |
接着上传一个26M的文件,然后:
1 2 | 6590 rails2 16 0 102m 54m 2852 R 11 5.3 2:45.13 ruby |
持续访问页面(不是文件上传)12次之后:
1 2 | 6590 rails2 16 0 103m 55m 2852 S 0 5.4 2:52.27 ruby |
5分钟之后:
1 2 | 6590 rails2 16 0 103m 55m 2852 S 15 5.4 3:08.58 ruby |
十小时之后:
1 2 | 6590 rails2 16 0 2152m 444m 1420 S 0 43.5 28:04.12 ruby |
以上是在服务器上的操作过程,到此可以初步确定在development模式下有内存泄露(服务器一个mongrel一度达到500M)。而且,应该不是由文件上传引起的(持续的页面访问会增加内存泄露)。
参考:
http://valgrind.org/
Tracking down a Rails app memory leak
Tracking a memory leak in Rails
bleak_house
Dike website
Linux on Power 上的调试工具和技术
Related posts:
- Rails 内存泄露检测:实践二 服务器一环境: rails2@server:~$ cat /proc/version Linux version 2.6.16.29-xen (shand@endor) (gcc version...
Related posts brought to you by Yet Another Related Posts Plugin.
Leave a Comment