[Ruby]Selenium::WebDriverでHeadless Chromeを起動したときのChromeのオプション設定

`Selenium::WebDriver`を使ってHeadless Chromeを起動したときのオプション設定について調べてみました。

## Selenium::WebDriverでHeadless Chromeを起動するには
通常のChromeとの違いは、オプション設定に`--headless`を渡すことだけです。

```ruby
require 'selenium-webdriver'

options = Selenium::WebDriver::Chrome::Options.new
options.add_argument('--headless')
@driver = Selenium::WebDriver.for :chrome, options: options
@driver.navigate.to "https://www.google.com/"
```

## Selenium::WebDriverからHeadless Chromeを起動するとどんなプロセスが生成されているか？
上記のコードを実行すると、

- chromedriver
- google-chrome webdriver
- chrome zygote   x2
- chrome gpu-process
- chrome broker
- chrome renderer

という6種類7つのプロセスが生成されます。（`zygote`プロセスは2つ生成されていました）
PID値を見る限り、この順番で生成されているようです。

## 各プロセス生成時のオプション設定
### chromedriver
`chromedriver`は以下のコマンドで起動されています。

```c
chromedriver --port=9515
```

オプション設定はポート指定のみです。

### google-chrome webdriver
`google-chrome`は`chrome`のラッパーとのこと。

Difference between “/opt/google/chrome/chrome” and “/opt/google/chrome/google-chrome” on Fedora 22
https://superuser.com/questions/975050/difference-between-opt-google-chrome-chrome-and-opt-google-chrome-google-ch

以下のように`google-chrome`コマンドに大量のオプションが渡されています。

```c
/opt/google/chrome/google-chrome --disable-background-networking --disable-client-side-phishing-detection --disable-default-apps --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --disable-web-resources --enable-automation --enable-logging --force-fieldtrials=SiteIsolationExtensions/Control --headless --ignore-certificate-errors --load-extension=/tmp/.org.chromium.Chromium.YZWaub/internal --log-level=0 --metrics-recording-only --no-first-run --password-store=basic --remote-debugging-port=0 --test-type=webdriver --use-mock-keychain --user-data-dir=/tmp/.org.chromium.Chromium.tVOWvT data:,
```

オプション設定の内容を表にまとめました。
実に23個のオプションが設定されています。

| options                                                    | 内容                                       |
|:-----------------------------------------------------------|:-------------------------------------------|
| disable-background-networking                              | 複数のサブシステムの動作オフ               |
| disable-client-side-phishing-detection                     | フィッシング検知機能オフ                   |
| disable-default-apps                                       | デフォルトアプリ設定オフ                   |
| disable-hang-monitor                                       | ページの停止監視機能オフ                   |
| disable-popup-blocking                                     | ポップアップブロック機能オフ               |
| disable-prompt-on-repost                                   | POSTリクエストの結果確認機能オフ           |
| disable-sync                                               | ブラウザデータとGoogleアカウントの同期オフ |
| disable-web-resources                                      | Webリソースのためのバックエンド機能オフ    |
| enable-automation                                          | 自動テスト中であることの通知機能オン       |
| enable-logging                                             | コンソールログ機能オン                     |
| force-fieldtrials=SiteIsolationExtensions/Control          | fieldtrialsオプションの設定                |
| headless                                                   | ヘッドレスChromeとして起動                 |
| ignore-certificate-errors                                  | 認証関連エラー無視                         |
| load-extension=/tmp/.org.chromium.Chromium.YZWaub/internal | 拡張機能へのパス設定                       |
| log-level=0                                                | ログレベルをINFOに設定                     |
| metrics-recording-only                                     | メトリックスの通知オフ、記録のみオン       |
| no-first-run                                               | First runタスク　オフ                      |
| password-store=basic                                       | パスワード用暗号化ストレージをbasicに指定  |
| remote-debugging-port=0                                    | ポート0のリモートデバッグ　オン            |
| test-type=webdriver                                        | テストに使うツールをwebdriverに設定        |
| use-mock-keychain                                          | macOS用の必須設定（詳細不明）              |
| user-data-dir=/tmp/.org.chromium.Chromium.tVOWvT           | ユーザープロフィールの保存場所指定         |
| data:,                                                     | user-data-dirのオプション？                |

