私はこれが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(Resident Set Size)またはVSZ(Virtual Size)情報を使用する必要がありますか?ウィキペディアとフォーラムでいくつかの内容を読んだが、違いを完全に理解したかどうかはわかりません。また、そのどれも信頼できるようではありません。それでは、メモリ使用量を確認するためにプロセスをどのように監視しますか?
この問題の助けが役に立ちます。
答え1
RSSは、プロセスが現在メインメモリ(RAM)に持っているメモリの量です。 VSZ は、プロセスの総仮想メモリ量です。これには、RAM内のメモリと交換されたメモリの両方を含むすべての種類のメモリが含まれます。この数には共有ライブラリやその他の種類のメモリも含まれているため、歪むことがあります。 500個のインスタンスを実行できbash
、そのインスタンスの合計メモリスペースはRSSまたはVSZ値の合計ではありません。
プロセスのメモリスペースをさらに詳しく調べるには、いくつかのオプションがあります。/proc/$PID/map
気に入らないものを見つけて削除できます。共有ライブラリであれば、必要に応じて計算が複雑になることがあります(私の記憶では)。
プロセスのヒープサイズにのみ興味がある場合は、いつでもファイル内[heap]
の項目を解析できますmap
。プロセスヒープにカーネルが割り当てるサイズは、プロセスが所有する正確なバイト数を反映したり、反映しない場合があります。尋ねる割り当てられます。この問題を解決できる小さな詳細、カーネルの内部、最適化があります。理想的には、プロセスに必要なサイズであり、システムページサイズの最も近い倍数に丸められます(getconf PAGESIZE
この値からわかります。PCではおそらく4,096バイトです)。
プロセスのメモリ量を確認するには割り当てられた、最良のアプローチの1つは、カーネル側の指標を放棄することです。代わりに、このメカニズムを使用してCライブラリのヒープメモリ(使用可能)割り当て機能を検出できますLD_PRELOAD
。個人的には、私はvalgrind
このようなことに関する情報を得ることを一種の乱用します。 (アプリを検出するにはプロセスを再起動する必要があります。)
おそらくランタイムもベンチマークするので、valgrind
プログラムは少し遅くなります(たぶん許容範囲内)。
答え2
最小限の実行可能な例
これを理解するには、ページ付けの基本を理解する必要があります。https://stackoverflow.com/questions/18431261/how-does-x86-paging-work特に、オペレーティングシステムは、実際にはRAMまたはディスク(RSS常駐メモリ)にバックアップストレージを持つ前に、ページテーブル/内部メモリ帳簿保持(VSZ仮想メモリ)を介して仮想メモリを割り当てることができます。
それでは、これが実際に機能していることを確認するためにプログラムを作成しましょう。
- 物理メモリよりも多くのRAMを割り当てます。
mmap
- 各ページが仮想メモリ(VSZ)から実際に使用されているメモリ(RSS)に移動するように、各ページに1バイトを書き込みます。
- 以下のいずれかの方法を使用して、プロセスのメモリ使用量を確認してください。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
テキスト領域、グローバル変数などの通常のプログラムの起動に割り当てられた小さな塊です。 - 2枚目の印刷まで、私たちはたくさんのページを書きました
8388608 KiB == 8GiB
。その結果、RSSは正確に8GIB増加しました。8390256 KiB == 8388608 KiB + 1648 KiB
- RSSは8GiB単位で増え続けています。最後の印刷では、約24GiBのメモリが表示され、32GiBを印刷する前に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でテストされました。