TL; DR;
定期的にスピードテストを行い、その結果をメトリクスとして公開するPrometheus Exporterを自作しました。
コードはKotaro7750/speedtest-exporterに、コンテナイメージは7750koutarou/speedtest-exporterに公開しています。
背景
我が家は普通の賃貸マンションですが、インターネット環境に関しては少しこだわっていて、OCN光の戸建タイプを契約し部屋に直接光ファイバーを引き入れて、YAMAHAのNVR700Wというルーターに光ファイバーを直接収容しています。
そのため、一般的なマンションで言われるような夜間の劣化とは無縁でインターネットの速度に悩まされることは特にありません。 (下の画像はSpeedtest by Ooklaの結果ですが、WiFiに繋いだオンボロMacでもこれくらいは出ます)
ただ、1日を通じてどのくらいの速度が出ているのかを把握してみたいのと、純粋にスピードテストの結果を可視化できたら面白そうという理由で、スピードテスト結果をメトリクスとする仕組みを作ることにしました。
仕組み
今回は、既に宅鯖で構築してあるKubernetes・その上で動いているPrometheusを流用し、Golangでprometheusのexporterを書いてスクレイプさせることにします。
Golangで書いたのは、prometheus用のライブラリが充実していること、スピードテストを行うライブラリ(showwin/speedtest-go)を見つけたためです。
ざっくりコード解説
コード全体はKotaro7750/speedtest-exporterを見れば全て乗っていますが、主要部分に対して軽く解説を書いていきたいと思います。
speedtest-exporterでは、
- スピードテストを行う部分:github.com/showwin/speedtest-go/speedtestを利用
- スピードテストを定期的に実行する部分:github.com/robfig/cronを利用
- prometheus exporterを実装する部分:github.com/prometheus/client_golang/prometheusを利用
の3つが主要部分です。
スピードテストを行う部分
main関数でスピードテストを実行するスレッド数・時間を設定して定期実行するようにしています。
スピードテスト用関数では、ダウンロード・アップロード両方のテストを実行した後でメトリクスに登録しています。
type Config struct {
...
SpeedTestDuration time.Duration `env:"SPEEDTEST_DURATION" envDefault:"15s"`
SpeedtestThreadCount int `env:"SPEEDTEST_THREAD_COUNT" envDefault:"64"`
...
}
func main() {
...
// 定期実行部分(後述)
c := cron.New()
err = c.AddFunc(config.SpeedTestCronSchedule, func() {
speedtestClient := speedtest.New()
// スピードテストを実行するスレッド数を指定する
speedtestClient.SetNThread(config.SpeedtestThreadCount)
user, err := speedtestClient.FetchUserInfo()
// スピードテストを実行する時間を指定する
speedtestClient.SetCaptureTime(config.SpeedTestDuration)
doSpeedTestMulti(*speedtestClient, &metrics)
})
...
}
func doSpeedTestMulti(speedtestClient speedtest.Speedtest, metrics *Metrics) error {
servers, err := speedtestClient.FetchServers()
// 利用可能なサーバーから1つ選択する
target := (*servers.Available())[0]
// ダウンロード・アップロードそれぞれを実行
err = target.MultiDownloadTestContext(context.Background(), servers)
err = target.MultiUploadTestContext(context.Background(), servers)
dlSpeed := target.DLSpeed
ulSpeed := target.ULSpeed
// メトリクスに登録
metrics.DLSpeed.Set(dlSpeed)
metrics.ULSpeed.Set(ulSpeed)
target.Context.Reset()
return nil
}
定期的に実行する部分
github.com/robfig/cronパッケージを用いて、Cron形式で指定したスケジュールでスピードテストを実行させるようにします。 スケジュールは環境変数で指定するようにしています。
type Config struct {
...
SpeedTestCronSchedule string `env:"SPEEDTEST_CRON_SCHEDULE" envDefault:"@every 30m"`
...
}
func main() {
...
c := cron.New()
err = c.AddFunc(config.SpeedTestCronSchedule, func() {
// スピードテストを行う部分
...
})
c.Start()
}
Prometheus exporterを実装する部分
exporterの実装部分ではGaugeタイプのメトリクスを定義・登録し、/metricsエンドポイントで公開しています。
type Metrics struct {
DLSpeed prometheus.Gauge
ULSpeed prometheus.Gauge
}
var registry prometheus.Registry
func main() {
...
registry = *prometheus.NewRegistry()
// メトリクスの定義・登録
metrics := Metrics{
prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "speedtest",
Name: "download_speed_mbps",
}),
prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "speedtest",
Name: "upload_speed_mbps",
}),
}
err = registry.Register(metrics.DLSpeed)
err = registry.Register(metrics.ULSpeed)
http.Handle("/metrics", promhttp.HandlerFor(®istry, promhttp.HandlerOpts{Registry: ®istry}))
if err := http.ListenAndServe(fmt.Sprintf(":%d", config.MetricPort), nil); err != nil {
slog.Error(err.Error())
os.Exit(1)
}
}
結果
作成したexporterに対して、テスト時間15s・スレッド数64・1分間隔で実行した結果をGrafanaで可視化したのが下の画像です。 有線ネットワークで構成されているクラスタでコンテナを走らせているため、WiFi律速とならず回線側の速度をフルで活用できています。
アップロード速度は安定して800Mbps程度でており、ダウンロード速度は550~800Mbps程度となっています。 測定は土曜日に行いましたが19:00くらいから徐々に速度が低下していることがわかります。
導入してから日が浅いのでデータが揃っていませんが、1日・1週間を通じてどのように変化していくのかが楽しみです。