`test-type=webdriver`とあるので、このプロセスと勝手に`webdriver`と呼ぶことにしました。
主にテストを実施する際に邪魔な機能をオフする目的のオプション設定になっていると思います。

### chrome zygote
`webdriver`のプロセス生成に続いて`Chrome`の一部である`zygote`というプロセスが２つ立て続けに生成されています。

`chrome`コマンドのオプションを見ると`--headless`オプションが重複していました。わざわざ渡さなくても良かった、というわけではないでしょうが、理由はわかりません。

```c
/opt/google/chrome/chrome --type=zygote --enable-logging --headless --log-level=0 --headless --enable-crash-reporter
```

| options               | 内容                                 |
|:----------------------|:-------------------------------------|
| type=zygote           | プロセスのタイプ表示のための名前設定 |
| enable-logging        | コンソールログ機能オン               |
| headless              | ヘッドレスChromeとして起動           |
| log-level=0           | ログレベルをINFOに設定               |
| enable-crash-reporter | ヘッドレスのためのクラッシュ通知オン |

https://chromium.googlesource.com/chromium/src/+/HEAD/docs/linux_zygote.md

> A zygote process is one that listens for spawn requests from a master process and forks itself in response. Generally they are used because forking a process after some expensive setup has been performed can save time and share extra memory pages.

`zygote`プロセスは時間短縮や省メモリを実現するための仕組みとして使われているとのこと。またChrome起動中に一部のライブラリがアップデートされたときの泣き別れ対策としても動作しているようです。
`Selenium`関連というより、`Chrome`の動作に必要なプロセスということでしょう。

### chrome gpu-process
次は`gpu-process`というプロセスが生成されます。

```c
/opt/google/chrome/chrome --type=gpu-process --enable-logging --headless --log-level=0 --headless --enable-crash-reporter --gpu-preferences=KAAAAAAAAACAAABAAQAAAAAAAAAAAGAAAAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAA --use-gl=swiftshader-webgl --override-use-software-gl-for-tests --headless --enable-crash-reporter --enable-logging --log-level=0 --service-request-channel-token=10264762006126535026
```

| options                                            | 内容                                           |
|:---------------------------------------------------|:-----------------------------------------------|
| type=gpu-process                                   | プロセスのタイプ表示のための名前設定           |
| enable-logging                                     | コンソールログ機能オン                         |
| headless                                           | ヘッドレスChromeとして起動                     |
| log-level=0                                        | ログレベルをINFOに設定                         |
| enable-crash-reporter                              | ヘッドレスのためのクラッシュ通知オン           |
| gpu-preferences=KAAAAAAAAACAAABAA...               | GPU情報の設定？                                |
| use-gl=swiftshader-webgl                           | GPUが使うGLをSwiftShaderに設定                 |
| override-use-software-gl-for-tests                 | ソフトウェアGLを使用するよう強制的に設定       |
| service-request-channel-token=1026476200612... | 子プロセスでのメッセージパイプのためのトークン |

`chrome`コマンドを見ると14個のオプションが設定されていますが、重複が多く、実際には上記表のように9種類のオプションが渡されています。

内容は`gpu-process`という名前から推測される通り、GPU関連の設定を行っています。というより、GPUを使わずにソフトウェアGLを使うように強制的に設定しています。

### chrome broker
`broker`プロセスはタイプの指定を行っているだけで、それ以外のオプションはありません。

```c
/opt/google/chrome/chrome --type=-broker
```

Chromiumのプロセス構成と Worker/SharedWorker/ServiceWorkerのうごき
https://qiita.com/amiq11/items/61cf100e5f9fac8533b6

> 「ブラウザ」、「GPUプロセス」という２つのプロセスに加え、おおよそそれぞれのタブが別のプロセスに割り振られていることがわかります。このタブごとのプロセスを「レンダラプロセス」といいます。
> たとえばa.comを開いたとすると、ブラウザプロセスがレンダラプロセスをつくり、ネットワークリクエストがブラウザプロセスへ投げられ（１）、ブラウザプロセスがネットワークにリクエストを投げ（２）、レスポンスがブラウザプロセスに帰ってきて（３）、それをレンダラになげる（４）、という手順になります。

https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md

> In Chromium, the broker is always the browser process. The broker, is in broad terms, a privileged controller/supervisor of the activities of the sandboxed processes.

`broker`プロセスはブラウザプロセスとしてレンダラプロセスを作るコントローラの役目を果たしているとのこと。

