我發現這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
。)
我應該使用駐留集大小 (RSS) 還是虛擬大小 (VSZ) 資訊來計算平均記憶體消耗?我在維基百科和論壇上閱讀了一些內容,但我不確定我是否完全理解其中的差異。另外,似乎沒有一個是可靠的。那麼,如何監控進程以獲取其記憶體使用情況呢?
關於這個問題的任何幫助都會很有用。
答案1
RSS 是該進程目前在主記憶體 (RAM) 中擁有的記憶體量。 VSZ 是進程總共有多少虛擬記憶體。這包括所有類型的內存,包括 RAM 中的內存和換出的內存。這些數字可能會出現偏差,因為它們還包括共享庫和其他類型的記憶體。您可以bash
執行 500 個實例,並且它們的記憶體佔用的總大小不會是它們的 RSS 或 VSZ 值的總和。
如果您需要更詳細地了解進程的記憶體佔用情況,您有一些選擇。你可以瀏覽/proc/$PID/map
並剔除你不喜歡的東西。如果它是共享庫,根據您的需要,計算可能會變得複雜(我想我記得)。
如果您只關心進程的堆大小,則始終可以只解析檔案[heap]
中的條目map
。核心為進程堆分配的大小可能反映也可能不反映進程擁有的確切位元組數問待分配。有一些微小的細節、內核內部結構和最佳化可以解決這個問題。在理想情況下,它會是您的進程需要的大小,四捨五入到最接近的系統頁面大小的倍數(getconf PAGESIZE
會告訴您它是什麼 - 在 PC 上,它可能是 4,096 位元組)。
如果你想查看一個進程有多少內存已分配,最好的方法之一是放棄內核端指標。相反,您可以使用該機制來偵測 C 函式庫的堆記憶體(解除)分配函數LD_PRELOAD
。就我個人而言,我有點濫用valgrind
獲取有關此類事情的資訊。 (請注意,應用程式偵測將需要重新啟動該過程。)
請注意,由於您可能也會對執行時間進行基準測試,這valgrind
將使您的程式稍微慢一些(但可能在您的容忍範圍內)。
答案2
最小可運行範例
為了使這一點有意義,您必須了解分頁的基礎知識:https://stackoverflow.com/questions/18431261/how-does-x86-paging-work特別是,作業系統可以在 RAM 或磁碟上實際擁有後備儲存(RSS 駐留記憶體)之前透過頁表/其內部記憶體簿保存(VSZ 虛擬記憶體)分配虛擬記憶體。
現在為了觀察其實際情況,讓我們建立一個程式:
- 分配比我們的實體記憶體更多的 RAM
mmap
- 在每一頁上寫入一個字節,以確保每一頁都從虛擬記憶體 (VSZ) 轉到實際使用的記憶體 (RSS)
- 使用以下提到的方法之一檢查進程的記憶體使用情況:https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-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:2x 我的電腦實體 RAM 32GiB
- 0x200000000 == 8GiB:每 8GiB 列印一次內存,因此我們應該在崩潰之前在 32GiB 左右打印 4 次
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
:Linux 需要允許我們進行大於物理 RAM 的 mmap 呼叫:https://stackoverflow.com/questions/2798330/maximum-memory-which-malloc-can-allocate/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
即信號殺死,由Linux發送記憶體不足殺手。
輸出解釋:
- mmap 之後,VSZ 虛擬記憶體保持恆定
printf '0x%X\n' 0x40009A4 KiB ~= 64GiB
(值以 KiB 為單位)。ps
- 只有當我們觸摸頁面時,RSS「實際記憶體使用量」才會緩慢增加。例如:
- 在第一次列印時,我們有
extra_memory_committed 0
,這意味著我們還沒有觸及任何頁面。 RSS 是一個小塊1648 KiB
,已指派用於正常程式啟動,如文字區域、全域變數等。 - 在第二次列印時,我們已經寫了
8388608 KiB == 8GiB
很多頁。結果,RSS 正好增加了 8GIB8390256 KiB == 8388608 KiB + 1648 KiB
- RSS 繼續以 8GiB 的增量成長。最後一次列印顯示了大約 24 GiB 的內存,在列印 32 GiB 之前,OOM 殺手殺死了該進程
- 在第一次列印時,我們有
也可以看看:需要有關駐留集大小/虛擬大小的說明
OOM 殺手日誌
我們的dmesg
命令已顯示 OOM 殺手日誌。
已在以下位置詢問了這些內容的確切解釋:
- 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 殺手,大概是在這個可憐的東西試圖分配一些內存時。
然而,OOM殺手並不一定會殺死喚醒它的人。
呼叫後,內核列印一個表或進程,包括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 殺手分數,如下所述:OOM 殺手如何決定先殺死哪個進程?
同樣有趣的是,一切顯然發生得如此之快,以至於在釋放的記憶體被計算之前,進程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 中測試。