web-dev-qa-db-fra.com

Besoin d'explication sur la taille de l'ensemble résident / taille virtuelle

J'ai trouvé que pidstat serait un bon outil pour surveiller les processus. Je veux calculer l'utilisation moyenne de la mémoire d'un processus particulier. Voici un exemple de sortie:

02:34:36 PM       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
02:34:37 PM      7276      2.00      0.00  349212 210176   7.14  scalpel

(Cela fait partie de la sortie de pidstat -r -p 7276.)

Dois-je utiliser les informations Resident Set Size (RSS) ou Virtual Size (VSZ) pour calculer la consommation moyenne de mémoire? J'ai lu quelques trucs sur Wikipédia et sur les forums mais je ne suis pas sûr de bien comprendre les différences. De plus, il semble qu'aucun d'entre eux ne soit fiable. Alors, comment puis-je surveiller un processus pour obtenir son utilisation de la mémoire?

Toute aide à ce sujet serait utile.

65
Flanfl

RSS est la quantité de mémoire que ce processus a actuellement dans la mémoire principale (RAM). VSZ est la quantité totale de mémoire virtuelle du processus. Cela inclut tous les types de mémoire, à la fois en RAM et échangés. Ces chiffres peuvent être biaisés car ils incluent également des bibliothèques partagées et d'autres types de mémoire. Vous pouvez avoir cinq cents instances de bash en cours d'exécution, et la taille totale de leur empreinte mémoire ne sera pas la somme de leurs valeurs RSS ou VSZ.

Si vous avez besoin d'avoir une idée plus détaillée de l'empreinte mémoire d'un processus, vous avez quelques options. Vous pouvez passer par /proc/$PID/map et éliminez les trucs que vous n'aimez pas. S'il s'agit de bibliothèques partagées, le calcul pourrait devenir complexe en fonction de vos besoins (dont je me souviens, je pense).

Si vous ne vous souciez que de la taille du segment de mémoire du processus, vous pouvez toujours simplement analyser le [heap] entrée dans le fichier map. La taille que le noyau a allouée au tas de processus peut refléter ou non le nombre exact d'octets que le processus a demandé à allouer. Il y a des détails infimes, des internes du noyau et des optimisations qui peuvent perturber cela. Dans un monde idéal, ce sera autant que votre processus a besoin, arrondi au multiple de taille de page système le plus proche (getconf PAGESIZE vous dira de quoi il s'agit - sur les PC, c'est probablement 4 096 octets).

Si vous voulez voir combien de mémoire un processus a alloué, l'une des meilleures façons est de renoncer aux métriques côté noyau. Au lieu de cela, vous instrumentez les fonctions d'allocation de mémoire de tas (de) de la bibliothèque C avec le LD_PRELOAD mécanisme. Personnellement, j'abuse légèrement valgrind pour obtenir des informations sur ce genre de chose. (Notez que l'application de l'instrumentation nécessite de redémarrer le processus.)

Veuillez noter, puisque vous pouvez également effectuer des analyses de performances, que valgrind rendra vos programmes très légèrement plus lents (mais probablement dans vos tolérances).

65
Alexios

Exemple exécutable minimal

Pour que cela ait du sens, vous devez comprendre les bases de la pagination: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work et en particulier que le système d'exploitation peut allouer de la mémoire virtuelle via des tables de pages/sa tenue de livres de mémoire interne (mémoire virtuelle VSZ) avant qu'il n'ait réellement un stockage de sauvegarde sur RAM ou disque (mémoire résidente RSS).

Maintenant, pour observer cela en action, créons un programme qui:

principal c

#define _GNU_SOURCE
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

typedef struct {
    unsigned long size,resident,share,text,lib,data,dt;
} ProcStatm;

