Achei que pidstat
seria 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 bash
execuçã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/map
e 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 map
arquivo. 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 PAGESIZE
dirá 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_PRELOAD
mecanismo. Pessoalmente, abuso um pouco valgrind
para 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 valgrind
tornará 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:
- aloca mais RAM do que nossa memória física com
mmap
- grava um byte em cada página para garantir que cada uma dessas páginas passe da memória somente virtual (VSZ) para a memória realmente usada (RSS)
- verifica o uso de memória do processo com um dos métodos mencionados em:https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c
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;
}
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:
- 0x1000000000 == 64GiB: 2x a RAM física do meu computador de 32GiB
- 0x200000000 == 8GiB: imprime a memória a cada 8GiB, então devemos obter 4 impressões antes da falha em torno de 32GiB
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
: necessário para o Linux nos permitir fazer uma chamada mmap maior que a RAM física:https://stackoverflow.com/questions/2798330/maximum-memory- which-malloc-can-allocate/57687432#57687432
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 signal
diz 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
(ps
os 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 arquivo1648 KiB
que foi alocado para inicialização normal de programas, como área de texto, globais, etc. - na segunda impressão, escrevemos o
8388608 KiB == 8GiB
equivalente 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
- na primeira impressão temos
Veja também:Precisa de explicação sobre o tamanho do conjunto residente/tamanho virtual
Registros do assassino OOM
Nossos dmesg
comandos mostraram os logs do assassino OOM.
Uma interpretação exata deles foi solicitada em:
- https://stackoverflow.com/questions/9199731/understanding-the-linux-oom-killers-logsmas vamos dar uma olhada rápida aqui.
- https://serverfault.com/questions/548736/how-to-read-oom-killer-syslog-messages
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.out
foi 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 865
que 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, oom
o DeadlineMonitor
processo 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.