自宅の通信制限 (?) を Mackerel で可視化する - c4se記:さっちゃんですよ☆

c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Cosense で公表中です。

Programming は GitHub で開發中です。

自宅の通信制限 (?) を Mackerel で可視化する

こんにちは。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 秒に延ばしてある。

こうするとグラフが見られる。

f:id:Kureduki_Maari:20201211122026p:plain

ひどい時にはほぼ 0 Mbps である事が見える。

アラートは仕掛けてゐない。通信が遲く成ってゐる事が判っても「遲く成ってるなぁ」と眺めるだけだからだ。即時に行動 (長期的傾向であれば ticket を登録する等を行ふ) を起こさないアラートを仕掛けてはいけない。

mac が sleep するとグラフが途切れる。また他のメトリックは投稿されるのに speedtest のグラフが途切れてゐると、50 秒でも timeout_seconds が足りなかった事も判る。

美しく夕方から夜更け迄通信が遲くなってゐる樣子はこれだ。

f:id:Kureduki_Maari:20201211122052p:plain

寢るか offline で遊んでろって事だ。