/* https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c/7212248#7212248 */
void ProcStat_init(ProcStatm *result) {
    const char* statm_path = "/proc/self/statm";
    FILE *f = fopen(statm_path, "r");
    if(!f) {
        perror(statm_path);
        abort();
    }
    if(7 != fscanf(
        f,
        "%lu %lu %lu %lu %lu %lu %lu",
        &(result->size),
        &(result->resident),
        &(result->share),
        &(result->text),
        &(result->lib),
        &(result->data),
        &(result->dt)
    )) {
        perror(statm_path);
        abort();
    }
    fclose(f);
}

int main(int argc, char **argv) {
    ProcStatm proc_statm;
    char *base, *p;
    char system_cmd[1024];
    long page_size;
    size_t i, nbytes, print_interval, bytes_since_last_print;
    int snprintf_return;

    /* Decide how many ints to allocate. */
    if (argc < 2) {
        nbytes = 0x10000;
    } else {
        nbytes = strtoull(argv[1], NULL, 0);
    }
    if (argc < 3) {
        print_interval = 0x1000;
    } else {
        print_interval = strtoull(argv[2], NULL, 0);
    }
    page_size = sysconf(_SC_PAGESIZE);

    /* Allocate the memory. */
    base = mmap(
        NULL,
        nbytes,
        PROT_READ | PROT_WRITE,
        MAP_SHARED | MAP_ANONYMOUS,
        -1,
        0
    );
    if (base == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    /* Write to all the allocated pages. */
    i = 0;
    p = base;
    bytes_since_last_print = 0;
    /* Produce the ps command that lists only our VSZ and RSS. */
    snprintf_return = snprintf(
        system_cmd,
        sizeof(system_cmd),
        "ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == \"%ju\") print}'",
        (uintmax_t)getpid()
    );
    assert(snprintf_return >= 0);
    assert((size_t)snprintf_return < sizeof(system_cmd));
    bytes_since_last_print = print_interval;
    do {
        /* Modify a byte in the page. */
        *p = i;
        p += page_size;
        bytes_since_last_print += page_size;
        /* Print process memory usage every print_interval bytes.
         * We count memory using a few techniques from:
         * https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c */
        if (bytes_since_last_print > print_interval) {
            bytes_since_last_print -= print_interval;
            printf("extra_memory_committed %lu KiB\n", (i * page_size) / 1024);
            ProcStat_init(&proc_statm);
            /* Check /proc/self/statm */
            printf(
                "/proc/self/statm size resident %lu %lu KiB\n",
                (proc_statm.size * page_size) / 1024,
                (proc_statm.resident * page_size) / 1024
            );
            /* Check ps. */
            puts(system_cmd);
            system(system_cmd);
            puts("");
        }
        i++;
    } while (p < base + nbytes);

    /* Cleanup. */
    munmap(base, nbytes);
    return EXIT_SUCCESS;
}

GitHub en amont .

Compiler et exécuter:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
echo 1 | Sudo tee /proc/sys/vm/overcommit_memory
Sudo dmesg -c
./main.out 0x1000000000 0x200000000
echo $?
Sudo dmesg

où:

Sortie du programme:

extra_memory_committed 0 KiB
/proc/self/statm size resident 67111332 768 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
  PID    VSZ   RSS
29827 67111332 1648

extra_memory_committed 8388608 KiB
/proc/self/statm size resident 67111332 8390244 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
  PID    VSZ   RSS
29827 67111332 8390256

extra_memory_committed 16777216 KiB
/proc/self/statm size resident 67111332 16778852 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
  PID    VSZ   RSS
29827 67111332 16778864

extra_memory_committed 25165824 KiB
/proc/self/statm size resident 67111332 25167460 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
  PID    VSZ   RSS
29827 67111332 25167472

Killed

Statut de sortie:

137

ce qui par la 128 + règle du numéro de signal signifie que nous avons obtenu le numéro de signal 9, lequel man 7 signal dit est SIGKILL , qui est envoyé par Linux tueur de mémoire insuffisante .

Interprétation de sortie:

  • La mémoire virtuelle VSZ reste constante à printf '0x%X\n' 0x40009A4 KiB ~= 64GiB (ps les valeurs sont en Kio) après le mmap.
  • RSS "l'utilisation réelle de la mémoire" augmente paresseusement uniquement lorsque nous touchons les pages. Par exemple:
    • sur la première impression, nous avons extra_memory_committed 0, ce qui signifie que nous n'avons encore touché aucune page. RSS est un petit 1648 KiB qui a été alloué pour le démarrage normal du programme comme la zone de texte, les globaux, etc.
    • sur la deuxième impression, nous avons écrit à 8388608 KiB == 8GiB valeur de pages. Par conséquent, RSS a augmenté exactement de 8 GIB à 8390256 KiB == 8388608 KiB + 1648 KiB
    • RSS continue d'augmenter par incréments de 8 Go. La dernière impression montre environ 24 GiB de mémoire, et avant que 32 GiB puisse être imprimé, le tueur OOM a tué le processus)

Voir aussi: Besoin d'explication sur la taille de l'ensemble résident/taille virtuelle

Journaux de tueur OOM

Nos commandes dmesg ont affiché les journaux de tueur OOM.

Une interprétation exacte de celles-ci a été demandée à:

La toute première ligne du journal était:

[ 7283.479087] mongod invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0

Nous voyons donc que ce qui est intéressant, c'est que le démon MongoDB qui s'exécute toujours dans mon ordinateur portable en arrière-plan a d'abord déclenché le tueur OOM, probablement lorsque le pauvre essayait d'allouer de la mémoire.

Cependant, le tueur OOM ne tue pas nécessairement celui qui l'a réveillé.

Après l'invocation, le noyau imprime une table ou des processus incluant le oom_score:

[ 7283.479292] [  pid  ]   uid  tgid total_vm      rss pgtables_bytes swapents oom_score_adj name
[ 7283.479303] [    496]     0   496    16126        6   172032      484             0 systemd-journal
[ 7283.479306] [    505]     0   505     1309        0    45056       52             0 blkmapd
[ 7283.479309] [    513]     0   513    19757        0    57344       55             0 lvmetad
[ 7283.479312] [    516]     0   516     4681        1    61440      444         -1000 systemd-udevd

et plus loin nous voyons que notre propre petit main.out a été tué lors de l'invocation précédente:

[ 7283.479871] Out of memory: Kill process 15665 (main.out) score 865 or sacrifice child
[ 7283.479879] Killed process 15665 (main.out) total-vm:67111332kB, anon-rss:92kB, file-rss:4kB, shmem-rss:30080832kB
[ 7283.479951] oom_reaper: reaped process 15665 (main.out), now anon-rss:0kB, file-rss:0kB, shmem-rss:30080832kB

Ce journal mentionne le score 865 quel processus avait, probablement le score le plus élevé (le pire) de tueur OOM comme mentionné à: Comment le tueur OOM décide-t-il quel processus tuer en premier?

Autre fait intéressant, tout s'est apparemment passé si vite qu'avant que la mémoire libérée ne soit prise en compte, le oom a été réveillé à nouveau par le processus DeadlineMonitor:

[ 7283.481043] DeadlineMonitor invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0

et cette fois qui a tué un processus de chrome, qui est généralement le porc de mémoire normal de mon ordinateur:

[ 7283.481773] Out of memory: Kill process 11786 (chromium-browse) score 306 or sacrifice child
[ 7283.481833] Killed process 11786 (chromium-browse) total-vm:1813576kB, anon-rss:208804kB, file-rss:0kB, shmem-rss:8380kB
[ 7283.497847] oom_reaper: reaped process 11786 (chromium-browse), now anon-rss:0kB, file-rss:0kB, shmem-rss:8044kB

Testé dans Ubuntu 19.04, noyau Linux 5.0.0.