cargo-flamegraph を Docker を利用して Mac 上で使う

以前に[cargo-profiler を Dockerを使ってMac上で実行する記事](https://qiita.com/gobi_tk/items/494e9b24fb7cbee28a8c)を書いたのですが、数ヶ月前に試したら上手く動かせなくなっていました[^1]。
メンテナンス状況もたまに自動ライブラリバージョンアップが入るだけ、になっているようだったので乗り換えを考えていたところ、cargo-flamegraph というプロファイラツールを見かけたのでこれを採用しました。

cargo-flamegraph も cargo-profiler 同様、Mac では動かなそうだったので、実行する上で Docker を使用しました。その手順をまとめています。

# 手順
## Dockerイメージのビルド
```
$ docker build -t flamegraph .
```

コマンドを、下記の Dockerfile を設置したディレクトリで実行する。


```Dockerfile:Dockerfile
FROM debian:buster-slim

ENV RUSTUP_HOME=/usr/local/rustup \
    CARGO_HOME=/usr/local/cargo \
    PATH=/usr/local/cargo/bin:$PATH \
    RUST_VERSION=1.68.2

RUN set -eux; \
    apt-get update; \
    apt-get install -y --no-install-recommends \
        ca-certificates \
        gcc \
        libc6-dev \
        wget \
        ; \
    dpkgArch="$(dpkg --print-architecture)"; \
    case "${dpkgArch##*-}" in \
        amd64) rustArch='x86_64-unknown-linux-gnu'; rustupSha256='bb31eaf643926b2ee9f4d8d6fc0e2835e03c0a60f34d324048aa194f0b29a71c' ;; \
        armhf) rustArch='armv7-unknown-linux-gnueabihf'; rustupSha256='6626b90205d7fe7058754c8e993b7efd91dedc6833a11a225b296b7c2941194f' ;; \
        arm64) rustArch='aarch64-unknown-linux-gnu'; rustupSha256='4ccaa7de6b8be1569f6b764acc28e84f5eca342f5162cd5c810891bff7ed7f74' ;; \
        i386) rustArch='i686-unknown-linux-gnu'; rustupSha256='34392b53a25c56435b411d3e575b63aab962034dd1409ba405e708610c829607' ;; \
        *) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; \
    esac; \
    url="https://static.rust-lang.org/rustup/archive/1.25.2/${rustArch}/rustup-init"; \
    wget "$url"; \
    echo "${rustupSha256} *rustup-init" | sha256sum -c -; \
    chmod +x rustup-init; \
    ./rustup-init -y --no-modify-path --profile minimal --default-toolchain $RUST_VERSION --default-host ${rustArch}; \
    rm rustup-init; \
    chmod -R a+w $RUSTUP_HOME $CARGO_HOME; \
    rustup --version; \
    cargo --version; \
    rustc --version; \
    apt-get remove -y --auto-remove \
        wget \
        ; \
    rm -rf /var/lib/apt/lists/*;

RUN apt update \
    && apt install -y linux-perf

RUN cargo install flamegraph \
    && chmod -R a+w $CARGO_HOME
```

（このファイルは、https://github.com/rust-lang/docker-rust/tree/61db4471f1ef23dacd217f4a67841cc09aa05858/1.68.2 の末尾に

```Dockerfile
RUN apt update \
    && apt install -y linux-perf

RUN cargo install flamegraph \
    && chmod -R a+w $CARGO_HOME
```

を書き加えたものになります。）

## perf_event_open が実行できない問題の解消
この時点で flamegraph を実行しても、

```
perf_event_open(..., PERF_FLAG_FD_CLOEXEC) failed with unexpected error 1 (Operation not permitted)
perf_event_open(..., 0) failed unexpectedly with error 1 (Operation not permitted)
Error:
No permission to enable cycles:u event.
```

のようなエラーが出て、異常終了してしまいます。
[こちらの記事](https://medium.com/@geekidea_81313/running-perf-in-docker-kubernetes-7eb878afcd42)によると、root 権限を持っていないために起きているらしいです。

今回は、同記事内に記載されている選択肢1の「'seccomp'を少しカスタマイズする」対処方法を取りました。手順は下記になります。

1. [seccomp のデフォルト設定ファイル](https://github.com/moby/moby/blob/master/profiles/seccomp/default.json)をダウンロード。`my_seccomp.json` という名前で保存。
1. `my_seccomp.json` の `syscalls` フィールドの配列に `perf_event_open` を書き加える。
1. `$ docker run` 実行時に `-security-opt seccomp=./my_seccomp.json` のコマンドを付けて、seccomp ファイルとして設定する。

※セキュリティリスクを増す可能性がありそうです。実施は自己責任でお願いします。

## プロファイル実行
下記を実行すると、`cargo run --release` を実行した場合のプロファイル結果が得られます。

```sh
$ docker run --rm --user "$(id -u)":"$(id -g)" \
    --security-opt seccomp=./my_seccomp.json \
    -v /your/cargo/project/path:/workspace \
    -w /workspace \
    flamegraph \
    cargo flamegraph
```

（`/your/cargo/project/path` には、プロファイル対象のcargoプロジェクトのルートディレクトリを記載してください。）


ただ、自分の主な用途[^2]では標準入出力のリダイレクトがしたくなるので、下記のようにコマンドを自由に表記できるオプションで使用しています。

```sh
$ docker run --rm --user "$(id -u)":"$(id -g)" \
    --security-opt seccomp=./my_seccomp.json \
    -v /your/cargo/project/path:/workspace \
    -w /workspace \
    flamegraph \
    bash -c "cargo flamegraph -- cargo run --release --bin a < in/0000.txt > out/0000.txt"
```

(`in/0000.txt` と `out/0000.txt` が、入出力ファイルに当たります。)

実行すると、`flamegraph.svg` と　`perf.data` というファイルが吐かれます。
`flamegraph.svg` が結果を図示した画像で、以下のようなものになります（`perf.data` の方はシステムデータ的なもののようなので気にしなくて良さそうです）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199002/45b97e43-073c-659d-a50d-0ea3c0b4c5b8.png)

関数ごとの実行時間がグラフの長さで表現されています。見やすくて良いですね。


[^1]: うろ覚えですが、出力表示が空、みたいな症状だった記憶です。
[^2]: 競技プログラミングのマラソンマッチという分野で主に使っています。この手のコンテストでは、入力を標準入力から受け取る形式が多いです。