Dai un'occhiata a /proc/[pid]/status , in particolare questo parametro.
- VmHWM:Dimensione del set residente di picco ("high water mark").
In alternativa, puoi usare /usr/bin/time -v comando. Ecco un esempio del suo out:
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
Le informazioni sull'high water mark della RAM per un processo sono già raccolte per te dal kernel (da 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 difficile è che questo valore dovrebbe essere letto un istante prima che il processo termini .
Ho provato diversi approcci (più su questo alla fine della risposta) e quello che ha funzionato per me è stata un'implementazione in C:
-
logmemoryinvocafork()per creare un processo figlio. -
Il processo figlio chiama
ptrace()in modo che il processo genitore (che èlogmemory) viene notificato ogni volta che il figlio esegue una chiamata di sistema. -
Il processo figlio utilizza
execvp()per eseguiremycmd. -
logmemoryattende pazientemente una notifica. Quando questo è il caso, controlla semycmdinvocatoexit_group. Se è così, legge/proc/<pid>/status, copia i valori inmem.loge si stacca dal bambino. Altrimenti,logmemoryconsentemycmdper continuare e attende fino alla notifica successiva.
Lo svantaggio è che il ptrace() rallenta il programma monitorato , mostro alcuni confronti di seguito.
Questa versione di logmemory non solo registra VmHWM ma anche:
-
VmPeak(massima dimensione della memoria virtuale, che include tutto il codice, i dati e le librerie condivise più le pagine che sono state scambiate e le pagine che sono state mappate ma non utilizzate) -
un timestamp
-
il nome del comando e gli argomenti
Questo è il codice, che può essere sicuramente migliorato - non sono esperto in C. Funziona come previsto, però (testato su Ubuntu 12.04 a 32 bit e SuSE Linux Enterprise Server 10 SP4 a 64 bit):
// 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;
}
Salvalo come logmemory.c e compila in questo modo:
$ gcc logmemory.c -o logmemory
Eseguilo in questo modo:
$ ./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
Ho scritto questo programma in C per testare logmemory accuratezza di:
// 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;
}
Compila come al solito ed eseguilo all'interno di 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
che riporta correttamente 200 MB utilizzati.
Come nota a margine:time (almeno su Ubuntu 12.04) restituisce sorprendentemente un valore che differisce ampiamente da quanto riportato dal kernel:
$ /usr/bin/time --format %M ./bigmalloc
823872
dove M (da man time ):
M Maximum resident set size of the process during its lifetime, in Kilobytes.
Come accennato in precedenza, questo ha un prezzo, perché logmemory rallenta l'esecuzione del programma monitorato, ad esempio:
$ 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
Altri approcci che ho provato (senza successo) sono stati:
-
Uno script di shell che crea un processo in background per leggere
/proc/<pid>/statusmentremycmdcorre. -
Un programma C che effettua il fork ed esegue
mycmddi Exec ma fa una pausa finché il bambino non diventa uno zombi, quindi evitaptracee il sovraccarico che crea. Bella idea, ho pensato, sfortunatamenteVmHWMeVmPeaknon sono più disponibili da/proc/<pid>/statusper uno zombi.
Anche se l'argomento è piuttosto vecchio, voglio condividere un altro progetto emerso dalla funzionalità del kernel Linux di cgroups.
https://github.com/gsauthof/cgmemtime:
cgmemtime misura l'alto consumo di memoria RSS+CACHE di un processo e dei suoi processi discendenti.
Per poterlo fare inserisce il processo nel proprio cgroup.
Ad esempio, il processo A alloca 10 MiB e esegue il fork di un figlio B che alloca 20 MiB e che esegue il fork di un figlio C che alloca 30 MiB. Tutti e tre i processi condividono una finestra temporale in cui le loro allocazioni risultano in un utilizzo di memoria RSS (dimensione del set residente) corrispondente.
La domanda ora è:quanta memoria viene effettivamente utilizzata come risultato dell'esecuzione di A?
Risposta:60 MiB
cgmemtime è lo strumento per rispondere a queste domande.