こんにちは。Mackerel Advent Calendar 2020 11 日目である。
最近は自宅で仕事をしてゐて、自宅の network が相當惡い事に氣附いた。頻繁に通信が途切れる時間帯が在る。特に夕方から夜更けにかけて、まるで通信制限でもかかってゐるかのうやうにずっと遲い。多くの Web site で畫像を讀み込めなく成る程に遲い。これに氣附いたのは歸宅作業で體力を使ひ果たさず遊ぶ樣に成ったからであらう。周りの家の方々も同じく夜に通信が増えたのかもしれない。歸宅と食事は疲れる。歸宅と食事を止めよう。
どれ程遲いか確認してみた。speedtest-cli を使ふ。
sivel/speedtest-cli: Command line interface for testing internet bandwidth using speedtest.net
brew install speedtest-cli speedtest
殆ど通信が停まってゐる時間に實行すると、1 Mbps を下囘ってゐる事が判った。判ったところでどうしようもない。囘線契約を替へるか引っ越すかするだけである。それ等は今放っておくとして、囘線狀況を可視化しやうと思ふ。speedtest の結果を Mackerel に投稿しよう。多分既に一億人くらいがやってゐらっしゃると思ふ。
speedtest を mackerel-agent に叩かせる事を考へる。さうすると毎分數 10 Mb の通信が行はれ、これが囘線を圧迫してしまふ。そこで數 10 分に 1 囘、例へば 10 分毎に speedtest を實行する。mackerel-agent にはこの機能が無いから適當に wrap してやる。どうせ speedtest の出力をメトリック形式に整へたり MACKEREL_AGENT_PLUGIN_META
を處理する爲に wrap はするのである。
#!/usr/bin/env ruby require 'json' # Metric for Mackerel. class Metric def self.from_s(expression) name, value_s, time_s = expression.split("\t") new(name, value_s.to_f, Time.at(time_s.to_i)) end attr_reader :name, :value, :time def initialize(name, value, time) @name = name @value = value @time = time end def to_s "#{@name}\t#{@value}\t#{@time.to_i}" end end # Runner for speedtest-cli. class Speedtest def measure(time) out_r, out_w = IO.pipe err_r, err_w = IO.pipe pid = spawn('/usr/local/bin/speedtest --json --secure', out: out_w, err: err_w) Process.wait(pid) out_w.close err_w.close raise err_r.read unless $?.success? result = JSON.parse(out_r.read) [ Metric.new('speedtest.speed.download', result['download'] / 8, time), Metric.new('speedtest.speed.upload', result['upload'] / 8, time), Metric.new('speedtest.ping.ping', result['ping'], time) ] end end # Runner for speedtest-cli. Cache the result. class SpeedtestWithCache CACHE_FILE = '/tmp/mackerel-plugin-speedtest'.freeze def measure(time) metrics = Speedtest.new.measure(time) IO.write(CACHE_FILE, metrics.map(&:to_s).join("\n"), mode: 'w', encoding: Encoding::UTF_8) metrics end def prev_metrics return [] unless File.exist?(CACHE_FILE) IO.readlines(CACHE_FILE, mode: 'r', encoding: Encoding::UTF_8, chomp: true) .map { |line| Metric.from_s(line) } end end def print_meta puts '# mackerel-agent-plugin' puts({ graphs: { 'speedtest.speed': { label: 'Speedtest', unit: 'bytes', metrics: [ { name: 'download', lebel: 'download' }, { name: 'upload', lebel: 'upload' } ] }, 'speedtest.ping': { label: 'Speedtest Ping', unit: 'float', metrics: [ { name: 'ping', lebel: 'ping' } # seconds ] } } }.to_json) end # Should run speedtest-cli once in a 10 minutes. def should_measure?(current_time, prev_metrics) duration = 10 prev_metrics.empty? || prev_metrics[0].time + duration * 60 < current_time || ( prev_metrics[0].time + 1 * 60 > current_time && (current_time.to_i / 60 % duration).zero? ) end if ENV['MACKEREL_AGENT_PLUGIN_META'] == '1' print_meta exit 0 end speedtest = SpeedtestWithCache.new prev_metrics = speedtest.prev_metrics time = Time.now metrics = if should_measure?(time, speedtest.prev_metrics) speedtest.measure(time) else prev_metrics.map { |metric| Metric.new(metric.name, metric.value, time) } end metrics.each { |metric| puts metric }
speedtest を實行したら結果を file に書き出し、10 分間は同じ値を投稿する。これを mackerel-agent で實行するやう設定する。
[plugin.metrics.speedtest] command = "$HOME/.local/bin/mackerel-plugin-speedtest" timeout_seconds = 50
default では command の實行に 30 秒以上かかると mackerel-agent は command が timeout したと見做す。普段は 20 秒程で完了し支障がないが、通信がほぼ停まってゐる時には 45 秒程かかるので timeout_seconds
を 50 秒に延ばしてある。
こうするとグラフが見られる。
ひどい時にはほぼ 0 Mbps である事が見える。
アラートは仕掛けてゐない。通信が遲く成ってゐる事が判っても「遲く成ってるなぁ」と眺めるだけだからだ。即時に行動 (長期的傾向であれば ticket を登録する等を行ふ) を起こさないアラートを仕掛けてはいけない。
mac が sleep するとグラフが途切れる。また他のメトリックは投稿されるのに speedtest のグラフが途切れてゐると、50 秒でも timeout_seconds
が足りなかった事も判る。
美しく夕方から夜更け迄通信が遲くなってゐる樣子はこれだ。
寢るか offline で遊んでろって事だ。