squid监控之前有一篇关于snmp的内容,不过这次是真要用上了,所以细细挑出来几个做监控。碰巧凯哥更新了一把modern perl的东西,我亦步亦趋,也试试dancer。不过花了两天时间,DBIx::Class::Schema还是没搞出来,最终还是简单的用DBI跳过了……
用的database就是之前nmap试验时生成的数据,有application/channel/intranet等column。
首先安装:
perlcpanm Dancer DBI DBD:mysql Template Dancer::Session::YAML dancer -a cachemoni
然后修改cachemoni/lib/cachemoni.pm如下:
```perl
package cachemoni;
use Dancer ‘:syntax’;
use Dancer::Plugin::Database;
use Net::SNMP;
use Digest::MD5 qw(md5_hex);
our $VERSION = ‘0.1’;

get ‘/monitor’ => sub {
my $app = params->{app} || ‘squid’;
return ‘Only support squid now’ unless $app eq ‘squid’;
my $checkstat = _snmp_check($app);
template ‘monitor’, { status => $checkstat,
name => [‘IP地址’,’流量命中率’,’请求数命中率’,’回源请求响应毫秒’,’当前客户端数’,’剩余文件描述符数’,’已缓存文件数’,’平均每秒请求数’],
};
};

any[‘get’, ‘post’] => ‘/login’ => sub {
my $err;
if ( request->method() eq ‘POST’ ) {
my $name = params->{username};
my $passwd = params->{password};
my $sth = database->prepare(
‘select audit,passwd from ops_user where name = ?’
);
$sth->execute($name);
my $ref = $sth->fetchrow_arrayref;
if (!defined $ref) {
redirect ‘/register’;
} elsif ( $ref->[0] ne ‘yes’ ) {
$err = ‘你的帐户还没通过审核’;
} elsif ( md5_hex(“$passwd”) ne “$ref->[1]” ) {
$err = ‘密码错误’;
} else {
session ‘logged_in’ => $name;
redirect ‘/’;
};
};
template ‘login’, { ‘err’ => $err, };
};

any[‘get’, ‘post’] => ‘/register’ => sub {
my $err;
if ( request->method() eq ‘POST’ ) {
my $name = params->{username};
my $passwd = params->{password};
my $check_sth = database->prepare(
‘select count(name) from ops_user where name = ?’
);
$check_sth->execute($name);
my $ref = $check_sth->fetchrow_arrayref;
if ( “$ref->[0]” == ‘0’ ) {
my $insert_sth = database->prepare(
‘insert into ops_user (name,passwd) value(?,?)’
);
$insert_sth->execute($name, md5_hex($passwd));
$err = ‘等待人工审核通过,3Q’;
} else {
$err = ‘该用户名已注册’;
};
};
template ‘register’, { ‘err’ => $err, };
};

get ‘/’ => sub {
template ‘index’;
};

get ‘/logout’ => sub {
session->destroy;
redirect ‘/’;
};

sub _snmp_check {
my $app = shift;
my $list = {};
#之前通过use加载了plugin::database,所以直接有database对象引用了
my $sth = database->prepare(
‘select channel,intranet from myhost where application = ?’,
);
$sth->execute($app);
while (my $ref = $sth->fetchrow_hashref()) {
$list->{“$ref->{‘channel’}”} = [] unless exists $list->{“$ref->{‘channel’}”};
my $ip = $ref->{‘intranet’};
my @snmpstat = _snmp_walk(“$ip”);
#这里第一是没想到比较好的把每个ip的各项检查结果导出的办法;所以干脆采用固定次序输出;
#第二是因为unshift很耗资源,所以先push再reverse
push @snmpstat, $ip;
@snmpstat = reverse @snmpstat;
push @{$list->{“$ref->{‘channel’}”}}, \@snmpstat;
}

push my @checkstat, map{
    { channel => $_,
      host    => $list->{"$_"},
    }
} keys %{$list};
return \@checkstat; };

sub _snmp_walk {
my $o_host = shift;
my $o_community = ‘public’;
my $result = {};
my %oids = (
‘cacheUptime’ => ‘.1.3.6.1.4.1.3495.1.1.3.0’,
‘cacheProtoClientHttpRequests’ => ‘.1.3.6.1.4.1.3495.1.3.2.1.1.0’,
‘cacheNumObjCount’ => ‘.1.3.6.1.4.1.3495.1.3.1.7.0’,
‘cacheCurrentUnusedFDescrCnt’ => ‘.1.3.6.1.4.1.3495.1.3.1.10.0’,
‘cacheClients’ => ‘.1.3.6.1.4.1.3495.1.3.2.1.15.0’,
‘cacheHttpMissSvcTime’ => ‘.1.3.6.1.4.1.3495.1.3.2.2.1.3.1’,
‘cacheRequestHitRatio’ => ‘.1.3.6.1.4.1.3495.1.3.2.2.1.9.1’,
‘cacheRequestByteRatio’ => ‘.1.3.6.1.4.1.3495.1.3.2.2.1.10.1’,
);

my ($session, $error) = Net::SNMP->session(
    -hostname  => $o_host,
    -community => $o_community, #默认是5秒超时,如果有没开snmp的,这页面打开时间一下就上去了,所以要调短
    -timeout   => 1,
);
if (defined $session) {
    $session->translate(Net::SNMP->TRANSLATE_NONE);
    my $squid_status = $session->get_request( -varbindlist => [$oids{'cacheUptime'}, $oids{'cacheProtoClientHttpRequests'}, $oids{'cacheNumObjCount'}, $oids{'cacheCurrentUnusedFDescrCnt'}, $oids{'cacheClients'}, $oids{'cacheHttpMissSvcTime'}, $oids{'cacheRequestHitRatio'}, $oids{'cacheRequestByteRatio'}, ], );
    $session->close;
    if (defined $squid_status) { #如果要算当前的rps,那得sleep 1再取一次,对几十台集群监控来说不可取; #姑且用总的平均值做个衡量,或许可以采用二八法则估算一个高峰时间的rps; #注意这个cacheUptime是保留两位ms的,所以算rps的时候要除以100; #上述原因也是一般大家对单机squid监控采用rrd的原因,采用COUNTER32数据源,直接递增显示。
        my $request_sum = $squid_status->{"$oids{'cacheProtoClientHttpRequests'}"};
        my $uptime = $squid_status->{"$oids{'cacheUptime'}"};
        return $request_sum / $uptime * 100, $squid_status->{"$oids{'cacheNumObjCount'}"}, $squid_status->{"$oids{'cacheCurrentUnusedFDescrCnt'}"}, $squid_status->{"$oids{'cacheClients'}"}, $squid_status->{"$oids{'cacheHttpMissSvcTime'}"}, $squid_status->{"$oids{'cacheRequestHitRatio'}"}, $squid_status->{"$oids{'cacheRequestByteRatio'}"};
    };
}; };

true; 修改cachemoni/config.yml如下: yaml
appname: “cachemoni”
layout: “main”
charset: “UTF-8”
#采用TT作为view模板,注意tt默认是[%%],而dancer里是<%%>,所以另外要定义标签
template: “template_toolkit”
engines:
template_toolkit:
encoding: ‘utf8’
start_tag: ‘[%’
end_tag: ‘%]’
#用默认的Simple,即存在内存的一个hash里,实际效果很不稳定,改用yaml,但也有yml文件在关闭浏览器后依旧存在的问题。
session: “YAML”
session_dir: “/tmp/dancer_session_dir”
session_expires: 300
plugins:
Database:
driver: ‘mysql’
database: ‘myops’
host: ‘10.1.1.25’
username: ‘user’
password: ‘password’
connection_check_threshold: 10
dbi_params:
RaiseError: 1
AutoCommit: 1
on_connect_do: [“SET NAMES ‘utf8’”, “SET CHARACTER SET ‘utf8’” ]
log_queries: 1 创建cachemoni/views/monitor.tt如下: html[% IF session.logged_in %]
[% FOREACH stat IN status %]


channel: [% stat.channel %]

[% FOREACH col IN name %] [% END %] [% FOREACH host IN stat.host %] [% FOREACH list IN host %] [% END %] [% END %]
[% col %]
[% list %]

[% END %]
[% ELSE %]
内部信息,请登陆后查看
[% END %] 创建cachemoni/views/login.tt如下: html

登陆页面

[% IF err %]<p class=error>Error: [% err %][% END %] <form action="/login" method=post>
用户名: <input type=text name=username>
密 码: <input type=password name=password>
<input type=submit value=login> ```注册页面类似,不贴了。 然后是layout层的共用部分,之前定义了是views/layouts/main.tt,如下: ```html…… <div class=metanav> 首页 | [% IF not session.logged_in %] 注册 | 登陆 [% ELSE %] 退出 [% END %] [% content %]