えいのうにっき

a-knowの日記です

mackerel-agent が取得するシステムメトリックの出どころを探る

この記事は、Mackerel アドベントカレンダー(全部CRE)の19日目の記事です。いよいよ終わりが見えてきた......!

Mackerelを使ってサーバーの監視をする場合、通常、監視対象となるサーバーにmackerel-agentという、Mackerel公式のエージェントソフトウェアをインストールします。

mackerel.io

このエージェントを、例えば Linux 系OSにインストールした場合、自動的に以下のような項目の監視・モニタリングがスタートします。

  • connectivity(死活監視)
  • ロードアベレージ
  • CPU使用率
  • Memory使用量
  • disk IOPS
  • ネットワークインターフェイス毎の転送量
  • filesystem毎の使用量

これらの項目の見方や考え方、といったところについては、以前僕がこのブログで書いた以下の記事などを入門編として活用していただけたら、と思うのですが、

blog.a-know.me

今回この記事では、それぞれの項目がどこからどうやって取得されているか、についてまとめておきたいと思います。

前提とするバージョン

まずそもそもなんですが、上記のようなシステムメトリック項目の取得、mackerel-agentのコード内で実施しているのか、というと現在一部のメトリックはそうではなく、github.com/mackerelio/go-osstatというパッケージに外出しされています。

github.com

この記事を書いている今日現在の mackerelio/go-osstat の最新のコミットハッシュは b878220なので、これを前提とします。また、エージェントのバージョンは v0.58.2です。

ロードアベレージ

まずはロードアベレージ。これの取得処理に該当するコードは以下ですね。

go-osstat/loadavg_unix.go at master · mackerelio/go-osstat · GitHub

C.getloadavg なるほど強い。 getloadavg のコードを見てみましょう。

glibc/getloadavg.c at master · lattera/glibc · GitHub

なるほど、 /proc/loadavg の内容をパースしてるみたいですね。/proc/loadavg の内容は通常どんな感じなのかというとー。

$ cat /proc/loadavg
0.00 0.01 0.05 1/172 7776

この出力例でいくと 0.00 が1分平均、 0.01 が5分平均、 0.05 が15分平均、ですね。mackerel-agentでは、以前は5分平均値のみをサポートしていたのですが、今年9月のリリースで3つ全ての値の取得に対応しています。

mackerel.io

CPU使用率

CPU使用率の取得処理に該当する、mackerelio/go-osstat のコードは以下。

go-osstat/cpu_linux.go at master · mackerelio/go-osstat · GitHub

/proc/stat の中身をパースしてますね。

$ cat /proc/stat
cpu  4539975 441 2810320 1946960026 8001 0 14328 960087 0 0
cpu0 2367260 210 1504679 973311005 4287 0 6794 541875 0 0
cpu1 2172715 231 1305641 973649020 3714 0 7533 418211 0 0
intr 7155580662 128 11 0 0 1239 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1903425 2897468 9810434 23688814 20700610 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 9700818896
btime 1535208165
processes 25990813
procs_running 1
procs_blocked 0
softirq 920913114 0 407577803 2010127 53910949 0 0 759654 327731574 0 128923007

cpu というところにある、スペースで区切られた10個の数値は、それぞれ以下。

  • User
  • Nice
  • System
  • Idle
  • Iowait
  • Irq
  • Softirq
  • Steal
  • Guest
  • GuestNice

