2010年8月1日日曜日

kstatは便利

このエントリーをはてなブックマークに追加
MRTGでサーバを監視するときには、普通はサーバにnet-snmpをインストールしてSNMPで情報を取得します。しかし、TCPのコネクション数が1万を超えると、snmpdがTCP-MIBのコネクションの情報を収集するためにCPUを浪費します。以前紹介したようにキャッシュを増やすと、かなり改善されますが浪費はします。TCP-MIBを外したsnmpdを作るという手もありますが、ftp.jaist.ac.jpではMRTGをローカルで動かしているのでSNMPにこだわる必要はありません。

監視に必要な情報は、Solarisならkstatコマンドでだいたい取得できます。たとえば、aggr1インターフェイスの合計送出バイト数(IF-MIB::ifHCOutOctets)を取得するには、以下のように実行します。
$ kstat aggr:1:aggr1:obytes64
module: aggr                            instance: 1
name:   aggr1                           class:    net
        obytes64                        448356394356753
引数に指定する値はモジュール:インスタンス番号:名前:カウンタ名です。引数なしで実行すると、すべての情報が表示されるので、何を指定すればよいかはだいたいわかります。

kstatコマンドは、kstatデバイスにアクセスするkstatモジュールを使ったPerlスクリプトです。このモジュールを使うと以下のようにPerlで値を取得できます。
$ /usr/bin/perl -le 'use Sun::Solaris::Kstat;
> $kstat = Sun::Solaris::Kstat->new();
> print $kstat->{aggr}{1}{aggr1}{obytes64};'
448553825756655

5分間と15分間のロードアベレージは、unix:0:system_misc:avenrun_{5min,15min}を256で割った値です。MRTGでは100倍された値を使うので2.56で割ります。以下のスクリプトをMRTGのターゲットに指定すればSNMPの代わりになります。
#!/usr/bin/perl
use strict;
use Sun::Solaris::Kstat;
use Sys::Hostname;

my $kstat = Sun::Solaris::Kstat->new();
my $results = $kstat->{unix}{0}{system_misc};

# make uptime
my $uptimedata = `uptime`;
$uptimedata =~ /up\s+(\S+.*),\s+\d+\s+user.*load average:\s(\S+.*)$/;
my $uptime = $1;

# print data
printf("%.0f\n", $results->{avenrun_5min} / 2.56);
printf("%.0f\n", $results->{avenrun_15min} / 2.56);
print "$uptime\n";
print hostname, "\n";

exit(0);

筐体ごとのディスクI/Oの集計は、SNMPのときはターゲットにOIDをずらずらと並べていたのですが、kstatモジュールを使ったスクリプトを呼ぶようにしたらすっきりしました。
Target[ftp.jaist.ac.jp_diskio_sas1]: `/opt/.../bin/mrtg_diskio.pl sd 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29`
mrtg_diskio.plはこんな感じです。
#!/usr/bin/perl
use strict;
use Sun::Solaris::Kstat;
use Sys::Hostname;
use Math::BigInt;

my $kstat = Sun::Solaris::Kstat->new();
my $read = Math::BigInt->new();
my $write = Math::BigInt->new();
my $device = shift(@ARGV);
foreach (@ARGV) {
  my $results = $kstat->{$device}{$_}{"$device$_"};
  $read += $results->{nread};
  $write += $results->{nwritten};
}

# make uptime
my $uptimedata = `uptime`;
$uptimedata =~ /up\s+(\S+.*),\s+\d+\s+user.*load average:\s(\S+.*)$/;
my $uptime = $1;

# print data
print "$read\n";
print "$write\n";
print "$uptime\n";
print hostname, "\n";

exit(0);

論理CPU32個の使用率の平均は、ティックの差分を取る必要があるので少し面倒ですが、こんな感じです。
#!/usr/bin/perl
use strict;
use Sun::Solaris::Kstat;
use Sys::Hostname;

my $log = "/tmp/cpu.log";
my $cpus = 32;

my %prev;
if (open(PREV, '<', $log)) {
  my $i = 0;
  while () {
    ($prev{idle}[$i], $prev{kernel}[$i], $prev{user}[$i]) = /(\d+) (\d+) (\d+)/;
    $i++
  }
  close(PREV);
}

my $kstat = Sun::Solaris::Kstat->new();
my %cur;
my %total;
for (my $i = 0; $i < $cpus; $i++) {
  my $results = $kstat->{cpu}{$i}{sys};
  foreach ("idle", "kernel", "user") {
    $cur{$_}[$i] = $results->{"cpu_ticks_$_"};
    $total{$_} += &ull_delta($prev{$_}[$i], $cur{$_}[$i]) if %prev;
  }
}

sub ull_delta
{
  my ($old, $new) = @_;
  if ($new >= $old) {
    return $new - $old;
  } else {
    return (18446744073709551615 - $old) + $new + 1;
  }
}

if (open(PREV, '>', $log)) {
  for (my $i = 0; $i < $cpus; $i++) {
    print PREV join(" ", $cur{idle}[$i], $cur{kernel}[$i], $cur{user}[$i]) ."\n";
  }
  close(PREV);
}

my $usage;
my $kernel;
if (%prev) {
  my $total_ticks = $total{user} + $total{kernel} + $total{idle};
  $usage = 100 * ($total{user} + $total{kernel}) / $total_ticks;
  $kernel = 100 * $total{kernel} / $total_ticks;
}

# make uptime
my $uptimedata = `uptime`;
$uptimedata =~ /up\s+(\S+.*),\s+\d+\s+user.*load average:\s(\S+.*)$/;
my $uptime = $1;

# print datas
if (%prev) {
  printf("%.0f\n", $usage);
  printf("%.0f\n", $kernel);
} else {
  print("UNKNOWN\n");
  print("UNKNOWN\n");
}
print "$uptime\n";
print hostname ."\n";

exit(0);
2回目の実行からユーザとカーネルの合計の使用率とカーネルのみの使用率を返します。返す値は前回の実行からの平均値です。

 物理メモリの総量と空き容量はunix:0:system_pages:{physmem,freemem}です。単位はページなので、SNMPで得られる値に合わせるには、pagesizeコマンドで求めた1ページのバイト数を掛けて1024で割ります。スワップの情報はSNMPと同じものをkstatで得ることはできません。ftp.jaist.ac.jpにはスワップがないので問題ありませんが、必要ならswap -lで表示される情報を元に計算できます。

こうしてlocalhostのSNMPを指定していたMRTGのターゲットを、すべてスクリプトに置き換えた結果、snmpdは要らなくなったので止めてしまいました。ただこの方法ですと、MRTGが実行されるたびに何度もperlが実行されたり、kstatデバイスがオープンされたりして効率が悪いので、いずれ改善したいと思っています。

0 件のコメント:

コメントを投稿