GPUを使っているdockerコンテナの見つけ方

## 前振り
複数人で1つのGPUサーバを使っている。
実験環境の棲み分けはdockerで出来ているのだが、
物理的なリソースはどうしても ~~奪い合い~~ 競合が発生する。

特にGPU。

GPU使いたいなー、と思っても誰かのコンテナが既に使っている。
1時間後も使われている。3時間後も使われている。
5時間後も使われている。翌日になっても誰かのコンテナがGPUを使っている。
~~いい加減にしろよｵﾗｧﾝ~~  

どのコンテナがどのGPUを使っているのか、を見つけたくなったので調べてみた。

## 環境
```
Ubuntu 16.04.3 LTS
Docker version 19.03.1
```
dockerは `17.12.0-ce` でもいける。

## step1:使われているGPUを見つけよう

`nvidia-smi` を使う。

```
~$ nvidia-smi
Wed Oct 30 14:59:19 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.67       Driver Version: 418.67       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla K40c          On   | 00000000:04:00.0 Off |                    0 |
| 29%   62C    P0   143W / 235W |  10963MiB / 11441MiB |     44%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla K40c          On   | 00000000:05:00.0 Off |                    0 |
| 31%   65C    P0   174W / 235W |  11025MiB / 11441MiB |     98%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Tesla K40c          On   | 00000000:08:00.0 Off |                    0 |
| 23%   23C    P8    20W / 235W |      0MiB / 11441MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

(略)

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0     14679      C   python3                                    10950MiB |
|    1     40974      C   /usr/bin/python3                           11010MiB |
+-----------------------------------------------------------------------------+
```

こんな感じでGPUを使用しているプロセスは見ることができる。
が、どのコンテナが使用しているのかまでは分からない。

## step2:稼働しているdockerコンテナを知ろう
`docker ps` で見る。

```
~$ docker ps
CONTAINER ID        IMAGE                                       COMMAND             CREATED             STATUS              PORTS                                            NAMES
370c2888bfe1        tensorflow/tensorflow:1.13.1-gpu-py3        "/bin/bash"         7 weeks ago         Up 7 weeks          0.0.0.0:8888->8888/tcp                           test01
5a0bb9435b77        tensorflow/tensorflow:latest-gpu-py3        "/bin/bash"         3 weeks ago         Up 3 weeks          0.0.0.0:9999->8888/tcp                           practice
```

こんな感じで稼働しているコンテナは見ることができる。
が、どのコンテナがどのプロセスを使用しているのかまでは分からない。


## step3:コンテナのプロセスIDを見つけよう
`/proc/[プロセスID]/cgroup` を見る。

```
~$ cat /proc/40974/cgroup
11:cpuset:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
10:memory:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
9:perf_event:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
8:pids:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
7:freezer:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
6:blkio:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
5:devices:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
4:cpu,cpuacct:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
3:net_cls,net_prio:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
2:hugetlb:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
1:name=systemd:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
```
こんな感じで謎の文字列が並んでいるが、 `/docker/` 以降はコンテナIDになっている。
先頭12桁は `docker ps` コマンドで照会できる値と同じ。

```
1:name=systemd:/docker/370c2888bfe1c309f91c4d1d77d50cda72cfc33d29602ed23c22f6c28692db55
                       └─この部分─┘
```
と

```
CONTAINER ID        IMAGE                                       COMMAND             CREATED             STATUS              PORTS                                            NAMES
370c2888bfe1        tensorflow/tensorflow:1.13.1-gpu-py3        "/bin/bash"         7 weeks ago         Up 7 weeks          0.0.0.0:8888->8888/tcp                           test01
└─この部分─┘
```
が対応しているのが分かる。


ここまでくればあとは簡単。

## step4:プロセスIDとコンテナIDとGPU-Noを紐づけよう

今までのstepを組み合わせるだけ。

1. `nvidia-smi` でGPUを使っているプロセスIDを取得
2. `/proc/[プロセスID]/cgroup` を見てGPUを使っているプロセスIDを使っているコンテナIDを取得
3. `docker ps` を使ってコンテナIDから該当するコンテナを見つける
4. ~~GPUを占有しているコンテナの管理者にGPU使用権を賭けて決闘を申し込む~~

以上。

## (おまけ)上記の紐づけ作業をshell化してみる
とりあえず見られれば良いや、という人向け。
エラー回避は一切考慮してないのでご了承ください。
多分もっとスマートでエレガントなやり方があるとは思う。

```bash:sample.sh
#!/bin/bash

ids_ary=() # プロセスID記録用の空配列

# nvidia-smiでGPUを使用しているプロセスIDを取得
for proc_id in `nvidia-smi | grep "MiB |$" | awk '{print $3}'`
do

  # PIDが記録配列に存在するか
  if echo "${ids_ary[@]}" | grep -q "${proc_id}"; then
    # 存在したら何もしない
    continue
  else
    # 存在しなければそのプロセスが動いているコンテナ情報(ID, 名称)を取得
    cnt_inf=`docker ps --format "{{.ID}} {{.Names}}" | grep \`awk -F/ 'END {print(substr($3,1,8))}' /proc/${proc_id}/cgroup\``

    # nvidia-smiっぽく出力してみる
    echo "+-----------------------------------------------------------------------------+"
    echo "| Processes:                                                       GPU Memory |"
    echo "|  GPU      PID    Type   Process name                             Usage      |"
    echo "|  CONTAINER ID           NAMES                                               |"
    echo "|=============================================================================|"
    nvidia-smi | awk -v prc="${proc_id}" '$3 == prc {print $0}'
    printf "|  %-12s           %-51s |\n" ${cnt_inf[0]} ${cnt_inf[1]}
    echo "+-----------------------------------------------------------------------------+"
    echo
  fi

  # 出力済みPIDを記録配列に追加
  ids_ary+=(${proc_id})
done

exit 0
```

出力は↓こんな感じになる。

```
~$ bash sample.sh
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU      PID    Type   Process name                             Usage      |
|  CONTAINER ID           NAMES                                               |
|=============================================================================|
|    0     14679      C   python3                                    10950MiB |
|  5a0bb9435b77           practice                                            |
+-----------------------------------------------------------------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU      PID    Type   Process name                             Usage      |
|  CONTAINER ID           NAMES                                               |
|=============================================================================|
|    1     40974      C   /usr/bin/python3                           11010MiB |
|  370c2888bfe1           test01                                              |
+-----------------------------------------------------------------------------+

```

1つのコンテナ(プロセス)が複数のGPUを使っているときに出力が重複しないようにしてみた。
その時は↓のような表示になる。

```
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU      PID    Type   Process name                             Usage      |
|  CONTAINER ID           NAMES                                               |
|=============================================================================|
|    1     40974      C   /usr/bin/python3                           11010MiB |
|    2     40974      C   /usr/bin/python3                           11010MiB |
|  370c2888bfe1           test01                                              |
+-----------------------------------------------------------------------------+
```

コンテナの中で複数のプロセス(プログラム)が動いている場合はホスト側でもプロセスIDが別になるので、
各プロセスが別のGPUを使っている場合は、同じコンテナの名前が何回も出てくることになる。
その辺りをまとめて表示したり、見せ方を変えたりもしたかったけどこの辺で~~妥協~~。


## 参考
[PID から Docker の container id を知る方法](https://eagletmt.hateblo.jp/entry/2016/02/28/102842)