Jetez un oeil à /proc/[pid]/status
, en particulier ce paramètre.
- VmHWM :Taille maximale de l'ensemble résident ("pointe haute").
Alternativement, vous pouvez utiliser /usr/bin/time -v
commande. Voici un exemple de sa sortie :
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
Les informations sur la marque d'eau haute de la RAM pour un processus sont déjà collectées pour vous par le noyau (à partir de 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 partie délicate est que cette valeur doit être lue un instant avant la fin du processus .
J'ai essayé différentes approches (plus à ce sujet à la fin de la réponse) et celle qui a fonctionné pour moi était une implémentation en C :
-
logmemory
invoquefork()
pour créer un processus enfant. -
Le processus enfant appelle
ptrace()
afin que le processus parent (qui estlogmemory
) est notifié chaque fois que l'enfant exécute un appel système. -
Le processus enfant utilise
execvp()
pour exécutermycmd
. -
logmemory
attend patiemment une notification. Lorsque c'est le cas, il vérifie simycmd
appeléexit_group
. Si c'est le cas, il lit/proc/<pid>/status
, copie les valeurs dansmem.log
et se détache de l'enfant. Sinon,logmemory
autorisemycmd
pour continuer et attend la prochaine notification.
L'inconvénient est que le ptrace()
ralentit le programme surveillé , je montre quelques comparaisons ci-dessous.
Cette version de logmemory
non seulement enregistre VmHWM
mais aussi :
-
VmPeak
(taille maximale de la mémoire virtuelle, qui inclut tout le code, les données et les bibliothèques partagées, ainsi que les pages qui ont été échangées et les pages qui ont été mappées mais non utilisées) -
un horodatage
-
le nom de la commande et les arguments
Voici le code, qui peut sûrement être amélioré - je ne maîtrise pas le C. Il fonctionne cependant comme prévu (testé sur un Ubuntu 12.04 32 bits et un SuSE Linux Enterprise Server 10 SP4 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;
}
Enregistrez-le sous logmemory.c
et compilez comme ceci :
$ gcc logmemory.c -o logmemory
Exécutez-le comme ceci :
$ ./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
J'ai écrit ce programme C pour tester logmemory
Précision :
// 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;
}
Compilez comme d'habitude et exécutez-le dans 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
qui signale correctement 200 Mo utilisés.
En passant :time
(au moins sur Ubuntu 12.04) génère étonnamment une valeur qui diffère largement de ce que rapporte le noyau :
$ /usr/bin/time --format %M ./bigmalloc
823872
où M
(à partir de man time
):
M Maximum resident set size of the process during its lifetime, in Kilobytes.
Comme mentionné ci-dessus, cela a un prix, car logmemory
ralentit l'exécution du programme surveillé, par exemple :
$ 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
D'autres approches que j'ai (sans succès) essayées étaient :
-
Un script shell qui crée un processus d'arrière-plan pour lire
/proc/<pid>/status
tandis quemycmd
court. -
Un programme C qui bifurque et exécute le
mycmd
de mais s'arrête jusqu'à ce que l'enfant soit un zombie, évitant ainsiptrace
et les frais généraux qu'il crée. Bonne idée, j'ai pensé, malheureusementVmHWM
etVmPeak
ne sont plus disponibles à partir du/proc/<pid>/status
pour un zombie.
Même si le sujet est assez ancien, je souhaite partager un autre projet qui a émergé de la fonctionnalité du noyau Linux cgroups.
https://github.com/gsauthof/cgmemtime :
cgmemtime mesure l'utilisation de la mémoire RSS+CACHE à haut débit d'un processus et de ses processus descendants.
Pour pouvoir le faire, il place le processus dans son propre groupe de contrôle.
Par exemple, le processus A alloue 10 MiB et bifurque un enfant B qui alloue 20 MiB et qui bifurque un enfant C qui alloue 30 MiB. Les trois processus partagent une fenêtre de temps où leurs allocations entraînent une utilisation correspondante de la mémoire RSS (taille de l'ensemble résident).
La question est maintenant :quelle quantité de mémoire est réellement utilisée suite à l'exécution de A ?
Réponse :60 Mio
cgmemtime est l'outil pour répondre à ces questions.