監視ツールはいかにしてカウンター値のチェックを行なうのか

1年ぐらい前に諸事情により調べたメモ。

監視ツールでリソース情報とかのメトリクスに対して、○○を超えたら、みたいな閾値のチェックを設ける場合、元の値がディスク使用率とかロードアベレージのようなそのままの値が取れるものなら良いのですが(いわゆる GAUGE 値)、CPU 使用率や Traffic などは普通はカウンター値として取れるので(いわゆる COUNTER 値)、前回値からの差分に対して閾値のチェックをかける必要があります。

Nagios

そういう機能は無い。というかプラグイン自体がアラートを判断するので Nagios 的にはメトリクスに対する閾値チェックという概念がない。そういうのはプラグイン側で実装する。

プラグインで COUNTER 値をチェックするのはちょっと工夫が必要。

例えば、vmstat を 1 秒間実行してその結果をチェックするとか、/proc/stat をどこかに保存しておいて前回値との差分値をチェックする、とかです。

Sensu

チェックプラグインは Nagios と同じ仕様なので、基本は Nagios と同じ。

がしかし、Sensu で取得したメトリクスを時系列データベースに保存し、チェックプラグインで時系列データベースに問い合わせてチェックすることができます(Nagios でも eventhandler でパフォーマンスデータを時系列データベースに保存しておけば同じことはできると思うけど)。

Munin

少し前のバージョンでは Perl の Storable モジュール?でホストごとに記録されているステートファイルに、前回値と今回値が記録されています。

https://github.com/munin-monitoring/munin/blob/2.0.25/master/lib/Munin/Master/LimitsOld.pm#L319-L321

my $state_file = sprintf ('%s/state-%s-%s.storable', $config->{dbdir}, $hash->{group}, $host);
DEBUG "[DEBUG] state_file: $state_file";
my $state = munin_read_storable($state_file) || {};

https://github.com/munin-monitoring/munin/blob/2.0.25/master/lib/Munin/Master/LimitsOld.pm#L350-L351

my $rrd_filename = munin_get_rrd_filename($field);
my ($current_updated_timestamp, $current_updated_value) = @{ $state->{value}{"$rrd_filename:42"}{current} || [ ] };
my ($previous_updated_timestamp, $previous_updated_value) = @{ $state->{value}{"$rrd_filename:42"}{previous} || [ ] };

このステートファイルは RRA ファイルを更新するときに一緒に更新されます。

https://github.com/munin-monitoring/munin/blob/2.0.25/master/lib/Munin/Master/UpdateWorker.pm#L52

my $state_file = sprintf ('%s/state-%s.storable', $config->{dbdir}, $path); 
DEBUG "[DEBUG] Reading state for $path in $state_file";
$self->{state} = munin_read_storable($state_file) || {};

:


my $last_updated_timestamp = $self->_update_rrd_files(\%service_config, \%service_data);

:

DEBUG "[DEBUG] Writing state for $path in $state_file";
munin_write_storable($state_file, $self->{state});

最新版だと SQLite に変わっていました。

https://github.com/munin-monitoring/munin/commit/ba5306d148f04269b3b6bcb61c43e2f1fb34375e

Cacti

Cacti 単体にそういう機能はなくて thold プラグインを使う必要があります。ので thold-v0.5.0 を見てみました。

DB の thold_data テーブルの lastread,lasttime,oldvalue 辺りが前回値を持っています。

select lastread, lasttime, oldvalue from thold_data \G
/*
lastread: 5.4033
lasttime: 2017-04-26 12:55:13
oldvalue: 3309610
*/

oldvalue が生の値、lastread が計算によって求められたチェック対象の値です。

この値は Cacti の poller_output というフックポイントの処理で更新されます。たぶん RRA ファイルを更新するときに呼ばれるフックポイントです。

# setup.php
api_plugin_register_hook('thold', 'poller_output', 'thold_poller_output', 'includes/polling.php');

# polling.php
function thold_poller_output ($rrd_update_array) {
    // ..snip..
    db_execute("UPDATE thold_data SET tcheck=1, lastread='$currentval',
        lasttime='" . date("Y-m-d H:i:s", $currenttime) . "',
        oldvalue='" . $item[$t_item['name']] . "'
        WHERE rra_id = " . $t_item['rra_id'] . "
        AND data_id = " . $t_item['data_id']);
}

それにしてもアレなコードだわ・・・

Prometheus

コードを見るまでもなく、アラートに PromQL(Prometheus の時系列データベースに問い合わせる DSL)が使える時点で、都度時系列データベースに問い合わせているのは確定的に明らか。

また、データの取得の間隔とアラートのチェックの間隔が別々に設定できるので、メトリクスの取得とアラートの取得は独立して動いている、たぶん。

Zabbix

わからん。

まとめ

Munin や Cacti は RRA ファイルを読んでいるのかと思ったけどそんなことはなかった。パフォーマンス的にそれだと辛いのだと思う。

Graphite や InfluxDB のようなそれっぽい時系列データベースを使っていれば、アラートのチェックの都度、時系列データベースに問い合わせるのでも良いのかもしれない。

ただ、データの取得と、アラートのチェックと、可視化のための時系列データの保存、はなるべく疎な方がきれいな気がするので、Cacti や Munin 風の方法が良い気もする。