CUBE SUGAR CONTAINER

技術系のこと書きます。

stress コマンドを使ってマシンに負荷をかける

stress(1) を使うと、Unix 系 OS で動作しているホストの CPU やメモリ、ディスクに簡単に負荷をかけられる。 今回は使い方や動作などを一通り見ていく。

使った環境は次のとおり。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04 LTS
Release:    22.04
Codename:   jammy
$ uname -srm
Linux 5.15.0-40-generic aarch64
$ stress --version
stress 1.0.5
$ dstat --version | head -n 1
pcp-dstat 5.3.6
$ taskset --version
taskset from util-linux 2.37.2

もくじ

下準備

あらかじめ必要なパッケージをインストールしておく。 肝心の stress(1) は stress パッケージで入る。

$ sudo apt-get -y install stress pcp util-linux

CPU に負荷をかける

まずは CPU に負荷をかける方法から。 今回は環境として CPU が 4 コアの仮想マシンを使っている。

$ cat /proc/cpuinfo | grep processor | wc -l
4

CPU に負荷をかけるときは stress コマンドと共に -c オプションを使う。 引数として、負荷をかけるのに使うワーカーのプロセス数を指定する。

$ stress -c 1

ワーカーはランダムな数値に対する平方根を無限ループで計算することで CPU に負荷をかける。 基本的にプロセス数 = 負荷をかけるコア数と考えれば良い。

dstat(1) で確認すると、-c 1 なら 4 コア環境において user 時間を 25% (1/4) 消費している。

$ dstat -c
----total-usage----
usr sys idl wai stl
 25   0  75   0   0
 25   0  75   0   0
 25   0  75   0   0
...

試しにワーカー数を 2 に増やしてみよう。

$ stress -c 2

すると、次のように消費する user 時間が 50% (2/4) に増えた。

$ dstat -c
----total-usage----
usr sys idl wai stl
 50   0  50   0   0
 51   0  50   0   0
 50   0  50   0   0
...

特定の CPU コアを指定して負荷をかけたい場合は taskset(1) と組み合わせるのが良い。 以下では stress(1) が 0 番コアで動作するように割り当てている。

$ taskset -c 0 stress -c 1

dstat(1) で確認すると、たしかに cpu0 で user 時間を消費していることが確認できる。

$ dstat -c -C 0,1,2,3
-----cpu0-usage----------cpu1-usage----------cpu2-usage----------cpu3-usage----
usr sys idl wai stl:usr sys idl wai stl:usr sys idl wai stl:usr sys idl wai stl
100   0   0   0   0:  0   0 100   0   0:  0   0 100   0   0:  0   0 101   0   0
100   0   0   0   0:  0   0  99   0   0:  0   0 100   0   0:  0   0 100   0   0
100   0   0   0   0:  0   0 100   0   0:  0   0 100   0   0:  0   0 100   0   0
...

次は試しに 1 番と 2 番コアに割り当ててみよう。

$ taskset -c 1,2 stress -c 2

次のとおり、ちゃんと cpu1 と cpu2 の user 時間を消費している。

$ dstat -c -C 0,1,2,3
-----cpu0-usage----------cpu1-usage----------cpu2-usage----------cpu3-usage----
usr sys idl wai stl:usr sys idl wai stl:usr sys idl wai stl:usr sys idl wai stl
  0   0 100   0   0:100   0   0   0   0:100   0   0   0   0:  0   0 101   0   0
  0   0 100   0   0:100   0   0   0   0:100   0   0   0   0:  0   0 100   0   0
  0   0 100   0   0:100   0   0   0   0:100   0   0   0   0:  0   0 100   0   0

メモリに負荷をかける

続いてはメモリに負荷をかけてみよう。

メモリに負荷をかけるときはオプションとして -m を指定する。 引数は CPU のときと同じで実際の処理をするワーカープロセスの数になる。 --vm-bytes は確保するメモリのサイズを指定している。 また、--vm-hang 0 はメモリを確保したまま処理を止めるオプションになっている。 -v はワーカープロセスの動作を詳細にログに残すため。

$ stress -m 1 --vm-bytes 512M --vm-hang 0 -v
stress: info: [23977] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [23977] using backoff sleep of 3000us
stress: dbug: [23977] --> hogvm worker 1 [23978] forked
stress: dbug: [23978] allocating 536870912 bytes ...
stress: dbug: [23978] touching bytes in strides of 4096 bytes ...
stress: dbug: [23978] sleeping forever with allocated memory

上記のログを見ると、ワーカープロセスが 512MB のメモリを確保した上で、そのメモリにアクセスしている。

ps(1) で確認すると、ワーカーのプロセスの VSZ (仮想メモリ) と RSS (物理メモリ) が増加している。

