Echa un vistazo a /proc/[pid]/status
, específicamente este parámetro.
- VmHWM:Tamaño máximo del conjunto residente ("marca de agua alta").
Alternativamente, puede usar /usr/bin/time -v
dominio. Aquí hay un ejemplo de su salida:
Command exited with non-zero status 1
Command being timed: "xz -9ek access_log.3 access_log.xz"
User time (seconds): 6.96
System time (seconds): 0.34
Percent of CPU this job got: 99%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:07.34
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
**Maximum resident set size (kbytes): 383456**
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 24000
Voluntary context switches: 3
Involuntary context switches: 225
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 1
El núcleo ya recopila la información de la marca de límite máximo de RAM para un proceso (desde 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").
(...)
La parte complicada es que este valor debe leerse un instante antes de que finalice el proceso .
Probé diferentes enfoques (más sobre eso al final de la respuesta) y el que funcionó para mí fue una implementación en C:
-
logmemory
invocafork()
para crear un proceso hijo. -
El proceso hijo llama a
ptrace()
para que el proceso principal (que eslogmemory
) es notificado cada vez que el niño ejecuta una llamada al sistema. -
El proceso hijo usa
execvp()
ejecutarmycmd
. -
logmemory
espera pacientemente una notificación. Cuando ese es el caso, comprueba simycmd
invocadoexit_group
. Si ese es el caso, lee/proc/<pid>/status
, copia los valores amem.log
y se separa del niño. De lo contrario,logmemory
permitemycmd
para continuar y espera hasta la próxima notificación.
La desventaja es que el ptrace()
ralentiza el programa supervisado , muestro algunas comparaciones a continuación.
Esta versión de logmemory
no solo registra VmHWM
pero también:
-
VmPeak
(tamaño máximo de la memoria virtual, que incluye todo el código, los datos y las bibliotecas compartidas más las páginas que se intercambiaron y las páginas que se asignaron pero no se usaron) -
una marca de tiempo
-
el nombre del comando y los argumentos
Este es el código, que seguramente se puede mejorar:no soy experto en C. Sin embargo, funciona según lo previsto (probado en un Ubuntu 12.04 de 32 bits y un SuSE Linux Enterprise Server 10 SP4 de 64 bits):
// 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;
}
Guárdalo como logmemory.c
y compilar así:
$ gcc logmemory.c -o logmemory
Ejecútalo así:
$ ./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
Escribí este programa en C para probar logmemory
Precisión de:
// 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;
}
Compile como de costumbre y ejecútelo dentro de 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
que informa correctamente 200 MB utilizados.
Como nota al margen:time
(al menos en Ubuntu 12.04) sorprendentemente genera un valor que difiere en gran medida de lo que informa el kernel:
$ /usr/bin/time --format %M ./bigmalloc
823872
donde M
(de man time
):
M Maximum resident set size of the process during its lifetime, in Kilobytes.
Como se mencionó anteriormente, esto tiene un precio, porque logmemory
ralentiza la ejecución del programa monitoreado, por ejemplo:
$ 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
Otros enfoques que probé (sin éxito) fueron:
-
Un script de shell que crea un proceso en segundo plano para leer
/proc/<pid>/status
mientrasmycmd
carreras. -
Un programa en C que bifurca y ejecuta
mycmd
pero hace una pausa hasta que el niño es un zombi, por lo que evitaptrace
y los gastos generales que crea. Buena idea, pensé, lamentablementeVmHWM
yVmPeak
ya no están disponibles en/proc/<pid>/status
para un zombi.
Aunque el tema es bastante antiguo, quiero compartir otro proyecto que surgió de la función del kernel de Linux de cgroups.
https://github.com/gsauthof/cgmemtime:
cgmemtime mide el alto uso de memoria RSS+CACHE de un proceso y sus procesos descendientes.
Para poder hacerlo, coloca el proceso en su propio cgroup.
Por ejemplo, el proceso A asigna 10 MiB y bifurca un elemento secundario B que asigna 20 MiB y bifurca un elemento secundario C que asigna 30 MiB. Los tres procesos comparten una ventana de tiempo en la que sus asignaciones dan como resultado el uso de memoria RSS (tamaño del conjunto residente) correspondiente.
La pregunta ahora es:¿Cuánta memoria se usa realmente como resultado de ejecutar A?
Respuesta:60 MiB
cgmemtime es la herramienta para responder estas preguntas.