ウェブブラウザのように複数のプロセスで構成されている linux アプリのメモリ使用量を出来るだけ正確に知りたい。こういうツールは既に存在するはずだと思って色々探したけど無かったので作ってみた。 https://github.com/propella/multimem
実行例
$ ./multimem $(pidof weston)
(PID) Name SWAP USS PSS RSS
----------------------------------------------------------------------------
(1000) weston 0 22504 36281 55708
(1293) ssh-agent ? ? ? ?
(1462) weston-desktop- 0 2188 3787 9832
(2849) weston-terminal 0 5312 7513 14924
(2850) bash 0 2108 2370 5864
(22555) weston-smoke 0 2416 3038 7384
(22520) weston-simple-e 0 14372 26144 41660
(22507) weston-flower 0 1524 2161 6524
(1461) weston-keyboard 0 2780 3884 9336
----------------------------------------------------------------------------
Total ?0 ?53204 ?85178 ?151232
途中メモリマップを読めないプロセスがあると、? として表示している。この場合合計値も不正確なので ? と表示した。
要件
- 共有メモリをダブって計測しないように Proportional set size (PSS) を使う。
- https://selenic.com/repo/smem からコードを拝借する。
- サブプロセスのメモリを全部足した値を表示する。
- 引数として PID をとる。
- またはコマンド文字列を受け取り実行して、その PID を対象とする。
- ある間隔で値を表示し続ける。
smem の構造
smem
コマンドはカーネルが出力するメモリの統計情報を見やすく表示する Python スクリプトだ。PSS を取得するために smem
を利用したいので、コードの中身を多少読んでおく。なんか。。。非常に汚くてビックリ。。。カーネルハッカーでも Python は下手くそなのだと微笑ましく思った。
中を見ると、ようするに、/proc/(pid)/smaps
というファイルから各メモリ領域の使い方を読めるのだが、その中の Pss という項目を足せばそれがプロセスの PSS になるというだけだった。PSS というのは他のプロセスと共有しているメモリを共有しているプロセスの数で割った物だが、私はてっきりカーネルは計算に必要な情報だけ出して Python スクリプトで割り算をやってると思いこんでいた。案外簡単だった。
-
procdata
- このオブジェクトを通して
/proc
ファイルシステムにアクセスする。 - 普通の
/proc
の他、--source
オプションで保存しておいた/proc
を読むことも出来る。
- このオブジェクトを通して
-
showpids
- 何もオプションを指定しないとこの関数を呼ぶ。
-
filters
- PID またはプロセス名が正規表現に引っかかれば False。普通逆やろ。
-
processtotals
- このコマンドの出力データを返す
-
totals[pid][key] = サイズ
の形になる
-
pidtotals
- 一つのプロセスの統計データを返す
- pidmaps によりメモリマップを取得
- メモリマップの中の統計データを全部足しあわせてそのプロセスの統計データとして返す。
-
pidmaps
- 一つのプロセスのメモリマップを表す
-
maps[start][key] = サイズ
の形になる。 - 行の末尾が
kB
で無ければメモリ領域とみなして start, end を更新 (16進文字列を数字に変換)。- メモリ領域の最後のフィールドが 5 文字以上あれば名前を保存。
- 行の末尾が
kB
ならサイズとみなして辞書に入れる。- キーを全て小文字にする。
-
mapdata
-
/proc/(pid)/smaps
を読んで文字列を返す。
-
子プロセスの PID を再帰的に求める。
子プロセスの PID を直接取得する方法は無いみたいだけど、/proc/(pid)/status
を読むと PPid:
という項目に親プロセスが現れる。なので最初に 子PID -> 親PID の辞書を作って、そこから 親PID -> [子PID, ...] の辞書を作ればあとは再帰的に全ての子プロセスを求める事が出来る。
子プロセスのメモリサイズも含めて計測する。
まず最初に multimem 1234
のように、PID を引数に取り自分と子プロセスのメモリ使用量を再帰的に足して出力するコマンドを作った。
# ./multimem 23234
PID Command Swap USS PSS RSS
23234 /usr/bin/qt5/qmlscene /usr/ 0 253384 290481 353312
受け取った PID と子プロセスの PID を全て入れたリストを元に、smem の pidtotals
関数を呼び出し、項目を全て足せば良い。これで目的は達成したのだが、なんだか物足りない。
まず、折角サブプロセスも対象になっているのに、表示だけでは本当にちゃんと計算出来ているのか心許ない。出来れば ps の表示と比較してちゃんと正しい値が取れているか検証した。それには、サブプロセスそれぞれのメモリサイズも表示する事にして、上の実行例のようになった。
PSS だけを一定時間おきに表示する。
こういうツールは実際には他のツールと組み合わせて使うことが多い。例えば、一体間隔で実行し、最大利用量を求めたりグラフを作ったりする。この場合あまり凝った表示は却って都合が悪いので --brief
というオプションを付けた。このオプションを付けると PSS だけを表示する。これで sh と組み合わせて経過を記録出来る。
$ while ./multimem $(pidof -s emacs) --brief; do sleep 2; done
51882
51881
51882
...
参考
- pstree: プロセスツリーを表示するツール
- htop: top のかっこいい版。process tree を見る事が出来る。
- ELC: How much memory are applications really using?: https://lwn.net/Articles/230975/
- smem: https://selenic.com/repo/smem
暇をみて https://github.com/anishathalye/seashells (popular python project をググって見つけた) 等を参考にして pip パッケージにしたい。