Я обнаружил, что pidstat
это хороший инструмент для мониторинга процессов. Я хочу рассчитать среднее использование памяти определенным процессом. Вот пример вывода:
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
(Это часть вывода из pidstat -r -p 7276
.)
Следует ли использовать информацию Resident Set Size (RSS) или Virtual Size (VSZ) для расчета среднего потребления памяти? Я прочитал несколько вещей в Википедии и на форумах, но я не уверен, что полностью понимаю разницу. Плюс, похоже, что ни один из них не является надежным. Итак, как я могу контролировать процесс, чтобы узнать его использование памяти?
Любая помощь по этому вопросу будет полезна.
решение1
RSS — это объем памяти, который в данный момент занимает этот процесс в основной памяти (ОЗУ). VSZ — это общий объем виртуальной памяти, который занимает процесс. Сюда входят все типы памяти, как в ОЗУ, так и в подкачке. Эти числа могут быть искажены, поскольку они также включают общие библиотеки и другие типы памяти. У вас может быть пятьсот запущенных экземпляров bash
, и общий размер их памяти не будет суммой их значений RSS или VSZ.
Если вам нужно получить более детальное представление о памяти, занимаемой процессом, у вас есть несколько вариантов. Вы можете просмотреть /proc/$PID/map
и отсеять то, что вам не нравится. Если это общие библиотеки, расчет может быть сложным в зависимости от ваших потребностей (которые я, кажется, помню).
Если вас интересует только размер кучи процесса, вы всегда можете просто проанализировать запись [heap]
в map
файле. Размер, который ядро выделило для кучи процесса, может отражать или не отражать точное количество байтов, которые имеет процессспросилдля выделения. Есть мелкие детали, внутренности ядра и оптимизации, которые могут сбить это с толку. В идеальном мире это будет столько, сколько нужно вашему процессу, округленное до ближайшего кратного размеру системной страницы ( getconf PAGESIZE
скажу вам, чему это равно — на ПК это, вероятно, 4096 байт).
Если вы хотите узнать, сколько памяти занимает процессвыделено, один из лучших способов — отказаться от метрик на стороне ядра. Вместо этого вы инструментируете функции выделения (де)памяти кучи библиотеки C с помощью механизма LD_PRELOAD
. Лично я немного злоупотребляю, valgrind
чтобы получить информацию о такого рода вещах. (Обратите внимание, что применение инструментирования потребует перезапуска процесса.)
Обратите внимание: поскольку вы также можете проводить бенчмаркинг времени выполнения, это valgrind
сделает ваши программы немного медленнее (но, вероятно, в пределах ваших допусков).
решение2
Минимальный работоспособный пример
Чтобы это имело смысл, вам нужно понимать основы пейджинга:https://stackoverflow.com/questions/18431261/how-does-x86-paging-workи в частности, что ОС может выделять виртуальную память через таблицы страниц / свою внутреннюю память (виртуальная память VSZ) до того, как у нее фактически появится резервное хранилище в оперативной памяти или на диске (резидентная память RSS).
Теперь, чтобы увидеть это в действии, давайте создадим программу, которая:
- выделяет больше оперативной памяти, чем наша физическая память с
mmap
- записывает один байт на каждую страницу, чтобы гарантировать, что каждая из этих страниц переходит из виртуальной памяти (VSZ) в фактически используемую память (RSS)
- проверяет использование памяти процессом одним из методов, упомянутых по адресу:https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c
основной.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;
}
Скомпилируйте и запустите:
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
где:
- 0x1000000000 == 64GiB: в 2 раза больше физического объема оперативной памяти моего компьютера — 32GiB
- 0x200000000 == 8GiB: печатать память каждые 8GiB, поэтому мы должны получить 4 отпечатка до сбоя около 32GiB
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
: требуется для Linux, чтобы мы могли сделать вызов mmap, превышающий размер физической оперативной памяти:https://stackoverflow.com/questions/2798330/максимальная-память-какая-malloc-может-выделять/57687432#57687432
Вывод программы:
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
Статус выхода:
137
который по128 + правило номера сигналаозначает, что мы получили номер сигнала 9
, который man 7 signal
говорит:SIGKILL, который отправляется Linuxубийца нехватки памяти.
Интерпретация выходных данных:
- Виртуальная память VSZ остается постоянной
printf '0x%X\n' 0x40009A4 KiB ~= 64GiB
(ps
значения указаны в КиБ) после mmap. - RSS "реальное использование памяти" увеличивается лениво только по мере того, как мы касаемся страниц. Например:
- на первом отпечатке у нас есть
extra_memory_committed 0
, что означает, что мы еще не коснулись ни одной страницы. RSS — это небольшой файл,1648 KiB
который был выделен для обычного запуска программы, например, текстовая область, глобальные переменные и т. д. - на второй печати мы написали на
8388608 KiB == 8GiB
сумму страниц. В результате RSS увеличился ровно на 8GIB до8390256 KiB == 8388608 KiB + 1648 KiB
- RSS продолжает увеличиваться с шагом 8GiB. Последняя распечатка показывает около 24 GiB памяти, и прежде чем 32 GiB удалось распечатать, OOM killer уничтожил процесс
- на первом отпечатке у нас есть
Смотрите также:Необходимо объяснение по размеру резидентного набора/виртуальному размеру
Журналы OOM killer
Наши dmesg
команды показали логи OOM killer.
Точная интерпретация этих данных была запрошена по адресу:
- https://stackoverflow.com/questions/9199731/understanding-the-linux-oom-killers-logsно давайте быстро взглянем сюда.
- https://serverfault.com/questions/548736/how-to-read-oom-killer-syslog-messages
Самая первая строка журнала была:
[ 7283.479087] mongod invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
Итак, мы видим, что, как ни странно, именно демон MongoDB, который всегда работает на моем ноутбуке в фоновом режиме, первым запустил OOM killer, предположительно, когда бедняга пытался выделить немного памяти.
Однако убийца ООМ не обязательно убивает того, кто его разбудил.
После вызова ядро выводит таблицу процессов, включая 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
и далее мы видим, что наш собственный малыш main.out
действительно погиб при предыдущем призыве:
[ 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
В этом журнале упоминается, score 865
какой именно процесс имел, предположительно, наивысший (наихудший) показатель OOM killer, как указано в:Как OOM killer решает, какой процесс завершить первым?
Также интересно, что все, по-видимому, произошло так быстро, что прежде чем освобожденная память была учтена, она oom
была снова разбужена процессом DeadlineMonitor
:
[ 7283.481043] DeadlineMonitor invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
и на этот раз это убило какой-то процесс Chromium, который обычно пожирает память моего компьютера:
[ 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
Протестировано в Ubuntu 19.04, ядро Linux 5.0.0.