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

61

Ich fand, das pidstatwäre ein gutes Werkzeug, um Prozesse zu überwachen. Ich möchte die durchschnittliche Speichernutzung eines bestimmten Prozesses berechnen. Hier ist ein Beispiel für eine Ausgabe:

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.)

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

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

Flanfl
quelle

Antworten:

63

RSS gibt an, wie viel Speicher dieser Prozess derzeit im Hauptspeicher (RAM) hat. VSZ gibt an, über wie viel virtuellen Speicher der Prozess insgesamt verfügt. Dies schließt alle Arten von Speicher ein, sowohl im RAM als auch ausgelagert. Diese Zahlen können verzerrt werden, da sie auch gemeinsam genutzte Bibliotheken und andere Speichertypen enthalten. Es können 500 Instanzen bashausgeführt werden, und die Gesamtgröße des Speicherbedarfs entspricht nicht der Summe der RSS- oder VSZ-Werte.

Wenn Sie eine detailliertere Vorstellung vom Speicherbedarf eines Prozesses erhalten möchten, haben Sie einige Optionen. Sie können durchgehen /proc/$PID/mapund die Sachen 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 Größe des Heapspeichers kümmern, können Sie immer nur den [heap]Eintrag in der mapDatei analysieren . Die Größe der Kernel für den Prozess Heap zugewiesen hat , kann oder kann nicht die genaue Anzahl von Bytes reflektieren den Prozess hat gefragt , zugeordnet werden. 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 PAGESIZEwird Ihnen sagen, was es ist - auf PCs sind es wahrscheinlich 4.096 Bytes).

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

Bitte beachten Sie, dass Sie möglicherweise auch Laufzeiten messen, wodurch valgrindIhre Programme etwas langsamer werden (aber wahrscheinlich innerhalb Ihrer Toleranzen).

Alexios
quelle
Danke vielmals! Ich werde die verschiedenen Optionen untersuchen. Sie waren mehr als hilfreich! :)
Flanfl
"Es können 500 Bash-Instanzen ausgeführt werden, und die Gesamtgröße des Speicherbedarfs ist nicht die Summe der RSS- oder VSZ-Werte." Aber wird die Summe ihrer RSS-Werte eine gute Annäherung sein? Wie die Summe der residenten Spalten von statm, brauche ich keinen super zuverlässigen exakten Wert, aber ich muss wissen, wie viel Speicher meine Java-Prozesse
verbrauchen
3
Auf Ubuntu /proc/$PID/mapsist es ein Tippfehler oder ein Unterschied in der Distribution?
dolzenko
1

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 virtuellen Speicher über Seitentabellen zuordnen kann / die interne Speicherbuchhaltung (virtueller VSZ-Speicher), bevor tatsächlich ein Backup-Speicher im RAM oder auf der 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

Status beenden:

137

was nach der 128 + Signalnummer-Regel bedeutet, dass wir eine Signalnummer haben 9, die man 7 signalbesagt , dass SIGKILL vom Linux -Killer für nicht genügend Arbeitsspeicher gesendet wird .

Ausgangsinterpretation:

  • Der virtuelle VSZ-Speicher bleibt nach der mmap konstant bei printf '0x%X\n' 0x40009A4 KiB ~= 64GiB( psWerte in KiB).
  • RSS "reale Speichernutzung" steigt nur träge, wenn wir die Seiten berühren. Zum Beispiel:
    • Auf dem ersten Druck haben wir extra_memory_committed 0, was bedeutet, dass wir noch keine Seiten berührt haben. RSS ist ein kleines 1648 KiBProgramm, das für den normalen Programmstart wie Textbereiche, Globals usw. vorgesehen ist.
    • auf dem zweiten druck haben wir seiten geschrieben 8388608 KiB == 8GiB. Dadurch erhöhte sich RSS um genau 8GIB auf8390256 KiB == 8388608 KiB + 1648 KiB
    • RSS nimmt weiterhin in Schritten von 8 GB zu. Der letzte Ausdruck zeigt ungefähr 24 GiB Speicher und bevor 32 GiB gedruckt werden konnten, hat der OOM-Killer den Vorgang abgebrochen

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

OOM Killer Logs

Unsere dmesgBefehle haben die OOM-Killer-Protokolle angezeigt.

Eine genaue Interpretation dieser wurde angefragt bei:

Die allererste Zeile des Protokolls lautete:

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

Interessanterweise war es der MongoDB-Daemon, der immer auf meinem Laptop im Hintergrund lief, der den OOM-Killer auslöste, vermutlich, als das arme Ding versuchte, Speicher zuzuweisen.

Der OOM-Killer muss jedoch nicht unbedingt denjenigen töten, der ihn aufgeweckt hat.

Nach dem Aufruf druckt der Kernel eine Tabelle oder Prozesse aus, 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 unsere eigene kleine main.outtatsächlich bei der vorherigen Anrufung 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 865Prozess erwähnt, der vermutlich die höchste (schlechteste) OOM-Killer-Punktzahl aufwies. Wie entscheidet der OOM-Killer, welcher Prozess zuerst beendet wird?

Interessanterweise geschah anscheinend alles so schnell, dass bevor der freigegebene Speicher abgerechnet wurde, der oomdurch den DeadlineMonitorVorgang wieder aufgeweckt wurde :

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

und diesmal, dass einige Chromium-Prozess, die in der Regel mein Computer normales Gedächtnisschwein ist getötet:

[ 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.

Ciro Santilli ist ein Schauspieler
quelle