Datadogを使ってLAN, WANのSmokeping的なネットワーク監視をする

## やりたかったこと
[Smokeping](http://oss.oetiker.ch/smokeping/)を使って、ネットワークの特定の区間のレイテンシやパケロス等のネットワーク品質監視をしていましたが、これが古めかしいOSSなのでどうにも辛い…。とはいえ、Pingdom等のサービスでは、ロケーションは選べても厳密な始点は選べず、また、LANの部分は監視できません。そして、Zabbixを立てるのも正直、やりたくなく…
というわけで、[Datadog](https://www.datadoghq.com/)を使って同様の監視をしてみました。ちなみに、お試しだけであれば無償版でも出来ます！

## 構成概要
登場人物は2種類だけです

### Datadog（クラウドサービス）
https://www.datadoghq.com/
メトリクスの保存やグラフ描画をさせます

### 計測サーバ（クラウド or 物理）
EC2でもGCEでもオンプレの物理サーバでも構いませんが、Datadog Agentをインストールして計測します。こちらが **ネットワーク監視の始点**になります。

## 出来上がりイメージ
3つのグラフは左からRTT, パケロス, 試行回数になっています。（試行回数は計測がうまくいっているかの確認用…）
そして、`$from`が計測元、`$locate`が計測先、`$isp`が計測先回線のISPで、これを選択すると絞り込みが出来るようになっています。

![WAN Monitor _ Datadog (3).png](https://qiita-image-store.s3.amazonaws.com/0/15830/4065ebd7-6730-567d-9226-15b585d5d299.png)

## 作り方
### 事前準備
Datadogを使える状態にするところは、以下のような記事を参考にお願いします…
[Datadogを導入する](http://qiita.com/soushiy/items/76f40adcef17f3ad1394)

### カスタムメトリクスの収集
Datadogのカスタムメトリクスを使いますので、`/etc/dd-agent/checks.d`と`/etc/dd-agent/conf.d`に以下のファイルを設置します。(conf.dの方はカスタマイズしてください）

```checks.d/ping.py
#!/usr/bin/env python

import subprocess
import re
import time
from checks.network_checks import NetworkCheck, Status


class PingCheck(NetworkCheck):
    def __init__(self, name, init_config, agentConfig, instances):
        NetworkCheck.__init__(self, name, init_config, agentConfig, instances)
        for instance in instances:
            if not instance.get('isp', None):
                raise Exception("All instances should have a 'isp' parameter")
            # for initialize loss cnt
            self.__simple_increment(instance, 'loss_cnt', 0)

    def _check(self, instance):
        start = time.time()
        p = Ping(instance['addr'], self.init_config.get('ping_timeout', 4))
        for i in range(self.init_config.get('check_times', 1)):
            try:
                p.run()
                self.__simple_histogram(instance, 'rtt', p._rtt)
                time.sleep(self.init_config.get('interval', 0.1))
                ret = (Status.UP, "UP")
            except:
                self.__simple_increment(instance, 'loss_cnt')
                ret = (Status.DOWN, "DOWN")
            finally:
                self.__simple_increment(instance, 'total_cnt')
        elapsed_time = time.time() - start
        self.log.info("name:%s, elapsed_time:%s[sec]"
                      % (instance['name'], round(elapsed_time, 2)))
        return ret

    def __simple_increment(self, instance, category, value=1):
        self.increment(
               '%s.%s' % (self.init_config.get('basename', 'ping'),
                          category),
               value,
               tags=['isp:%s' % instance['isp'],
                     'locate:%s' % instance['name']]
        )

    def __simple_histogram(self, instance, category, value):
        self.histogram(
               '%s.%s' % (self.init_config.get('basename', 'ping'),
                          category),
               value,
               tags=['isp:%s' % instance['isp'],
                     'locate:%s' % instance['name']]
        )

    def report_as_service_check(self, sc_name, status, instance, msg=None):
        pass

    def _create_status_event(self, sc_name, status, msg, instance):
        # TODO 5.3 remove that
        pass


class Ping(object):
    def __init__(self, host, timeout):
        self._host = host
        self._timeout = timeout

    def run(self):
        ping = subprocess.Popen(
                ["ping", "-c", "1", "-W", str(self._timeout), self._host],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
                )
        out, error = ping.communicate()
        if error:
            raise Exception('[NG]: ServerName->%s, Msg->"%s"'
                            % (self._host, error.rstrip()))
        else:
            try:
                self._ttl = int(re.search("(?<=ttl=)[\d\.]+", out).group())
                self._rtt = float(re.search("(?<=time=)[\d\.]+", out).group())
            except AttributeError:
                raise Exception('[NG]: ServerName->%s, Msg->"%s"'
                                % (self._host, 'cannot connect'))
```
※最新版は[Gist](https://gist.github.com/unagi/fd73eeebbd80fc2ec38000cfc195a86b)で公開しています

```conf.d/ping.yaml
init_config:
    basename: ping
    check_times: 10
    threads_count: 6
    interval: 0.1

instances:
    - addr: x.x.x.x
      isp: HOGEHOGE
      name: [location name]
    - addr: x.x.x.y
      isp: HOGEHOGE2
      name: [location name]
```

これらのファイルを設置したあと、設定がうまくいっているかは以下のコマンドで確認できます

```
sudo -u dd-agent dd-agent check ping
```

問題ないようであれば、agentを再起動して、メトリクスの収集を始めます。


### ダッシュボード＆グラフの作成
収集されるメトリクスそのものだと、データとしてはちょっと見辛いので、ダッシュボード＆グラフを作成して見やすくします。それぞれのグラフの作り方は以下の画像の通りです。

#### RTT
![WAN Monitor _ Datadog (1).png](https://qiita-image-store.s3.amazonaws.com/0/15830/4a4c9cbb-7515-6a1c-8812-4769238b5e4d.png)

#### パケロス
![WAN Monitor _ Datadog.png](https://qiita-image-store.s3.amazonaws.com/0/15830/164342b3-26e0-3aa0-e3a2-a9f836a5ed2e.png)

#### データ取得件数
![WAN Monitor _ Datadog (2).png](https://qiita-image-store.s3.amazonaws.com/0/15830/9329c7b2-8306-3939-194b-5f9d32b57a23.png)

※`$from`にはhostタグ（datadogのデフォルト）、`$locate`にはnameタグ、`$isp`にはispタグを当てています

## おわりに
計測サーバには30カ所くらいを計測させていますが、Pingしてメトリクスを送信しているだけなので、かなりスペックの低いサーバでも動作しています。