### chrome renderer
最後に`renderer`プロセスが生成されます。`renderer`プロセスはいわゆるタブに相当するものなので、タブが3つあれば3つの`renderer`プロセスが生成されます。

`chrome`コマンドは以下の通り。これも大量のオプションが渡されています。

```c
/opt/google/chrome/chrome --type=renderer --enable-automation --enable-logging --log-level=0 --test-type=webdriver --use-gl=swiftshader-webgl --disable-gpu-compositing --service-pipe-token=4567683379984847303 --lang=en-US --headless --enable-crash-reporter --num-raster-threads=1 --service-request-channel-token=4567683379984847303 --renderer-client-id=4 --shared-files=v8_context_snapshot_data:100,v8_natives_data:101
```

| options                                                       | 内容                                 |
|:--------------------------------------------------------------|:-------------------------------------|
| type=renderer                                                 | プロセスのタイプ表示のための名前設定 |
| enable-automation                                             | 自動テスト中であることの通知機能オン |
| enable-logging                                                | コンソールログ機能オン               |
| log-level=0                                                   | ログレベルをINFOに設定               |
| test-type=webdriver                                           | テストに使うツールをwebdriverに設定  |
| use-gl=swiftshader-webgl                                      | GPUが使うGLをSwiftShaderに設定       |
| disable-gpu-compositing                                       | GPUの使用をオフ                      |
| service-pipe-token=4567683379984847303                        | メッセージパイプを使うためのトークン |
| lang=en-US                                                    | 言語をen-USに設定                    |
| headless                                                      | ヘッドレスChromeとして起動           |
| enable-crash-reporter                                         | ヘッドレスのためのクラッシュ通知オン |
| num-raster-threads=1                                          | ラスタ変換に使うスレッド数を1に設定  |
| service-request-channel-token=4567683379984847303             | メッセージパイプを作るためのトークン |
| renderer-client-id=4                                          | ???                                  |
| shared-files=v8_context_snapshot_data:100,v8_natives_data:101 | 子プロセスへ渡すファイル記述子の設定 |

重複なく15個のオプションが設定されています。子プロセスとのやりとりに必要となるオプションがいくつか設定されていることが特徴的です。そしてこのプロセスが最も多くのメモリを使います。

## まとめ
以上が`Selenium::WebDriver`を使ってHeadless Chromeを起動した際に生成されるプロセスとそのプロセスに設定されるオプションです。
ブラウザの動作は奥が深いですが、なんとなく各プロセスの役割分担がオプション設定の内容を通してイメージできてきたように思います。

## おまけ　各プロセス生成とSelenium::WebDriverのコードとの対応関係について
各プロセスとそれを生成している`Selenium::WebDriver`のコードの対応関係を以下のようになっていると思われます。

| PID | %MEM | RSS    |  type         | codes                                                       |
|:--- |:---- |:------ | ------------ | ----------------------------------------------------------- |
| 046 | 1.0  | 10664  |  chromedriver | @driver = Selenium::WebDriver.for :chrome, options: options |
| 055 | 9.2  | 93320  |  webdriver    | @driver = Selenium::WebDriver.for :chrome, options: options |
| 063 | 4.2  | 43152  |  zygote       | @driver = Selenium::WebDriver.for :chrome, options: options |
| 065 | 1.0  | 11088  |  zygote       | @driver = Selenium::WebDriver.for :chrome, options: options |
| 084 | 5.4  | 54848  |  gpu-process  | @driver = Selenium::WebDriver.for :chrome, options: options |
| 092 | 1.2  | 12712  |  broker       | @driver = Selenium::WebDriver.for :chrome, options: options |
| 101 | 13.8 | 139744 |  renderer     | @driver.navigate.to "https://www.google.com/"               |

メモリ使用量は`renderer`が最も多く、次が`webdriver`となっていました。

## 余談
Headless Chromeを起動させる処理を繰り返しているうちにメモリエラーを起こしてサーバーがダウンしてしまい、メモリ容量を圧迫している原因を探っている過程で今回のオプション設定の調査を行いました。

メモリエラーの原因は`@driver.quit`が抜けていたという初歩的ミスでした。
`Selenium::WebDriver`を正常に終了させないまま繰り返しHeadless Chromeを起動していたため、上記のプロセスがひたすら重複して生成されてメモリを使いまくっていました。反省。