また Total の値はこれらの値の合計値から GuestGuestNice を差っ引いたものになっています。理由についてもちゃんとコメントが書かれていて( https://github.com/mackerelio/go-osstat/blob/master/cpu/cpu_linux.go#L69-L70 )、GUESTのcputimeが重複して加算されていることによるもの、のようす。

mackerelio/go-osstat の仕事は /proc/stat からの値の取得までで、取得できた値を使って実際のCPU使用率の計算をおこなうのは mackerel-agent のお仕事。該当コードはここ。やってることの概要としては、カウンターメトリックであるCPUカウンターの各値を前回(1分前)との差を取り、Totalの値を用いて割合を算出・その1分間でのそれぞれのCPU使用率とする、というかんじ。CPUコア数を乗じているのもここで、です。

Memory使用量

Memory使用状況の取得処理に該当する、mackerelio/go-osstat のコードは以下。

go-osstat/memory_linux.go at master · mackerelio/go-osstat · GitHub

必要な情報は /proc/meminfo から取得していることがわかります。 free コマンドの結果とかじゃないですよ。

$ cat /proc/meminfo
MemTotal:         476788 kB
MemFree:           90232 kB
MemAvailable:     227632 kB
Buffers:               0 kB
Cached:           147940 kB
SwapCached:         1096 kB
...(中略)

結構素直な出力結果。ここから拝借した値をMackerelで表示している形になります。

Mackerelで取得しているのは以下のような項目ですが、

  • MemTotal
  • MemFree
  • MemAvailable
  • Buffers
  • Cached
  • Active
  • Inactive
  • SwapCached
  • SwapTotal
  • SwapFree

この中で気をつける点があるとすれば、SwapUsedUsed 、そして MemAvailable まわりかな?

SwapUsed については、SwapTotal から SwapFree を差っ引いた値をそれとして扱っている、というだけ。そして Used については、そのホストが MemAvailable に対応しているかどうかで以下のように分岐します。

  • MemAvailable に対応している
    • MemTotal から MemAvailable を差っ引いた値
  • MemAvailable に対応していない
    • MemTotal から MemFreeBuffersCached の値を差っ引いた値

MemAvailable については、しばらく Mackerel でも対応できていない時期があったのですが、今年、エージェントのアップデートにより対応されました。

mackerel.io

disk IOPS

コードはこちら!

mackerel-agent/disk.go at c9a0a6db219fb0f7720a0bd8c3169d30aec73872 · mackerelio/mackerel-agent · GitHub

/proc/diskstats を読んでいます。

$ cat /proc/diskstats
 259       0 nvme0n1 51278 461 3161371 42607 4439999 416208 70871825 3213412 0 690477 3256068
 259       1 nvme0n1p1 51192 461 3157227 42580 4435002 416208 70871817 3213281 0 690344 3255607

スペースで区切られた各フィールドの値は https://www.kernel.org/doc/Documentation/iostats.txt このとおりで、概要 - Mackerel ヘルプ にも記載しているとおり、カーネルバージョン2.6以降のフォーマットを前提としているかんじ。

たくさんある項目のうち、disk IOPS の計算に使っているのは reads completedwrites completed の値。いずれもカウンターメトリックのため、前回値と今回値の差分を取ってメトリック取得間隔(60秒)で割り、その1分間における秒あたりの read / write それぞれのIOPS値を計算、disk.デバイス名.reads.delta disk.デバイス名.writes.delta というシステムメトリックとして扱っています。

ネットワークインターフェイス毎の転送量

この値の取得に関わるコードは以下。

go-osstat/network_linux.go at master · mackerelio/go-osstat · GitHub

/proc/net/dev を読んでいますね。

$ cat /proc/net/dev
Inter-|   Receive                                                |  Transmit
 face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
  ens5: 7849794393 24296257    0    0    0     0          0         0 7937298204 24463307    0    0    0     0       0          0
    lo: 3502578773 9789245    0    0    0     0          0         0 3502578773 9789245    0    0    0     0       0          0

出力フォーマット、頑張ってる。

上記のコードでは、ローカルループバックインターフェイスは除外した上で、ネットワークデバイスごとに Receive bytesTransmit bytes の値を抽出しています。差分の計算については disk IOPS と同様で、それを実施しているのも mackerel-agent サイド( https://github.com/mackerelio/mackerel-agent/blob/d9e3082a32b96c17560a375e5e78babcb0f34e8d/metrics/interface.go )です。つまり、MackerelのWebコンソールで確認できる値は bytes/sec ですね。

filesystem毎の使用量

システムメトリックではこれが最後!コードは以下!

mackerel-agent/filesystem.go at de886fe24500f6c4cd1792d5c78e183f7f8ad9d8 · mackerelio/mackerel-agent · GitHub

これまでの疑似ファイルからの抽出ではなく、コマンド df -Pkl (もしくは df -k )の実行結果からパースしていますね!

$ df -Pkl
Filesystem     1024-blocks     Used Available Capacity Mounted on
/dev/nvme0n1p1      10474240 3750956 6723284  36% /
devtmpfs              215804       0  215804   0% /dev
tmpfs                 238392       0  238392   0% /dev/shm
tmpfs                 238392   29112  209280  13% /run
tmpfs                 238392       0  238392   0% /sys/fs/cgroup
tmpfs                  47680       0   47680   0% /run/user/1001

/dev/mapper/docker-/dev/dm- ではじまるデバイス名のものなどを読み飛ばしたのち、 /dev/ で始まる名前のファイルシステムを対象としています。Mackerelのグラフで表示させる size used の値の計算はエージェント側( mackerel-agent/filesystem.go at d9e3082a32b96c17560a375e5e78babcb0f34e8d · mackerelio/mackerel-agent · GitHub )で実施しており、used はそのまんまですが、size は df コマンドの UsedAvailable の和を採用しています。

ちなみに、mackerel-agent は ignore = "/dev/ram.*"use_mountpoint = true といったオプション指定をサポートしていて( https://mackerel.io/ja/docs/entry/spec/agent#config-file-filesystems )、その考慮をおこなっているのもエージェント側のコードになります。

mackerel.io

ちなみに、connectivity(死活監視)は?

こちらについては、すでにFAQが存在していますので、こちらをご確認ください!

mackerel.io

まとめ

以上、mackerel-agent が取得しているシステムメトリック(Linux系OSのみ)がどういうところから計算されているか?を紐解いてみました。ちょいちょい端折ってる部分もあるので、細かい部分はぜひコードを読んでみてくれ!!

今回のこの記事は、書いてる私にとっても一部なぁなぁにしてしまっていたところがあり、改めて勉強になりました。あと、結構フォーマットがバラバラでパースする側の気持ちになると大変だな、と。w

眠い目をこすりながら調べて書いたので、なんかコードを読み間違っちゃってるかも......? マサカリ、お待ちしております!!