(Mac OS X / LINUX での) 外部コマンドの消費メモリのモニタリング
(Mac OS X での) プロセスの消費メモリの測り方 - ny23の日記でしばらくごにょごにょやってたけど,Mac では rusage / task_info の連携がどこかで腐っており,getrusage/wait3/wait4 などは (>2GB では) まともに動かないため,現時点で root 権限なしに 2GB 以上のメモリを計測することは簡単ではない.task port を親子のプロセス間でやりとりすればできるようだが,中の人的にオススメではないようだ.この手順を頑張って解釈した人がいたのでその結果をそのまま拝借して,以下のようにすれば動く.
// run.cc (tested on Mac OS X 10.5/10.6) #include <mach/mach.h> #include <sys/time.h> #include <sys/wait.h> #include <unistd.h> #include <cerrno> #include <cstdio> #include <cstdlib> #include <csignal> #define SLEEP_NSEC 10000 // 0.01s // smaller value => fine-grained memory monitoring (overhead increases runtime) struct mach_send_port_msg { mach_msg_header_t header; mach_msg_body_t body; mach_msg_port_descriptor_t task_port; }; struct mach_recv_port_msg { mach_send_port_msg m; mach_msg_trailer_t trailer; }; static int setup_recv_port (mach_port_t *recv_port) { mach_port_t port = MACH_PORT_NULL; if (KERN_SUCCESS != mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &port)) std::fprintf (stderr, "mach_port_allocate failed.\n"); if (KERN_SUCCESS != mach_port_insert_right (mach_task_self (), port, port, MACH_MSG_TYPE_MAKE_SEND)) std::fprintf (stderr, "mach_port_insert_right failed.\n"); *recv_port = port; return 0; } static int send_port (mach_port_t remote_port, mach_port_t port) { mach_send_port_msg msg; msg.header.msgh_remote_port = remote_port; msg.header.msgh_local_port = MACH_PORT_NULL; msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX; msg.header.msgh_size = sizeof (msg); msg.body.msgh_descriptor_count = 1; msg.task_port.name = port; msg.task_port.disposition = MACH_MSG_TYPE_COPY_SEND; msg.task_port.type = MACH_MSG_PORT_DESCRIPTOR; if (KERN_SUCCESS != mach_msg_send (&msg.header)) std::fprintf (stderr, "mach_msg_send failed.\n"); return 0; } static int recv_port (mach_port_t recv_port, mach_port_t *port) { mach_recv_port_msg msg; if (KERN_SUCCESS != mach_msg (&msg.m.header, MACH_RCV_MSG, 0, sizeof (msg), recv_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL)) std::fprintf (stderr, "mach_msg failed.\n"); *port = msg.m.task_port.name; return 0; } int main (int argc, char **argv) { char ** cmd = &argv[1]; struct timeval start, end; task_t task = MACH_PORT_NULL; mach_port_t parent_recv_port = MACH_PORT_NULL; mach_port_t child_recv_port = MACH_PORT_NULL; // setup port as bootstrap port if (setup_recv_port (&parent_recv_port) != 0) return -1; if (KERN_SUCCESS != task_set_bootstrap_port (mach_task_self (), parent_recv_port)) std::fprintf (stderr, "task_set_bootstrap_port failed.\n"); // set timer gettimeofday (&start, 0); // create new task pid_t pid = fork (); if (pid < 0) { if (KERN_SUCCESS != mach_port_deallocate (mach_task_self(), parent_recv_port)) std::fprintf (stderr, "mach_port_deallocate failed.\n"); std::fprintf (stderr, "cannot fork.\n"); std::exit (1); } else if (pid == 0) { // child process if (KERN_SUCCESS != task_get_bootstrap_port (mach_task_self (), &parent_recv_port)) std::fprintf (stderr, "task_get_bootstrap_port failed.\n"); if (setup_recv_port (&child_recv_port) != 0) return -1; if (send_port (parent_recv_port, mach_task_self ()) != 0) return -1; if (send_port (parent_recv_port, child_recv_port) != 0) return -1; if (recv_port (child_recv_port, &bootstrap_port) != 0) return -1; if (KERN_SUCCESS != task_set_bootstrap_port (mach_task_self (), bootstrap_port)) std::fprintf (stderr, "task_set_bootstrap_port failed.\n"); execvp (cmd[0], cmd); std::fprintf (stderr, "cannot run %s\n", cmd[0]); _exit (errno == ENOENT ? 127 : 126); } // parent process std::signal (SIGINT, SIG_IGN); std::signal (SIGQUIT, SIG_IGN); if (KERN_SUCCESS != task_set_bootstrap_port (mach_task_self (), bootstrap_port)) std::fprintf (stderr, "task_set_bootstrap_port failed.\n"); if (recv_port (parent_recv_port, &task) != 0) return -1; if (recv_port (parent_recv_port, &child_recv_port) != 0) return -1; if (send_port (child_recv_port, bootstrap_port) != 0) return -1; if (KERN_SUCCESS != mach_port_deallocate (mach_task_self(), parent_recv_port)) std::fprintf (stderr, "mach_port_deallocate failed.\n"); // monitor memory size_t mem = 0; struct task_basic_info t_info; mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; do { if (KERN_SUCCESS != task_info (task, TASK_BASIC_INFO, (task_info_t) &t_info, &t_info_count)) break; if (mem < t_info.resident_size) mem = t_info.resident_size; } while (! usleep (SLEEP_NSEC)); // stop timer int status; if (waitpid (pid, &status, 0) == -1) // std::fprintf (stderr, "error waiting for child process.\n"); if ((status & 0xff) == 0x7f) std::fprintf (stderr, "Command stopped by signal %d\n", (status >> 8) & 0xff); else if ((status & 0xff) != 0) std::fprintf (stderr, "Command terminated by signal %d\n", status & 0xff); else if ((status >> 8) & 0xff) std::fprintf (stderr, "Command exited with non-zero status %d\n", (status >> 8) & 0xff); gettimeofday (&end, 0); std::fprintf (stderr, "elapsed (real): %.3fs; RSS=%.1fM\n", end.tv_sec - start.tv_sec + (end.tv_usec - start.tv_usec) * 1e-6, static_cast <double> (mem) / (1024.0 * 1024.0)); std::signal (SIGINT, SIG_DFL); std::signal (SIGQUIT, SIG_DFL); return 0; }
task_for_pid を呼べないせいでここまで複雑になるか(最初,root 権限でコマンドを実行するソースを公開してしまっていたよ).LINUX では /proc があるので同様のプログラムは簡単に書ける.
#include <sys/time.h> #include <sys/wait.h> #include <unistd.h> #include <cerrno> #include <cstdio> #include <cstdlib> #include <csignal> #include <sys/resource.h> #define SLEEP_NSEC 500 // 0.0005s int main (int argc, char **argv) { char ** cmd = &argv[1]; // set timer struct timeval start, end; gettimeofday (&start, 0); // fork pid_t pid = fork (); if (pid < 0) { std::fprintf (stderr, "cannot fork.\n"); std::exit (1); } else if (pid == 0) { // child process // must not delete this execvp (cmd[0], cmd); std::fprintf (stderr, "cannot run %s\n", cmd[0]); _exit (errno == ENOENT ? 127 : 126); } // dislable signals signal (SIGINT, SIG_IGN); signal (SIGQUIT, SIG_IGN); // parent process size_t mem = 0; int status; char statm[32]; std::sprintf (&statm[0], "/proc/%d/statm", static_cast <int> (pid)); do { FILE *reader = fopen (statm, "r"); size_t dummy (0), vm (0); std::fscanf (reader, "%ld %ld ", &dummy, &vm); // get resident (see procps) if (mem < vm) mem = vm; std::fclose (reader); if (! vm) break; } while (! usleep (SLEEP_NSEC)); mem *= getpagesize (); // stop timer if (waitpid (pid, &status, 0) == -1) std::fprintf (stderr, "error waiting for child process.\n"); if ((status & 0xff) == 0x7f) std::fprintf (stderr, "Command stopped by signal %d\n", (status >> 8) & 0xff); else if ((status & 0xff) != 0) std::fprintf (stderr, "Command terminated by signal %d\n", status & 0xff); else if ((status >> 8) & 0xff) std::fprintf (stderr, "Command exited with non-zero status %d\n", (status >> 8) & 0xff); gettimeofday (&end, 0); std::fprintf (stderr, "elapsed (real): %.3fs; RSS=%.1fM\n", end.tv_sec - start.tv_sec + (end.tv_usec - start.tv_usec) * 1e-6, static_cast <double> (mem) / (1024.0 * 1024.0)); // enable signals signal (SIGINT, SIG_DFL); signal (SIGQUIT, SIG_DFL); return 0; }
で,適当にメモリを確保するプログラムを作成.
// mem.cc #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> int main (int argc, char** argv) { size_t size = 1; char &uni = argv[1][std::strlen (argv[1])-1]; switch (uni) { case 'G': size <<= 10; case 'M': size <<= 10; case 'K': size <<= 10; uni = '\0'; default: size *= std::strtol (argv[1], NULL, 10); } std::fprintf (stderr, "allocating memory (%ld)..", size); char * mem = new char[size]; // * std::fill (&mem[0], &mem[size], 0); // not to optimize out * std::fprintf (stderr, "done.\n"); delete [] mem; return 0; }
実行してみる.run.cc は -m64 をつけてコンパイル(LUNUX 版の方は無くてもソースを少し変えれば動くとは思うが).
# Mac OS X (10.5; 10.6 でも 1G では動作確認) // > g++ -O2 -march=core2 -m64 -o mem mem.cc > run mem 5G allocating memory (5368709120)..done. elapsed (real): 5.668s; RSS=5120.5M # GNU/Linux > run mem 5G allocating memory (5368709120)..done. elapsed (real): 5.117s; RSS=5120.4M
時間計測部は GNU time コマンドのソースから拝借しているので,時間の方も time コマンドの real とほぼ同様に使える.GNU time もメモリ計測に対応しているのだけど,OS ごとの情報取得方法の違いに翻弄されている感じなので,その置き換えのつもり.linux 版は /proc/
を参照.ru_maxrss は hiwater_rss=(VmHWM) と同じだったり).
子プロセスで実行した外部コマンドのメモリ消費は,本来情報の受け渡しをすべき gerusage や wait4 (wait3) がちゃんと情報を伝えない関係で,↑みたいな方法をとらざるを得ないが,自プロセスのメモリ消費を確認する場合は,Mac OS X なら task_info を mach_task_self () (または current_task ())を第一引数にして呼べば良いし,Linux なら /proc/
[追記; 2011/01/29] シグナルの取り扱いを改善して,segmentation fault や bus error などの異常終了が分かるようにした.