Descubrí que pidstat
sería una buena herramienta para monitorear procesos. Quiero calcular el uso promedio de memoria de un proceso en particular. A continuación se muestra un resultado de ejemplo:
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
(Esto es parte del resultado de pidstat -r -p 7276
).
¿Debo utilizar la información del tamaño del conjunto residente (RSS) o del tamaño virtual (VSZ) para calcular el consumo promedio de memoria? He leído algunas cosas en Wikipedia y en foros, pero no estoy seguro de entender completamente las diferencias. Además, parece que ninguno de ellos es fiable. Entonces, ¿cómo puedo monitorear un proceso para conocer su uso de memoria?
Cualquier ayuda sobre este asunto sería útil.
Respuesta1
RSS es la cantidad de memoria que tiene actualmente este proceso en la memoria principal (RAM). VSZ es la cantidad de memoria virtual que tiene el proceso en total. Esto incluye todo tipo de memoria, tanto en RAM como intercambiada. Estos números pueden estar sesgados porque también incluyen bibliotecas compartidas y otros tipos de memoria. Puede tener quinientas instancias en bash
ejecución y el tamaño total de su huella de memoria no será la suma de sus valores RSS o VSZ.
Si necesita tener una idea más detallada sobre la huella de memoria de un proceso, tiene algunas opciones. Puedes revisar /proc/$PID/map
y eliminar las cosas que no te gustan. Si se trata de bibliotecas compartidas, el cálculo podría volverse complejo según sus necesidades (que creo recordar).
Si solo le importa el tamaño del montón del proceso, siempre puede analizar la [heap]
entrada en el map
archivo. El tamaño que el kernel ha asignado para el montón del proceso puede reflejar o no el número exacto de bytes que tiene el proceso.preguntópara ser asignado. Hay detalles minuciosos, aspectos internos del kernel y optimizaciones que pueden alterar esto. En un mundo ideal, será tanto como su proceso necesite, redondeado al múltiplo más cercano del tamaño de la página del sistema ( getconf PAGESIZE
le dirá cuál es; en las PC, probablemente sea 4096 bytes).
Si quieres ver cuánta memoria tiene un procesoasignado, una de las mejores maneras es renunciar a las métricas del lado del kernel. En su lugar, instrumenta las funciones de (des)asignación de memoria dinámica de la biblioteca C con el LD_PRELOAD
mecanismo. Personalmente, abuso un poco valgrind
para obtener información sobre este tipo de cosas. (Tenga en cuenta que para aplicar la instrumentación será necesario reiniciar el proceso).
Tenga en cuenta que, dado que es posible que también esté comparando tiempos de ejecución, eso valgrind
hará que sus programas sean un poco más lentos (pero probablemente dentro de sus tolerancias).
Respuesta2
Ejemplo mínimo ejecutable
Para que esto tenga sentido, debes comprender los conceptos básicos de la paginación:https://stackoverflow.com/questions/18431261/how-does-x86-paging-worky, en particular, que el sistema operativo puede asignar memoria virtual a través de tablas de páginas/su contabilidad de memoria interna (memoria virtual VSZ) antes de tener realmente un almacenamiento de respaldo en RAM o disco (memoria residente RSS).
Ahora, para observar esto en acción, creemos un programa que:
- asigna más RAM que nuestra memoria física con
mmap
- escribe un byte en cada página para garantizar que cada una de esas páginas pase de la memoria virtual única (VSZ) a la memoria realmente utilizada (RSS)
- comprueba el uso de memoria del proceso con uno de los métodos mencionados en:https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c
C Principal
#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;
}
Compilar y ejecutar:
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
dónde:
- 0x1000000000 == 64GiB: 2x la RAM física de mi computadora de 32GiB
- 0x200000000 == 8GiB: imprime la memoria cada 8GiB, por lo que deberíamos obtener 4 impresiones antes del bloqueo en alrededor de 32GiB
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
: requerido para que Linux nos permita realizar una llamada mmap más grande que la RAM física:https://stackoverflow.com/questions/2798330/maximum-memory-what-malloc-can-allocate/57687432#57687432
Salida del 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 salida:
137
que por el128 + regla del número de señalsignifica que tenemos el número de señal 9
, que man 7 signal
dice esSIGKILL, que es enviado por Linuxasesino sin memoria.
Interpretación de salida:
- La memoria virtual VSZ permanece constante en
printf '0x%X\n' 0x40009A4 KiB ~= 64GiB
(ps
los valores están en KiB) después del mmap. - El "uso de memoria real" de RSS aumenta perezosamente sólo cuando tocamos las páginas. Por ejemplo:
- en la primera impresión tenemos
extra_memory_committed 0
, lo que significa que aún no hemos tocado ninguna página. RSS es un pequeño1648 KiB
que ha sido asignado para el inicio normal de programas como área de texto, globales, etc. - en la segunda impresión, hemos escrito un
8388608 KiB == 8GiB
total de páginas. Como resultado, RSS aumentó exactamente 8GIB para8390256 KiB == 8388608 KiB + 1648 KiB
- RSS continúa aumentando en incrementos de 8GiB. La última impresión muestra aproximadamente 24 GiB de memoria, y antes de que se pudieran imprimir 32 GiB, el asesino de OOM finalizó el proceso.
- en la primera impresión tenemos
Ver también:Necesita explicación sobre el tamaño del conjunto residente/tamaño virtual
Registros asesinos de OOM
Nuestros dmesg
comandos han mostrado los registros asesinos de OOM.
Se ha solicitado una interpretación exacta de ellos en:
- https://stackoverflow.com/questions/9199731/understanding-the-linux-oom-killers-logspero echemos un vistazo rápido aquí.
- https://serverfault.com/questions/548736/how-to-read-oom-killer-syslog-messages
La primera línea del registro fue:
[ 7283.479087] mongod invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
Entonces vemos que, curiosamente, fue el demonio MongoDB que siempre se ejecuta en mi computadora portátil en segundo plano el que activó por primera vez el asesino OOM, presumiblemente cuando el pobre estaba tratando de asignar algo de memoria.
Sin embargo, el asesino de OOM no necesariamente mata a quien lo despertó.
Después de la invocación, el kernel imprime una tabla o procesos que incluyen 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
y más adelante vemos que nuestro pequeño main.out
murió en la invocación 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 registro menciona que score 865
tuvo ese proceso, presumiblemente el puntaje más alto (peor) de OOM Killer como se menciona en:¿Cómo decide el asesino OOM qué proceso matar primero?
También es interesante que aparentemente todo sucedió tan rápido que antes de que se contabilizara la memoria liberada, oom
el proceso la despertó nuevamente DeadlineMonitor
:
[ 7283.481043] DeadlineMonitor invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
y esta vez eso eliminó algún proceso de Chromium, que generalmente es el que acapara la memoria normal de mi computadora:
[ 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
Probado en Ubuntu 19.04, kernel de Linux 5.0.0.