Precisa de explicação sobre o tamanho do conjunto residente/tamanho virtual

Precisa de explicação sobre o tamanho do conjunto residente/tamanho virtual

Achei que pidstatseria uma boa ferramenta para monitorar processos. Quero calcular o uso médio de memória de um processo específico. Aqui está um exemplo de saída:

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

(Isso faz parte da saída de pidstat -r -p 7276.)

Devo usar as informações de Resident Set Size (RSS) ou Virtual Size (VSZ) para calcular o consumo médio de memória? Li algumas coisas na Wikipedia e em fóruns, mas não tenho certeza se entendi completamente as diferenças. Além disso, parece que nenhum deles é confiável. Então, como posso monitorar um processo para obter o uso de memória?

Qualquer ajuda sobre este assunto seria útil.

Responder1

RSS é a quantidade de memória que esse processo possui atualmente na memória principal (RAM). VSZ é a quantidade de memória virtual que o processo possui no total. Isso inclui todos os tipos de memória, tanto na RAM quanto na trocada. Esses números podem ficar distorcidos porque também incluem bibliotecas compartilhadas e outros tipos de memória. Você pode ter quinhentas instâncias em bashexecução e o tamanho total do consumo de memória não será a soma dos valores RSS ou VSZ.

Se precisar ter uma ideia mais detalhada sobre o consumo de memória de um processo, você tem algumas opções. Você pode examinar /proc/$PID/mape eliminar as coisas que não gosta. Se forem bibliotecas compartilhadas, o cálculo pode ficar complexo dependendo das suas necessidades (das quais acho que me lembro).

Se você se preocupa apenas com o tamanho do heap do processo, você pode simplesmente analisar a [heap]entrada no maparquivo. O tamanho que o kernel alocou para o heap do processo pode ou não refletir o número exato de bytes que o processo possuiperguntadoa ser alocado. Existem detalhes minuciosos, detalhes internos do kernel e otimizações que podem atrapalhar isso. Em um mundo ideal, será o tamanho necessário para o seu processo, arredondado para o múltiplo mais próximo do tamanho da página do sistema ( getconf PAGESIZEdirá a você o que é - em PCs, provavelmente tem 4.096 bytes).

Se você quiser ver quanta memória um processo possuialocado, uma das melhores maneiras é renunciar às métricas do lado do kernel. Em vez disso, você instrumenta as funções de (des)alocação de memória heap da biblioteca C com o LD_PRELOADmecanismo. Pessoalmente, abuso um pouco valgrindpara obter informações sobre esse tipo de coisa. (Observe que a aplicação da instrumentação exigirá a reinicialização do processo.)

Observe que, como você também pode fazer benchmarking de tempos de execução, isso valgrindtornará seus programas um pouco mais lentos (mas provavelmente dentro de suas tolerâncias).

Responder2

Exemplo mínimo executável

Para que isso faça sentido, você precisa entender os fundamentos da paginação:https://stackoverflow.com/questions/18431261/how-does-x86-paging-worke, em particular, que o sistema operacional pode alocar memória virtual por meio de tabelas de páginas/manutenção de livros de memória interna (memória virtual VSZ) antes de realmente ter um armazenamento de apoio em RAM ou disco (memória residente RSS).

Agora, para observar isso em ação, vamos criar um programa que:

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;
}

upstream do GitHub.

Compile e execute:

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

onde:

Saída do programa:

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

Estado de saída:

137

que pelo128 + regra do número do sinalsignifica que obtivemos o número do sinal 9, que man 7 signaldiz que éSIGKILL, que é enviado pelo Linuxassassino sem memória.

Interpretação de saída:

  • A memória virtual VSZ permanece constante em printf '0x%X\n' 0x40009A4 KiB ~= 64GiB( psos valores estão em KiB) após o mmap.
  • O "uso real da memória" do RSS aumenta preguiçosamente apenas à medida que tocamos nas páginas. Por exemplo:
    • na primeira impressão temos extra_memory_committed 0, o que significa que ainda não tocamos em nenhuma página. RSS é um pequeno arquivo 1648 KiBque foi alocado para inicialização normal de programas, como área de texto, globais, etc.
    • na segunda impressão, escrevemos o 8388608 KiB == 8GiBequivalente a páginas. Como resultado, o RSS aumentou exatamente 8GIB para8390256 KiB == 8388608 KiB + 1648 KiB
    • RSS continua a aumentar em incrementos de 8GiB. A última impressão mostra cerca de 24 GiB de memória e, antes que 32 GiB pudessem ser impressos, o assassino OOM interrompeu o processo

Veja também:Precisa de explicação sobre o tamanho do conjunto residente/tamanho virtual

Registros do assassino OOM

Nossos dmesgcomandos mostraram os logs do assassino OOM.

Uma interpretação exata deles foi solicitada em:

A primeira linha do log foi:

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

Então, vemos que, curiosamente, foi o daemon MongoDB que sempre roda em meu laptop em segundo plano que primeiro acionou o assassino OOM, provavelmente quando o pobre coitado estava tentando alocar alguma memória.

No entanto, o assassino OOM não mata necessariamente quem o acordou.

Após a invocação, o kernel imprime uma tabela ou processos incluindo 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

e mais adiante vemos que o nosso pequeno main.outfoi morto na invocação anterior:

[ 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

Este log menciona o score 865que esse processo teve, provavelmente a maior (pior) pontuação matadora de OOM, conforme mencionado em:Como o assassino OOM decide qual processo matar primeiro?

Também é interessante que tudo aparentemente aconteceu tão rápido que antes que a memória liberada fosse contabilizada, oomo DeadlineMonitorprocesso foi despertado novamente:

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

e desta vez isso matou algum processo do Chromium, que geralmente consome memória normal do meu computador:

[ 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

Testado no Ubuntu 19.04, kernel Linux 5.0.0.

informação relacionada