$ ps -C stress -o command,pid,vsz,rss
COMMAND                         PID    VSZ   RSS
stress -m 1 --vm-bytes 512M   23977   3704  1316
stress -m 1 --vm-bytes 512M   23978 527996 525276
$ pstree -p $(pgrep stress | head -n 1)
stress(23977)───stress(23978)

--vm-hang 0 を指定しないと、どのような挙動になるだろうか。

$ stress -m 1 --vm-bytes 512M

プロセスのメモリを見ると、VSZ はそのままで RSS が定期的に増えたり減ったりするはず。

$ watch -n 1 ps -C stress -o command,pid,vsz,rss

なぜこのような挙動になるかは -v オプションをつけて実行すると理解できる。 一瞬でログが流れるので head(1) を使って先頭だけ確認する。

$ stress -m 1 --vm-bytes 512M -v | head -n 15
stress: info: [23993] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [23993] using backoff sleep of 3000us
stress: dbug: [23993] --> hogvm worker 1 [23995] forked
stress: dbug: [23995] allocating 536870912 bytes ...
stress: dbug: [23995] touching bytes in strides of 4096 bytes ...
stress: dbug: [23995] freed 536870912 bytes
stress: dbug: [23995] allocating 536870912 bytes ...
stress: dbug: [23995] touching bytes in strides of 4096 bytes ...
stress: dbug: [23995] freed 536870912 bytes
stress: dbug: [23995] allocating 536870912 bytes ...
stress: dbug: [23995] touching bytes in strides of 4096 bytes ...
stress: dbug: [23995] freed 536870912 bytes
stress: dbug: [23995] allocating 536870912 bytes ...
stress: dbug: [23995] touching bytes in strides of 4096 bytes ...
stress: dbug: [23995] freed 536870912 bytes
stress: FAIL: [23993] (416) <-- worker 23995 got signal 13
stress: WARN: [23993] (418) now reaping child worker processes
stress: FAIL: [23993] (452) failed run completed in 1s

上記から、メモリを確保してアクセスしたらすぐに開放するのをずっと繰り返すことが分かる。

ちなみに --vm-hang はメモリをアクセスした後に開放するまでの時間を制御できる。 たとえば引数に 2 を指定すると、アクセス後に 2 秒待ってから開放する挙動になる。

$ stress -m 1 --vm-bytes 512M --vm-hang 2 -v | head -n 15
stress: info: [24077] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [24077] using backoff sleep of 3000us
stress: dbug: [24077] --> hogvm worker 1 [24079] forked
stress: dbug: [24079] allocating 536870912 bytes ...
stress: dbug: [24079] touching bytes in strides of 4096 bytes ...
stress: dbug: [24079] sleeping for 2s with allocated memory
stress: dbug: [24079] freed 536870912 bytes
stress: dbug: [24079] allocating 536870912 bytes ...
stress: dbug: [24079] touching bytes in strides of 4096 bytes ...
stress: dbug: [24079] sleeping for 2s with allocated memory
stress: dbug: [24079] freed 536870912 bytes
stress: dbug: [24079] allocating 536870912 bytes ...
stress: dbug: [24079] touching bytes in strides of 4096 bytes ...
stress: dbug: [24079] sleeping for 2s with allocated memory
stress: dbug: [24079] freed 536870912 bytes
stress: FAIL: [24077] (416) <-- worker 24079 got signal 13
stress: WARN: [24077] (418) now reaping child worker processes
stress: FAIL: [24077] (452) failed run completed in 7s

なお、-m の引数を増やしたときは、各ワーカープロセスごとにメモリの確保・アクセス・開放をすることになる。

$ stress -m 2 --vm-bytes 512M
$ pstree -p $(pgrep stress | head -n 1)
stress(21021)─┬─stress(21022)
              └─stress(21023)
$ ps -C stress -o command,pid,vsz,rss
COMMAND                         PID    VSZ   RSS
stress -m 2 --vm-bytes 512M   21021   3704  1376
stress -m 2 --vm-bytes 512M   21022 527996 273084
stress -m 2 --vm-bytes 512M   21023 527996 383700

また、--vm-keep を指定すると、メモリを開放しなくなる。 つまり、一度確保したメモリにアクセスすることだけを何度も繰り返す。 たとえば --vm-hang と組み合わせると、インターバルを入れながらメモリにアクセスする。

$ stress -m 1 --vm-keep --vm-hang 2 -v | head -n 10
stress: info: [27420] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [27420] using backoff sleep of 3000us
stress: dbug: [27420] --> hogvm worker 1 [27422] forked
stress: dbug: [27422] allocating 268435456 bytes ...
stress: dbug: [27422] touching bytes in strides of 4096 bytes ...
stress: dbug: [27422] sleeping for 2s with allocated memory
stress: dbug: [27422] touching bytes in strides of 4096 bytes ...
stress: dbug: [27422] sleeping for 2s with allocated memory
stress: dbug: [27422] touching bytes in strides of 4096 bytes ...
stress: dbug: [27422] sleeping for 2s with allocated memory
stress: FAIL: [27420] (416) <-- worker 27422 got signal 13
stress: WARN: [27420] (418) now reaping child worker processes
stress: FAIL: [27420] (452) failed run completed in 6s

