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 %]
  <hr> channel: [% stat.channel %]
  <table width="100%" cellspacing="0" cellpadding="0" border="1">
  <tr>
  [% FOREACH col IN name %] 
    <th><center>
    [% col %]
    </center></th>
  [% END %]
  </tr>
  [% FOREACH host IN stat.host %]
    <tr>
    [% FOREACH list IN host %]
      <td>
      [% list %]
      </td>
    [% END %]
  [% END %]
  </tr></table>
[% END %]
[% ELSE %]
  内部信息,请登陆后查看
[% END %]```
创建cachemoni/views/login.tt如下:
```html
<center>
<h2>登陆页面</h2>
[% IF err %]<p class=error><strong>Error:</strong> [% err %][% END %]
<form action="/login" method=post>
  <dl>
    <dt>用户名:
    <input type=text name=username>
    <dt>密  码:
    <input type=password name=password>
    <dd><input type=submit value=login>
  </dl>
</form>
</center>```注册页面类似,不贴了。
然后是layout层的共用部分,之前定义了是views/layouts/main.tt,如下:
```html……
<body>
<div class=metanav>
  <a href="/">首页</a> | 
  [% IF not session.logged_in %]
    <a href="/register">注册</a> | 
    <a href="/login">登陆</a>
  [% ELSE %]
    <a href="/logout">退出</a>
  [% END %]

[% content %]
<div id="footer">
Powered by <a href="http://perldancer.org/">Dancer</a> [% dancer_version %]

</body>
</html>```
这里修改了<%%>为[%%],其他的tt模版也要改。
然后运行perl cachemoni/bin/app.pl,启动3000端口的监听,访问一下如下:
<img src="/images/uploads/squid-monitor-demo.jpg" alt="" title="123" width="571" height="401" class="alignnone size-full wp-image-2505" /></a>
这就算完成一个小的网页了,然后开始配置进apache:
```bashcpanm Plack::Handler::Apache2
wget http://search.cpan.org/CPAN/authors/id/P/PH/PHRED/mod_perl-2.0.5.tar.gz
tar zxvf mod_perl-2.0.5.tar.gz
cd mod_perl-2.0.5
perl Makefile.PL
然后会提示输入apxs的全路径:/usr/local/apache2/bin/apxs
make && make install
sed -i s'/LoadModule/i\LoadModule perl_module modules/mod_perl.so\' /usr/local/apache2/conf/httpd.conf
cat >> /usr/local/apache2/conf/httpd.conf <<EOF
<VirtualHost *:80>
        ServerName dancer.test.china.com
        DocumentRoot /www/html/cachemoni
        <Directory /www/html/cachemoni>
            AllowOverride None
            Order allow,deny
            Allow from all
        </Directory>
        <Location />
            SetHandler perl-script
            PerlHandler Plack::Handler::Apache2
            PerlSetVar psgi_app /www/html/cachemoni/bin/app.pl
        </Location>
</VirtualHost>
EOF
/usr/local/apache2/bin/apachectl restart

访问一下,OK~ 各种和webserver搭配的方法(其实就是两种:mod_perl和各种cgi)详见:CPAN文档