Monitor the broadband speed with the Mackerel

Monitor the broadband speed with the Mackerel

最近気持ちが忙しくてブログ更新久しぶりな気がする。。タイトルの通りネットワーク回線の速度を監視し始めたのでメモ。

モチベーション

SNS の方で呟いてましたが引越しすることになりました。

我々インターネットで生活している者たちにとって引越しで一番気になるのは回線の速度ですよね。次のおうちでも光回線が通っているのはもちろん確認済みなのですが、速度や安定性が気になるところです。

そこで回線速度を監視し、引越し後どれくらいの違いが出るか確認することにしました。
結果は以下。Wi-Fi での数値です。有線だと数倍早かった…。

有線、無線両方監視しようと思いましたが次のおうちは実質無線がメインになりそうなので、無線だけ監視してます。

方法

最近 Raspberry Pi と Greengrass の組み合わせを試してます。

Greengrass ではデバイス上 (今回の場合 Raspberry Pi) で Lambda 関数を動作させることができます。

そこで Greengrass デバイス上に回線速度を取得し Mackerel のサービスメトリックに投稿する Lambda 関数をデプロイし利用することにしました。
デバイスに mackerel-agent をインストールし利用するのが一般的かと思いますが、あえて Greengrass で行う理由は以下。

  • Greengrass 使いたいからw
  • デバイスが故障するなどして入れ替えても Greengrass 環境さえあればすぐに機能を復活させられるメリットがある。
  • Lambda 関数をいじればいいのでデバイスの環境上で試行錯誤し汚さずに済む。
  • 後々他のデバイスを Greengrass 配下に置きたいので都合が良い。

Greengrass の使い方としてはちょっとズレてるかもですがやってみます。
回線速度を測るツールとしては speedtest-cli を使用します。

ただし、API が使えず (Greengrass 上で動かすとなぜか競合が発生しエラーになる…) スクリプトを直で叩いて利用します。スクリプトは以下。

ダウンロードしたスクリプトは以下のようにして利用できます。

$ curl -Lo speedtest-cli https://raw.githubusercontent.com/sivel/speedtest-cli/master/speedtest.py
$ chmod 0755 speedtest-cli

$ ./speedtest-cli --bytes --json | jq
{
  "client": {
    "rating": "0",
    "loggedin": "0",
    "isprating": "3.7",
    "ispdlavg": "0",
    "ip": "x.x.x.x",
    "isp": "Softbank BB",
    "lon": "139.6546",
    "ispulavg": "0",
    "country": "JP",
    "lat": "35.6468"
  },
  "bytes_sent": 142606336,
  "download": 436866717.4021921,
  "timestamp": "2020-07-08T12:35:53.280362Z",
  "share": null,
  "bytes_received": 409373932,
  "ping": 11.548,
  "upload": 372341648.8401565,
  "server": {
    "latency": 11.548,
    "name": "Tokyo",
    "url": "http://speed.open.ad.jp:8080/speedtest/upload.php",
    "country": "Japan",
    "lon": "139.6833",
    "cc": "JP",
    "host": "speed.open.ad.jp:8080",
    "sponsor": "OPEN Project (via 20G SINET)",
    "lat": "35.6833",
    "id": "15047",
    "d": 4.816079162659125
  }
}

Lambda 関数はとりあえずこんな感じに。ログ仕込んだりとかはまだ…。

import time
import urllib.request
import os
import json
import subprocess

mackerel_api_key      = os.environ['MACKEREL_API_KEY']
mackerel_service_name = os.environ['MACKEREL_SERVICE_NAME']
mackerel_endpoint     = f'https://api.mackerelio.com/api/v0/services/{mackerel_service_name}/tsdb'

def test_updown_speed():
    results = json.loads(
        subprocess.check_output(['./speedtest-cli', '--bytes', '--json']).decode())
    
    return {
        # 小数点以下切り捨て。でも切り捨てなくてもいいかも。
        'upload_speed': int(results['upload']),
        'download_speed': int(results['download'])
    }

def send_to_mackerel(upload_speed, download_speed, api_key, endpoint):
    now = int(time.time())
    payload = [
        {
            'name': 'netspeed.down',
            'time': now,
            'value': download_speed
        },
        {
            'name': 'netspeed.up',
            'time': now,
            'value': upload_speed
        }
    ]
    json_payload = json.dumps(payload).encode("utf-8")

    headers = {
        'Content-Type' : 'application/json',
        'X-Api-Key': api_key
    }
    
    request = urllib.request.Request(
        endpoint, data=json_payload, method='POST', headers=headers)
    with urllib.request.urlopen(request) as response:
        response_body = response.read().decode("utf-8")
    
    print("Sent metric.")

def main():
    # Greengrass 上で永続的に動かしたいのでループさせます。ループはハンドラ外で行う必要があるので注意。
    # https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/lambda-functions.html#lambda-lifecycle
    # > 実行中のコードを無期限で実行する場合には、ハンドラ外でこれを開始する必要があります。
    while True:
        speed = test_updown_speed()
        send_to_mackerel(
            speed['upload_speed'], speed['download_speed'], mackerel_api_key, mackerel_endpoint)
        time.sleep(60)

def function_handler(event, context):
    return

main()

上記コメントにある通り、Greengrass で「長い存続期間」を設定することで関数を永続的に動作させることができます。永続的に動かすために関数内に無限ループを入れていますが、これはハンドラ外で実装する必要があります。

長い存続期間の関数には、そのハンドラの呼び出しに関連付けられたタイムアウトがあることに注意してください。実行中のコードを無期限で実行する場合には、ハンドラ外でこれを開始する必要があります。関数の初期化の完了を妨害するようなハンドラ外のブロックコードがないことを確認します。

これらの関数は、コアが停止する(グループのデプロイ中やデバイスの再起動中など)か、関数がエラー状態(ハンドラのタイムアウト、キャッチされない例外、またはメモリ制限を超えたときなど)にならない限り実行されます。

AWS IoT Greengrass Core での Lambda 関数の実行 - AWS IoT Greengrass

サービスメトリックが投稿されたらメトリックの単位を bytes に変更します。

以上!

感想

将来的にはおうちのメトリックを増やしていってダッシュボードで俯瞰できるようにしたい。🐟