Информация о верхней отметке ОЗУ для процесса уже собрана ядром (из man proc):
/proc/[pid]/status
Provides much of the information in /proc/[pid]/stat and /proc/[pid]/statm in a format that's easier for humans to parse.
(...)
* VmHWM: Peak resident set size ("high water mark").
(...)
Сложность в том, что это значение следует прочитать за мгновение до завершения процесса.
Я пробовал разные подходы (подробнее об этом в конце ответа), и тот, который работал для меня, был реализацией в C:
logmemory вызывает fork() для создания дочернего процесса.
Дочерний процесс вызывает ptrace() так что родительский процесс (который является logmemory) уведомляется каждый раз, когда дочерний процесс выполняет системный вызов.
Дочерний процесс использует execvp() для запуска mycmd .
logmemory терпеливо ждет уведомления. В этом случае он проверяет, вызвал ли mycmd exit_group . Если это так, он читает /proc/<pid>/status , копирует значения в mem.log и отсоединяет от дочернего. В противном случае logmemory позволяет mycmd продолжить работу и ожидает следующего уведомления.
Недостатком является то, что ptrace() замедляет отслеживаемую программу, ниже я приведу некоторые сравнения.
Эта версия logmemory не только регистрирует VmHWM но также:
VmPeak (пиковый размер виртуальной памяти, который включает в себя весь код, данные и общие библиотеки, а также страницы, которые были выгружены, и страницы, которые были отображены, но не использовались)
отметка времени
имя команды и аргументы
Это код, который, безусловно, можно улучшить - я не обладаю достаточными знаниями в C. Он работает, как и предполагалось (протестировано на 32-битной Ubuntu 12.04 и 64-битной SuSE Linux Enterprise Server 10 SP4):
// logmemory.c
#include <stdio.h>
#include <sys/ptrace.h>
#include <unistd.h>
#include <syscall.h>
#include <sys/reg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define STRINGLENGTH 2048
int main(int argc, char **argv)
{
pid_t child_pid;
long syscall;
int status, index;
FILE *statusfile, *logfile;
char opt, statusfile_path[STRINGLENGTH], line[STRINGLENGTH], command[STRINGLENGTH], logfile_path[STRINGLENGTH] = "";
time_t now;
extern char *optarg;
extern int optind;
// Error checking
if (argc == 1) {
printf("Error: program to execute is missing. Exiting...\n");
return 0;
}
// Get options
while ((opt = getopt (argc, argv, "+o:")) != -1)
switch (opt) {
case 'o':
strncpy(logfile_path, optarg, 2048);
break;
case ':':
fprintf (stderr, "Aborting: argument for option -o is missing\n");
return 1;
case '?':
fprintf (stderr, "Aborting: only valid option is -o\n");
return 1;
}
// More error checking
if (!strcmp(logfile_path, "")) {
fprintf(stderr, "Error: log filename can't be empty\n");
return 1;
}
child_pid = fork();
// The child process executes this:
if (child_pid == 0) {
// Trace child process:
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
// Execute command using $PATH
execvp(argv[optind], (char * const *)(argv+optind));
// The parent process executes this:
} else {
// Loop until child process terminates
do {
// Set ptrace to stop when syscall is executed
ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL);
wait(&status);
// Get syscall number
syscall = ptrace(PTRACE_PEEKUSER, child_pid,
#ifdef __i386__
4 * ORIG_EAX,
#else
8 * ORIG_RAX,
#endif
NULL);
} while (syscall != SYS_exit_group);
// Construct path to status file and check whether status and log file can be opened
snprintf(statusfile_path, STRINGLENGTH, "/proc/%d/status", child_pid);
if ( !(logfile = fopen(logfile_path, "a+")) || !(statusfile = fopen(statusfile_path, "r")) ) {
ptrace(PTRACE_DETACH, child_pid, NULL, NULL);
return 1;
}
// Copy timestamp and command to logfile
now = time(NULL);
fprintf(logfile, "Date: %sCmd: ", asctime(localtime(&now)));
for (index = optind; index < argc; index++)
fprintf(logfile, " %s", argv[index]);
fprintf(logfile, "\n");
// Read status file line by line and copy lines containing VmPeak and VmHWM to logfile
while (fgets(line, STRINGLENGTH, statusfile)) {
if (strstr(line,"VmPeak") || strstr(line,"VmHWM"))
fprintf(logfile, "%s", line);
}
fprintf(logfile, "\n");
// Close files
fclose(statusfile);
fclose(logfile);
// Detach from child process
ptrace(PTRACE_DETACH, child_pid, NULL, NULL);
}
return 0;
}
Сохраните его как logmemory.c и скомпилируйте так:
$ gcc logmemory.c -o logmemory
Запустите это так:
$ ./logmemory
Error: program to execute is missing. Exiting...
$ ./logmemory -o mem.log ls -l
(...)
$ ./logmemory -o mem.log free
total used free shared buffers cached
Mem: 1025144 760660 264484 0 6644 143980
-/+ buffers/cache: 610036 415108
Swap: 1046524 544228 502296
$ ./logmemory -o mem.log find /tmp -name \*txt
(...)
$ cat mem.log
Date: Mon Feb 11 21:17:55 2013
Cmd: ls -l
VmPeak: 5004 kB
VmHWM: 1284 kB
Date: Mon Feb 11 21:18:01 2013
Cmd: free
VmPeak: 2288 kB
VmHWM: 448 kB
Date: Mon Feb 11 21:18:26 2013
Cmd: find /tmp -name *txt
VmPeak: 4700 kB
VmHWM: 908 kB
Я написал эту программу на C для проверки logmemory :
// bigmalloc.c
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define ITERATIONS 200
int main(int argc, char **argv)
{
int i=0;
for (i=0; i<ITERATIONS; i++) {
void *m = malloc(1024*1024);
memset(m,0,1024*1024);
}
return 0;
}
Скомпилируйте как обычно и запустите его внутри logmemory:
$ gcc bigmalloc.c -o bigmalloc
$ ./logmemory -o mem.log ./bigmalloc
$ tail mem.log
Date: Mon Feb 11 21:26:01 2013
Cmd: ./bigmalloc
VmPeak: 207604 kB
VmHWM: 205932 kB
который правильно сообщает о 200 МБ.
Как примечание: time (по крайней мере, в Ubuntu 12.04) неожиданно выдает значение, которое сильно отличается от того, что сообщает ядро:
$ /usr/bin/time --format %M ./bigmalloc
823872
где M (от man time):
M Maximum resident set size of the process during its lifetime, in Kilobytes.
Как упомянуто выше, это приводит к цене, потому что logmemory замедляет выполнение отслеживаемой программы, например:
$ time ./logmemory -o mem.log ./bigmalloc
real 0m0.288s
user 0m0.000s
sys 0m0.004s
$ time ./bigmalloc
real 0m0.104s
user 0m0.008s
sys 0m0.092s
$ time find /var -name \*log
(...)
real 0m0.036s
user 0m0.000s
sys 0m0.032s
$ time ./logmemory -o mem.log find /var -name \*log
(...)
real 0m0.124s
user 0m0.000s
sys 0m0.052s
Другие подходы, которые я (безуспешно) попробовал, были:
Сценарий оболочки, который создает фоновый процесс для чтения /proc/<pid>/status во время работы mycmd .
Программа AC, которая разветвляет и mycmd но делает паузу, пока ребенок не станет зомби, поэтому избегает ptrace и накладных расходов, которые он создает. Хорошая идея, подумал я, к сожалению, VmHWM и VmPeak больше не доступны из /proc/<pid>/status для зомби.