Dockerコンテナでのcyclictest実行結果
前回はDockerコンテナでcyclictestを動作させるところまでやってみました。
今回はcyclictestの結果をもう少し詳しく見てみます。
環境
以下の環境で試しました。
docker version Client: Version: 18.09.7 API version: 1.39 Go version: go1.10.1 Git commit: 2d0083d Built: Fri Aug 16 14:20:06 2019 OS/Arch: linux/amd64 Experimental: false Server: Engine: Version: 18.09.9 API version: 1.39 (minimum version 1.12) Go version: go1.13.4 Git commit: 9552f2b Built: Sat Nov 16 01:07:48 2019 OS/Arch: linux/amd64 Experimental: false
cyclictestについて
cyclictestは以下の図のように一定時間ごとにリアルタイムスレッドを起動して,理想とするスレッドの起動時刻と実際にスレッドが起動した時刻との差(レイテンシ)を計測するテストです。
当然レイテンシは低ければ低いほどよいと言えます。
CPU負荷の与え方
CPU負荷が高い場合にもリアルタイムスレッドが定周期実行されることを確認したいので,以下のようなスクリプトを実行しながらcyclictestを実行します。
実行するとCPU使用率がほぼ100%になります。
while true; do dd if=/dev/zero of=bigfile bs=1024000 count=1024; done & while true; do killall hackbench; sleep 5; done & while true; do ./hackbench 20; done & ( cd ltp-full-20040707; su mingo -c ./run40; ) & ping -l 100000 -q -s 10 -f v & du / & ./dortc &
cyclictestの実行
前回の記事と同じ手順でrttestコンテナを起動し,以下のコマンドを実行します。
100ms周期で1時間テストを実行し,レイテンシ3000[us]までのヒストグラムを表示します。
# cyclictest --smp --priority=99 --interval=100000 --distance=0 -l36000 -h3000
結果
以下のような結果が得られました。
T: 0 ( 31) P:99 I:100000 C: 36000 Min: 5 Act: 11 Avg: 10 Max: 3417 T: 1 ( 32) P:99 I:100000 C: 36000 Min: 7 Act: 21 Avg: 14 Max: 4488 T: 2 ( 33) P:99 I:100000 C: 36000 Min: 5 Act: 14 Avg: 14 Max: 4390 T: 3 ( 34) P:99 I:100000 C: 36000 Min: 5 Act: 11 Avg: 10 Max: 4498 # Histogram 000000 000000 000000 000000 000000 000001 000000 000000 000000 000000 000002 000000 000000 000000 000000 000003 000000 000000 000000 000000 000004 000000 000000 000000 000000 000005 000001 000000 000007 000017 000006 000058 000000 000498 000537 000007 001017 000003 004293 005340 000008 006554 000034 008831 009400 000009 009585 000258 007380 008309 000010 007967 001102 005890 005945 000011 005389 003659 003540 003257 000012 002894 007126 001574 001387 000013 001071 006705 000727 000624 000014 000442 005549 000406 000293 000015 000200 004313 000270 000208 000016 000136 003121 000205 000124 000017 000098 001697 000159 000081 000018 000053 000799 000113 000058 000019 000038 000385 000096 000033 000020 000035 000205 000076 000029 000021 000027 000176 000086 000017 000022 000018 000111 000073 000026 000023 000009 000087 000087 000011 000024 000021 000064 000102 000010 000025 000039 000058 000065 000022 ... 002996 000000 000000 000000 000000 002997 000000 000000 000000 000000 002998 000000 000000 000000 000000 002999 000000 000000 000000 000000 # Total: 000035997 000035996 000035993 000035998 # Min Latencies: 00005 00007 00005 00005 # Avg Latencies: 00010 00014 00014 00010 # Max Latencies: 03417 04488 04390 04498 # Histogram Overflows: 00003 00004 00007 00002 # Histogram Overflow at cycle number: # Thread 0: 19250 24091 34394 # Thread 1: 24091 30057 34394 34806 # Thread 2: 16096 17984 24091 28377 30057 32070 34394 # Thread 3: 19250 28377
最大レイテンシは4498[us]ですが,平均は約10[us]です。
これくらいの精度で十分な用途であればコンテナでのリアルタイムアプリケーションも使えるのではないでしょうか。
ヒストグラムを25[us]までグラフ化すると以下のようになります。
結構10us付近にまとまっています。CPU1はどうしたのか・・・
Dockerでリアルタイム処理(cyclictest)を実行する
Dockerコンテナでリアルタイムタスクは動かせないの?と思ったので試してみました。
docker docs | Runtime options with Memory, CPUs, and GPUsにはConfigure the realtime schedulerという項目があり,リアルタイム処理を実現できそうな感じがします。
以下の手順でDockerコンテナ上でcyclictestを実行するところまでやってみます。
- カーネルのビルド
- リアルタイムスケジューラを使ったコンテナの起動
- cyclictestの実行
CONFIG_RT_GROUP_SCHEDを有効にしたカーネルをビルドする
docker docsを見るとCONFIG_RT_GROUP_SCHEDが有効になっているカーネルでないとコンテナがリアルタイムスケジューラを使用できないようです。RT PREEMPTIONパッチを当てがてら,CONFIG_RT_GROUP_SCHEDを有効にしてカーネルをビルドします。
パッチとカーネルソースのダウンロード
実はPreemption ModelをLowlatency DesktopとしないとCONFIG_RT_GROUP_SCHEDを有効にできないので,RT PREEMTTIONパッチは必要はなさそうですが一応当てておきます。
まずは以下からRT PREEMPTIONパッチを探します。
https://mirrors.edge.kernel.org/pub/linux/kernel/projects/rt/5.4/
5.4.13-rt7が最新版のようなので,以下から5.4.13のソースをダウンロードします。
https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/
以下の.tar.gzファイルをダウンロードしました。 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.13.tar.gz
RT PREEMPTIONパッチの適用
カーネルソースとパッチを同じディレクトリに保存し,以下を実行します。
$ tar xf linux-5.4.13.tar.gz $ cd linux-5.4.13 $ gzip -cd ../patch-5.4.13-rt7.patch.gz | patch -p1 --verbose
Preemption Modelの変更
$ make menuconfig
以下のように
General setup > Preemption Model > Preemptible Kernel(Low-Latency Desktop)を選択します。
以下のようにGeneral setup > Control Group support > CPUControllerのGroup scheduling for SCHED_RR/FIFOにチェックを入れます。
上記のPreemption ModelでFully Preemptible Kernel(Real-Time)を選択するとGroup scheduling for SCHED_RR/FIFOが選択できなくなります。
上記2つを設定したら下部メニューのExitを選択し,menuconfigを終了します。終了時に.configに内容を保存するか聞かれるのでYesを選択します。
カーネルイメージの作成
以下のコマンドを実行し,カーネルイメージとヘッダイメージを作成します。
$ make-kpkg -j 8 --rootcmd fakeroot --initrd kernel_image kernel_headers
ソースが展開されているディレクトリの一つ上の階層に.debファイルが作成されるので以下のコマンドを実行して.debファイルをインストールします。
$ cd .. $ sudo dpkg -i linux-headers-5.4.13-rt7_5.4.13-rt7-10.00.Custom_amd64.deb linux-image-5.4.13-rt7_5.4.13-rt7-10.00.Custom_amd64.deb
GRUBメニューが表示されなくなることの対策
ビルドしたカーネルをインストールして再起動しGRUBメニューでカーネルを選択するわけですが,起動時にGRUBメニューが表示されない場合があります。
そのような場合は,/etc/default/grubファイルを編集してGRUBメニューが表示されるようにします。
$ sudo vi /etc/default/grub 以下のように先頭に#をつけて,2行をコメントアウトする # GRUB_TIMEOUT_STYLE=hidden # GRUB_TIMEOUT=0
編集後はupdate-grub
を実行しておきます。
Invalid Signatureによりカーネルがロードできない場合の対策
ビルドしたカーネルで起動しようとした際にInvalid Signatureエラーによりカーネルがロードできない場合があります。
以下のようにしてSecure Bootを無効にすることで回避できます。
PCをシャットダウンする前に以下のコマンドを実行します。
$ sudo mokutil --disable-validation
再起動後にmokutilを実行するか確認するダイアログが表示されます。mokutilを実行すると上記のmokutilコマンド実行時に設定したパスワード入力を求められます。
入力したパスワードをすべて入力するのではなく,パスワードの何文字目は何かという形式で1文字ずつ入力します。
リアルタイムスケジューラを使うコンテナの起動
起動前の確認
以下のファイルがあることを確認します。
/sys/fs/cgroup/cpu.rt_runtime_us
Dockerデーモンが動作していることを確認します。
$ docker ps
起動していない場合,以下コマンドにより再起動します。
$ sudo service docker restart
Dockerfileの作成とイメージのビルド
以下のようなDockerfileを作成します。ubuntu18.04にRT-Testsをインストールしています。
FROM ubuntu:18.04 RUN apt-get update && apt-get upgrade -y && apt-get install -y build-essential libnuma-dev git python RUN git clone git://git.kernel.org/pub/scm/utils/rt-tests/rt-tests.git && \ cd rt-tests && \ git checkout stable/v1.0 && \ make all && \ make install
Dockerファイルをビルドします。
$ docker build -t rttest:latest .
コンテナの起動
以下のコマンドによりrttestイメージを起動します。
$ docker run -it --cpu-rt-runtime=950000 --ulimit rtprio=99 --cap-add=sys_nice rttest:latest
起動時に以下のようなエラーが出る場合があります。
docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "process_linux.go:281: applying cgroup configuration for process caused \"failed to write 950000 to cpu.rt_runtime_us: write /sys/fs/cgroup/cpu,cpuacct/docker/e2a68990c554b501acfdee02a2d6be94240edd8860335c6ce5b64f559e82eba3/cpu.rt_runtime_us: invalid argument\"": unknown.
上記のようなエラーが出るのは/sys/fs/cgroup/cpu/docker/cpu.rt_runtime_usに設定されているリアルタイムタスクの実行時間が0になっているためのようなので,以下のコマンドにより0以上の値を設定します。
$ sudo -s # echo 950000 > /sys/fs/cgroup/cpu/docker/cpu.rt_runtime_us
/sys/fs/cgroup/cpu/docker/cpu.rt_period_usには1000000が設定されており,これとの比率で割り当てられたCPU処理時間の何%をリアルタイム処理に回すかを設定できます。上記の場合はcpu.rt_runtime_usが950000なので,95%がリアルタイム処理に回されます。
上記を実行後,再度docker run
コマンドを実行し,rttestコンテナのコンソールに接続します。
cyclictestの実行
起動したコンテナ上でcyclictestを実行します。
# cyclictest --smp --priority=80 --interval=200 --distance=0
以下のように表示され,cyclictestが実行できました。
WARN: stat /dev/cpu_dma_latency failed: No such file or directory policy: fifo: loadavg: 6.18 3.00 1.65 6/1057 23 T: 0 ( 20) P:80 I:200 C: 618500 Min: 2 Act: 7 Avg: 8 Max: 2419 T: 1 ( 21) P:80 I:200 C: 617407 Min: 2 Act: 6 Avg: 8 Max: 2513 T: 2 ( 22) P:80 I:200 C: 618459 Min: 2 Act: 12 Avg: 7 Max: 2540 T: 3 ( 23) P:80 I:200 C: 616966 Min: 2 Act: 6 Avg: 9 Max: 2431
しかし,スレッドの起動周期200us(cyclictestの引数--inteval=200)に対して最大のレイテンシが2540usとなっています。
平均は約10usなので概ね大丈夫そうですが,たまに遅れるようです。
まとめ
Dockerコンテナ上でcyclictestを実行するところまで試しました。
次回はcyclictestの結果をヒストグラム表示してもう少し詳しく見てみます。
yoctoでNano Pi NEO 2用イメージをビルドする
公式イメージもいいですが,meta-allwinner-hxレイヤーというものがあるのでyoctoでNano Pi NEO 2用イメージをビルドすることができます。
環境
以下の環境を使いました。
- ホストPC:ubuntu 18.04 LTS
- poky warrior 2.7.1
- meta-allwinner-hx (warrior)
- meta-openembedded(warrior)
ビルド環境のセットアップ
meta-allwinner-hxのREADMEを見ながら ホームディレクトリの下に以下のような構成のbuild_nanopiディレクトリを作っていきます。(名前は何でもいいです)
build_nanopi/ ├── flash_sd.sh ├── setup-environment.sh └── sources ├── meta-allwinner-hx ├── meta-openembedded └── poky
yocto環境は通常~/poky-warriorなどに作成されているかと思います。上記のようなフォルダ構成を実現するためにまた同じものをダウンロードしてくるのも無駄なので,sourcesディレクトリ以下はシンボリックリンクを作成します。
以下コマンドを実行していきます。
$ mkdir ~/build_nanopi $ cd ~/build_nanopi $ mkdir sources $ cd sources $ ln -s ~/poky-warrior/meta-allwinner-hx meta-allwinner-hx $ ln -s ~/poky-warrior poky $ ln -s ~/poky-warrior/meta-openembedded meta-openembedded
セットアップスクリプトをコピーします。
$ cd ~build_nanopi $ cp sources/meta-allwinner-hx/scripts/setup-environment.sh . $ cp sources/meta-allwinner-hx/scripts/flash_sd.sh .
環境変数MACHINEにnanopi-neo2を設定します。
$ export MACHINE=nanopi-neo2
setup-environment.shを実行します。
$ source ./setup-environment.sh build
以下のようにsetup-environment-append.shが見つからないというメッセージが表示されますが,buildディレクトリは正常に生成されます。
Configuring for nanopi-neo2 Searching for append setup scripts... ls: '/home/***/build_nanopi/sources/*/setup-environment-append.sh' にアクセスできません: そのようなファイルやディレクトリはありません These are the default supported images: allwinner-image You can now build your image. To build the allwinner-image then run this: $ bitbake allwinner-image
buildディレクトリが生成され,confディレクトリ内にlocal.confとbblayers.confが自動生成されます。
私の環境ではbitbakeのdo_fetchでダウンロードするファイルが~/poky-warrior/downloadsに入っているのでlocal.confのDL_DIRは以下のように変更しました。
DL_DIR = "${HOME}/poky-warrior/downloads"
local.confを設定したら以下のコマンドを実行するとNano Pi NEO 2用のイメージを作成できます。(bitbakeへのパスが通っている必要があります)
$ bitbake allwinner-image
microSDカードへの書き込み
イメージが作成できたらmicroSDカードへ書き込みます。
~/build_nanopiにあるflash_sd.shを使用します。flasah_sd.shからbmaptoolが呼ばれるので,まずは必要なパッケージをインストールします。
$ sudo apt-get install bmap-tools
MACHINEと書き込み先を指定して./flash_sd.shを実行します。
デフォルトでは13.8GBのイメージを書き込むようになっているため,16GB以上のmicroSDカードを用意する必要があります。
$ sudo MACHINE=nanopi-neo2 ./flash_sd.sh /dev/sdb
meta-allwinner-hxのREADMEには「デフォルトだと13.8GBのイメージを作るが,本当に必要なのは60MB弱だから中身スカスカで,bmaptoolを使えば14GBを数秒で書き込めるんだ」みたいなことが書いてありますが,ならばもっと低容量のSDに入るようにしたいですね。
作成されるイメージを小さくするにはmeta-allwinner-hx/classes/allwinner-wks-defs.inc
を編集し,ROOT_EXTRA_SPACEを変更してルートディレクトリの領域を小さくします。デフォルトでは10240となっていますが,1024に変更すると1.2GBくらいのイメージが作成されます。
meta-allwinner-hx/classes/allwinner-wks-defs.inc
のROOT_EXTRA_SPACEを以下のように変更します。
ROOT_EXTRA_SPACE ?= "1024"
起動確認
イメージを書き込んだmicroSDをNano Pi NEO 2に挿入し,LANケーブルも接続して電源を入れます。
シリアルコンソールを繋いでいない場合,Nano Pi NEO 2のIPアドレスを知るにはNmapを使ってスキャンする必要があります。
IPアドレスがわかったらSSHで接続します。
$ sudo nmap -sP 192.168.0.* ・・・ Nmap scan report for nanopi-neo2 (192.168.0.32) ・・・ $ ssh root@192.168.0.32
なんとパスワード無しでログインできました。
早めにパスワードを変えておきましょう。
Raspberry Piに増設したSDカードの速度を測る 1bitモードと4bitモードの比較
Raspberry Piに増設したSDカードのパフォーマンスをfioで測ってみました。
増設方法は以下で紹介しています。
tshell.hatenablog.com
実は前の記事のようにdtboファイルを作らなくても以下の設定を/boot/config.txtに追記するだけで使えました。
dtoverlay=sdio,poll_once=off,sdio_overclock=50
前回はbus_width=1
を指定して動作させ,とりあえず動いて喜んでいましたがこれを指定すると1bitモードで動作するようです。せっかくDAT0〜3まで結線しているのにDAT0しか使っていません。
今回はbus_width=1の指定を外してデフォルトの4bitモードで動作させ,どれくらい速度が違うのか測ってみます。
環境
以下のものを使用しました。
- Raspberry Pi Compute Module 3+(8GBモデル)
- Compute Module IO
- Raspbian Buster Lite
- MicroSDカード(SDSDQAF3-008G-I) datasheet
測り方
SDカードをマウントしたあと,fioを使用してSDカードへの書き込み速度を測ってみます。
以下のコマンドを実行します。
$ sudo mount -t ext4 /dev/mmcblk1 /mnt/sdcard $ fio -filename=/mnt/sdcard/test1g -direct=1 -rw=write -bs=4k -size=1G -name=file1
/mnt/sdcardに1GBのファイルをブロックサイズ4kBで書き込んでアクセス速度を測ります。
1bitモード
まずは/boot/config.txtへの追記内容を以下のようにしたときの速度を測ってみます。
dtoverlay=sdio,poll_once=off,bus_width=1,sdio_overclock=50
結果は以下のようになりました。
file1: (g=0): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=psync, iodepth=1 fio-3.12 Starting 1 process file1: Laying out IO file (1 file / 1024MiB) Jobs: 1 (f=1): [W(1)][100.0%][w=1628KiB/s][w=407 IOPS][eta 00m:00s] file1: (groupid=0, jobs=1): err= 0: pid=667: Mon Dec 2 04:37:06 2019 write: IOPS=406, BW=1626KiB/s (1665kB/s)(1024MiB/644787msec); 0 zone resets clat (usec): min=1602, max=20014, avg=2442.16, stdev=763.70 lat (usec): min=1603, max=20019, avg=2444.70, stdev=763.72 clat percentiles (usec): | 1.00th=[ 1926], 5.00th=[ 1975], 10.00th=[ 2040], 20.00th=[ 2073], | 30.00th=[ 2089], 40.00th=[ 2114], 50.00th=[ 2147], 60.00th=[ 2311], | 70.00th=[ 2507], 80.00th=[ 2900], 90.00th=[ 3064], 95.00th=[ 3294], | 99.00th=[ 3851], 99.50th=[ 9110], 99.90th=[10814], 99.95th=[11338], | 99.99th=[13042] bw ( KiB/s): min= 1552, max= 1736, per=100.00%, avg=1626.02, stdev=20.09, samples=1289 iops : min= 388, max= 434, avg=406.50, stdev= 5.02, samples=1289 lat (msec) : 2=8.00%, 4=91.17%, 10=0.61%, 20=0.22%, 50=0.01% cpu : usr=0.59%, sys=7.93%, ctx=265811, majf=0, minf=23 IO depths : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% issued rwts: total=0,262144,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=1 Run status group 0 (all jobs): WRITE: bw=1626KiB/s (1665kB/s), 1626KiB/s-1626KiB/s (1665kB/s-1665kB/s), io=1024MiB (1074MB), run=644787-644787msec Disk stats (read/write): mmcblk1: ios=0/262310, merge=0/279, ticks=0/592877, in_queue=592690, util=91.91%
IOPS(1秒あたりのIO数)が406,アクセス速度は1665kB/sでした。
4bitモード
次に/boot/config.txtの追記内容を以下のようにして4bitモードでの速度を測ってみます。
dtoverlay=sdio,poll_once=off,sdio_overclock=50
結果は以下のようになりました。
file1: (g=0): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=psync, iodepth=1 fio-3.12 Starting 1 process Jobs: 1 (f=1): [W(1)][100.0%][w=2312KiB/s][w=578 IOPS][eta 00m:00s] file1: (groupid=0, jobs=1): err= 0: pid=530: Mon Dec 2 04:47:50 2019 write: IOPS=577, BW=2310KiB/s (2366kB/s)(1024MiB/453837msec); 0 zone resets clat (usec): min=921, max=21609, avg=1715.19, stdev=743.28 lat (usec): min=922, max=21611, avg=1717.64, stdev=743.29 clat percentiles (usec): | 1.00th=[ 1205], 5.00th=[ 1254], 10.00th=[ 1319], 20.00th=[ 1352], | 30.00th=[ 1369], 40.00th=[ 1385], 50.00th=[ 1418], 60.00th=[ 1582], | 70.00th=[ 1795], 80.00th=[ 2114], 90.00th=[ 2311], 95.00th=[ 2376], | 99.00th=[ 3228], 99.50th=[ 8029], 99.90th=[ 9503], 99.95th=[10159], | 99.99th=[11207] bw ( KiB/s): min= 2160, max= 2504, per=100.00%, avg=2310.03, stdev=34.79, samples=907 iops : min= 540, max= 626, avg=577.46, stdev= 8.71, samples=907 lat (usec) : 1000=0.01% lat (msec) : 2=75.77%, 4=23.49%, 10=0.68%, 20=0.06%, 50=0.01% cpu : usr=0.77%, sys=3.67%, ctx=263680, majf=0, minf=26 IO depths : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0% submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0% issued rwts: total=0,262144,0,0 short=0,0,0,0 dropped=0,0,0,0 latency : target=0, window=0, percentile=100.00%, depth=1 Run status group 0 (all jobs): WRITE: bw=2310KiB/s (2366kB/s), 2310KiB/s-2310KiB/s (2366kB/s-2366kB/s), io=1024MiB (1074MB), run=453837-453837msec Disk stats (read/write): mmcblk1: ios=1/262264, merge=0/91, ticks=1/435935, in_queue=435340, util=95.99%
IOPSは577,アクセス速度は2366kB/sです。
まとめ
アクセス速度を比較すると1bitモードのとき1665kB/s,4bitモードのとき2366kB/sで,4bitモードにすると1.4倍ほど速くなります。
データシートにはシーケンシャル書き込みは最大50MB/sと書いてありますが,ブロックサイズを大きくすればこれくらい出るということかもしれません。
Fuegoにテストを追加する(スクリプト編)
前回はホストPCにFuegoをインストールし,サンプルを実行しました。 tshell.hatenablog.com
今回はシェルスクリプトとして記述したテストを追加する方法です。
環境
前回と同様以下の環境を使用しました。
- ホストPC : ubuntu 18.04 LTS
- ターゲット : Raspberry Pi 3(Raspbian Buster Lite)
- fuego (Commit eeb3c08c...)
作業手順
以下の作業を行ってテストを追加します。
今回はmytestという名前のテストを作成します。
ディレクトリ作成
Fuegoのインストールディレクトリにテストを作成します。Fuegoは/home/ユーザー名/fuegoにインストールされているとします。
ホストPCのターミナルで以下のコマンドを実行します。
cd /home/ユーザー名/fuego/fuego-core/tests mkdir Functional.mytest cd Functional.mytest
テストスクリプトの作成
作成したFunctional.mytest
ディレクトリの中にmytest-target.sh
を以下の内容で作成します。
与えられた引数をそのまま表示するスクリプトです。
#!/bin/bash echo $1
spec.jsonの作成
以下の内容でテストスペックを設定するファイルspec.json
を作成します。このファイルは必ずspec.jsonという名称で作成する必要があります。
{ "testName": "Functional.mytest", "specs":{ "fail":{ "ARG":"FAIL" }, "success":{ "ARG":"SUCCESS" }, "default":{ "ARG":"SUCCESS" } } }
ここで設定したARGはあとで述べるfuego_test.sh
の中でFUNCTIONAL_MYTEST_ARGとして使用できます。spec.jsonの中でARGとだけ記述したものに自動的にテスト名が付加されます。
fuego_test.shの作成
以下の内容でfuego_test.sh
を作成します。このファイルもfuego_test.shという名前で作成する必要があります。
function test_deploy { put $TEST_HOME/mytest-target.sh $BOARD_TESTDIR/fuego.$TESTDIR/ } function test_run { report "cd $BOARD_TESTDIR/fuego.$TESTDIR; \ ./mytest-target.sh $FUNCTIONAL_MYTEST_ARG" } function test_processing { log_compare "$TESTDIR" "1" "SUCCESS" "p" }
function test_deploy
ではテスト実行前にターゲットに対して何を転送するか記述しています。
function test_run
ではテストを実行する際に何をするか記述しています。
Fuegoのreportコマンドは引数で指定されたコマンドを実行して結果をファイルに保存するコマンドです。
test_runの中で,spec.jsonで設定したARGを$FUNCTIONAL_MYTEST_ARGとして使っています。
function test_processing
はテスト結果を判定する設定です。log_compareコマンドで結果を判定します。
Fuego wiki - function log_compare
各引数の意味は以下のようになっています。
$1 = reflog_prefix = prefix of reference parsed log filename
$2 = match_count = number of matching results
$3 = results_expression
$4 = results_category (either 'p', 'n', or something else)
log_compare "$TESTDIR" "1" "SUCCESS" "p"
と記述した場合は,$TESTDIRからログを読み取ってSUCCESSが含まれれば成功とする,という意味になります。
テストの登録
前回のサンプルと同様にFuegoコンテナのコンソールで以下のコマンドを実行して,作成したテストを登録します。
ftc add-jobs -b myboard -t Functional.mytest
上記のコマンドで登録するとspec.jsonのdefaultに記述した内容が有効になります。別のspecを指定したい場合は以下のようにコマンドを実行します。
ftc add-jobs -b mytest -t Functional.mytest -s fail
以上のようにしてシェルスクリプトで作成したテストをFuegoで実行することができます。
FuegoでRaspberry Piをテストする
Fuegoとは
Fuegoとは組み込みLinux用テストフレームワークです。ターゲットにSSH接続してターゲット上でテストを実行し,Jenkinsでテストを管理することができます。
ターゲット上で動作するソフトの単体テストを実行するものではなく,組み込みシステムとしてのテストを実行するものです。
スペイン語で炎という意味です。(炎上しそうですね)
Fuego wikiのArchitectureのページに詳しく載っていますが,
Fuego = (Jenkins + abstraction scripts + pre-packed tests) inside a container
ということでJenkinsとテスト実行のためのスクリプト群,よく使いそうなテストの記述がDockerコンテナとして提供されています。
今回はFuegoのインストールとサンプルのテストの実行までやってみます。
構成
Fuegoの構成は上のような感じになっています。ホストPCからターゲット(ここではRaspberry Pi)に対してSSH接続できるようになっている必要があります。
ホストPCではJenkinsとFuegoのスクリプトが含まれるDockerコンテナが起動し,Jenkinsからテストを実行できます。
テストプログラムはホストPC上でクロスコンパイルされ,Raspberry Piに転送されます。Raspberry Pi上で実行されたテストが標準出力上に結果を返し,この結果によってテストの成功・失敗を判定します。なにをもって成功とするかはテストの設定ファイルに記述します。
テストの設定ファイルはFuegoのインストールディレクトリ内に含まれ,Fuegoのコンテナが起動するときにReadOnlyでコンテナにマウントされます。
開発時には以下のものを操作しながら作業することになります。
- JenkinsのWebUI操作
- Fuegoコンテナのコンソール操作
- Fuegoインストールディレクトリ内のファイル編集
参考情報
公式のWikiを読むとだいたい使えるようになります。
環境
以下の環境を使いました。
- ホストPC : ubuntu 18.04 LTS
- ターゲット : Raspberry Pi 3 (Raspbian Buster Lite)
- Fuego (Commit eeb3c08c...)
Fuegoのインストールとテスト実行
Quick Start Guideに記載されているコマンドを実行していきます。
大まかに以下のような流れです。
- ホストPCにFuegoをインストールする
- Raspberry Piの設定をする
- ホストPCでJenkinsにジョブを登録する
Fuegoのインストール
ホストPC上で以下のコマンドを実行し,必要なパッケージをインストールする。
sudo apt-get update sudo apt-get install git docker.io
ホストPCで以下のコマンドを実行し,Fuegoのリポジトリをクローンする。
git clone https://bitbucket.org/fuegotest/fuego.git
fuegoディレクトリに移動し,インストール用スクリプトを実行する。
(数十分かかります)
cd fuego ./install.sh
インストールが終了したら以下のコマンドを実行する。
./start.sh
start.sh
を実行すると以下のように,Fuegoコンテナのコンソールにアタッチされる。
http://localhost:8090/fuegoを開き,以下のような画面が表示されればOK
Raspberry Pi側の準備
既存のユーザー(piなど)でテストを実行しても良いと思いますが,ここでは以下のコマンドを実行し,ユーザーfuegoを作成します。
sudo su mkdir /home/fuego adduser fuego (fuegoのパスワード等設定)
rootでの接続が必要な場合
テストによってはrootでの実行が必要かもしれません。
rootでのSSH接続が必要な場合は/etc/ssh/sshd_configのPermitRootLogin
をyesに変更し,SSHサーバーを再起動します。
sudo vi /etc/ssh/sshd_config (PermitRootLogin をyesに変更) sudo systemctl restart sshd
ボードファイルの作成
テストの実行対象を設定するボードファイルを作成します。 fuego/fuego-ro/boards内にraspberrypi3.boardがすでにあるので,これをmyboard.boardにコピーして編集します。
cd fuego/fuego-ro/boards cp raspberrypi3.board myboard.board
myboard.boardは以下のように修正しました。(修正箇所のみ載せています)
IPADDRはRaspberry PiのIPアドレス,SRV_IPはホストPCのIPアドレスを設定します。また,PASSWORDはRaspberry Piに作成したユーザーfuegoのパスワードを指定します。
IPADDR="192.168.0.10 SSH_PORT="22" SRV_IP="192.168.0.11" LOGIN="fuego" BOARD_TESTDIR="/home/fuego/" PASSWORD="****" TOOLCHAIN="debian-armhf" #TARGET_SETUP_LINK="fuego-lava-target-setup" #TARGET_TEARDOWN_LINK="fuego-lava-target-teardown"
クロス開発用ツールチェインのインストール
Fuegoコンテナのコンソールで以下のコマンドを実行します。
cd /fuego-ro/toolchains ./install_cross_toolchain.sh
Jenkinsにジョブを登録
Fuegoコンテナのコンソールで以下のコマンドを実行し,Jenkinsにノードを登録します。上で作成したmyboard.boardの内容が登録されます。
ftc add-nodes -b myboard
Fuegoコンテナのコンソールで以下のコマンドを実行し,ジョブを登録します。
ftc add-jobs -b myboard -t Functional.hello_world
テストの実行
Jenkinsでビルドの実行をクリックするとテストがビルドされ,Raspberry Piに転送されたあと実行されます。
Functional.hello_worldはデフォルトでは常に成功します。
FuegoからRaspberry Pi上でテストを実行させることができました。次回はテストの記述方法です。
Raspbian buster-liteでCM3+のeMMCをROM化
前回はRaspberry Pi Compute Module 3+(CM3+)にSDカードを増設しました。 tshell.hatenablog.com
産業用途で使う場合であってもなくても,予期しない電源断によってファイルシステムが飛んで二度と起動しなくなるというのは避けたいところです。
CM3+はeMMCからOSが起動するようになっていますが,今回はeMMCをリードオンリーに設定してみます。
環境
- Raspberry Pi Compute Module 3+
- Compute Module IO Board v3.0
- Raspbian Buster-lite
スワップ領域をSDカードに変更する
メモリが1GBもあるのでスワップなしでもよさそうですが一応スワップありの構成で進めます。以降の作業でeMMCへの書き込みがRAMに保持されるようになるのでスワップファイルがデフォルトの/var/swapのままではスワップの意味をなさなくなります。
前回の記事で増設したSDカード上にスワップファイルを作成し,こちらを使用するようにします。
まずはSDカードをフォーマットして,起動時に/mnt/sdcardにマウントされるようにします。
フォーマット
sudo mkfs -t ext4 /dev/mmcblk1
マウントポイントを作成する
sudo mkdir /mnt/sdcard
UUIDを含む行をfstabに追加しておく
sudo bash lsblk -f | grep mmcblk1 >> /etc/fstab
fstabを編集する
sudo vi /etc/fstab
以下の行を追加する。(あらかじめUUIDを含む行を追加しているのでそのまま利用する。)
UUID=... /mnt/sdcard ext4 defaults 0 2
再起動して/mnt/sdcardにlost+foundがあることを確認します。
スワップファイルの作成とdphys-swapfileの設定
SDカード上に256MBのスワップファイルを作成します。
sudo dd if=/dev/zero of=/mnt/sdcard/swap/swap bs=1M count=256 sudo chmod 600 /mnt/sdcard/swap/swap sudo mkswap /mnt/sdcard/swap/swap
作成したスワップファイルが使えるかテストする
sudo swapoff --all sudo swapon /mnt/sdcard/swap/swap
free -h
を実行してswapのサイズが256MBになっていることを確認します。
/etc/dphys-swapfileを以下のようにコメントアウトを解除して編集します。
CONF_SWAPFILE=/mnt/sdcard/swap/swap CONF_SWAPSIZE=256
再起動後にfree -h
を実行し,swapのサイズが256MBであることを確認します。
以上でスワップファイルを増設したSDカードに移すことができました。
fsprotectをインストールする
続いてeMMCをリードオンリー化していきます。
まずはfsprotectをインストールします。
sudo apt-get install fsprotect
aufs-dkmsのセットアップでエラーが出た場合は,以下のようにpsotinstとpreermを削除してから再度セットアップします。
sudo rm /var/lib/dpkg/info/aufs-dkms.postinst sudo rm /var/lib/dpkg/info/aufs-dkms.prerm sudo apt-get install fsprotect
initramfsイメージの作成
以降のステップで編集するhook-functionsなどのファイルを生成するため,update-initramfsを実行します。
sudo update-initramfs -c -k `uname -r`
上記を実行すると/usr/share/initramfs-tools/hooks/fsprotect
の処理でコケます。
/usr/bin/touchを読みに行くところで失敗しており,/usr/bin/touchは/bin/touchへのシンボリックリンクなので,/usr/share/initramfs-tools/hooks/fsprotectの最終行を以下のように変更します。
copy_exec /bin/touch "/bin"
再度update-initramfsを実行します。
sudo update-initramfs -c -k `uname -r`
hook-functionsの修正
以下のページを参考に,hook-functionsを修正します。
https://www.raspberrypi.org/forums/viewtopic.php?t=161416
/usr/share/initramfs-tools/hook-functionsの524行目付近を以下のように編集します。
(modulesの最後にoverlayを追加する)
for arg in "$@" ; do case "$arg" in base) modules="$modules btrfs ext2 ext3 ext4 ext4dev overlay"
新しいbootスクリプトを作成する
既存のスクリプトをコピーしてoverlayファイルシステムを使用するように変更します。
cd /usr/share/initramfs-tools/scripts sudo cp local overlay sudo cp -rp local-premount overlay-premount sudo cp -rp local-bottom overlay-bottom
/usr/share/initramfs-tools/scripts/overlayのlocal_mount_root()の中を以下のように修正します。
ここに示した行でlocal_mount_root()が終わるようにします。
# if [ "${readonly}" = "y" ]; then roflag=-r # else # roflag=-w # fi # FIXME This has no error checking modprobe ${FSTYPE} checkfs ${ROOT} root # FIXME This has no error checking # Mount root mkdir /upper /lower if [ "${FSTYPE}" != "unknown" ]; then mount ${roflag} -t ${FSTYPE} ${ROOTFLAGS} ${ROOT} /lower else mount ${roflag} ${ROOTFLAGS} ${ROOT} /lower fi modprobe overlay mount -t tmpfs tmpfs /upper mkdir /upper/data /upper/work mount -t overlay \ -olowerdir=/lower,upperdir=/upper/data,workdir=/upper/work \ overlay ${rootmnt}
initramfsの作成
以下のコマンドを実行し,initramfsを作成してリネームします。
sudo update-initramfs -c -k `uname -r` sudo mv /boot/initrd.img-4.19.66-v7+ /boot/initrd7.img
/boot/config.txt,/boot/cmdline.txtの編集
/boot/config.txtに以下を追記します。
kernel=kernel7.img initramfs initrd7.img
/boot/cmdline.txtの先頭にboot=overlay
を追加して再起動します。
動作確認
再起動後,mount
を実行し,overlay on / type overlay
のような行があればルートファイルシステムがoverlay fs でマウントされています。
適当なファイルを作成して,再起動すると消えることを見てみましょう。
参考にしたページはRaspbian Pixelを使っていましたが,Raspbian busterでも以前と同様にして実現できるようです。