GNU/Linux >> Tutoriales Linux >  >> Linux

registro de la marca de agua máxima de la memoria RAM de un proceso de Linux

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 invoca fork() para crear un proceso hijo.

  • El proceso hijo llama a ptrace() para que el proceso principal (que es logmemory ) es notificado cada vez que el niño ejecuta una llamada al sistema.

  • El proceso hijo usa execvp() ejecutar mycmd .

  • logmemory espera pacientemente una notificación. Cuando ese es el caso, comprueba si mycmd invocado exit_group . Si ese es el caso, lee /proc/<pid>/status , copia los valores a mem.log y se separa del niño. De lo contrario, logmemory permite mycmd 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 mientras mycmd 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 evita ptrace y los gastos generales que crea. Buena idea, pensé, lamentablemente VmHWM y VmPeak 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.


Linux
  1. Linux:¿limitar el uso de memoria para un solo proceso de Linux?

  2. Linux:¿necesita una explicación sobre el tamaño del conjunto residente/tamaño virtual?

  3. Cómo verificar el tamaño total de RAM y el uso de memoria en Linux

  4. ¿Número máximo de subprocesos por proceso en Linux?

  5. ¿Recuperar el uso de la CPU y el uso de la memoria de un solo proceso en Linux?

Instalación de la herramienta de prueba de memoria RAM Memtest+ en Redhat 7 Linux

Swappiness en Linux:Todo lo que necesitas saber

Encuentra el tamaño de RAM en Linux

Cómo verificar el tamaño del montón para un proceso en Linux

Linux:averigüe qué proceso está utilizando toda la RAM?

Limite el uso de memoria para un solo proceso de Linux