Schauen Sie sich /proc/[pid]/status
an , speziell dieser Parameter.
- VmHWM:Peak Resident Set Size ("High Water Mark").
Alternativ können Sie /usr/bin/time -v
verwenden Befehl. Hier ist ein Beispiel dafür:
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
Die RAM-High-Watermark-Informationen für einen Prozess werden bereits vom Kernel für Sie gesammelt (ab 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").
(...)
Der knifflige Teil ist, dass dieser Wert kurz vor Beendigung des Prozesses gelesen werden sollte .
Ich habe verschiedene Ansätze ausprobiert (mehr dazu am Ende der Antwort) und der, der für mich funktioniert hat, war eine Implementierung in C:
-
logmemory
ruftfork()
auf um einen untergeordneten Prozess zu erstellen. -
Der untergeordnete Prozess ruft
ptrace()
auf sodass der übergeordnete Prozess (das istlogmemory
) wird jedes Mal benachrichtigt, wenn das Kind einen Systemaufruf ausführt. -
Der untergeordnete Prozess verwendet
execvp()
ummycmd
auszuführen . -
logmemory
wartet geduldig auf eine Benachrichtigung. Wenn das der Fall ist, prüft es, obmycmd
aufgerufenexit_group
. Wenn dies der Fall ist, lautet es/proc/<pid>/status
, kopiert die Werte nachmem.log
und löst sich vom Kind. Andernfallslogmemory
erlaubtmycmd
um fortzufahren und wartet bis zur nächsten Benachrichtigung.
Der Nachteil ist, dass die ptrace()
verlangsamt das überwachte Programm , zeige ich unten einige Vergleiche.
Diese Version von logmemory
protokolliert nicht nur VmHWM
sondern auch:
-
VmPeak
(Spitzengröße des virtuellen Speichers, die alle Code-, Daten- und gemeinsam genutzten Bibliotheken sowie Seiten enthält, die ausgelagert wurden, und Seiten, die zugeordnet, aber nicht verwendet wurden) -
ein Zeitstempel
-
den Befehlsnamen und die Argumente
Dies ist der Code, der sicherlich verbessert werden kann - ich kenne mich mit C nicht aus. Er funktioniert jedoch wie vorgesehen (getestet auf einem 32-Bit-Ubuntu 12.04 und einem 64-Bit-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;
}
Speichern Sie es als logmemory.c
und so kompilieren:
$ gcc logmemory.c -o logmemory
Führen Sie es so aus:
$ ./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
Ich habe dieses C-Programm geschrieben, um logmemory
zu testen Genauigkeit von :
// 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;
}
Kompilieren Sie wie gewohnt und führen Sie es in logmemory
aus :
$ 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
die korrekt 200 MB belegt meldet.
Nebenbei bemerkt:time
(zumindest unter Ubuntu 12.04) gibt überraschenderweise einen Wert aus, der sich stark von dem unterscheidet, was der Kernel meldet:
$ /usr/bin/time --format %M ./bigmalloc
823872
wobei M
(ab man time
):
M Maximum resident set size of the process during its lifetime, in Kilobytes.
Wie oben erwähnt, hat dies seinen Preis, denn logmemory
verlangsamt die Ausführung des überwachten Programms, zum Beispiel:
$ 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
Andere Ansätze, die ich (erfolglos) ausprobiert habe, waren:
-
Ein Shell-Skript, das einen Hintergrundprozess zum Lesen von
/proc/<pid>/status
erstellt währendmycmd
läuft. -
Ein C-Programm, das
mycmd
forkt und ausführt aber pausiert, bis das Kind ein Zombie ist, wodurchptrace
vermieden wird und der damit verbundene Overhead. Gute Idee, dachte ich, leiderVmHWM
undVmPeak
sind ab/proc/<pid>/status
nicht mehr erhältlich für einen Zombie.
Auch wenn das Thema schon ziemlich alt ist, möchte ich ein anderes Projekt teilen, das aus der cgroups-Linux-Kernel-Funktion hervorgegangen ist.
https://github.com/gsauthof/cgmemtime:
cgmemtime misst die Hochwasser-RSS+CACHE-Speichernutzung eines Prozesses und seiner untergeordneten Prozesse.
Um dies tun zu können, legt es den Prozess in seine eigene Kontrollgruppe.
Zum Beispiel weist Prozess A 10 MiB zu und verzweigt ein Kind B, das 20 MiB zuweist, und ein Kind C, das 30 MiB zuweist. Alle drei Prozesse teilen sich ein Zeitfenster, in dem ihre Zuordnungen zu einer entsprechenden RSS-Speichernutzung (resident set size) führen.
Die Frage ist nun:Wie viel Speicher wird tatsächlich verbraucht, wenn A ausgeführt wird?
Antwort:60 MiB
cgmemtime ist das Werkzeug, um solche Fragen zu beantworten.