ちなみに、上記で「メモリにアクセスする」というのは char の 'Z' を各アドレスに書き込むことを指している。

ディスクに負荷をかける

続いてはディスクに負荷をかける方法について。

ディスクに負荷をかけるときは -d オプションを指定する。 引数はこれまでと同じでワーカープロセスの数になる。

$ stress -d 1

dstat(1) で確認すると、次のように確かにディスクに書き込みが生じている。

$ dstat -d -D sda,total
--dsk/sda----dsk/total-
 read  writ: read  writ
   0   822M:   0   822M
   0  1710M:   0  1710M
   0   651M:   0   651M
...

stress(1) の実装としてはカレントワーキングディレクトリ以下に mkstemp(3) を使ってファイルを作ることで実現している。 そのため負荷をかけたい特定のディスクがあるならマウントしたパスで実行する必要がある。 上記はカレントワーキングディレクトリが sda デバイス上にあることを示す。

挙動は -v オプションをつけて実行すると分かりやすい。 実行していても作業中のファイルが見えないのは、ファイルを開いた直後に unlink(2) しているため。

$ stress -d 1 -v
stress: info: [22636] dispatching hogs: 0 cpu, 0 io, 0 vm, 1 hdd
stress: dbug: [22636] using backoff sleep of 3000us
stress: dbug: [22636] --> hoghdd worker 1 [22637] forked
stress: dbug: [22637] seeding 1048575 byte buffer with random data
stress: dbug: [22637] opened ./stress.H9UjIh for writing 1073741824 bytes
stress: dbug: [22637] unlinking ./stress.H9UjIh
stress: dbug: [22637] fast writing to ./stress.H9UjIh
stress: dbug: [22637] slow writing to ./stress.H9UjIh
stress: dbug: [22637] closing ./stress.H9UjIh after 1073741824 bytes
stress: dbug: [22637] opened ./stress.0X3Feo for writing 1073741824 bytes
stress: dbug: [22637] unlinking ./stress.0X3Feo
stress: dbug: [22637] fast writing to ./stress.0X3Feo
stress: dbug: [22637] slow writing to ./stress.0X3Feo
stress: dbug: [22637] closing ./stress.0X3Feo after 1073741824 bytes
stress: dbug: [22637] opened ./stress.ErRA1c for writing 1073741824 bytes
stress: dbug: [22637] unlinking ./stress.ErRA1c
stress: dbug: [22637] fast writing to ./stress.ErRA1c
stress: dbug: [22637] slow writing to ./stress.ErRA1c
stress: dbug: [22637] closing ./stress.ErRA1c after 1073741824 bytes

作られるファイルサイズはデフォルトで 1GB になっている。 ファイルサイズは --hdd-bytes オプションで指定できる。 指定したファイルサイズが小さいと、短いスパンで作業用のファイルが作られたり消えたりする。

$ stress -d 1 --hdd-bytes 1M -v
stress: info: [22663] dispatching hogs: 0 cpu, 0 io, 0 vm, 1 hdd
stress: dbug: [22663] using backoff sleep of 3000us
stress: dbug: [22663] --> hoghdd worker 1 [22665] forked
stress: dbug: [22665] seeding 1048575 byte buffer with random data
stress: dbug: [22665] opened ./stress.r7I03v for writing 1048576 bytes
stress: dbug: [22665] unlinking ./stress.r7I03v
stress: dbug: [22665] fast writing to ./stress.r7I03v
stress: dbug: [22665] slow writing to ./stress.r7I03v
stress: dbug: [22665] closing ./stress.r7I03v after 1048576 bytes
stress: dbug: [22665] opened ./stress.lN80Ka for writing 1048576 bytes
stress: dbug: [22665] unlinking ./stress.lN80Ka
stress: dbug: [22665] fast writing to ./stress.lN80Ka
stress: dbug: [22665] slow writing to ./stress.lN80Ka
stress: dbug: [22665] closing ./stress.lN80Ka after 1048576 bytes
...

また、これは直接的なディスクへの負荷ではないけど、関連するオプションとして -i がある。 これは sync(2) を発行しまくるワーカープロセスを作るためのオプション。 sync(2) は、キャッシュされた書き込みを永続ストレージに同期するためのシステムコール。

$ stress -i 1
stress: info: [22656] dispatching hogs: 0 cpu, 1 io, 0 vm, 0 hdd

上記を実行すると無限ループで sync(2) を実行しまくるワーカープロセスが生成される。 ユースケースとしては -d オプションと組み合わせて使う感じなのかな。

まとめ

今回は stress(1) を使って Unix 系 OS で動作するホストに様々な負荷をかける方法について見てみた。

参考

github.com