Datadog Process Checks の exact_match とは何者か

# これはなんですか

- Datadog Agent 設定でプロセス監視をするときにたまに使う `exact_match` キーについて調べたのでメモとして残しておく
- これを読めば、どんなときに `exact_match: false` を設定すれば良いかが分かるようになるはず

# 先に結論

- `exact_match: true` のときはプロセス名を検索するが、何かしらのプログラム上でデーモンとして動いている場合に検索がうまく効かないことがある
- ruby だったり java だったりのデーモンとして動いているプロセスについては、`exact_match: false` を設定した上でマッチング文字列にプロセス起動時のコマンドの一部を指定する
    - ex) td-agent は ruby のデーモンとして動作するので、コマンドを指定する必要がある
- プロセス起動時のコマンドは `ps -ef` で確認できる。また、各プロセスのプロセス名は `ps -ef c` で確認できる

# 背景

- Datadog で td-agent のプロセス監視をするとき、多くの場合は `exact_match: false` という項目が必要となる
- なんでだろうと思って気になったので調べてみた。

# 前提: Datadog における Process Checks

https://docs.datadoghq.com/ja/integrations/process/

Datadog Agent の Config に Process Check を設定することで、特定プロセスの死活状況やプロセス数、プロセスのリソース使用状況が取得できるようになる。

# `exact_match` を調べてみた

## ドキュメントを読む

Process Check では、いわゆるプロセス名をプロセス一覧から検索してPIDを取得し、各種メトリクスを収集する。 `exact_match` はプロセス一覧の検索に関わるオプションで、ドキュメント内では次のように説明されている: 

> By setting exact_match to False in your yaml file, the agent looks at the command used to launch your process and recognizes every process that contains your keywords.

つまり、`exact_match: false` とすることで、検索対象をプロセス名ではなくプロセス起動時のコマンドに変えることができる。

## Datadog Agent の コードを読む

実際にどんな処理が行われているかを知るため、Datadog Agent のコードを読んでみた。すると、次の様に `psutil.Process()` を使ってプロセス名を検索していることがわかった。

https://github.com/DataDog/integrations-core/blob/master/process/datadog_checks/process/process.py#L114

```python
        for proc in psutil.process_iter():
            # Skip access denied processes
            if not refresh_ad_cache and proc.pid in self.ad_cache:
                continue

            found = False
            for string in search_string:
                try:
                    # FIXME 8.x: All has been deprecated
                    # from the doc, should be removed
                    if string == 'All':
                        found = True
                    if exact_match:
                        if os.name == 'nt':
                            if proc.name().lower() == string.lower():
                                found = True
                        else:
                            if proc.name() == string:
                                found = True

                    else:
                        cmdline = proc.cmdline()
                        if os.name == 'nt':
                            lstring = string.lower()
                            if re.search(lstring, ' '.join(cmdline).lower()):
                                found = True
                        else:
                            if re.search(string, ' '.join(cmdline)):
                                found = True
```

コードから、`exact_match` が True の場合は `psutil.Process().name()` の値が、False の場合は `psutil.Process().cmdline()` の値が使われていることがわかる。

## エージェントのプロセス名

当初の疑問に戻って、 td-agent のプロセス監視にはなぜ `exact_match: false` が必要なのかを考える。

これまで調べたことから、td-agent のプロセス名 (`psutil.Process.name()` の値) はおそらく `td-agent` ではないことが推測される。詳しく調べたところ、td-agentはRuby製のプログラムで、Rubyのデーモンとして起動するらしい。実際、`ps-ef` 出力を見ると、rubyプログラムとして起動していた。(`c` オプションを渡すとよりわかりやすい。)

```
$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD

...

td-agent 15397     1  0 07:06 ?        00:00:00 /opt/td-agent/embedded/bin/ruby /usr/sbin/td-agent --log /var/log/td-agent/td-agent.log --use-v1-config --group td-agent --daemon /var/run/td-agent/td-agent.pid
td-agent 15402 15397  0 07:06 ?        00:00:01 /opt/td-agent/embedded/bin/ruby -Eascii-8bit:ascii-8bit /usr/sbin/td-agent --log /var/log/td-agent/td-agent.log --use-v1-config --group td-agent --daemon /var/run/td-agent/td-agent.pid --under-supervisor

...


$ ps -ef -c
UID        PID  PPID  C STIME TTY      STAT   TIME CMD

...

td-agent 15397     1  0 07:06 ?        Sl     0:00 ruby
td-agent 15402 15397  0 07:06 ?        Sl     0:01 ruby

...
```

つまり、td-agentにおけるプロセス名は `ruby` であるため `exact_match: true` では検索がうまくできず、結果として `exact_match: false` として起動時のコマンドから検索する必要があったということになる。

## どんなときに `exact_match: false` を指定するか

`exact_match: false` 指定は次の場合に必要となる: 

- 検索対象のプロセスが ruby, java, python といったプログラムのデーモンとして動いていて、プロセス名からそのプロセスであることを特定できない場合

今回の td-agent が典型的な例である。他のパターンは知らないけど、自前でスクリプトをデーモン化した場合は気をつける必要がありそう。