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

61

Eu 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 tamanho do conjunto residente (RSS) ou tamanho virtual (VSZ) para calcular o consumo médio de memória? Eu li algumas coisas na Wikipedia e em fóruns, mas não tenho certeza de entender completamente as diferenças. Além disso, parece que nenhum deles é confiável. Então, como posso monitorar um processo para obter seu uso de memória?

Qualquer ajuda sobre este assunto seria útil.

Flanfl
fonte

Respostas:

63

RSS é a quantidade de memória que esse processo tem 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 trocados. 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 da área ocupada por memória não será a soma dos valores RSS ou VSZ.

Se você precisar obter uma idéia mais detalhada sobre o espaço ocupado por memória de um processo, terá algumas opções. Você pode /proc/$PID/mapeliminar e eliminar as coisas que não gosta. Se forem bibliotecas compartilhadas, o cálculo pode ficar complexo dependendo de suas necessidades (das quais acho que me lembro).

Se você se importa apenas com o tamanho da pilha do processo, sempre pode 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 solicitou para ser alocado. Existem detalhes minuciosos, detalhes internos do kernel e otimizações que podem desencadear isso. Em um mundo ideal, será o máximo que seu processo precisa, arredondado para o múltiplo mais próximo do tamanho da página do sistema ( getconf PAGESIZEdirá o que é - nos PCs, são provavelmente 4.096 bytes).

Se você deseja ver quanta memória um processo alocou , 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, eu abusei um pouco valgrindpara obter informações sobre esse tipo de coisa. (Observe que a aplicação da instrumentação exigirá reiniciar o processo.)

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

Alexios
fonte
Muito obrigado! Vou investigar as diferentes opções. Você foi mais do que útil! :)
Flanfl
"Você pode ter quinhentas instâncias de bash em execução, e o tamanho total da área ocupada por memória não será a soma dos valores RSS ou VSZ". Mas a soma de seus valores de RSS será uma boa aproximação? Como soma da coluna residente de statm, eu não preciso de um valor exato de super confiável, mas eu preciso saber alto nível quanta memória os meus processos Java estão usando
iloveretards
3
No Ubuntu, /proc/$PID/mapsé diferença de erro de digitação ou distribuição?
dolzenko
1

Exemplo mínimo executável

Para que isso faça sentido, é necessário entender o básico da paginação: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work e, em particular, que o sistema operacional pode alocar memória virtual por meio de tabelas de páginas / sua manutenção de livro de memória interna (memória virtual VSZ) antes que ele realmente tenha um armazenamento de backup na RAM ou no disco (memória residente em RSS).

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

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

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

Status de saída:

137

que pela regra do número de sinal 128 + significa que obtivemos o número do sinal 9, que man 7 signalé SIGKILL , enviado pelo assassino de memória insuficiente do Linux .

Interpretação de saída:

  • A memória virtual VSZ permanece constante em printf '0x%X\n' 0x40009A4 KiB ~= 64GiB(os psvalores estão em KiB) após o mmap.
  • O "uso real da memória" do RSS aumenta preguiçosamente apenas quando tocamos as 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 1648 KiBque foi alocado para a inicialização normal do programa, como área de texto, globais, etc.
    • na segunda impressão, escrevemos para o 8388608 KiB == 8GiBvalor de páginas. Como resultado, o RSS aumentou exatamente 8GIB para8390256 KiB == 8388608 KiB + 1648 KiB
    • O 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 da OOM interrompeu o processo.

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

Logs assassinos de OOM

Nossos dmesgcomandos mostraram os logs do OOM killer.

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

Portanto, vemos que, curiosamente, foi o daemon MongoDB que sempre roda no meu laptop em segundo plano, o que desencadeou o assassino do OOM, provavelmente quando o pobre estava tentando alocar alguma memória.

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

Após a chamada, 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 nosso pouco 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

Esse registro menciona o score 865que esse processo teve, presumivelmente a maior (pior) pontuação do assassino da OOM, conforme mencionado em: Como o assassino da OOM decide qual processo matar primeiro?

Curiosamente, tudo aparentemente aconteceu tão rápido que, antes que a memória liberada fosse contabilizada, ela oomfoi despertada novamente pelo DeadlineMonitorprocesso:

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

e desta vez que matou algum processo do Chromium, que geralmente é a memória normal de meus computadores:

[ 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 do Linux 5.0.0.

Ciro Santilli adicionou uma nova foto
fonte