(Mac OS X での) プロセスの消費メモリの測り方
[追記] (Mac OS X / LINUX での) 外部コマンドの消費メモリのモニタリング - ny23の日記 にて task_info を使う方法を書いた.
Mac OS X でプロセスの(最大)消費メモリを測るにはどうすれば良いのだろう.ある時点での消費メモリを知るには,ちょっと調べた限り以下のような方法があるのだが,
- ps -p $PID -o rss (32bit Leopard だと,4GB までしか測れず; 64bit Snow Leopard だと 4GB 以上でも測れるのだろうか)
- top -l 1 (オーバーヘッドが大きそう)
- valgrind --tool=massif command && ms_print massif.out.$PID (実行速度が極端に遅くなる,粒度が細か過ぎる; vector 等のコンテナのリサイズで瞬間的に 1.5 倍のメモリ消費になるのまで記録してしまう)
getrusage (Linux のみ?)(間違い.ru_maxrss を読むべきなのだがセットされない)
// #include <sys/resource.h> static void _printMemUsage (const char* label) { struct rusage rs; long long int sz = getpagesize (); getrusage (RUSAGE_SELF, &rs); std::fprintf (stderr, "memory usage (%s): %d bytes\n", label, rs.ru_minflt * sz); // これはpage faultの総計なので誤り(1Gで10回new/deleteすると10Gになってしまう). }
- /proc/$PID/status (vmRSS; Linux のみ)
どれも帯に短し襷に長しで,オーバヘッドの無視出来る(時間計測と同時にやりたい)測り方が分からない.Activity Monitor とにらめっこするのもアホなので、以下のようなゴミスクリプトで時間計測とは別に実行している.
#!/bin/env ruby command = ARGV.shift pid = spawn(command) mem = Array.new while 1 do sleep 1 # ps = open("| ps -p #{pid} -o rss") # < 4GB ps = open ("| top -cn -RFX -l 1 -o rsize -P 'PID RSIZE' -p '$aaaaa $jjjjjjjjjjjj'") str = ps.read ps.close if str =~ /^\s*#{pid}\s+(\d+)/o size = $1.to_i mem.push size else break end end b, = mem.sort.reverse kb = b / 1024.0 mb = kb / 1024.0 # top = top.to_s # 1 while top.sub! (/^([-+]?\d+)(\d\d\d)/) { "#{$1},#{$2}" } # splitting printf("MAX RSS: %d b = %.1f Kb = %.1f Mb\n", b, kb, mb);
で,
> ruby1.9 memory_monitor.rb command
他に良い手段は無いものか.
[追記] GNU time (/usr/bin/time) だと,最大使用メモリサイズ (Maximum Resident Set Size) が出せるようだ.Mac OS X には,オプションが違う BSD time が入っていたので man time してみると rusage を出してるだけ,とある.rusage は Mac OS X プログラム内から読んでも ru_maxrss (maximum resident set size) も ru_minflt (page reclaims) も 0 なのだが BSD time (-lp) だと正の値が帰ってくる・・・何故だ・・・ただ,やはり 4GB (signed long なので 2GB) を超えると適切に表示できなさげ.こういう時は,ソースに当たれということで,GNU timeの方のソースを見ると,プロセスをフォークして rusage を見ているだけ.子プロセスなら値が入るのか.オーバーフローするのは rusage の ru_maxrss の型が long のせいだろうか(long が 8 bytes なら良かろうと -m64 をつけてコンパイルしてみたけどダメだった).
[追記] 対応する xnu のソースコードを眺めてみると,64bit 版の rusage は入っているようで,自プロセスのメモリを計測する場合には task_info を直接呼べば 2GB 以上の最大消費メモリも計測できるようだ.しかし,実際には time コマンドがしているように,fork して子プロセスでコマンド実行して wait4 で得られる rusage で 64 bit の値が保存されていないと,任意のコマンドのメモリを計測するのには使えない (task_for_pid で調べたいプロセスの pid を入力して返り値を task_info に渡せば ok なはずだけど,root じゃないと task_for_pid の実行に失敗するため使えず).getrusage や wait4 などではプロセスが終了時に書き出す maxrss を読みに行っているのだけど,代入するどこかの値で 32bit の rusage を経由しているのだろうと思われる.ここかな.もしそうだとしても,コンパイルされたカーネルを使っている限り手も足も出ないわけだけど.
結局まとめると,10.5 (Leopard) だと,rusage 構造体の変数 (ru) を用意して,getrusage (RUSAGE_CHILDREN, ..) か,wait* を使って,32bit (signed なので 2GB)の範囲で外部コマンドの消費メモリを (ru.ru_maxrss 経由で) 計測できる.10.6 (Snow Leopard) だとどちらも無効.自プロセスの消費メモリは 10.5/10.6 とも task_info を直接呼べば >2GB でも計測可能.
なお,上で ps だとダメだけど top だと64bitで使用メモリを見れると書いたが,実際,top のソースコードではTASK_BASIC_INFO_64_COUNTをセットして64bitでメモリサイズを取得している.root 権限で実行しないと動かないのには変わりないが (/usr/bin の top は root が owner で chmod u+s してある).