it-swarm.com.de

Benötigen Sie eine Erklärung zu Resident Set Size / Virtual Size

Ich fand, dass pidstat ein gutes Werkzeug zur Überwachung von Prozessen wäre. Ich möchte die durchschnittliche Speichernutzung eines bestimmten Prozesses berechnen. Hier ist eine Beispielausgabe:

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

(Dies ist Teil der Ausgabe von pidstat -r -p 7276.)

Sollte ich die Informationen zur Resident Set Size (RSS) oder Virtual Size (VSZ) verwenden, um den durchschnittlichen Speicherverbrauch zu berechnen? Ich habe ein paar Dinge auf Wikipedia und in Foren gelesen, bin mir aber nicht sicher, ob ich die Unterschiede vollständig verstehen kann. Außerdem scheint keiner von ihnen zuverlässig zu sein. Wie kann ich einen Prozess überwachen, um seine Speichernutzung zu ermitteln?

Jede Hilfe in dieser Angelegenheit wäre nützlich.

65
Flanfl

RSS gibt an, wie viel Speicher dieser Prozess derzeit im Hauptspeicher (RAM) hat. VSZ gibt an, wie viel virtueller Speicher der Prozess insgesamt hat. Dies umfasst alle Arten von Speicher, sowohl in RAM als auch ausgelagert. Diese Zahlen können verzerrt sein, da sie auch gemeinsam genutzte Bibliotheken und andere Arten von Speicher enthalten. Sie können fünfhundert Instanzen von bash wird ausgeführt, und die Gesamtgröße ihres Speicherbedarfs ist nicht die Summe ihrer RSS- oder VSZ-Werte.

Wenn Sie eine detailliertere Vorstellung vom Speicherbedarf eines Prozesses erhalten möchten, haben Sie einige Optionen. Sie können /proc/$PID/map Durchgehen und die Dinge aussortieren, die Sie nicht mögen. Wenn es sich um gemeinsam genutzte Bibliotheken handelt, kann die Berechnung je nach Ihren Anforderungen (an die ich mich zu erinnern glaube) komplex werden.

Wenn Sie sich nur um die Heap-Größe des Prozesses kümmern, können Sie immer einfach den Eintrag [heap] In der Datei map analysieren. Die Größe, die der Kernel für den Prozessheap zugewiesen hat, kann die genaue Anzahl der Bytes widerspiegeln, die dem Prozess zugewiesen werden sollen gefragt. Es gibt winzige Details, Kernel-Interna und Optimierungen, die dies verhindern können. In einer idealen Welt wird es so viel sein, wie Ihr Prozess benötigt, auf das nächste Vielfache der Systemseitengröße aufgerundet (getconf PAGESIZE Sagt Ihnen, was es ist - auf PCs sind es wahrscheinlich 4.096 Bytes).

Wenn Sie sehen möchten, wie viel Speicher ein Prozess hat zugewiesen, können Sie am besten auf die kernelseitigen Metriken verzichten. Stattdessen instrumentieren Sie die Zuordnungsfunktionen des Heapspeichers (De) der C-Bibliothek mit dem Mechanismus LD_PRELOAD. Persönlich missbrauche ich valgrind leicht, um Informationen über solche Dinge zu erhalten. (Beachten Sie, dass zum Anwenden der Instrumentierung ein Neustart des Prozesses erforderlich ist.)

Bitte beachten Sie, dass valgrind Ihre Programme etwas langsamer macht (aber wahrscheinlich innerhalb Ihrer Toleranzen), da Sie möglicherweise auch Laufzeiten messen.

65
Alexios

Minimal lauffähiges Beispiel

Damit dies sinnvoll ist, müssen Sie die Grundlagen des Paging verstehen: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work und insbesondere, dass das Betriebssystem dies kann Zuweisen des virtuellen Speichers über Seitentabellen/dessen interne Speicherbuchhaltung (virtueller VSZ-Speicher), bevor tatsächlich ein Sicherungsspeicher auf RAM oder Festplatte (RSS-residenter Speicher) vorhanden ist.

Um dies in Aktion zu beobachten, erstellen wir ein Programm, das:

haupt 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 Upstream .

Kompilieren und ausführen:

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

wo:

Programmausgabe:

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

Ausgangsstatus:

137

was durch die 128 + Signalnummernregel bedeutet, dass wir die Signalnummer 9 haben, die man 7 signal sagt, ist SIGKILL , der vom Linux gesendet wird Out-of-Memory-Killer .

Ausgangsinterpretation:

  • Der virtuelle VSZ-Speicher bleibt nach der mmap konstant bei printf '0x%X\n' 0x40009A4 KiB ~= 64GiB (ps -Werte sind in KiB angegeben).
  • RSS "Real Memory Use" nimmt nur träge zu, wenn wir die Seiten berühren. Zum Beispiel:
    • beim ersten Druck haben wir extra_memory_committed 0, was bedeutet, dass wir noch keine Seiten berührt haben. RSS ist ein kleines 1648 KiB, Das für den normalen Programmstart wie Textbereich, Globals usw. zugewiesen wurde.
    • beim zweiten Druck haben wir Seiten im Wert von 8388608 KiB == 8GiB geschrieben. Infolgedessen erhöhte sich RSS um genau 8 GIB auf 8390256 KiB == 8388608 KiB + 1648 KiB
    • RSS nimmt in 8-GB-Schritten weiter zu. Der letzte Druck zeigt ungefähr 24 GiB Speicher), und bevor 32 GiB gedruckt werden konnte), hat der OOM-Killer den Prozess abgebrochen

Siehe auch: Erläuterungen zu Resident Set Size/Virtual Size

OOM-Killerprotokolle

Unsere dmesg -Befehle haben die OOM-Killerprotokolle angezeigt.

Eine genaue Interpretation dieser wurde gefragt bei:

Die allererste Zeile des Protokolls war:

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

Interessanterweise sehen wir also, dass es der MongoDB-Daemon war, der immer in meinem Laptop im Hintergrund ausgeführt wird, der zuerst den OOM-Killer auslöste, vermutlich als das arme Ding versuchte, etwas Speicher zuzuweisen.

Der OOM-Killer tötet jedoch nicht unbedingt denjenigen, der ihn geweckt hat.

Nach dem Aufruf druckt der Kernel eine Tabelle oder Prozesse einschließlich 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

und weiter vorne sehen wir, dass unser eigenes kleines main.out beim vorherigen Aufruf tatsächlich getötet wurde:

[ 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

In diesem Protokoll wird der score 865 Erwähnt, den dieser Prozess hatte, vermutlich die höchste (schlechteste) OOM-Killer-Punktzahl, wie unter: Wie entscheidet der OOM-Killer, welcher Prozess zuerst getötet werden soll?

Interessanterweise ging anscheinend alles so schnell, dass das oom vor dem Abrechnen des freigegebenen Speichers durch den DeadlineMonitor -Prozess wieder geweckt wurde:

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

und diesmal hat das einen Chromium-Prozess beendet, der normalerweise der normale Speicher meines Computers ist:

[ 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

Getestet in Ubuntu 19.04, Linux Kernel 5.0.0.