クロス開発環境でperf

# はじめに
組み込みの現場では、リソースの制約からデバッグシンボルが`strip`された実行ファイルが実機にインストールされていることが多いです。
結果として、`perf top`を実行してもどの関数を実行中なのか基本的には解決できず、アドレスで表示されてしまいます。

`perf`コマンドはそのような環境のために、実機で保存したprofile情報を、PCでデバッグシンボルとともに解析するオフライン解析の仕組みが用意されています。

本記事では、QEMU環境を例にクロス環境で`perf`を使いオフライン解析をする流れを、備忘メモとして残しておきます。

今回使った環境は以下のとおりです。

- buildroot : 2020.02.3
- QEMU: 4.2.0
- OS (host) : Ubuntu 18.04.4
- linux kernel (host) : 5.3.0-62-generic
- perf (host) : 5.3.18
- linux kernel (target) : 4.19.91
- perf (target) : 4.19.91

# 実験環境構築

`buildroot`を使って、`aarch64`のQEMU環境を用意します。
（いつからかQEMUもビルドしてくれるようになったので、構築がさらに楽になってます。ありがたや。）

```shell-session
wget https://buildroot.org/downloads/buildroot-2020.02.3.tar.bz2
tar xf buildroot-2020.02.3.tar.bz2 
cd buildroot-2020.02.3/

cat <<EOS >> configs/qemu_aarch64_virt_defconfig
BR2_ENABLE_DEBUG=y
BR2_PACKAGE_LINUX_TOOLS_PERF=y
EOS

cat <<EOS >> board/qemu/aarch64-virt/linux.config
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_KERNEL=y
EOS

make qemu_aarch64_virt_defconfig
make BR2_JLEVEL=$(nproc)
export PATH="$PWD/output/host/bin/:$PATH"
```

いくつか今回の実験用に、configを有効にしています。

- Buildrootのconfig
  - `BR2_ENABLE_DEBUG`: 各パッケージのビルド時に、デバッグシンボルを有効にします。`staging`以下にデバッグシンボル付きのバイナリを保存します。
  - `BR2_PACKAGE_LINUX_TOOLS_PERF`: `perf`コマンドを追加します。

- Linuxのconfig
  - `CONFIG_DEBUG_INFO`: vmlinuxをデバッグシンボル付きでビルドします。
  - `CONFIG_DEBUG_KERNEL`: kernelのデバッグ機能を有効にします。`CONFIG_DEBUG_INFO`を有効にするために必要です。

[readme.txt](https://github.com/buildroot/buildroot/blob/2020.02.3/board/qemu/aarch64-virt/readme.txt)のとおり、下記コマンドでビルドした仮想環境を立ち上げることができます。

```shell-session
qemu-system-aarch64 -M virt -cpu cortex-a53 -nographic -smp 1 -kernel output/images/Image -append "rootwait root=/dev/vda console=ttyAMA0" -netdev user,id=eth0 -device virtio-net-device,netdev=eth0 -drive file=output/images/rootfs.ext4,if=none,format=raw,id=hd0 -device virtio-blk-device,drive=hd0
```

# `perf record`: プロファイル情報の取得

`perf record`コマンドでプロファイル情報を取得できます。

取得するプロファイル情報はオプションによって色々と変更できますが、
ここでは`-a`オプションでシステム全体のプロファイルを取得します。
また、`--call-graph fp`オプションで、backtrace情報を併せて取得しています。
`perf`はbacktraceの算出方法をいくつかサポートしていますが、ここではframe pointerを使た方式を指定しています。

```shell-session
[On Target (QEMU)]
perf record -a --call-graph fp
```

計測が完了したらCtrl-Cで終了します。
するとカレントディレクトリに`perf.data`が保存されているので、これを解析するためホストPCに転送します。
ネットワーク転送など方法は色々ありますが、今回はQEMUなのでディスクイメージをそのまま覗きます。

```
[On Host PC]
cd output/images
mkdir -p mnt
sudo mount -t ext4 rootfs.ext2 mnt
sudo cp mnt/root/perf.data perf.data
sudo umount mnt
cd ../../
```

なお、多少負荷がかかっていないと見ても面白くないので、とりあえず計測中にseqを回しました。

```shell_session
[On Target (QEMU)]
seq 100000 > /dev/null
```

# `perf report`: 取得したプロファイル情報の解析

kernelのシンボルは`-k`オプションで、ユーザ空間のシンボルは`--symfs`で読み込むことができます。
~~確認した限り、kernelのシンボルとユーザ空間のシンボルを同時に読み込むことはできないようです。
少し手間ですが、都度切り替える必要がありそうです。~~
(2020/08/10追記) `-k`オプションに対して絶対パスでkernelイメージを指定することで、kernelのシンボルとユーザ空間のシンボルを同時に読み込むことができます。

```
[On Host PC]
perf report -i output/images/perf.data --symfs output/staging/ -k ${PWD}/output/build/linux-4.19.91/vmlinux 
```

![Screenshot from 2020-08-10 19-26-09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37714/3b8cc4d7-2b8d-3968-e146-047e028201c7.png)


`seq`として起動された`busybox`の`seq_main`から呼び出された`printf`で時間がかかっていることが読み取れます。
さらに`a`を押下すると、命令・行レベルでどこに時間がかかったかを表示することができます。(`perf annotate`相当)

`[k]`となっているのがkernel空間で、`[.]`となっているのがユーザ空間です。
例えばkernelで時間を使っているのは、`swapper`コンテキストの`arch_cpu_idle`であることが読み取れます。

なお、クロス環境でなければ`-F srcline`や`-F srcfile`などで、対応する行番号やファイル名も表示することができますが、
内部で`addr2line`が呼び分けられていないのか、今回の環境では表示できませんでした。
とはいえプロファイル情報全体に対して`addr2line`を実行するとかなり時間がかかり使い勝手が良くないので、
実際に行番号・ファイル名が必要になったときだけ別途`addr2line`を呼び出す、というのはどちらにせよ現実的なやり方だと思います（言い訳）。


# 参考
[Perf Wiki](https://perf.wiki.kernel.org/index.php/Main_Page)
[Profiling your Applications using the Linux Perf Tools](https://www.slideshare.net/emBO_Conference/profiling-your-applications-using-the-linux-perf-tools)
[Buildroot - Making Embedded Linux Easy](https://buildroot.org/)