├── .gitignore ├── README.md ├── Vagrantfile ├── agent_fatpack ├── Dockerfile ├── SOURCES │ ├── kurado_agent │ ├── kurado_agent.initd │ ├── kurado_agent.logrotate │ └── kurado_agent.sysconf ├── SPECS │ └── kurado_agent.spec.tmpl ├── Vagrantfile ├── build-with-vagrant.sh ├── cpanfile ├── fatpack.sh ├── kurado_agent └── plenv_profile.sh ├── bin ├── kurado_agent ├── kurado_config └── kurado_worker ├── cpanfile ├── etc └── conf.d │ ├── sample.toml │ └── test.toml ├── lib ├── Kurado.pm └── Kurado │ ├── Agent.pm │ ├── Agent │ ├── Collector.pm │ ├── Config.pm │ └── TOML.pm │ ├── Config.pm │ ├── ConfigLoader.pm │ ├── Host.pm │ ├── MQ.pm │ ├── Metrics.pm │ ├── Object │ ├── Host.pm │ ├── Msg.pm │ ├── Plugin.pm │ └── Roll.pm │ ├── Plugin.pm │ ├── Plugin │ └── Compile.pm │ ├── RRD.pm │ ├── ScoreBoard.pm │ ├── Storage.pm │ ├── TinyTCP.pm │ ├── Util.pm │ ├── Web.pm │ ├── Worker.pm │ └── Worker │ ├── Fetcher.pm │ ├── TimeMage.pm │ └── Updater.pm ├── metrics_plugins ├── fetch │ ├── .gitkeep │ ├── gaurun.pl │ ├── goapp.pl │ ├── http.pl │ ├── jolokia.pl │ ├── memcached.pl │ ├── mysql.pl │ ├── nginx.pl │ ├── opcache.pl │ ├── redis.pl │ └── squid.pl └── view │ ├── .gitkeep │ ├── base.pl │ ├── gaurun.pl │ ├── goapp.pl │ ├── http.pl │ ├── jolokia.pl │ ├── memcached.pl │ ├── mysql.pl │ ├── nginx.pl │ ├── opcache.pl │ ├── redis.pl │ └── squid.pl ├── public ├── css │ ├── bootstrap-datetimepicker.min.css │ ├── bootstrap-switch.min.css │ ├── bootstrap.min.css │ └── local.css ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── img │ ├── ZeroClipboard.swf │ ├── glyphicons-halflings-white.png │ ├── glyphicons-halflings.png │ └── kurado.png └── js │ ├── ZeroClipboard.min.js │ ├── bootstrap-datetimepicker.min.js │ ├── bootstrap-switch.min.js │ ├── bootstrap.min.js │ ├── jquery.color-2.1.2.min.js │ ├── jquery.cookie.js │ ├── jquery.min.js │ └── jquery.shiftcheckbox.js ├── sample_kurado.yml ├── sample_rolls ├── base.yml ├── httpd.yml ├── httpd_memcached.yml └── mysql.yml ├── t ├── 005-Kurado-Util │ └── 00-compile.t ├── 010-Kurado-Config │ └── 00-compile.t ├── 020-Kurado-ConfigLoader.pm │ └── 00-compile.t ├── 030-Kurado-RRD │ ├── 00-compile.t │ └── 01-basic.t ├── 040-Kurado-Storage │ ├── 00-compile.t │ └── 01-basic.t ├── 050-Kurado-Metrics │ └── 00-compile.t └── 060-Kurodo-Host │ └── 00-compile.t └── views ├── base.tx ├── index.tx ├── server.tx └── servers.tx /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#* 3 | .#* 4 | .vagrant/ 5 | fatlib/ 6 | fatpacker.trace 7 | packlists 8 | agent_fatpack/RPMS/noarch/kurado_agent-latest.noarch.rpm 9 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | VAGRANTFILE_API_VERSION = "2" 2 | 3 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 4 | config.vm.box = "chef/centos-6.5" 5 | config.vm.network "forwarded_port", guest: 5434, host: 5434 6 | end 7 | 8 | -------------------------------------------------------------------------------- /agent_fatpack/Dockerfile: -------------------------------------------------------------------------------- 1 | # How to fatpack with docker 2 | # 3 | # docker build -t `whoami`/perl-build . 4 | # docker run -v `pwd`/../:/perl-build `whoami`/perl-build 5 | # 6 | 7 | FROM centos:centos6 8 | 9 | RUN yum install -y make gcc 10 | RUN yum install -y git curl 11 | RUN yum install -y tar bzip2 patch 12 | 13 | RUN git clone git://github.com/tokuhirom/plenv.git /usr/share/plenv 14 | RUN git clone git://github.com/tokuhirom/Perl-Build.git ~/.plenv/plugins/perl-build 15 | ENV PATH ${PATH}:/usr/share/plenv/bin 16 | ADD plenv_profile.sh /etc/profile.d/plenv.sh 17 | RUN . /etc/profile.d/plenv.sh 18 | RUN plenv install 5.8.5 19 | RUN plenv global 5.8.5 20 | ENV PLENV_VERSION 5.8.5 21 | RUN curl -L http://cpanmin.us/ | plenv exec perl - -n ExtUtils::MakeMaker@6.66 22 | RUN curl -L http://cpanmin.us/ | plenv exec perl - -n App::cpanminus 23 | RUN curl -L http://cpanmin.us/ | plenv exec perl - -n Perl::Strip App::FatPacker 24 | RUN plenv rehash 25 | 26 | RUN yum install -y rpm-build 27 | 28 | CMD bash -l -c 'cd /fatpack; cpanm -n --installdeps /fatpack/agent_fatpack ; bash agent_fatpack/fatpack.sh' 29 | -------------------------------------------------------------------------------- /agent_fatpack/SOURCES/kurado_agent.initd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # /etc/rc.d/init.d/kurado_agent 4 | # 5 | # Daemon for Kurado Server Performance Metrics 6 | # 7 | # chkconfig: 2345 95 95 8 | # description: Daemon for Kurado Server Performance Metrics 9 | 10 | ### BEGIN INIT INFO 11 | # Provides: kurado_agent 12 | # Short-Description: start and stop kurado_agent 13 | # Description: Daemon for Kurado Server Performance Metrics 14 | ### END INIT INFO 15 | 16 | # Source function library. 17 | . /etc/rc.d/init.d/functions 18 | 19 | prog="kurado_agent" 20 | 21 | [ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog 22 | 23 | BIN=${BIN:="/usr/local/bin/$prog"} 24 | LOGFILE=${LOGILE:="/var/log/$prog.log"} 25 | PIDFILE=${PIDFILE:="/var/run/$prog.pid"} 26 | 27 | lockfile=/var/lock/subsys/$prog 28 | 29 | start() { 30 | [ -x $exec ] || exit 5 31 | 32 | echo -n $"Starting $prog:" 33 | $BIN ${MQ:+--mq=$MQ} \ 34 | --pidfile=$PIDFILE \ 35 | $OTHER_OPTS >>$LOGFILE 2>&1 & 36 | sleep 5 37 | if ! kill -0 $(cat $PIDFILE 2>/dev/null) >/dev/null 2>&1; then 38 | failure 39 | echo 40 | 41 | return 1 42 | fi 43 | 44 | touch $lockfile 45 | success 46 | echo 47 | 48 | return 0 49 | } 50 | 51 | stop() { 52 | echo -n $"Stopping $prog: " 53 | killproc -p $PIDFILE -TERM 54 | retval=$? 55 | echo 56 | [ $retval -eq 0 ] && rm -f $lockfile 57 | return $retval 58 | } 59 | 60 | restart() { 61 | stop 62 | start 63 | } 64 | 65 | rh_status() { 66 | status -p $PIDFILE $prog 67 | } 68 | 69 | rh_status_q() { 70 | rh_status >/dev/null 2>&1 71 | } 72 | 73 | case "$1" in 74 | start) 75 | rh_status_q && exit 0 76 | $1 77 | ;; 78 | stop) 79 | rh_status_q || exit 0 80 | $1 81 | ;; 82 | restart) 83 | $1 84 | ;; 85 | status) 86 | rh_status 87 | ;; 88 | *) 89 | echo $"Usage: $0 {start|stop|status|restart}" 90 | exit 2 91 | esac 92 | 93 | exit $? 94 | -------------------------------------------------------------------------------- /agent_fatpack/SOURCES/kurado_agent.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/kurado_agent.log { 2 | daily 3 | rotate 7 4 | missingok 5 | sharedscripts 6 | notifempty 7 | compress 8 | copytruncate 9 | } 10 | 11 | -------------------------------------------------------------------------------- /agent_fatpack/SOURCES/kurado_agent.sysconf: -------------------------------------------------------------------------------- 1 | # MQ="127.0.0.1:6379" 2 | # OTHER_OPTS="--max-delay=10 --self-ip 10.1.2.3" 3 | OTHER_OPTS="--conf-d=/etc/kurado_agent" 4 | -------------------------------------------------------------------------------- /agent_fatpack/SPECS/kurado_agent.spec.tmpl: -------------------------------------------------------------------------------- 1 | Name: kurado_agent 2 | Version: 3 | Release: 4 | Summary: Daemon for Kurado Server Performance Metrics 5 | 6 | Group: Application/Deamon 7 | License: Artistic 8 | URL: https://github.com/kazeburo/Kurado 9 | Source0: https://raw.githubusercontent.com/kazeburo/Kurado/master/agent_fatpack/kurado_agent 10 | Source1: kurado_agent.initd 11 | Source2: kurado_agent.logrotate 12 | Source3: kurado_agent.sysconf 13 | BuildArch: noarch 14 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n) 15 | Requires: perl 16 | AutoReqProv: no 17 | 18 | %description 19 | Daemon for Kurado Server Performance Metrics 20 | 21 | %install 22 | rm -rf $RPM_BUILD_ROOT 23 | 24 | mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/kurado_agent 25 | mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/init.d 26 | install -m 755 %{SOURCE1} $RPM_BUILD_ROOT%{_sysconfdir}/init.d/kurado_agent 27 | mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d 28 | install -m 755 %{SOURCE2} $RPM_BUILD_ROOT%{_sysconfdir}/logrotate.d/kurado_agent 29 | mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig 30 | install -m 755 %{SOURCE3} $RPM_BUILD_ROOT%{_sysconfdir}/sysconfig/kurado_agent 31 | mkdir -p $RPM_BUILD_ROOT/usr/local/bin 32 | install -m 755 %{SOURCE0} $RPM_BUILD_ROOT/usr/local/bin/kurado_agent 33 | 34 | %post 35 | /sbin/chkconfig --add kurado_agent 36 | 37 | %files 38 | %defattr(-,root,root,-) 39 | %dir %{_sysconfdir}/kurado_agent 40 | /usr/local/bin/kurado_agent 41 | %config %{_sysconfdir}/init.d/kurado_agent 42 | %config(noreplace) %{_sysconfdir}/sysconfig/kurado_agent 43 | %config(noreplace) %{_sysconfdir}/logrotate.d/kurado_agent 44 | 45 | %changelog 46 | 47 | -------------------------------------------------------------------------------- /agent_fatpack/Vagrantfile: -------------------------------------------------------------------------------- 1 | # How to fatpack with Vagrant 2 | # vagrant >= 1.6.3 is required 3 | # 4 | # * fatpack build-perl 5 | # $ cd author 6 | # $ vagrant up --provision 7 | # 8 | # * halt vm 9 | # $ vagrant halt 10 | # 11 | # * retry fatpack 12 | # $ vagrant up --provision 13 | # or 14 | # $ vagrant reload --provision 15 | # 16 | 17 | VAGRANTFILE_API_VERSION = "2" 18 | 19 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 20 | config.vm.box = "hashicorp/precise64" 21 | config.vm.synced_folder "../", "/fatpack" 22 | config.vm.provision "docker", run: "always" do |d| 23 | d.build_image "/fatpack/agent_fatpack", 24 | args: "-t fatpack" 25 | end 26 | # docker provisoner does not block until docker run is finished, so I use shell provisioner for docker run 27 | config.vm.provision "shell", run: "always", 28 | inline: "docker run -v /fatpack:/fatpack fatpack" 29 | config.vm.provision "destroy", destroy: false, run: "always" 30 | end 31 | -------------------------------------------------------------------------------- /agent_fatpack/build-with-vagrant.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | CDIR=$(cd $(dirname $0) && pwd) 4 | cd $CDIR 5 | vagrant up 6 | s3cmd -v -P sync RPMS/ s3://kurado-agent/RPMS/ 7 | -------------------------------------------------------------------------------- /agent_fatpack/cpanfile: -------------------------------------------------------------------------------- 1 | requires 'perl' => '5.008005'; 2 | requires 'Getopt::Long'; 3 | requires 'Pod::Usage', '1.63'; 4 | requires 'Cwd::Guard'; 5 | requires 'TOML::Parser', '== 0.04'; 6 | requires 'boolean', '0.38'; 7 | requires 'Proc::Pidfile'; 8 | 9 | 10 | -------------------------------------------------------------------------------- /agent_fatpack/fatpack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SRC=bin/kurado_agent 5 | DST=agent_fatpack/SOURCES/kurado_agent 6 | export PLENV_VERSION=5.8.5 7 | export PERL5LIB=`dirname $0`/../lib/ 8 | 9 | fatpack trace $SRC 10 | perl -nle 'print unless m!^(Cwd\.pm$|File/Spec|List/Util|Scalar/Util)!' fatpacker.trace > fatpacker.trace.tmp 11 | mv fatpacker.trace.tmp fatpacker.trace 12 | fatpack packlists-for `cat fatpacker.trace` >packlists 13 | fatpack tree `cat packlists` 14 | 15 | if type perlstrip >/dev/null 2>&1; then 16 | find fatlib -type f -name '*.pm' | xargs -n1 perlstrip -s 17 | fi 18 | 19 | (echo "#!/usr/bin/env perl"; fatpack file; cat $SRC) > $DST 20 | perl -pi -e 's|^#!/usr/bin/perl|#!/usr/bin/env perl|' $DST 21 | chmod +x $DST 22 | perl $DST --version 23 | 24 | echo "%_topdir $(pwd)/agent_fatpack" > $HOME/.rpmmacros 25 | echo "%debug_package %{nil}" >> $HOME/.rpmmacros 26 | 27 | cp -af agent_fatpack/SPECS/kurado_agent.spec.tmpl agent_fatpack/SPECS/kurado_agent.spec 28 | D_VER=$(date +%Y%m%d) 29 | D_REL=$(date +%H%M|sed 's/^0//') 30 | sed -i "s//$D_VER/" agent_fatpack/SPECS/kurado_agent.spec 31 | sed -i "s//$D_REL/" agent_fatpack/SPECS/kurado_agent.spec 32 | head agent_fatpack/SPECS/kurado_agent.spec 33 | rpmbuild -bb agent_fatpack/SPECS/kurado_agent.spec 34 | rm -f agent_fatpack/SPECS/kurado_agent.spec 35 | mv agent_fatpack/RPMS/noarch/kurado_agent-$D_VER-$D_REL.noarch.rpm \ 36 | agent_fatpack/RPMS/noarch/kurado_agent-latest.noarch.rpm 37 | 38 | -------------------------------------------------------------------------------- /agent_fatpack/plenv_profile.sh: -------------------------------------------------------------------------------- 1 | export PATH=${PATH}:/usr/share/plenv/bin/ 2 | eval "$(plenv init -)" 3 | plenv rehash 4 | -------------------------------------------------------------------------------- /bin/kurado_agent: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use FindBin; 7 | use lib "$FindBin::Bin/../lib"; 8 | use Getopt::Long; 9 | use Pod::Usage; 10 | 11 | use Proc::Pidfile; 12 | use Kurado::Agent; 13 | use Kurado::Util; 14 | use Kurado::MQ; 15 | use Kurado::Agent::Collector; 16 | use Kurado::Agent::Config; 17 | 18 | my $interval = 1; 19 | my $max_delay = 0; 20 | Getopt::Long::Configure ("no_ignore_case"); 21 | GetOptions( 22 | "self-ip=s" => \my $self_ip, 23 | "conf-d=s" => \my $config_dir, 24 | "dump" => \my $dump, 25 | "mq=s" => \my $mq, 26 | "pidfile=s" => \my $pidfile, 27 | "interval|i=i" => \$interval, 28 | "max-delay=i" => \$max_delay, 29 | "h|help" => \my $help, 30 | "v|version" => \my $version, 31 | ); 32 | 33 | if ( $version ) { 34 | print "Kurado::Agent version $Kurado::Agent::VERSION\n"; 35 | print "Try `kurado_agent --help` for more options.\n\n"; 36 | exit 0; 37 | } 38 | 39 | if ( $help ) { 40 | pod2usage(-verbose=>2,-exitval=>0); 41 | } 42 | 43 | my $plugins = {}; 44 | if ( $config_dir ) { 45 | eval { 46 | my $conf = Kurado::Agent::Config->new($config_dir); 47 | $plugins = $conf->plugins; 48 | }; 49 | die "Failed to load config: $@\n" if $@; 50 | } 51 | 52 | my $collector = Kurado::Agent::Collector->new($plugins); 53 | 54 | if ( $dump ) { 55 | my $message; 56 | eval { 57 | $message = $collector->collect("dump"); 58 | $message .= $collector->collect_plugins("dump"); 59 | }; 60 | die "$@\n" if $@; 61 | print $message; 62 | exit; 63 | } 64 | 65 | if ( !$mq || !$interval) { 66 | pod2usage(-verbose=>0,-exitval=>1); 67 | } 68 | 69 | my $pp; 70 | if ( $pidfile ) { 71 | $pp = Proc::Pidfile->new( pidfile => $pidfile ); 72 | } 73 | 74 | $0 = "kurado_agent master"; 75 | supervisor(sub { 76 | $0 = "kurado_agent"; 77 | my $mq = Kurado::MQ->new(server => $mq); 78 | local $SIG{TERM} = sub { 79 | $mq->{stop_loop} = 1; 80 | }; 81 | $self_ip ||= $mq->{sock}->sockhost; 82 | warn sprintf('start kurado_agent. mq:%s:%s self_ip:%s interval:%ssec max_delay:%ssec'."\n", 83 | $mq->{sock}->peerhost, $mq->{sock}->peerport, $self_ip, $interval*60, $max_delay); 84 | $mq->timetick_publisher( 85 | $interval*60, 86 | $max_delay, 87 | sub { 88 | my $message; 89 | eval { 90 | $message = $collector->collect($self_ip); 91 | $message .= $collector->collect_plugins($self_ip); 92 | }; 93 | die "failed collect metrics: $@\n" if $@; 94 | return ['kurado-update', $message]; 95 | } 96 | ); 97 | exit(); 98 | }, interval => 3); 99 | 100 | 1; 101 | 102 | __END__ 103 | 104 | =head1 NAME 105 | 106 | kurado_agent - Kurado agent 107 | 108 | =head1 SYNOPSIS 109 | 110 | # dry-run 111 | % kurado_agent --dump 112 | 113 | # run 114 | % kurado_agent --interval 1 --mq 127.0.0.1:6379 --conf-d /etc/kurado_agent/conf.d 115 | 116 | =head1 DESCRIPTION 117 | 118 | Kurado agent 119 | 120 | =head1 OPTIONS 121 | 122 | =over 4 123 | 124 | =item --conf-d 125 | 126 | path to config directory. files shuold be '*.toml' (optional) 127 | 128 | =item --interval 129 | 130 | interval minutes default 1 (60sec) 131 | 132 | =item --mq 133 | 134 | Redis Server for message queue (required) 135 | 136 | =item --self-ip 137 | 138 | ip address of this host. If not exists, use socket addr of connection to redis server 139 | 140 | =item --pidfile 141 | 142 | pidfile 143 | 144 | =item --max-delay 145 | 146 | max delay second of interval metrics sending. for avoiding thundering herd of "0" seconds 147 | 148 | =item -v --version 149 | 150 | Display version 151 | 152 | =item -h --help 153 | 154 | Display help 155 | 156 | =back 157 | 158 | =head1 AUTHOR 159 | 160 | Masahiro Nagano 161 | 162 | =head1 LICENSE 163 | 164 | This library is free software; you can redistribute it and/or modify it 165 | under the same terms as Perl itself. 166 | 167 | # Local Variables: 168 | # mode: cperl 169 | # End: 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /bin/kurado_config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use FindBin; 7 | use lib "$FindBin::Bin/../lib"; 8 | use Getopt::Long; 9 | use Pod::Usage; 10 | use Log::Minimal; 11 | use JSON::XS; 12 | 13 | use Kurado; 14 | use Kurado::ConfigLoader; 15 | 16 | $Log::Minimal::AUTODUMP = 1; 17 | 18 | my $interval = 1; 19 | my $max_delay = 0; 20 | Getopt::Long::Configure ("no_ignore_case"); 21 | GetOptions( 22 | "c|config=s" => \my $path, 23 | "h|help" => \my $help, 24 | "v|version" => \my $version, 25 | "dump-json" => \my $dump_json, 26 | ); 27 | 28 | if ( $version ) { 29 | print "Kurado version $Kurado::VERSION\n"; 30 | print "Try `kurado_worker --help` for more options.\n\n"; 31 | exit 0; 32 | } 33 | 34 | if ( $help ) { 35 | pod2usage(-verbose=>2,-exitval=>0); 36 | } 37 | 38 | if ( !$path ) { 39 | pod2usage(-verbose=>0,-exitval=>1); 40 | } 41 | 42 | infof("loading config.."); 43 | my $loader = Kurado::ConfigLoader->new(path=>$path); 44 | 45 | if ( $dump_json ) { 46 | print JSON::XS->new->convert_blessed->canonical->pretty->encode($loader->dump); 47 | exit; 48 | } 49 | 50 | print $loader->statistics; 51 | print "OK. config loaded!\n\n"; 52 | 53 | __END__ 54 | 55 | =head1 NAME 56 | 57 | kurado_config - Kurado config test 58 | 59 | =head1 SYNOPSIS 60 | 61 | 62 | # run 63 | % kurado_config -c config.yml 64 | 65 | =head1 DESCRIPTION 66 | 67 | test config 68 | 69 | =head1 OPTIONS 70 | 71 | =over 4 72 | 73 | =item -v --version 74 | 75 | Display version 76 | 77 | =item -h --help 78 | 79 | Display help 80 | 81 | =back 82 | 83 | =head1 AUTHOR 84 | 85 | Masahiro Nagano 86 | 87 | =head1 LICENSE 88 | 89 | This library is free software; you can redistribute it and/or modify it 90 | under the same terms as Perl itself. 91 | 92 | 93 | # Local Variables: 94 | # mode: cperl 95 | # End: 96 | -------------------------------------------------------------------------------- /bin/kurado_worker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use FindBin; 7 | use lib "$FindBin::Bin/../lib"; 8 | use Getopt::Long; 9 | use Pod::Usage; 10 | use Log::Minimal; 11 | use Encode; 12 | 13 | use Kurado; 14 | use Kurado::ConfigLoader; 15 | use Kurado::Worker; 16 | 17 | $Log::Minimal::AUTODUMP = 1; 18 | 19 | my $interval = 1; 20 | my $max_delay = 0; 21 | Getopt::Long::Configure ("no_ignore_case"); 22 | GetOptions( 23 | "c|config=s" => \my $path, 24 | "h|help" => \my $help, 25 | "v|version" => \my $version, 26 | ); 27 | 28 | if ( $version ) { 29 | print "Kurado version $Kurado::VERSION\n"; 30 | print "Try `kurado_worker --help` for more options.\n\n"; 31 | exit 0; 32 | } 33 | 34 | if ( $help ) { 35 | pod2usage(-verbose=>2,-exitval=>0); 36 | } 37 | 38 | if ( !$path ) { 39 | pod2usage(-verbose=>0,-exitval=>1); 40 | } 41 | 42 | my $root_dir = "$FindBin::Bin/.."; 43 | 44 | infof("loading config.."); 45 | my $loader = Kurado::ConfigLoader->new(path=>$path); 46 | infof(Encode::encode_utf8($_)) for grep { $_ } split /\n/, $loader->statistics; 47 | 48 | infof("run workers"); 49 | my $worker = Kurado::Worker->new( 50 | config_loader => $loader, 51 | root_dir => $root_dir, 52 | ); 53 | $worker->run; 54 | 55 | __END__ 56 | 57 | =head1 NAME 58 | 59 | kurado_worker - Kurado worker 60 | 61 | =head1 SYNOPSIS 62 | 63 | 64 | # run 65 | % kurado_worker -c config.yml 66 | 67 | =head1 DESCRIPTION 68 | 69 | Kurado worker 70 | 71 | =head1 OPTIONS 72 | 73 | =over 4 74 | 75 | =item -v --version 76 | 77 | Display version 78 | 79 | =item -h --help 80 | 81 | Display help 82 | 83 | =back 84 | 85 | =head1 AUTHOR 86 | 87 | Masahiro Nagano 88 | 89 | =head1 LICENSE 90 | 91 | This library is free software; you can redistribute it and/or modify it 92 | under the same terms as Perl itself. 93 | 94 | # Local Variables: 95 | # mode: cperl 96 | # End: 97 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Alien::RRDtool', '0.05'; 2 | requires 'Kossy', '0.37'; 3 | requires 'HTTP::Date'; 4 | requires 'Log::Minimal', '0.16'; 5 | requires 'List::MoreUtils'; 6 | requires 'Starlet', '0.21'; 7 | requires 'HTTP::Parser::XS', '0.16'; 8 | requires 'Proclet', '0.35'; 9 | requires 'Plack::Builder::Conditionals', '0.03'; 10 | requires 'JSON', 2; 11 | requires "JSON::XS"; 12 | requires 'Class::Accessor::Lite'; 13 | requires 'URI::Escape'; 14 | requires 'DBD::mysql'; 15 | 16 | requires 'boolean'; 17 | requires 'Cwd::Guard'; 18 | requires 'TOML'; 19 | requires 'Mouse'; 20 | requires 'YAML::XS'; 21 | requires 'Data::Validator'; 22 | requires 'Redis::Fast'; 23 | requires 'Parallel::Prefork'; 24 | requires 'File::Zglob'; 25 | requires 'Unicode::EastAsianWidth'; 26 | requires 'Text::MicroTemplate::DataSection'; 27 | requires 'Text::MicroTemplate::Extended'; 28 | requires 'Parallel::Scoreboard'; 29 | requires 'Furl'; 30 | 31 | on 'test' => sub { 32 | requires 'Test::More', '0.96'; 33 | requires 'Test::RedisServer'; 34 | }; 35 | 36 | 37 | -------------------------------------------------------------------------------- /etc/conf.d/sample.toml: -------------------------------------------------------------------------------- 1 | # kurodo_agent config 2 | 3 | # pluginの設定 4 | # 5 | # pluginは 6 | # [plugin.metrics.${plugin_name} 7 | # として設定する。ドットの数は2つである必要がある 8 | # 9 | # pluginから以下の形式で出力する 10 | # 11 | # metrics: 12 | # metrics.${name1}[.${name2}[.{gauge,counter,derive,absolute}]]\t${value}\t${timestamp} 13 | # meta: 14 | # meta.${name1}[.${name2}\t${text} 15 | # 16 | # keyの最初がmetricsやmetaではない場合、"metrics"が追加される 17 | # keyの最後が .{gauge,..} 等でなかった場合は、gaugeが使われる 18 | # 19 | # metaはmetricsの付属情報としてDBに保存される。サーバ情報などに使われる。履歴は残らない 20 | # 21 | 22 | [plugin.metrics.process] 23 | command = "echo -e \"fork.derive\t\"$(cat /proc/stat |grep processes | awk '{print $2}')\"\t\"$(date +%s)" 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /etc/conf.d/test.toml: -------------------------------------------------------------------------------- 1 | [plugin.metrics.dice] 2 | command = "perl -e 'print join(qq!\t!,q!stats.saikoro!,int(rand(6)),time).qq!\n!'" 3 | 4 | 5 | -------------------------------------------------------------------------------- /lib/Kurado.pm: -------------------------------------------------------------------------------- 1 | package Kurado; 2 | 3 | use 5.10.0; 4 | use strict; 5 | use warnings; 6 | 7 | our $VERSION = '0.01'; 8 | 9 | 1; 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/Kurado/Agent.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Agent; 2 | 3 | use 5.008005; 4 | use strict; 5 | use warnings; 6 | 7 | our $VERSION = '0.01'; 8 | 9 | 1; 10 | 11 | -------------------------------------------------------------------------------- /lib/Kurado/Agent/Config.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Agent::Config; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use Kurado::Agent::TOML; 7 | use boolean qw//; 8 | use Cwd::Guard qw/cwd_guard/; 9 | 10 | my $PARSER = Kurado::Agent::TOML->new; 11 | 12 | sub new { 13 | my $class = shift; 14 | my $path = shift; 15 | my $self = bless { 16 | plugins => { 17 | } 18 | }, $class; 19 | my $dir = cwd_guard($path); 20 | for my $inc_path ( glob '*.toml' ) { 21 | $self->parse_file($inc_path); 22 | } 23 | $self; 24 | } 25 | 26 | sub parse_file { 27 | my ($self, $path) = @_; 28 | my $ref = eval { 29 | $PARSER->parse_file($path); 30 | }; 31 | die "can't load config $path: $@\n" if $@; 32 | 33 | my $plugins = delete $ref->{plugin}; 34 | if ( $plugins && $plugins->{metrics} ) { 35 | for (keys %{$plugins->{metrics}}) { 36 | $self->{plugins}->{$_} = $plugins->{metrics}->{$_}->{command}; 37 | } 38 | } 39 | } 40 | 41 | sub plugins { $_[0]->{plugins} || {} }; 42 | 43 | 44 | 45 | 1; 46 | 47 | 48 | -------------------------------------------------------------------------------- /lib/Kurado/Agent/TOML.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Agent::TOML; 2 | 3 | use 5.008005; 4 | use strict; 5 | use warnings; 6 | use utf8; 7 | 8 | use boolean qw//; 9 | use TOML::Parser::Tokenizer qw/:constant/; 10 | use TOML::Parser::Util qw/unescape_str/; 11 | 12 | sub new { 13 | my $class = shift; 14 | my $args = (@_ == 1 and ref $_[0] eq 'HASH') ? +shift : +{ @_ }; 15 | return bless +{ 16 | inflate_datetime => sub { $_[0] }, 17 | inflate_boolean => sub { $_[0] eq 'true' ? boolean::true : boolean::false }, 18 | strict_mode => 0, 19 | %$args, 20 | } => $class; 21 | } 22 | 23 | sub parse_file { 24 | my ($self, $file) = @_; 25 | open my $fh, '<:encoding(utf-8)', $file or die $!; 26 | return $self->parse_fh($fh); 27 | } 28 | 29 | sub parse_fh { 30 | my ($self, $fh) = @_; 31 | my $src = do { local $/; <$fh> }; 32 | return $self->parse($src); 33 | } 34 | 35 | sub _tokenizer_class { 36 | my $self = shift; 37 | return 'TOML::Parser::Tokenizer'; 38 | } 39 | 40 | our @TOKENS; 41 | our $ROOT; 42 | our $CONTEXT; 43 | sub parse { 44 | my ($self, $src) = @_; 45 | 46 | local $ROOT = {}; 47 | local $CONTEXT = $ROOT; 48 | local @TOKENS = $self->_tokenizer_class->tokenize($src); 49 | return $self->_parse_tokens(); 50 | } 51 | 52 | sub _parse_tokens { 53 | my $self = shift; 54 | 55 | while (my $token = shift @TOKENS) { 56 | my ($type, $val) = @$token; 57 | if ($type eq TOKEN_TABLE) { 58 | $self->_parse_table($val); 59 | } 60 | elsif ($type eq TOKEN_ARRAY_OF_TABLE) { 61 | $self->_parse_array_of_table($val); 62 | } 63 | elsif ($type eq TOKEN_KEY) { 64 | my $token = shift @TOKENS; 65 | die "Duplicate key. key:$val" if exists $CONTEXT->{$val}; 66 | $CONTEXT->{$val} = $self->_parse_value_token($token); 67 | } 68 | elsif ($type eq TOKEN_COMMENT) { 69 | # pass through 70 | } 71 | else { 72 | die "Unknown case. type:$type"; 73 | } 74 | } 75 | 76 | return $CONTEXT; 77 | } 78 | 79 | sub _parse_table { 80 | my ($self, $key) = @_; 81 | 82 | local $CONTEXT = $ROOT; 83 | for my $k (split /\./, $key) { 84 | if (exists $CONTEXT->{$k}) { 85 | $CONTEXT = ref $CONTEXT->{$k} eq 'ARRAY' ? $CONTEXT->{$k}->[-1] : 86 | ref $CONTEXT->{$k} eq 'HASH' ? $CONTEXT->{$k} : 87 | die "invalid structure. $key cannot be `Table`"; 88 | } 89 | else { 90 | $CONTEXT = $CONTEXT->{$k} ||= +{}; 91 | } 92 | } 93 | 94 | $self->_parse_tokens(); 95 | } 96 | 97 | sub _parse_array_of_table { 98 | my ($self, $key) = @_; 99 | my @keys = split /\./, $key; 100 | my $last_key = pop @keys; 101 | 102 | local $CONTEXT = $ROOT; 103 | for my $k (@keys) { 104 | if (exists $CONTEXT->{$k}) { 105 | $CONTEXT = ref $CONTEXT->{$k} eq 'ARRAY' ? $CONTEXT->{$k}->[-1] : 106 | ref $CONTEXT->{$k} eq 'HASH' ? $CONTEXT->{$k} : 107 | die "invalid structure. $key cannot be `Array of table`."; 108 | } 109 | else { 110 | $CONTEXT = $CONTEXT->{$k} ||= +{}; 111 | } 112 | } 113 | 114 | $CONTEXT->{$last_key} = [] unless exists $CONTEXT->{$last_key}; 115 | die "invalid structure. $key cannot be `Array of table`" unless ref $CONTEXT->{$last_key} eq 'ARRAY'; 116 | push @{ $CONTEXT->{$last_key} } => $CONTEXT = {}; 117 | 118 | $self->_parse_tokens(); 119 | } 120 | 121 | sub _parse_value_token { 122 | my $self = shift; 123 | my $token = shift; 124 | 125 | my ($type, $val) = @$token; 126 | if ($type eq TOKEN_COMMENT) { 127 | return; # pass through 128 | } 129 | elsif ($type eq TOKEN_INTEGER || $type eq TOKEN_FLOAT) { 130 | return 0+$val; 131 | } 132 | elsif ($type eq TOKEN_BOOLEAN) { 133 | return $self->inflate_boolean($val); 134 | } 135 | elsif ($type eq TOKEN_DATETIME) { 136 | return $self->inflate_datetime($val); 137 | } 138 | elsif ($type eq TOKEN_STRING) { 139 | return unescape_str($val); 140 | } 141 | elsif ($type eq TOKEN_ARRAY_BEGIN) { 142 | my @data; 143 | while (my $token = shift @TOKENS) { 144 | last if $token->[0] eq TOKEN_ARRAY_END; 145 | push @data => $self->_parse_value_token($token); 146 | } 147 | return \@data; 148 | } 149 | else { 150 | die "Unknown case. type:$type"; 151 | } 152 | } 153 | 154 | sub inflate_datetime { 155 | my $self = shift; 156 | return $self->{inflate_datetime}->(@_); 157 | } 158 | 159 | sub inflate_boolean { 160 | my $self = shift; 161 | return $self->{inflate_boolean}->(@_); 162 | } 163 | 164 | 1; 165 | -------------------------------------------------------------------------------- /lib/Kurado/Config.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Config; 2 | 3 | use strict; 4 | use warnings; 5 | use Mouse; 6 | use Mouse::Util::TypeConstraints; 7 | use File::Basename; 8 | 9 | subtype 'Natural' 10 | => as 'Int' 11 | => where { $_ > 0 }; 12 | 13 | subtype 'Uint' 14 | => as 'Int' 15 | => where { $_ >= 0 }; 16 | 17 | subtype 'Flag' 18 | => as 'Int' 19 | => where { $_ <= 1 }; 20 | 21 | no Mouse::Util::TypeConstraints; 22 | 23 | sub load { 24 | my ($class,$ref,$path) = @_; 25 | $ref->{_path} = $path; 26 | __PACKAGE__->new($ref); 27 | } 28 | 29 | # path 30 | 31 | has '_path' => ( 32 | is => 'ro', 33 | isa => 'Str', 34 | required => 1 35 | ); 36 | 37 | # config 38 | 39 | has 'redis' => ( 40 | is => 'ro', 41 | isa => 'Str', 42 | default => '127.0.0.1:6379', 43 | ); 44 | 45 | has 'data_dir' => ( 46 | is => 'ro', 47 | isa => 'Str', 48 | default => 'data', 49 | ); 50 | 51 | has 'rolls_dir' => ( 52 | is => 'ro', 53 | isa => 'Str', 54 | default => 'sample_rolls', 55 | ); 56 | 57 | has 'metrics_plugin_dir' => ( 58 | is => 'ro', 59 | isa => 'ArrayRef[Str]', 60 | default => 'sub { [qw/metrics_plugins metrics_site_plugins/] }', 61 | ); 62 | 63 | # worker process numbers 64 | 65 | has 'web_worker' => ( 66 | is => 'ro', 67 | isa => 'Natural', 68 | default => 5, 69 | ); 70 | 71 | has 'update_worker' => ( 72 | is => 'ro', 73 | isa => 'Natural', 74 | default => 2, 75 | ); 76 | 77 | has 'fetch_worker' => ( 78 | is => 'ro', 79 | isa => 'Natural', 80 | default => 2, 81 | ); 82 | 83 | 84 | #rel2abs 85 | 86 | around ['data_dir','rolls_dir'] => sub { 87 | my $orig = shift; 88 | my $self = shift; 89 | my $dir = @_ ? $self->$orig(@_) : $self->$orig(); 90 | File::Spec->rel2abs($dir, File::Basename::dirname($self->_path) ); 91 | }; 92 | 93 | around 'metrics_plugin_dir' => sub { 94 | my $orig = shift; 95 | my $self = shift; 96 | my $dirs = @_ ? $self->$orig(@_) : $self->$orig(); 97 | [ map { File::Spec->rel2abs($_, File::Basename::dirname($self->_path)) } @$dirs ]; 98 | }; 99 | 100 | __PACKAGE__->meta->make_immutable(); 101 | 102 | sub dump { 103 | my $self = shift; 104 | my %dump; 105 | $dump{$_} = $self->$_ for qw/_path redis data_dir rolls_dir metrics_plugin_dir web_worker update_worker fetch_worker/; 106 | return \%dump; 107 | } 108 | 109 | 110 | 1; 111 | 112 | -------------------------------------------------------------------------------- /lib/Kurado/ConfigLoader.pm: -------------------------------------------------------------------------------- 1 | package Kurado::ConfigLoader; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use YAML::XS qw//; 7 | use JSON::XS; 8 | use File::Spec; 9 | use Mouse; 10 | use Unicode::EastAsianWidth; 11 | 12 | use Kurado::Config; 13 | use Kurado::Object::Host; 14 | use Kurado::Object::Roll; 15 | use Kurado::Object::Plugin; 16 | use Kurado::Plugin::Compile; 17 | 18 | has 'path' => ( 19 | is => 'ro', 20 | isa => 'Str', 21 | required => 1 22 | ); 23 | 24 | __PACKAGE__->meta->make_immutable(); 25 | 26 | 27 | sub yaml_head { 28 | my $ref = shift; 29 | my $dump = YAML::XS::Dump($ref); 30 | chomp($dump); 31 | my @dump = split /\n/, $dump; 32 | join("\n", map { "> $_"} splice(@dump,0,8), (@dump > 8 ? "..." : ""))."\n"; 33 | } 34 | 35 | sub BUILD { 36 | my ($self) = @_; 37 | $self->{_roll_cache} = {}; 38 | $self->{load_plugins} = {}; 39 | $self->{service_hosts} = {}; 40 | $self->parse_file(); 41 | } 42 | 43 | sub parse_file { 44 | my ($self) = @_; 45 | my $path = $self->{path}; 46 | my @configs = eval { 47 | YAML::XS::LoadFile($path); 48 | }; 49 | die "Failed to load $path: $@" if $@; 50 | 51 | for ( @configs ) { 52 | if ( ! ref $_ || ref $_ ne "HASH" ) { 53 | die "config shuold be HASH(or dictionary):\n" . yaml_head($_); 54 | } 55 | } 56 | 57 | my $main_config = shift @configs; 58 | $self->parse_main_config($main_config); 59 | 60 | $self->{services} = {}; 61 | for my $service_config ( @configs ) { 62 | $self->parse_service_config($service_config); 63 | } 64 | } 65 | 66 | sub parse_main_config { 67 | my ($self, $config) = @_; 68 | 69 | my $main_config = $config->{config}; 70 | die "There is no 'config' in:\n".yaml_head($config) unless $main_config; 71 | eval { 72 | $self->{config} = Kurado::Config->load($main_config, $self->{path}); 73 | }; 74 | die "failed to config: $@\n===\n".yaml_head($config) if $@; 75 | 76 | $self->{metrics_config} = $config->{metrics_config}; 77 | $self->{metrics_config} ||= {}; 78 | die "metrics_config should be HASH(or dictionary)\n".yaml_head($self->{metrics_config}) 79 | if ! ref $self->{metrics_config} || ! ref $self->{metrics_config} eq 'HASH'; 80 | } 81 | 82 | sub parse_service_config { 83 | my ($self,$config) = @_; 84 | my $service = $config->{service}; 85 | die "There is no 'service' in:\n".yaml_head($config) unless $service; 86 | die "found duplicated service '$service'".yaml_head($config) if exists $self->{services}->{$service}; 87 | my $servers_config = $config->{servers}; 88 | $servers_config ||= []; 89 | die "metrics_config should be Array\n".yaml_head($servers_config) 90 | if ! ref $servers_config || ! ref $servers_config eq 'ARRAY'; 91 | my @sections; 92 | my %labels; 93 | for my $server_config ( @$servers_config ) { 94 | my $roll = $server_config->{roll} 95 | or die "cannot find roll in service:$service servers:".yaml_head($server_config); 96 | my $hosts = $server_config->{hosts} || []; 97 | my $label = $server_config->{label} // ''; 98 | 99 | # lebel の2重チェック 100 | if ( $label ) { 101 | die "found duplicated label '$label'".yaml_head($config) if exists $labels{$label}; 102 | } 103 | 104 | my @hosts; 105 | for my $host_line ( @$hosts ) { 106 | my $host = eval { 107 | $self->parse_host( $host_line, $roll, $service ); 108 | }; 109 | die "$@".yaml_head($config) if $@; 110 | $self->{service_hosts}{$service}++; 111 | push @hosts, $host; 112 | } 113 | 114 | if ( @sections && !$label ) { 115 | push @{$sections[-1]->{hosts}}, @hosts; 116 | next; 117 | } 118 | 119 | push @sections, { 120 | label => $label, 121 | hosts => \@hosts, 122 | }; 123 | $labels{$label} = 1; 124 | } 125 | 126 | $self->{services}->{$service} = \@sections; 127 | } 128 | 129 | sub parse_host { 130 | my ($self, $line, $roll_name, $service) = @_; 131 | 132 | my ( $address, $hostname, $comments ) = split /\s+/, $line, 3; 133 | die "cannot find address in '$line'\n" unless $address; 134 | $hostname //= $address; 135 | $comments //= ""; 136 | die "duplicated host entry address $address in '$line'\n" if exists $self->{hosts}{$address}; 137 | 138 | my $roll = $self->load_roll( $roll_name ); 139 | $self->{hosts}{$address} = Kurado::Object::Host->new( 140 | address => $address, 141 | hostname => $hostname, 142 | comments => $comments, 143 | roll => $roll_name, 144 | metrics_config => $roll->metrics_config, 145 | plugins => $roll->plugins, 146 | service => $service 147 | ); 148 | $self->{hosts}{$address}; 149 | } 150 | 151 | sub load_roll { 152 | my ($self, $roll_name) = @_; 153 | # cache 154 | return $self->{_roll_cache}{$roll_name} if $self->{_roll_cache}{$roll_name}; 155 | my $path = File::Spec->catfile($self->config->rolls_dir, $roll_name); 156 | my ($roll_config) = eval { 157 | YAML::XS::LoadFile($path); 158 | }; 159 | die "Failed to load roll $path: $@" if $@; 160 | if ( ! ref $roll_config || ref $roll_config ne "HASH" ) { 161 | die "roll config shuold be HASH(or dictionary):\n" . yaml_head($roll_config); 162 | } 163 | my $metrics_config = $self->merge_metrics_config($roll_config->{metrics_config} || {}); 164 | my @plugins; 165 | for ( @{$roll_config->{metrics} || []} ) { 166 | push @plugins, $self->parse_plugin($_); 167 | } 168 | 169 | $self->{_roll_cache}{$roll_name} = Kurado::Object::Roll->new( 170 | metrics_config => $metrics_config, 171 | plugins => \@plugins 172 | ); 173 | $self->{_roll_cache}{$roll_name}; 174 | } 175 | 176 | sub parse_plugin { 177 | my ($self, $line) = @_; 178 | my ( $plugin, @arguments ) = split /:/, $line; 179 | die "cannot find plugin name: in '$line'\n" unless $plugin; 180 | 181 | # compile plugin 182 | my $pc = Kurado::Plugin::Compile->new(config=>$self->config); 183 | my @loaded_plugins; 184 | for my $type (qw/view fetch/) { 185 | my $compiled = eval { 186 | $pc->compile( 187 | plugin => $plugin, 188 | type => $type, 189 | ); 190 | }; 191 | die "failed load plugin plugin:$plugin,type:$type $@\n" if $@; 192 | push @loaded_plugins, $type if $compiled; 193 | } 194 | die "Could not find plugin '$plugin'\n" if @loaded_plugins == 0; 195 | $self->{load_plugins}{$plugin} = \@loaded_plugins; 196 | return Kurado::Object::Plugin->new( 197 | plugin => $plugin, 198 | arguments => \@arguments, 199 | ); 200 | } 201 | 202 | sub config { 203 | $_[0]->{config}; 204 | } 205 | 206 | sub metrics_config { 207 | $_[0]->{metrics_config}; 208 | } 209 | 210 | my $_JSON = JSON::XS->new->utf8; 211 | sub merge_metrics_config { 212 | my ($self,$ref) = @_; 213 | $_JSON->decode($_JSON->encode({ 214 | %{$self->{metrics_config}}, 215 | %$ref 216 | })); 217 | } 218 | 219 | sub services { 220 | $_[0]->{services}; 221 | } 222 | 223 | sub sorted_services { 224 | my $self = shift; 225 | [ 226 | map {{ 227 | service => $_, 228 | sections => $self->services->{$_}, 229 | host_num => $self->{service_hosts}{$_}, 230 | }} sort { lc($a) cmp lc($b) } keys %{$self->services} 231 | ]; 232 | } 233 | 234 | sub hosts { 235 | $_[0]->{hosts}; 236 | } 237 | 238 | sub host_by_address { 239 | my ($self,$address) = @_; 240 | return unless exists $self->{hosts}{$address}; 241 | $self->{hosts}{$address}; 242 | } 243 | 244 | sub plugins { 245 | my $self = shift; 246 | [ keys %{$self->{load_plugins}} ]; 247 | } 248 | 249 | sub has_fetch_plugin { 250 | my ($self,$plugin) = @_; 251 | exists $self->{load_plugins}{$plugin} && grep { $_ eq 'fetch' } @{$self->{load_plugins}{$plugin}}; 252 | } 253 | 254 | sub dump { 255 | my $self = shift; 256 | +{ 257 | config => $self->config->dump, 258 | metrics_config => $self->metrics_config, 259 | services => $self->sorted_services, 260 | } 261 | } 262 | 263 | sub zlength { 264 | my $str = shift; 265 | my $width = 0; 266 | while ($str =~ m/(?:(\p{InFullwidth}+)|(\p{InHalfwidth}+))/go) { 267 | $width += ($1 ? length($1) * 2 : length($2)); 268 | } 269 | $width; 270 | } 271 | 272 | sub statistics { 273 | my $self = shift; 274 | $self->sorted_services; 275 | my ($maxlen) = sort { $b <=> $a } map { zlength($_) } keys %{$self->services}; 276 | 277 | $maxlen += 2; 278 | $maxlen = 50 if $maxlen < 50; 279 | my $body = "# REGISTERED HOSTS\n"; 280 | $body .= "-". "-"x$maxlen . "+" ."-------\n"; 281 | $body .= " SERVICE" . (" "x($maxlen-7)) . "| HOSTS \n"; 282 | $body .= "-". "-"x$maxlen . "+" ."-------\n"; 283 | for my $service (@{$self->sorted_services}) { 284 | $body .= " " . $service->{service} . (" "x($maxlen - zlength($service->{service}))) . '| ' . sprintf('% 5d',$service->{host_num}) . " \n" 285 | } 286 | $body .= "-"."-"x$maxlen . "+" ."-------\n"; 287 | 288 | $body .= "\n# LOADED PLUGINS\n"; 289 | 290 | $body .= " PLUGIN" . (" "x($maxlen-6)) . "| TYPE \n"; 291 | $body .= "-". "-"x$maxlen . "+" ."-------\n"; 292 | for my $load_plugin (keys %{$self->{load_plugins}}) { 293 | for my $type ( @{$self->{load_plugins}{$load_plugin}} ) { 294 | $body .= " " 295 | . $load_plugin 296 | . (" "x($maxlen - zlength($load_plugin))) 297 | . '| ' 298 | . sprintf('% 5s',$type) . " \n" 299 | } 300 | } 301 | $body .= "-"."-"x$maxlen . "+" ."-------\n"; 302 | 303 | return "$body\n"; 304 | } 305 | 306 | 1; 307 | 308 | -------------------------------------------------------------------------------- /lib/Kurado/Host.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Host; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use 5.10.0; 7 | use Mouse; 8 | use Log::Minimal; 9 | use Data::Validator; 10 | 11 | use Kurado::Plugin::Compile; 12 | use Kurado::Storage; 13 | use Kurado::RRD; 14 | 15 | use Text::Xslate qw/mark_raw/; 16 | 17 | has 'config_loader' => ( 18 | is => 'ro', 19 | isa => 'Kurado::ConfigLoader', 20 | required => 1 21 | ); 22 | 23 | has 'host' => ( 24 | is => 'ro', 25 | isa => 'Kurado::Object::Host', 26 | required => 1 27 | ); 28 | 29 | __PACKAGE__->meta->make_immutable(); 30 | 31 | our $LAST_RECEIVED_EXPIRES = 300; 32 | 33 | sub config { 34 | $_[0]->config_loader->config; 35 | } 36 | 37 | sub plugins { 38 | my $self = shift; 39 | $self->host->plugins; 40 | } 41 | 42 | sub address { 43 | my $self = shift; 44 | $self->host->address; 45 | } 46 | 47 | sub hostname { 48 | my $self = shift; 49 | $self->host->hostname; 50 | } 51 | 52 | sub service { 53 | my $self = shift; 54 | $self->host->service; 55 | } 56 | 57 | sub comments { 58 | my $self = shift; 59 | $self->host->comments; 60 | } 61 | 62 | sub compile { 63 | my $self = shift; 64 | $self->{compile} ||= Kurado::Plugin::Compile->new(config=>$self->config); 65 | } 66 | 67 | sub storage { 68 | my $self = shift; 69 | $self->{storage} ||= Kurado::Storage->new(redis=>$self->config->redis); 70 | } 71 | 72 | sub metrics_list { 73 | my $self = shift; 74 | 75 | my @list; 76 | for my $plugin (@{$self->plugins}) { 77 | my $warn = $self->storage->get_warn_by_plugin( 78 | plugin => $plugin, 79 | address => $self->address, 80 | ); 81 | 82 | my $last_received = $self->storage->get_last_recieved( 83 | plugin => $plugin, 84 | address => $self->address, 85 | ); 86 | if ( (!$last_received || $last_received < time - $LAST_RECEIVED_EXPIRES ) 87 | && !$self->config_loader->has_fetch_plugin($plugin->plugin) ) { 88 | $warn->{'__system__'} = 'Metrics are not updated in the last 5 minutes. This host or kurado_agent has been down'; 89 | $warn->{'__system__'} .= '.last updated: ' . localtime($last_received) if $last_received; 90 | } 91 | 92 | #run list 93 | my $metrics = eval { 94 | my ($stdout, $stderr, $success) = $self->compile->run( 95 | host => $self->host, 96 | plugin => $plugin, 97 | type => 'view', 98 | ); 99 | die "$stderr\n" unless $success; 100 | warnf $stderr if $stderr; 101 | $self->parse_metrics_list($stdout); 102 | }; 103 | if ( $@ ) { 104 | $warn->{_exec_plugin_} = $@; 105 | } 106 | push @list, { 107 | plugin => $plugin, 108 | warn => $warn, 109 | metrics => $metrics 110 | }; 111 | } 112 | \@list; 113 | } 114 | 115 | # # uptime up 0 days, 8:56 116 | # # Traffic(eth0) 117 | # traffic-eth0 118 | # # CPU Memory 119 | # cpu 120 | # load-avg 121 | # memory-usage 122 | # tcp-established 123 | # # Disk Usage(/) 124 | # disk-usage-mapper_VolGroup-lv_root 125 | # # Disk Usage(mapper_VolGroup-lv_root) 126 | # disk-io-mapper_VolGroup-lv_root 127 | 128 | sub parse_metrics_list { 129 | my $self = shift; 130 | my $list = shift; 131 | my @metrics; 132 | for my $line ( split /\n/, $list ) { 133 | next unless $line; 134 | # comment // だけ。# はタイトルで使っている 135 | next if $line =~ m!^\s*//!; 136 | if ( $line =~ m/^#/ ) { 137 | $line =~ s!^# *!!g; 138 | $line =~ s! *$!!g; 139 | my ($label, @args) = split /\t/,$line; 140 | #die "odd number of metrics_list meta in '# $line'" if @args % 2; 141 | my @meta; 142 | while ( @args ) { 143 | my $key = shift(@args); 144 | my $val = shift(@args); 145 | if ( $val =~ m!>\|\|! ) { #keyの頭が >|| から始まっている場合HTML escapeせずにそのまま表示 146 | $val =~ s!^>\|\|!!; 147 | $val = mark_raw($val); 148 | } 149 | push @meta, {key=>$key,value=>$val}; 150 | } 151 | my %meta = @args; 152 | # label 153 | push @metrics, { 154 | graphs => [], 155 | label => $label, 156 | meta => \@meta 157 | }; 158 | } 159 | else { 160 | $line =~ s!^ *!!g; 161 | $line =~ s! *$!!g; 162 | if (!@metrics) { 163 | push @metrics, { 164 | graphs => [$line], 165 | label => "", 166 | meta => [], 167 | }; 168 | } 169 | else { 170 | push @{$metrics[-1]{graphs}}, $line; 171 | } 172 | } 173 | } 174 | return \@metrics; 175 | } 176 | 177 | sub metrics_graph { 178 | state $rule = Data::Validator->new( 179 | graph => 'Str', 180 | plugin => 'Kurado::Object::Plugin', 181 | term => 'Str', 182 | from => 'Str', 183 | to => 'Str', 184 | width => 'Str' 185 | )->with('Method'); 186 | my ($self, $args) = $rule->validate(@_); 187 | my ($img,$data); 188 | eval { 189 | my ($stdout, $stderr, $success) = $self->compile->run( 190 | host => $self->host, 191 | plugin => $args->{plugin}, 192 | type => 'view', 193 | graph => $args->{graph}, 194 | ); 195 | die "$stderr\n" unless $success; 196 | warnf $stderr if $stderr; 197 | my $rrd = Kurado::RRD->new(data_dir => $self->config->data_dir); 198 | ($img,$data) = $rrd->graph( 199 | def => $stdout, 200 | host => $self->host, 201 | plugin => $args->{plugin}, 202 | term => $args->{term}, 203 | from => $args->{from}, 204 | to => $args->{to}, 205 | width => $args->{width}, 206 | ); 207 | }; 208 | die sprintf('address:%s plugin:%s graph:%s: %s'."\n",$self->host->address, $args->{plugin}->plugin, $args->{graph}, $@) if $@; 209 | return ($img,$data); 210 | } 211 | 212 | 213 | sub fetch_metrics { 214 | state $rule = Data::Validator->new( 215 | plugin => 'Kurado::Object::Plugin', 216 | )->with('Method'); 217 | my ($self, $args) = $rule->validate(@_); 218 | my $body = ''; 219 | eval { 220 | my ($stdout, $stderr, $success) = $self->compile->run( 221 | host => $self->host, 222 | plugin => $args->{plugin}, 223 | type => 'fetch', 224 | ); 225 | die "$stderr\n" unless $success; 226 | warnf sprintf('[%s][%s] %s',$self->host->address, $args->{plugin}->plugin, $stderr) if $stderr; 227 | $body .= $self->parse_fetched_metrics( 228 | plugin => $args->{plugin}, 229 | result => $stdout 230 | ); 231 | }; 232 | if ( $@ ) { 233 | my $warn = $@; 234 | $warn =~ s!(?:\n|\r)!!g; 235 | my $time = time; 236 | my $plugin_key = $args->{plugin}->plugin_identifier_escaped; 237 | my $self_ip = $self->host->address; 238 | $body .= "$self_ip\t$plugin_key.warn.command\t$warn\t$time\n"; 239 | } 240 | $body; 241 | } 242 | 243 | sub parse_fetched_metrics { 244 | state $rule = Data::Validator->new( 245 | plugin => 'Kurado::Object::Plugin', 246 | result => 'Str', 247 | )->with('Method'); 248 | my ($self, $args) = $rule->validate(@_); 249 | my $time = time; 250 | my $result = $args->{result}; 251 | my $plugin_key = $args->{plugin}->plugin_identifier_escaped; 252 | my $self_ip = $self->host->address; 253 | my $body = ''; 254 | for my $ret (split /\n/, $result) { 255 | chomp($ret); 256 | # comment 257 | next if ($ret =~ m!^\s*#! || $ret =~ m!^\s*//!); 258 | my @ret = split /\t/,$ret; 259 | if ( $ret[0] !~ m!^(?:metrics|meta)\.! ) { 260 | $ret[0] = "metrics.$ret[0]"; 261 | } 262 | if ( $ret[0] =~ m!^metrics\.! && $ret[0] !~ m!\.(?:gauge|counter|derive|absolute)$! ) { 263 | $ret[0] = "$ret[0].gauge"; 264 | } 265 | $ret[0] = "$plugin_key.$ret[0]"; 266 | $ret[2] ||= $time; 267 | $body .= join("\t", $self_ip, @ret[0,1,2])."\n"; 268 | } 269 | $body; 270 | } 271 | 272 | # 2 = critical 273 | # 1 = warn 274 | # 0 = ok 275 | 276 | sub status { 277 | my $self = shift; 278 | 279 | if ( my ($base_plugin) = $self->host->has_plugin('base') ) { 280 | my $last_received = $self->storage->get_last_recieved( 281 | plugin => $base_plugin, 282 | address => $self->address, 283 | ); 284 | if ( !$last_received || $last_received < time - $LAST_RECEIVED_EXPIRES ) { 285 | return 2; 286 | } 287 | } 288 | 289 | my $has_warn = $self->storage->has_warn( 290 | address => $self->address, 291 | ); 292 | return 1 if $has_warn; 293 | 294 | for my $plugin ( @{$self->host->plugins} ) { 295 | next if $self->config_loader->has_fetch_plugin($plugin->plugin); 296 | my $last_received = $self->storage->get_last_recieved( 297 | plugin => $plugin, 298 | address => $self->address, 299 | ); 300 | if ( !$last_received || $last_received < time - $LAST_RECEIVED_EXPIRES ) { 301 | return 1; 302 | } 303 | } 304 | 305 | return 0; 306 | } 307 | 308 | 1; 309 | 310 | 311 | -------------------------------------------------------------------------------- /lib/Kurado/Metrics.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Metrics; 2 | 3 | use strict; 4 | use warnings; 5 | use Mouse; 6 | use Log::Minimal; 7 | use URI::Escape; 8 | 9 | use Kurado::RRD; 10 | use Kurado::Storage; 11 | use Kurado::Object::Msg; 12 | 13 | has 'config' => ( 14 | is => 'ro', 15 | isa => 'Kurado::Config', 16 | required => 1 17 | ); 18 | 19 | __PACKAGE__->meta->make_immutable(); 20 | 21 | sub process_message { 22 | my ($self,$message) = @_; 23 | my $rrd = Kurado::RRD->new(data_dir=>$self->config->data_dir); 24 | my $storage = Kurado::Storage->new(redis=>$self->config->redis); 25 | my %uniq_address_plugin; 26 | foreach my $line ( split /\n/, $message ) { 27 | chomp $line; 28 | # comment 29 | next if ($line =~ m!^\s*#! || $line =~ m!^\s*//!); 30 | my $msg = eval { 31 | $self->parse_metrics_line($line); 32 | }; 33 | if ($@) { 34 | warnf("'$line' has error '$@'. ignore it"); 35 | next; 36 | } 37 | my $key = $msg->address ."\t".$msg->plugin->plugin_identifier; 38 | $uniq_address_plugin{$key} = $msg; 39 | if ( $msg->metrics_type eq 'metrics' ) { 40 | # rrd update 41 | #debugf("rrd update %s",$msg); 42 | eval { 43 | $rrd->update(msg=>$msg); 44 | }; 45 | critf('failed update rrd %s : %s', $msg, $@) if $@; 46 | } 47 | elsif ( $msg->metrics_type eq 'meta' ) { 48 | # update storage 49 | $storage->set( 50 | msg => $msg, 51 | expires => 60*60 52 | ); 53 | } 54 | elsif ( $msg->metrics_type eq 'warn' ) { 55 | # update storage 56 | $storage->set_warn(msg => $msg); 57 | } 58 | } 59 | 60 | for my $key (keys %uniq_address_plugin) { 61 | $storage->set_last_recieved( 62 | msg => $uniq_address_plugin{$key}, 63 | ); 64 | } 65 | 66 | } 67 | 68 | sub parse_metrics_line { 69 | my ($self, $line) = @_; 70 | my @msg = split /\t/, $line; 71 | if ( @msg != 4 ) { 72 | die "msg does not have 4 column. ignore it\n"; 73 | } 74 | my ($address, $key, $value, $timestamp) = @msg; 75 | 76 | # base.metrics.tcp-established.gauge 77 | # base.meta.traffic-interfaces 78 | # base.warn.traffic-interfaces 79 | 80 | my ($plugin, $type, $metrics_key) = split /\./, $key, 3; 81 | if ( !$type || !$metrics_key ) { 82 | die "key does not contains two dot\n"; 83 | } 84 | 85 | if ( $type !~ m!^(?:metrics|meta|warn)$! ) { 86 | die "invalid metrics-type\n"; 87 | } 88 | 89 | if ( $type eq 'metrics' && $metrics_key !~ m!\.(?:gauge|counter|derive|absolute)$! ) { 90 | die "invalid rrd data type\n"; 91 | } 92 | 93 | my $obj_plugin = Kurado::Object::Plugin->new_from_identifier(uri_unescape($plugin)); 94 | return Kurado::Object::Msg->new( 95 | address => $address, 96 | plugin => $obj_plugin, 97 | metrics_type => $type, 98 | key => $metrics_key, 99 | value => $value, 100 | timestamp => $timestamp 101 | ); 102 | } 103 | 104 | 1; 105 | 106 | -------------------------------------------------------------------------------- /lib/Kurado/Object/Host.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Object::Host; 2 | 3 | use strict; 4 | use warnings; 5 | use 5.10.0; 6 | use Mouse; 7 | use JSON::XS; 8 | 9 | has 'address' => ( 10 | is => 'ro', 11 | isa => 'Str', 12 | required => 1 13 | ); 14 | 15 | has 'hostname' => ( 16 | is => 'ro', 17 | isa => 'Str', 18 | required => 1 19 | ); 20 | 21 | has 'comments' => ( 22 | is => 'ro', 23 | isa => 'Str', 24 | required => 1 25 | ); 26 | 27 | has 'roll' => ( 28 | is => 'ro', 29 | isa => 'Str', 30 | required => 1 31 | ); 32 | 33 | has 'metrics_config' => ( 34 | is => 'ro', 35 | isa => 'HashRef[Any]', 36 | required => 1 37 | ); 38 | 39 | has 'plugins' => ( 40 | is => 'ro', 41 | isa => 'ArrayRef[Kurado::Object::Plugin]', 42 | required => 1 43 | ); 44 | 45 | has 'service' => ( 46 | is => 'ro', 47 | isa => 'Str', 48 | required => 1 49 | ); 50 | 51 | __PACKAGE__->meta->make_immutable(); 52 | 53 | sub has_plugin { 54 | my $self = shift; 55 | my $plugin = shift; 56 | grep { $_->plugin eq $plugin } @{$self->plugins} 57 | } 58 | 59 | sub TO_JSON { 60 | my $self = shift; 61 | +{map { ($_ => $self->$_) } qw/address hostname comments roll metrics_config plugins/}; 62 | } 63 | 64 | 1; 65 | 66 | -------------------------------------------------------------------------------- /lib/Kurado/Object/Msg.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Object::Msg; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use 5.10.0; 7 | use Mouse; 8 | 9 | has 'plugin' => ( 10 | is => 'ro', 11 | isa => 'Kurado::Object::Plugin', 12 | required => 1 13 | ); 14 | 15 | has 'address' => ( 16 | is => 'ro', 17 | isa => 'Str', 18 | required => 1 19 | ); 20 | 21 | has 'metrics_type' => ( 22 | is => 'ro', 23 | isa => 'Str', 24 | required => 1 25 | ); 26 | 27 | has 'key' => ( 28 | is => 'ro', 29 | isa => 'Str', 30 | required => 1 31 | ); 32 | 33 | has 'value' => ( 34 | is => 'ro', 35 | isa => 'Str', 36 | required => 1 37 | ); 38 | 39 | has 'timestamp' => ( 40 | is => 'ro', 41 | isa => 'Str', 42 | required => 1 43 | ); 44 | 45 | 46 | __PACKAGE__->meta->make_immutable(); 47 | 48 | 49 | 1; 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/Kurado/Object/Plugin.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Object::Plugin; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use 5.10.0; 7 | use Mouse; 8 | use JSON::XS; 9 | use URI::Escape; 10 | 11 | has 'plugin' => ( 12 | is => 'ro', 13 | isa => 'Str', 14 | required => 1 15 | ); 16 | 17 | has 'arguments' => ( 18 | is => 'ro', 19 | isa => 'ArrayRef[Str]', 20 | required => 1 21 | ); 22 | 23 | __PACKAGE__->meta->make_immutable(); 24 | 25 | sub new_from_identifier { 26 | my $class = shift; 27 | my $identifier = shift; 28 | my ($plugin, @args) = split /:/, $identifier; 29 | $class->new( 30 | plugin => $plugin, 31 | arguments => \@args, 32 | ); 33 | } 34 | 35 | 36 | sub plugin_identifier { 37 | my $self = shift; 38 | my $str = $self->plugin . ((@{$self->arguments}) ? ':'.join(":",@{$self->arguments}) : ''); 39 | $str; 40 | } 41 | 42 | sub plugin_identifier_short { 43 | my $self = shift; 44 | my $str = $self->plugin_identifier; 45 | if ( length $str <= 27 ) { 46 | return $str; 47 | } 48 | substr($str,0,25) . '..'; 49 | } 50 | 51 | 52 | sub plugin_identifier_escaped { 53 | my $self = shift; 54 | uri_escape($self->plugin_identifier, "^A-Za-z0-9\-_"); #escape dot 55 | } 56 | 57 | sub TO_JSON { 58 | my $self = shift; 59 | +{map { ($_ => $self->$_) } qw/plugin arguments/}; 60 | } 61 | 62 | 1; 63 | 64 | 65 | -------------------------------------------------------------------------------- /lib/Kurado/Object/Roll.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Object::Roll; 2 | 3 | use strict; 4 | use warnings; 5 | use 5.10.0; 6 | use Mouse; 7 | use JSON::XS; 8 | 9 | has 'metrics_config' => ( 10 | is => 'ro', 11 | isa => 'HashRef[Any]', 12 | required => 1 13 | ); 14 | 15 | has 'plugins' => ( 16 | is => 'ro', 17 | isa => 'ArrayRef[Kurado::Object::Plugin]', 18 | required => 1 19 | ); 20 | 21 | __PACKAGE__->meta->make_immutable(); 22 | 23 | sub TO_JSON { 24 | my $self = shift; 25 | +{map { ($_ => $self->$_) } qw/metrics_config plugins/}; 26 | } 27 | 28 | 29 | 1; 30 | 31 | -------------------------------------------------------------------------------- /lib/Kurado/Plugin.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Plugin; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use 5.10.0; 7 | use Getopt::Long; 8 | use Pod::Usage; 9 | use JSON::XS; 10 | use Text::MicroTemplate::DataSectionEx; 11 | 12 | my $_JSON = JSON::XS->new->utf8; 13 | 14 | our %BRIDGE = (); 15 | 16 | sub new { 17 | my $class = shift; 18 | my @args = @_; 19 | my @caller = caller; 20 | # ($package, $filename, $line) = caller; 21 | my $self = bless { 22 | caller => \@caller, 23 | args => \@args, 24 | }, $class; 25 | $self->parse_options(); 26 | $self; 27 | } 28 | 29 | sub parse_options { 30 | my $self = shift; 31 | local @ARGV = @{$self->{args}}; 32 | 33 | my $parser = Getopt::Long::Parser->new( 34 | config => [ "no_auto_abbrev", "no_ignore_case", "pass_through" ], 35 | ); 36 | 37 | $parser->getoptions( 38 | "address=s" => \$self->{address}, 39 | "hostname=s" => \$self->{hostname}, 40 | "comments=s" => \my @comments, 41 | "plugin-arguments=s" => \my @plugin_arguments, 42 | "graph=s" => \$self->{graph}, 43 | "h|help" => \my $help, 44 | "v|version" => \my $version, 45 | "metrics-config-json=s" => \$self->{metrics_config_json}, 46 | "metrics-meta-json=s" => \$self->{metrics_meta_json}, 47 | ); 48 | 49 | my $plugin_version = eval '$'.$self->{caller}->[0]."::VERSION"; 50 | $plugin_version //= 'unknown'; 51 | 52 | if ( $version ) { 53 | print "display/base.pl version $plugin_version\n"; 54 | print "Try `$self->{caller}[1] --help` for more options.\n\n"; 55 | exit 0; 56 | } 57 | 58 | if ( $help ) { 59 | pod2usage({ 60 | -verbose => 99, 61 | -exitval => 'noexit', 62 | -output => *STDOUT, 63 | }); 64 | exit(0); 65 | } 66 | if ( !$self->{address} || !$self->{hostname} ) { 67 | pod2usage({ 68 | -msg => 'ERR: address and hostname are required', 69 | -verbose => 1, 70 | -exitval => 'noexit', 71 | -output => *STDERR, 72 | }); 73 | exit(2); 74 | } 75 | 76 | $self->{comments} = \@comments; 77 | $self->{plugin_arguments} = \@plugin_arguments; 78 | } 79 | 80 | sub address { 81 | $_[0]->{address}; 82 | } 83 | 84 | sub hostname { 85 | $_[0]->{hostname}; 86 | } 87 | 88 | sub comments { 89 | $_[0]->{comments}; 90 | } 91 | 92 | sub plugin_arguments { 93 | $_[0]->{plugin_arguments}; 94 | } 95 | 96 | sub graph { 97 | $_[0]->{graph}; 98 | } 99 | 100 | use Log::Minimal; 101 | 102 | sub meta_config { 103 | my ($self,$key) = @_; 104 | 105 | return $self->{$key} if $self->{$key}; 106 | if ( $self->{"${key}_json"} ) { 107 | if ( $self->{"${key}_json"} =~ m!^{! ) { 108 | $self->{$key} = eval { $_JSON->decode($self->{"${key}_json"}) }; 109 | die $@ if $@; 110 | } 111 | else { 112 | $self->{$key} = eval { 113 | open(my $fh, '<', $self->{"${key}_json"}) or die $!; 114 | my $json_text = do { local $/; <$fh> }; 115 | $_JSON->decode($json_text) 116 | }; 117 | die $@ if $@; 118 | } 119 | } 120 | elsif ( $BRIDGE{"kurado.${key}"} && ref $BRIDGE{"kurado.${key}"}) { 121 | $self->{$key} = $BRIDGE{"kurado.${key}"}; 122 | } 123 | elsif ( exists $ENV{"kurado.${key}_json"} ) { 124 | $self->{$key} = eval { $_JSON->decode($ENV{"kurado.${key}_json"}) }; 125 | die $@ if $@; 126 | } 127 | else { 128 | $self->{$key} = {}; 129 | } 130 | $self->{$key} 131 | } 132 | 133 | sub metrics_config { 134 | my $self = shift; 135 | $self->meta_config('metrics_config'); 136 | } 137 | 138 | sub metrics_meta { 139 | my $self = shift; 140 | $self->meta_config('metrics_meta'); 141 | } 142 | 143 | sub uptime2str { 144 | my $self = shift; 145 | my $uptime = shift; 146 | my $day = int( $uptime /86400 ); 147 | my $hour = int( ( $uptime % 86400 ) / 3600 ); 148 | my $min = int( ( ( $uptime % 86400 ) % 3600) / 60 ); 149 | sprintf("up %d days, %2d:%02d", $day, $hour, $min); 150 | } 151 | 152 | sub unit { 153 | my $self = shift; 154 | my $n = shift; 155 | my($base, $unit); 156 | 157 | return $n unless $n =~ /^\d+$/; 158 | if ($n >= 1073741824) { 159 | $base = 1073741824; 160 | $unit = 'GB'; 161 | } elsif ($n >= 1048576) { 162 | $base = 1048576; 163 | $unit = 'MB'; 164 | } elsif ($n >= 1024) { 165 | $base = 1024; 166 | $unit = 'KB'; 167 | } else { 168 | $base = 1; 169 | $unit = 'B'; 170 | } 171 | 172 | $n = sprintf '%.2f', $n/$base; 173 | while($n =~ s/(.*\d)(\d\d\d)/$1,$2/){}; 174 | return $n.$unit; 175 | } 176 | 177 | my @info_order = ( 178 | [qr/^version$/i => 1], 179 | [qr/^uptime$/i => 2], 180 | [qr/^version/i => 3], 181 | [qr/^uptime/i => 4], 182 | [qr/version$/i => 5], 183 | [qr/uptime$/i => 6], 184 | ); 185 | sub match_order { 186 | my $key = shift; 187 | my ($hit) = grep { $key =~ m!$_->[0]! } @info_order; 188 | return 999 unless $hit; 189 | $hit->[1]; 190 | } 191 | sub sort_info { 192 | my $self = shift; 193 | sort { 194 | match_order($a) <=> match_order($b) || $a cmp $b 195 | } @_; 196 | } 197 | 198 | sub render { 199 | my $self = shift; 200 | my $template = shift; 201 | my $args = defined $_[0] && ref $_[0] ? $_[0] : { @_ }; 202 | my $mt = Text::MicroTemplate::DataSectionEx->new( 203 | extension => "", 204 | package => $self->{caller}->[0], 205 | template_args => $args, 206 | ); 207 | $mt->render($template); 208 | } 209 | 210 | 1; 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /lib/Kurado/Plugin/Compile.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Plugin::Compile; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use 5.10.0; 7 | use Mouse; 8 | use Log::Minimal; 9 | use Data::Validator; 10 | use File::Spec; 11 | use File::Basename; 12 | use Cwd::Guard; 13 | use POSIX 'SEEK_SET'; 14 | use SelectSaver; 15 | use JSON::XS; 16 | use Capture::Tiny; 17 | 18 | use Kurado::Storage; 19 | 20 | my $_JSON = JSON::XS->new->utf8; 21 | 22 | has 'config' => ( 23 | is => 'ro', 24 | isa => 'Kurado::Config', 25 | required => 1 26 | ); 27 | 28 | __PACKAGE__->meta->make_immutable(); 29 | 30 | our $RETURN_EXIT_VAL = undef; 31 | our $USE_REAL_EXIT; 32 | BEGIN { 33 | $USE_REAL_EXIT = 1; 34 | 35 | my $orig = *CORE::GLOBAL::exit{CODE}; 36 | 37 | my $proto = $orig ? prototype $orig : prototype 'CORE::exit'; 38 | 39 | $proto = $proto ? "($proto)" : ''; 40 | 41 | $orig ||= sub { 42 | my $exit_code = shift; 43 | 44 | CORE::exit(defined $exit_code ? $exit_code : 0); 45 | }; 46 | 47 | no warnings 'redefine'; 48 | 49 | *CORE::GLOBAL::exit = eval qq{ 50 | sub $proto { 51 | my \$exit_code = shift; 52 | 53 | \$orig->(\$exit_code) if \$USE_REAL_EXIT; 54 | 55 | die [ "EXIT\n", \$exit_code || 0 ] 56 | }; 57 | }; 58 | die $@ if $@; 59 | } 60 | 61 | # this helper function is placed at the top of the file to 62 | # hide variables in this file from the generated sub. 63 | sub _eval { 64 | no strict; 65 | no warnings; 66 | 67 | eval $_[0]; 68 | } 69 | 70 | my %COMPILE; 71 | sub compile { 72 | state $rule = Data::Validator->new( 73 | plugin => 'Str', 74 | type => 'Str', 75 | )->with('Method'); 76 | my ($self, $args) = $rule->validate(@_); 77 | my $path = $self->find_plugin($args); 78 | 79 | return unless $path; 80 | 81 | return $COMPILE{$path} if $COMPILE{$path}; 82 | 83 | infof "found plugin %s/%s at %s", $args->{type}, $args->{plugin}, $path; 84 | my $dir = dirname $path; 85 | my $package = $self->_build_package($path); 86 | my $code = $self->_read_source($path); 87 | 88 | my $warnings = $code =~ /^#!.*\s-w\b/ ? 1 : 0; 89 | $code =~ s/^__END__\r?\n.*//ms; 90 | $code =~ s/^__DATA__\r?\n(.*)//ms; 91 | my $data = $1; 92 | 93 | my $eval = join "\n", 94 | "package $package;", 95 | "sub {", 96 | ' local $Kurado::Plugin::Compile::USE_REAL_EXIT = 0;', 97 | ' local ($0, $Kurado::Plugin::Compile::_dir, *DATA);', 98 | ' {', 99 | ' my ($data, $path, $dir) = @_[0..2];', 100 | ' $0 = $path;', 101 | ' $Kurado::Plugin::Compile::_dir = Cwd::Guard::cwd_guard $dir;', 102 | q! open DATA, '<', \$data;!, 103 | ' }', 104 | # NOTE: this is a workaround to fix a problem in Perl 5.10 105 | q! local @SIG{keys %SIG} = do { no warnings 'uninitialized'; @{[]} = values %SIG };!, 106 | ' local $^W = $warnings;', 107 | ' my $rv = eval {', 108 | ' local @ARGV = @{ $_[3] };', # args to @ARGV 109 | ' local @_ = @{ $_[3] };', # args to @_ as well 110 | " #line 1 $path", 111 | " $code", 112 | ' };', 113 | ' my $exit_code = 0;', 114 | ' if ($@) {', 115 | ' die "$@\n" unless (ref($@) eq "ARRAY" and $@->[0] eq "EXIT\n");', #no exit 116 | q! $exit_code = unpack('C', pack('C', sprintf('%.0f', $@->[1])));!, # ~128 117 | ' die "exited nonzero: $exit_code" if $exit_code != 0;', 118 | ' }', 119 | ' return $exit_code;', 120 | '};',"\n"; 121 | 122 | my $sub = do { 123 | no warnings 'uninitialized'; # for 5.8 124 | # NOTE: this is a workaround to fix a problem in Perl 5.10 125 | local @SIG{keys %SIG} = @{[]} = values %SIG; 126 | local $USE_REAL_EXIT = 0; 127 | 128 | my $code = _eval $eval; 129 | my $exception = $@; 130 | 131 | die "Could not compile $path: $exception\n" if $exception; 132 | 133 | sub { 134 | my @args = @_; 135 | $code->($data, $path, $dir, \@args) 136 | }; 137 | }; 138 | infof "succeeded compiling plugin %s/%s", $args->{type}, $args->{plugin}, $path; 139 | $COMPILE{$path} = $sub; 140 | return $sub; 141 | } 142 | 143 | sub jdclone { 144 | my $ref = shift; 145 | $_JSON->decode($_JSON->encode($ref)); 146 | } 147 | 148 | sub run { 149 | state $rule = Data::Validator->new( 150 | host => 'Kurado::Object::Host', 151 | plugin => 'Kurado::Object::Plugin', 152 | type => 'Str', 153 | graph => { isa => 'Str', optional => 1 }, 154 | )->with('Method'); 155 | my ($self, $args) = $rule->validate(@_); 156 | 157 | my $sub = $self->compile( 158 | plugin => $args->{plugin}->plugin, 159 | type => $args->{type}, 160 | ); 161 | 162 | my $storage = Kurado::Storage->new( redis => $self->config->redis ); 163 | my $meta = $storage->get_by_plugin( 164 | plugin => $args->{plugin}, 165 | address => $args->{host}->address, 166 | ); 167 | 168 | my @params = (); 169 | push @params, '--address', $args->{host}->address; 170 | push @params, '--hostname', $args->{host}->hostname; 171 | push @params, '--comments', $args->{host}->comments if length $args->{host}->comments; 172 | for my $p_a ( @{$args->{plugin}->arguments} ) { 173 | push @params, '--plugin-arguments', $p_a; 174 | } 175 | push @params, '--graph', $args->{graph} if exists $args->{graph}; 176 | 177 | my ($stdout, $stderr, $success) = Capture::Tiny::capture { 178 | local $Kurado::Plugin::BRIDGE{'kurado.metrics_config'} = jdclone($args->{host}->metrics_config); 179 | local $Kurado::Plugin::BRIDGE{'kurado.metrics_meta'} = jdclone($meta); 180 | #local $ENV{'kurado.metrics_config_json'} = $_JSON->encode($args->{metrics_config}); 181 | #local $ENV{'kurado.metrics_meta_json'} = $_JSON->encode($meta); 182 | eval { 183 | $sub->(@params); 184 | }; 185 | if ( $@ ) { 186 | warn "$@\n"; 187 | return 0; 188 | } 189 | return 1; 190 | }; 191 | return ($stdout, $stderr, $success); 192 | } 193 | 194 | sub find_plugin { 195 | state $rule = Data::Validator->new( 196 | plugin => 'Str', 197 | type => 'Str', 198 | )->with('Method'); 199 | my ($self, $args) = $rule->validate(@_); 200 | 201 | for my $dir ( @{$self->config->metrics_plugin_dir} ) { 202 | my $path = File::Spec->catfile($dir,$args->{type},$args->{plugin}.'.pl'); 203 | if ( -f $path ) { 204 | return $path; 205 | } 206 | } 207 | 208 | return; 209 | } 210 | 211 | sub _read_source { 212 | my($self, $file) = @_; 213 | 214 | open my $fh, "<", $file or die "$file: $!"; 215 | return do { local $/; <$fh> }; 216 | } 217 | 218 | sub _build_package { 219 | my($self, $path) = @_; 220 | 221 | my ($volume, $dirs, $file) = File::Spec->splitpath($path); 222 | my @dirs = File::Spec->splitdir($dirs); 223 | my $package = join '_', grep { defined && length } $volume, @dirs, $file; 224 | 225 | # Escape everything into valid perl identifiers 226 | $package =~ s/([^A-Za-z0-9_])/sprintf("_%2x", unpack("C", $1))/eg; 227 | 228 | # make sure that the sub-package doesn't start with a digit 229 | $package =~ s/^(\d)/_$1/; 230 | 231 | $package = "Kurado::Plugin::Compile::ROOT" . "::$package"; 232 | return $package; 233 | } 234 | 235 | 1; 236 | 237 | -------------------------------------------------------------------------------- /lib/Kurado/ScoreBoard.pm: -------------------------------------------------------------------------------- 1 | package Kurado::ScoreBoard; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use File::Temp qw/tempdir/; 7 | use File::Path qw/remove_tree/; 8 | use Parallel::Scoreboard; 9 | use Mouse; 10 | use Log::Minimal; 11 | 12 | has 'config' => ( 13 | is => 'ro', 14 | isa => 'Kurado::Config', 15 | required => 1 16 | ); 17 | 18 | __PACKAGE__->meta->make_immutable(); 19 | 20 | sub BUILD { 21 | my $self = shift; 22 | $self->{scoreboard_dir} = tempdir( CLEANUP => 0, DIR => $self->config->data_dir ); 23 | $self->{sb} = Parallel::Scoreboard->new(base_dir=>$self->{scoreboard_dir}); 24 | $self->{pid} = $$; 25 | } 26 | 27 | sub DEMOLISH { 28 | my $self = shift; 29 | if ( $self->{pid} == $$ && $self->{scoreboard_dir}) { 30 | delete $self->{sb}; 31 | remove_tree(delete $self->{scoreboard_dir}); 32 | } 33 | } 34 | 35 | sub idle { 36 | my ($self,$caller) = @_; 37 | ($caller) = caller unless $caller; 38 | $self->{sb}->update(sprintf('%s %s %s',0,time,$caller)); 39 | } 40 | 41 | sub busy { 42 | my $self = shift; 43 | my ($caller) = caller; 44 | $self->{sb}->update(sprintf('%s %s %s',1,time,$caller)); 45 | return Kurado::ScoreBoard::Guard->new(sub { $self->idle($caller) }) if defined wantarray; 46 | 1; 47 | } 48 | 49 | sub kill_zombie { 50 | my $self = shift; 51 | my $threshold = shift; 52 | $threshold ||= 30; 53 | my $stats = $self->{sb}->read_all(); 54 | my $now = time ; 55 | for my $pid ( keys %$stats) { 56 | my($status,$time,$type) = split /\s+/, $stats->{$pid}, 3; 57 | if ( $status == 1 && $now - $time > $threshold ) { 58 | warnf 'kill zombie %s pid:%s', $type, $pid; 59 | kill 'KILL', $pid; 60 | } 61 | } 62 | } 63 | 64 | 1; 65 | 66 | package Kurado::ScoreBoard::Guard; 67 | 68 | sub new { 69 | my $class = shift; 70 | my $cb = shift; 71 | bless { cb => $cb, pid => $$ }, $class; 72 | } 73 | 74 | sub DESTROY { 75 | my $self = shift; 76 | if ( defined $self->{pid} && $self->{pid} == $$ ) { 77 | $self->{cb}->(); 78 | } 79 | } 80 | 81 | 1; 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /lib/Kurado/Storage.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Storage; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use 5.10.0; 7 | use Mouse; 8 | use Data::Validator; 9 | use Redis::Fast; 10 | use List::MoreUtils; 11 | use Log::Minimal; 12 | 13 | has 'redis' => ( 14 | is => 'ro', 15 | isa => 'Str', 16 | required => 1 17 | ); 18 | 19 | __PACKAGE__->meta->make_immutable(); 20 | 21 | my %REDIS_CONNECTION; 22 | sub connect { 23 | my $self = shift; 24 | $REDIS_CONNECTION{"$$-".$self->redis} ||= Redis::Fast->new( 25 | server => $self->redis, 26 | reconnect => 10, 27 | every => 100 28 | ); 29 | $REDIS_CONNECTION{"$$-".$self->redis}; 30 | } 31 | 32 | sub set { 33 | state $rule = Data::Validator->new( 34 | msg => 'Kurado::Object::Msg', 35 | expires => 'Str', 36 | )->with('Method'); 37 | my ($self, $args) = $rule->validate(@_); 38 | $self->_set( 39 | (map { ( $_ => $args->{$_} ) } qw/msg expires/), 40 | type => 'storage', 41 | ); 42 | } 43 | 44 | 45 | sub set_warn { 46 | state $rule = Data::Validator->new( 47 | msg => 'Kurado::Object::Msg' 48 | )->with('Method'); 49 | my ($self, $args) = $rule->validate(@_); 50 | 51 | my @lt = localtime($args->{msg}->{timestamp}); 52 | my $timestr = sprintf '%04d-%02d-%02dT%02d:%02d:%02d', $lt[5]+1900, $lt[4]+1, @lt[3,2,1,0]; 53 | $self->_set( 54 | msg => $args->{msg}, 55 | type => '__warn__', 56 | expires => 5*60, 57 | value => "$timestr ".$args->{msg}->value 58 | ); 59 | } 60 | 61 | sub _set { 62 | state $rule = Data::Validator->new( 63 | msg => 'Kurado::Object::Msg', 64 | expires => 'Str', 65 | type => 'Str', 66 | value => { isa => 'Str', optional => 1}, 67 | )->with('Method'); 68 | my ($self, $args) = $rule->validate(@_); 69 | 70 | my $set_key = join "/", $args->{type}, 71 | $args->{msg}->address, $args->{msg}->plugin->plugin_identifier_escaped; 72 | my $key = join "/", $args->{type}, 73 | $args->{msg}->address, $args->{msg}->plugin->plugin_identifier_escaped, $args->{msg}->key; 74 | my $connect = $self->connect; 75 | my $now = time; 76 | my $expire_at = $now + $args->{expires}; 77 | my $value = (exists $args->{value}) ? $args->{value} : $args->{msg}->value; 78 | my @res; 79 | $connect->multi(sub {}); 80 | $connect->zadd($set_key, $expire_at, $args->{msg}->key, sub {}); 81 | $connect->set($key, $value, sub {}); 82 | $connect->expireat($key, $expire_at, sub {}); 83 | if ( $args->{type} eq '__warn__' ) { #XXX 84 | my $has_warn_key = join "/", '__warn__', $args->{msg}->address; 85 | $connect->set($has_warn_key, $now, 'EX', $args->{expires}, sub {}); 86 | } 87 | $connect->exec(sub { @res = @_ }); 88 | $connect->wait_all_responses; 89 | if ( my @err = grep { ! defined $_->[0] } @{$res[0]} ) { 90 | die "Storage->set error: $err[0][1]\n"; 91 | } 92 | return 1; 93 | } 94 | 95 | sub delete { 96 | state $rule = Data::Validator->new( 97 | plugin => 'Kurado::Object::Plugin', 98 | address => 'Str', 99 | key => 'Str' 100 | )->with('Method'); 101 | my ($self, $args) = $rule->validate(@_); 102 | 103 | my $set_key = join "/", 'storage', 104 | $args->{address}, $args->{plugin}->plugin_identifier_escaped; 105 | my $key = join "/", 'storage', 106 | $args->{address}, $args->{plugin}->plugin_identifier_escaped, $args->{key}; 107 | 108 | my $connect = $self->connect; 109 | 110 | my @res; 111 | $connect->multi(sub{}); 112 | $connect->zrem($set_key, $args->{key}, sub{}); 113 | $connect->del($key, sub{}); 114 | $connect->exec(sub { @res = @_ }); 115 | $connect->wait_all_responses; 116 | if ( my @err = grep { ! defined $_->[0] } @{$res[0]} ) { 117 | die "Storage->delete error: $err[0][1]\n"; 118 | } 119 | return 1; 120 | } 121 | 122 | *remove = \&delete; 123 | 124 | sub get_by_plugin { 125 | state $rule = Data::Validator->new( 126 | plugin => 'Kurado::Object::Plugin', 127 | address => 'Str', 128 | )->with('Method'); 129 | my ($self, $args) = $rule->validate(@_); 130 | 131 | my $set_key = join "/", 'storage', 132 | $args->{address}, $args->{plugin}->plugin_identifier_escaped; 133 | 134 | my $connect = $self->connect; 135 | my $time = time; 136 | $connect->zremrangebyscore($set_key, '-inf', '('.$time); 137 | my @keys = $connect->zrangebyscore($set_key, $time, '+inf'); 138 | return {} unless @keys; 139 | my @values = $connect->mget(map { $set_key.'/'.$_ } @keys); 140 | my %ret = List::MoreUtils::pairwise { ($a, $b) } @keys, @values; 141 | return \%ret; 142 | } 143 | 144 | sub get_warn_by_plugin { 145 | state $rule = Data::Validator->new( 146 | plugin => 'Kurado::Object::Plugin', 147 | address => 'Str', 148 | )->with('Method'); 149 | my ($self, $args) = $rule->validate(@_); 150 | 151 | my $set_key = join "/", '__warn__', 152 | $args->{address}, $args->{plugin}->plugin_identifier_escaped; 153 | 154 | my $connect = $self->connect; 155 | my $time = time; 156 | $connect->zremrangebyscore($set_key, '-inf', '('.$time); 157 | my @keys = $connect->zrangebyscore($set_key, $time, '+inf'); 158 | if ( !@keys ) { 159 | return {}; 160 | } 161 | my @values = $connect->mget(map { $set_key.'/'.$_ } @keys); 162 | my %ret = List::MoreUtils::pairwise { ($a, $b) } @keys, @values; 163 | return \%ret; 164 | } 165 | 166 | sub set_last_recieved { 167 | state $rule = Data::Validator->new( 168 | msg => 'Kurado::Object::Msg', 169 | )->with('Method'); 170 | my ($self, $args) = $rule->validate(@_); 171 | 172 | my $value = time; 173 | my $key = join "/", '__last_recieved__', $args->{msg}->address, $args->{msg}->plugin->plugin_identifier_escaped; 174 | $self->connect->set($key, $value, 'EX', 365*86400); 175 | } 176 | 177 | 178 | sub get_last_recieved { 179 | state $rule = Data::Validator->new( 180 | plugin => 'Kurado::Object::Plugin', 181 | address => 'Str', 182 | )->with('Method'); 183 | my ($self, $args) = $rule->validate(@_); 184 | 185 | my $value = time; 186 | my $key = join "/", '__last_recieved__', $args->{address}, $args->{plugin}->plugin_identifier_escaped; 187 | $self->connect->get($key); 188 | } 189 | 190 | sub has_warn { 191 | state $rule = Data::Validator->new( 192 | address => 'Str', 193 | )->with('Method'); 194 | my ($self, $args) = $rule->validate(@_); 195 | my $key = join "/", '__warn__', $args->{address}; 196 | $self->connect->get($key); 197 | 198 | } 199 | 200 | 201 | 1; 202 | 203 | 204 | -------------------------------------------------------------------------------- /lib/Kurado/TinyTCP.pm: -------------------------------------------------------------------------------- 1 | package Kurado::TinyTCP; 2 | 3 | use strict; 4 | use warnings; 5 | use POSIX qw(EINTR EAGAIN EWOULDBLOCK :sys_wait_h); 6 | use IO::Socket qw(IPPROTO_TCP TCP_NODELAY); 7 | use IO::Socket::INET; 8 | use IO::Select; 9 | use Time::HiRes qw//; 10 | 11 | our $READ_BYTES = 16 * 1024; 12 | 13 | sub new { 14 | my $class = shift; 15 | my %args = ref $_ ? %{$_[0]} : @_; 16 | %args = ( 17 | server => '127.0.0.1:6379', 18 | timeout => 10, 19 | %args, 20 | ); 21 | my $server = shift; 22 | my $self = bless \%args, $class; 23 | $self->connect; 24 | $self; 25 | } 26 | 27 | sub connect { 28 | my $self = shift; 29 | return $self->{sock} if $self->{sock}; 30 | my $socket; 31 | eval { 32 | local $SIG{ALRM} = sub { die "Timed out\n"; }; 33 | alarm(4); 34 | $socket = IO::Socket::INET->new( 35 | PeerAddr => $self->{server}, 36 | ) or die "Socket connect failed: $!\n"; 37 | }; 38 | alarm(0); 39 | die $@ if $@; 40 | $socket->blocking(0); 41 | $socket->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) 42 | or die "setsockopt(TCP_NODELAY) failed:$!\n"; 43 | $self->{sock} = $socket; 44 | $socket; 45 | } 46 | 47 | sub read : method { 48 | my ($self, $timeout) = @_; 49 | $timeout ||= $self->{timeout}; 50 | my $timeout_at = Time::HiRes::time + $timeout; 51 | my $buf = ''; 52 | my $n = $self->do_io(undef, \$buf, $READ_BYTES, 0, $timeout_at); 53 | die $! != 0 ? "$!\n" : "timeout or socket closed\n" if !defined $n; 54 | die "Socket closed by remote server: $!" if $n == 0; 55 | return $buf; 56 | } 57 | 58 | sub read_until_close { 59 | my ($self, $timeout) = @_; 60 | $timeout ||= $self->{timeout}; 61 | my $timeout_at = Time::HiRes::time + $timeout; 62 | my $buf = ''; 63 | while (1) { 64 | my $off = length($buf); 65 | my $n = $self->do_io(undef, \$buf, $READ_BYTES, $off, $timeout_at); 66 | die $! != 0 ? "$!\n" : "timeout\n" if !defined $n; 67 | last if $n == 0; #close 68 | } 69 | return $buf; 70 | } 71 | 72 | sub write : method { 73 | my ($self, $buf, $timeout) = @_; 74 | $timeout ||= $self->{timeout}; 75 | my $timeout_at = Time::HiRes::time + $timeout; 76 | my $off = 0; 77 | while (my $len = length($buf) - $off) { 78 | my $n = $self->do_io(1, $buf, $len, $off, $timeout_at); 79 | die $! != 0 ? "$!\n" : "timeout\n" if !$n; 80 | die "Socket closed by remote server: $!" if $n == 0; 81 | $off += $n; 82 | } 83 | return length $buf; 84 | } 85 | 86 | 87 | 88 | # returns value returned by $cb, or undef on timeout or network error 89 | sub do_io { 90 | my ($self, $is_write, $buf, $len, $off, $timeout_at) = @_; 91 | my $sock = $self->{sock}; 92 | my $ret; 93 | local $SIG{PIPE} = 'IGNORE'; 94 | 95 | if ( !$is_write ){ #read 96 | goto DO_SELECT; 97 | } 98 | DO_READWRITE: 99 | # try to do the IO 100 | if ($is_write) { 101 | defined($ret = syswrite $sock, $buf, $len, $off) 102 | and return $ret; 103 | } else { 104 | defined($ret = sysread $sock, $$buf, $len, $off) 105 | and return $ret; 106 | } 107 | if ( $! != EINTR && $! != EAGAIN && $! != EWOULDBLOCK ) { 108 | return; 109 | } 110 | # wait for data 111 | DO_SELECT: 112 | while (1) { 113 | my $timeout = $timeout_at - Time::HiRes::time; 114 | return if $timeout <= 0; 115 | my ($rfd, $wfd); 116 | my $efd = ''; 117 | vec($efd, fileno($sock), 1) = 1; 118 | if ($is_write) { 119 | ($rfd, $wfd) = ('', $efd); 120 | } else { 121 | ($rfd, $wfd) = ($efd, ''); 122 | } 123 | my $nfound = select($rfd, $wfd, $efd, $timeout); 124 | last if $nfound; 125 | } 126 | goto DO_READWRITE; 127 | } 128 | 129 | 1; 130 | -------------------------------------------------------------------------------- /lib/Kurado/Util.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Util; 2 | 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use base qw/Exporter/; 7 | use POSIX ":sys_wait_h"; 8 | 9 | our @EXPORT = qw/cap_cmd to_byte supervisor/; 10 | 11 | sub cap_cmd { 12 | my ($cmdref) = @_; 13 | pipe my $logrh, my $logwh 14 | or die "Died: failed to create pipe:$!\n"; 15 | my $pid = fork; 16 | if ( ! defined $pid ) { 17 | die "Died: fork failed: $!\n"; 18 | } 19 | 20 | elsif ( $pid == 0 ) { 21 | #child 22 | close $logrh; 23 | open STDOUT, '>&', $logwh 24 | or die "Died: failed to redirect STDOUT\n"; 25 | close $logwh; 26 | exec @$cmdref; 27 | die "Died: exec failed: $!\n"; 28 | } 29 | close $logwh; 30 | my $result; 31 | while(<$logrh>){ 32 | $result .= $_; 33 | } 34 | close $logrh; 35 | while (wait == -1) {} 36 | my $exit_code = $?; 37 | $exit_code = $exit_code >> 8; 38 | return ($result, $exit_code); 39 | } 40 | 41 | # Convert string like a "123 KB" into as byte 42 | sub to_byte { 43 | my $s = shift; 44 | my $b = 0; 45 | 46 | ($s) = ($s =~ /^\s*(.+?)\s*$/); # trim 47 | 48 | if ($s =~ /^[0-9]+$/) { 49 | $b = $s; 50 | } elsif ($s =~ /^([0-9]+)\s*([a-zA-Z]+)$/) { 51 | $b = $1; 52 | my $u = lc $2; 53 | if ($u eq 'kb') { 54 | $b = $b * 1024; 55 | } elsif ($u eq 'mb') { 56 | $b = $b * 1024 * 1024; 57 | } elsif ($u eq 'gb') { 58 | $b = $b * 1024 * 1024 * 1024; 59 | } elsif ($u eq 'tb') { 60 | $b = $b * 1024 * 1024 * 1024 * 1024; 61 | } else { 62 | warnf("Unknown unit: %s", $u); 63 | } 64 | } else { 65 | warnf("Failed to convert into as byte: %s", $s); 66 | } 67 | 68 | return $b; 69 | } 70 | 71 | sub supervisor { 72 | my $cb = shift; 73 | my $opts = @_ == 1 ? shift : { @_ }; 74 | $opts->{interval} ||= 3; 75 | 76 | my @signals_received; 77 | $SIG{$_} = sub { 78 | warn "sig:$_[0]"; 79 | push @signals_received, $_[0]; 80 | } for (qw/INT TERM HUP/); 81 | $SIG{PIPE} = 'IGNORE'; 82 | $SIG{CHLD} = sub {}; 83 | 84 | my $pid; 85 | my $initial=1; 86 | while (1) { 87 | if ( $pid ) { 88 | my $kid = waitpid($pid, WNOHANG); 89 | if ( $kid == -1 ) { 90 | $pid = undef; 91 | } 92 | elsif ( $kid ) { 93 | my $status = $? >> 8; 94 | warn "[supervisor] process $pid died with status:$status\n" unless @signals_received; 95 | $pid = undef; 96 | } 97 | } 98 | 99 | if ( grep { $_ ne 'HUP' } @signals_received ) { 100 | warn "[supervisor] signals_received: " . join(",", @signals_received) . "\n"; 101 | last; 102 | } 103 | 104 | while ( my $signals_received = shift @signals_received ) { 105 | if ( $pid && $signals_received eq 'HUP' ) { 106 | warn "[supervisor] HUP signal received, send TERM to $pid\n"; 107 | kill 'TERM', $pid; 108 | waitpid( $pid, 0 ); 109 | $pid = undef; 110 | } 111 | } 112 | 113 | select( undef, undef, undef, $pid ? 60 : $opts->{interval}) if ! $initial; 114 | $initial=0; 115 | 116 | if ( ! defined $pid ) { 117 | $pid = fork(); 118 | die "failed fork: $!\n" unless defined $pid; 119 | next if $pid; #main process 120 | 121 | # child process 122 | $SIG{$_} = 'DEFAULT' for (qw/INT TERM HUP CHLD/); 123 | 124 | $cb->(); 125 | POSIX::_exit(255); 126 | } 127 | } 128 | 129 | if ( $pid ) { 130 | kill 'TERM', $pid; 131 | waitpid( $pid, 0 ); 132 | } 133 | } 134 | 135 | 136 | 1; 137 | 138 | -------------------------------------------------------------------------------- /lib/Kurado/Worker.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Worker; 2 | 3 | use strict; 4 | use warnings; 5 | use Mouse; 6 | use Proclet; 7 | use Plack::Loader; 8 | use Plack::Builder; 9 | 10 | use Kurado::ScoreBoard; 11 | use Kurado::Worker::Updater; 12 | use Kurado::Worker::TimeMage; 13 | use Kurado::Worker::Fetcher; 14 | use Kurado::Web; 15 | 16 | has 'config_loader' => ( 17 | is => 'ro', 18 | isa => 'Kurado::ConfigLoader', 19 | required => 1 20 | ); 21 | 22 | has 'root_dir' => ( 23 | is => 'ro', 24 | isa => 'Str', 25 | required => 1 26 | ); 27 | 28 | 29 | __PACKAGE__->meta->make_immutable(); 30 | 31 | sub run { 32 | my $self = shift; 33 | 34 | my $sb = Kurado::ScoreBoard->new( 35 | config => $self->config_loader->config, 36 | ); 37 | my $proclet = Proclet->new( 38 | err_respawn_interval => 3, 39 | exec_notice => 0, 40 | ); 41 | my $updater = Kurado::Worker::Updater->new( 42 | scoreboard => $sb, 43 | config_loader => $self->config_loader 44 | ); 45 | my $fetcher = Kurado::Worker::Fetcher->new( 46 | scoreboard => $sb, 47 | config_loader => $self->config_loader 48 | ); 49 | 50 | my $timemage = Kurado::Worker::TimeMage->new( 51 | config_loader => $self->config_loader 52 | ); 53 | 54 | $proclet->service( 55 | code => sub { 56 | local $Log::Minimal::PRINT = sub { 57 | my ( $time, $type, $message, $trace,$raw_message) = @_; 58 | warn "[$type] $message at $trace\n"; 59 | }; 60 | local $0 = 'Kurado::Worker::Updater'; 61 | $updater->run(); 62 | }, 63 | worker => 1, 64 | tag => 'updater' 65 | ); 66 | 67 | $proclet->service( 68 | code => sub { 69 | local $Log::Minimal::PRINT = sub { 70 | my ( $time, $type, $message, $trace,$raw_message) = @_; 71 | warn "[$type] $message at $trace\n"; 72 | }; 73 | local $0 = 'Kurado::Worker::Fetcher'; 74 | $fetcher->run(); 75 | }, 76 | worker => 1, 77 | tag => 'fetcher' 78 | ); 79 | 80 | $proclet->service( 81 | code => sub { 82 | local $Log::Minimal::PRINT = sub { 83 | my ( $time, $type, $message, $trace,$raw_message) = @_; 84 | warn "[$type] $message at $trace\n"; 85 | }; 86 | local $0 = 'Kurado::Worker::TimeMage'; 87 | $timemage->run(); 88 | }, 89 | worker => 1, 90 | tag => 'timemage', 91 | every => '* * * * *', # every minutes 92 | ); 93 | 94 | $proclet->service( 95 | code => sub { 96 | local $Log::Minimal::PRINT = sub { 97 | my ( $time, $type, $message, $trace,$raw_message) = @_; 98 | warn "[$type] $message at $trace\n"; 99 | }; 100 | local $0 = 'Kurado::Worker::KillZombie'; 101 | while(1) { 102 | $sb->kill_zombie(30); 103 | select undef, undef, undef, 3; 104 | } 105 | }, 106 | worker => 1, 107 | tag => 'watcher' 108 | ); 109 | 110 | 111 | my $app = Kurado::Web->new( 112 | config_loader => $self->config_loader, 113 | root_dir => $self->root_dir, 114 | ); 115 | my $psgi_app = builder { 116 | enable 'ReverseProxy'; 117 | enable 'Static', 118 | path => qr!^/(?:(?:css|fonts|js|img)/|favicon\.ico$)!, 119 | root => $self->root_dir . '/public'; 120 | $app->psgi; 121 | }; 122 | 123 | $proclet->service( 124 | code => sub { 125 | local $Log::Minimal::PRINT = sub { 126 | my ( $time, $type, $message, $trace,$raw_message) = @_; 127 | warn "[$type] $message at $trace\n"; 128 | }; 129 | local $0 = 'Kurado:Web'; 130 | my $loader = Plack::Loader->load( 131 | 'Starlet', 132 | port => 5434, 133 | host => 0, 134 | max_workers => $self->config_loader->config->web_worker, 135 | ); 136 | $loader->run($psgi_app); 137 | }, 138 | tag => 'web', 139 | ); 140 | 141 | $proclet->run; 142 | } 143 | 144 | 1; 145 | 146 | -------------------------------------------------------------------------------- /lib/Kurado/Worker/Fetcher.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Worker::Fetcher; 2 | 3 | use strict; 4 | use warnings; 5 | use Mouse; 6 | use Parallel::Prefork; 7 | use Log::Minimal; 8 | 9 | use Kurado::MQ; 10 | use Kurado::Host; 11 | 12 | has 'config_loader' => ( 13 | is => 'ro', 14 | isa => 'Kurado::ConfigLoader', 15 | required => 1 16 | ); 17 | 18 | has 'scoreboard' => ( 19 | is => 'ro', 20 | isa => 'Kurado::ScoreBoard', 21 | required => 1 22 | ); 23 | 24 | 25 | __PACKAGE__->meta->make_immutable(); 26 | 27 | sub config { 28 | $_[0]->config_loader->config; 29 | } 30 | 31 | sub run { 32 | my $self = shift; 33 | my $pm = Parallel::Prefork->new({ 34 | max_workers => $self->config->fetch_worker, 35 | trap_signals => { 36 | TERM => 'TERM', 37 | HUP => 'TERM', 38 | } 39 | }); 40 | while ($pm->signal_received ne 'TERM') { 41 | $pm->start(sub{ 42 | srand(); 43 | my $mq = Kurado::MQ->new(server => $self->config->redis); 44 | local $SIG{TERM} = sub { 45 | $mq->{stop_loop} = 1; 46 | }; 47 | $self->scoreboard->idle; 48 | my $process = 0; 49 | $mq->subscribe( 50 | "kurado-fetch" => sub { 51 | my ($topic, $message) = @_; 52 | my $gurad = $self->scoreboard->busy; 53 | my ($address, $plugin_identifier) = split /\t/, $message, 2; 54 | my $host = $self->config_loader->host_by_address($address); 55 | if (!$host) { 56 | warnf 'address"%s is not found. skip it', $address; 57 | return; 58 | } 59 | $process++; 60 | $mq->{stop_loop} = 1 if $process > 500; 61 | my $plugin = Kurado::Object::Plugin->new_from_identifier($plugin_identifier); 62 | my $host_obj = Kurado::Host->new( 63 | config_loader => $self->config_loader, 64 | host => $host, 65 | ); 66 | my $metrics = $host_obj->fetch_metrics(plugin => $plugin); 67 | $mq->enqueue('kurado-update',$metrics); 68 | }, 69 | ); 70 | }); 71 | } 72 | $pm->wait_all_children(); 73 | } 74 | 75 | 76 | 77 | 1; 78 | 79 | 80 | -------------------------------------------------------------------------------- /lib/Kurado/Worker/TimeMage.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Worker::TimeMage; 2 | 3 | use strict; 4 | use warnings; 5 | use Mouse; 6 | use Parallel::Prefork; 7 | use Log::Minimal; 8 | 9 | use Kurado::MQ; 10 | 11 | has 'config_loader' => ( 12 | is => 'ro', 13 | isa => 'Kurado::ConfigLoader', 14 | required => 1 15 | ); 16 | 17 | __PACKAGE__->meta->make_immutable(); 18 | 19 | sub run { 20 | my $self = shift; 21 | sleep 3; # 0秒を避ける 22 | my $hosts = $self->config_loader->hosts; 23 | my $mq = Kurado::MQ->new( server => $self->config_loader->config->redis ); 24 | for my $adrs ( keys %$hosts ) { 25 | my $host = $hosts->{$adrs}; 26 | for my $plugin ( @{$host->plugins} ) { 27 | next unless $self->config_loader->has_fetch_plugin($plugin->plugin); 28 | my $msg = $host->address ."\t". $plugin->plugin_identifier; 29 | debugf("enqueue %s", $msg); 30 | $mq->enqueue('kurado-fetch',$msg); 31 | } 32 | } 33 | } 34 | 35 | 36 | 1; 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/Kurado/Worker/Updater.pm: -------------------------------------------------------------------------------- 1 | package Kurado::Worker::Updater; 2 | 3 | use strict; 4 | use warnings; 5 | use Mouse; 6 | use Parallel::Prefork; 7 | use Log::Minimal; 8 | 9 | use Kurado::MQ; 10 | use Kurado::Metrics; 11 | 12 | has 'config_loader' => ( 13 | is => 'ro', 14 | isa => 'Kurado::ConfigLoader', 15 | required => 1 16 | ); 17 | 18 | has 'scoreboard' => ( 19 | is => 'ro', 20 | isa => 'Kurado::ScoreBoard', 21 | required => 1 22 | ); 23 | 24 | 25 | __PACKAGE__->meta->make_immutable(); 26 | 27 | sub config { 28 | $_[0]->config_loader->config 29 | } 30 | 31 | sub run { 32 | my $self = shift; 33 | my $pm = Parallel::Prefork->new({ 34 | max_workers => $self->config->update_worker, 35 | trap_signals => { 36 | TERM => 'TERM', 37 | HUP => 'TERM', 38 | } 39 | }); 40 | while ($pm->signal_received ne 'TERM') { 41 | $pm->start(sub{ 42 | srand(); 43 | my $mq = Kurado::MQ->new(server => $self->config->redis); 44 | my $metrics = Kurado::Metrics->new(config => $self->config); 45 | local $SIG{TERM} = sub { 46 | $mq->{stop_loop} = 1; 47 | }; 48 | $self->scoreboard->idle; 49 | $mq->subscribe( 50 | "kurado-update" => sub { 51 | my ($topic, $message) = @_; 52 | my $gurad = $self->scoreboard->busy; 53 | while(1) { 54 | if ( -f $self->config->data_dir . "/stop" && time - [stat($self->config->data_dir . "/stop")]->[9] < 300 ) { 55 | sleep 1; 56 | next; 57 | } 58 | last; 59 | } 60 | $metrics->process_message($message); 61 | }, 62 | ); 63 | }); 64 | } 65 | $pm->wait_all_children(); 66 | } 67 | 68 | 69 | 70 | 1; 71 | 72 | 73 | -------------------------------------------------------------------------------- /metrics_plugins/fetch/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/metrics_plugins/fetch/.gitkeep -------------------------------------------------------------------------------- /metrics_plugins/fetch/gaurun.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | use Furl; 9 | use JSON; 10 | 11 | our $VERSION = '0.01'; 12 | 13 | my $plugin = Kurado::Plugin->new(@ARGV); 14 | my $host = $plugin->address; 15 | my ($port,$path) = @{$plugin->plugin_arguments}; 16 | $port ||= 1056; 17 | $path ||= '/stat/app'; 18 | 19 | my $furl = Furl->new( 20 | agent => 'kurado-plugin', 21 | timeout => 10, 22 | ); 23 | my $time = time; 24 | 25 | 26 | my $res = $furl->request( 27 | scheme => 'http', 28 | host => $host, 29 | port => $port, 30 | path_query => $path 31 | ); 32 | die "request failed: " .$res->status_line."\n" 33 | unless $res->is_success; 34 | my $data = JSON->new->utf8->decode($res->content); 35 | 36 | for my $os ( qw/ios android/ ) { 37 | my $stat = $data->{$os} || {}; 38 | for my $key (qw/push_success push_error/) { 39 | my $metrics = exists $stat->{$key} ? $stat->{$key} : 'U'; 40 | print "metrics.${os}_${key}.counter\t$stat->{$key}\t$time\n"; 41 | } 42 | } 43 | 44 | for my $key (qw/queue_usage queue_max/) { 45 | my $metrics = exists $data->{$key} ? $data->{$key} : 'U'; 46 | print "metrics.$key.gauge\t$data->{$key}\t$time\n"; 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /metrics_plugins/fetch/goapp.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | use Furl; 9 | use JSON; 10 | 11 | our $VERSION = '0.01'; 12 | 13 | my $plugin = Kurado::Plugin->new(@ARGV); 14 | my $host = $plugin->address; 15 | my ($port,$path) = @{$plugin->plugin_arguments}; 16 | $port ||= 1056; 17 | $path ||= '/stat/go'; 18 | 19 | my $furl = Furl->new( 20 | agent => 'kurado-plugin', 21 | timeout => 10, 22 | ); 23 | my $time = time; 24 | 25 | 26 | my $res = $furl->request( 27 | scheme => 'http', 28 | host => $host, 29 | port => $port, 30 | path_query => $path 31 | ); 32 | die "request failed: " .$res->status_line."\n" 33 | unless $res->is_success; 34 | my $data = JSON->new->utf8->decode($res->content); 35 | 36 | 37 | for my $key (qw/cgo_call_num gc_num memory_mallocs memory_frees/) { 38 | my $metrics = exists $data->{$key} ? $data->{$key} : 'U'; 39 | print "metrics.$key.counter\t$data->{$key}\t$time\n"; 40 | } 41 | 42 | 43 | for my $key (qw/goroutine_num memory_alloc/) { 44 | my $metrics = exists $data->{$key} ? $data->{$key} : 'U'; 45 | print "metrics.$key.gauge\t$data->{$key}\t$time\n"; 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /metrics_plugins/fetch/http.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | use Furl; 9 | use IO::Socket qw/inet_aton pack_sockaddr_in/; 10 | 11 | our $VERSION = '0.01'; 12 | 13 | my $plugin = Kurado::Plugin->new(@ARGV); 14 | my $host = $plugin->address; 15 | my ($port,$path,$http_host) = @{$plugin->plugin_arguments}; 16 | $port ||= 80; 17 | $path ||= '/server-status?auto'; 18 | $http_host ||= $host; 19 | 20 | my $furl = Furl->new( 21 | agent => 'kurado-plugin', 22 | timeout => 10, 23 | get_address => sub { 24 | pack_sockaddr_in($port, inet_aton($host)); 25 | } 26 | ); 27 | 28 | my $res = $furl->request( 29 | scheme => 'http', 30 | host => $http_host, 31 | port => $port, 32 | path_query => $path, 33 | ); 34 | 35 | die "server-status failed: " .$res->status_line."\n" 36 | unless $res->is_success; 37 | 38 | my $time = time; 39 | 40 | my %meta; 41 | if ( my $server_version = $res->header('Server') ) { 42 | $meta{server} = $server_version; 43 | } 44 | 45 | my $body = $res->body; 46 | my %metrics; 47 | foreach my $line ( split /[\r\n]+/, $body ) { 48 | if ( $line =~ /^Busy.+: (\d+)/ ) { 49 | $metrics{busy} = $1; 50 | } 51 | if ( $line =~ /^Idle.+: (\d+)/ ) { 52 | $metrics{idle} = $1; 53 | } 54 | if ( $line =~ /^Uptime\s*: (\d+)/ ) { 55 | my $uptime = $1; 56 | $uptime = $time - $uptime if $uptime > 20*365*86400; 57 | $meta{uptime} = $uptime; 58 | } 59 | if ( $line =~ /^Total Accesses\s*: (\d+)/ ) { 60 | $meta{'has-reqs'} = 1; 61 | $metrics{reqs} = $1; 62 | } 63 | } 64 | 65 | for my $key (keys %meta) { 66 | print "meta.$key\t$meta{$key}\t$time\n"; 67 | } 68 | 69 | 70 | for my $key (qw/busy idle/) { 71 | my $metrics = exists $metrics{$key} ? $metrics{$key} : 'U'; 72 | print "metrics.$key.gauge\t$metrics{$key}\t$time\n"; 73 | } 74 | 75 | if ( $meta{'has-reqs'} ) { 76 | print "metrics.reqs.derive\t$metrics{reqs}\t$time\n"; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /metrics_plugins/fetch/jolokia.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | use Furl; 9 | use JSON; 10 | 11 | our $VERSION = '0.01'; 12 | 13 | my $plugin = Kurado::Plugin->new(@ARGV); 14 | my $host = $plugin->address; 15 | my ($port,$path,$http_host) = @{$plugin->plugin_arguments}; 16 | $port ||= 8778; 17 | $path ||= '/jolokia'; 18 | 19 | my $furl = Furl->new( 20 | agent => 'kurado-plugin', 21 | timeout => 10, 22 | ); 23 | my $time = time; 24 | 25 | { 26 | my $res = $furl->request( 27 | scheme => 'http', 28 | host => $host, 29 | port => $port, 30 | path_query => $path . '/read/java.lang:type=ClassLoading/LoadedClassCount', 31 | ); 32 | die "request failed: " .$res->status_line."\n" 33 | unless $res->is_success; 34 | my $data = JSON->new->utf8->decode($res->content); 35 | print "metrics.loaded_class.gauge\t$data->{value}\t$time\n"; 36 | } 37 | 38 | { 39 | my $res = $furl->request( 40 | scheme => 'http', 41 | host => $host, 42 | port => $port, 43 | path_query => $path . '/read/java.lang:type=Threading/ThreadCount,DaemonThreadCount', 44 | ); 45 | die "request failed: " .$res->status_line."\n" 46 | unless $res->is_success; 47 | my $data = JSON->new->utf8->decode($res->content); 48 | print "metrics.thread_count.gauge\t$data->{value}->{ThreadCount}\t$time\n"; 49 | print "metrics.daemon_thread_count.gauge\t$data->{value}->{DaemonThreadCount}\t$time\n"; 50 | } 51 | 52 | { 53 | my $res = $furl->request( 54 | scheme => 'http', 55 | host => $host, 56 | port => $port, 57 | path_query => $path . '/read/java.lang:type=GarbageCollector,name=*/CollectionCount,CollectionTime', 58 | ); 59 | die "request failed: " .$res->status_line."\n" 60 | unless $res->is_success; 61 | my $data = JSON->new->utf8->decode($res->content); 62 | my $value = $data->{value}; 63 | my($ygc_c, $ygc_t, $fgc_c, $fgc_t) = ('U', 'U', 'U', 'U'); 64 | for my $collector (keys %$value) { 65 | if ($collector =~ /name=(?:PS Scavenge|ParNew|G1 Young Generation)/) { 66 | $ygc_c = $value->{$collector}{CollectionCount}; 67 | $ygc_t = $value->{$collector}{CollectionTime}; 68 | } elsif ($collector =~ /name=(?:PS MarkSweep|ConcurrentMarkSweep|G1 Old Generation)/) { 69 | $fgc_c = $value->{$collector}{CollectionCount}; 70 | $fgc_t = $value->{$collector}{CollectionTime}; 71 | } 72 | } 73 | print "metrics.young_gc_count.derive\t$ygc_c\t$time\n"; 74 | print "metrics.young_gc_time.derive\t$ygc_t\t$time\n"; 75 | print "metrics.full_gc_count.derive\t$fgc_c\t$time\n"; 76 | print "metrics.full_gc_time.derive\t$fgc_t\t$time\n"; 77 | 78 | my $gc = '-'; 79 | # Detect name of garbage collector 80 | for my $collector (keys %$value) { 81 | if ($collector =~ /name=MarkSweep/) { 82 | $gc = "Serial"; 83 | last; 84 | } elsif ($collector =~ /name=PS /) { 85 | $gc = "Parallel"; 86 | last; 87 | } elsif ($collector =~ /name=ConcurrentMarkSweep/) { 88 | $gc = "Concurrent Mark & Sweep"; 89 | last; 90 | } elsif ($collector =~ /name=G1 /) { 91 | $gc = "G1"; 92 | last; 93 | } 94 | } 95 | print "meta.gc\t$gc\t$time\n"; 96 | } 97 | 98 | { 99 | my $res = $furl->request( 100 | scheme => 'http', 101 | host => $host, 102 | port => $port, 103 | path_query => $path . '/read/java.lang:type=Memory/HeapMemoryUsage,NonHeapMemoryUsage', 104 | ); 105 | die "request failed: " .$res->status_line."\n" 106 | unless $res->is_success; 107 | my $data = JSON->new->utf8->decode($res->content); 108 | my $value = $data->{value}; 109 | for my $k ( qw/max committed used/ ) { 110 | print "metrics.heap_memory_usage.$k.gauge\t$value->{HeapMemoryUsage}->{$k}\t$time\n"; 111 | print "metrics.non_heap_memory_usage.$k.gauge\t$value->{NonHeapMemoryUsage}->{$k}\t$time\n"; 112 | } 113 | } 114 | 115 | { 116 | my $res = $furl->request( 117 | scheme => 'http', 118 | host => $host, 119 | port => $port, 120 | path_query => $path . '/read/java.lang:type=MemoryPool,name=*/Type,Usage,MemoryManagerNames', 121 | ); 122 | die "request failed: " .$res->status_line."\n" 123 | unless $res->is_success; 124 | my $data = JSON->new->utf8->decode($res->content); 125 | my $value = $data->{value}; 126 | my @mp_eden = ('U', 'U', 'U'); 127 | my @mp_surv = ('U', 'U', 'U'); 128 | my @mp_old = ('U', 'U', 'U'); 129 | my @mp_perm = ('U', 'U', 'U'); 130 | for my $mp (keys %$value) { 131 | if ($mp =~ /name=.*Eden Space/) { 132 | @mp_eden = @{ $value->{$mp}{Usage} }{qw(max committed used)} 133 | } elsif ($mp =~ /name=.*Survivor Space/) { 134 | @mp_surv = @{ $value->{$mp}{Usage} }{qw(max committed used)} 135 | } elsif ($mp =~ /name=.*(?:Tenured|Old) Gen/) { 136 | @mp_old = @{ $value->{$mp}{Usage} }{qw(max committed used)} 137 | } elsif ($mp =~ /name=.*Perm Gen/) { 138 | @mp_perm = @{ $value->{$mp}{Usage} }{qw(max committed used)} 139 | } elsif ($mp =~ /name=Code Cache/) { 140 | ; 141 | } 142 | } 143 | 144 | print "metrics.memory_pool.eden.max.gauge\t$mp_eden[0]\t$time\n"; 145 | print "metrics.memory_pool.eden.committed.gauge\t$mp_eden[1]\t$time\n"; 146 | print "metrics.memory_pool.eden.used.gauge\t$mp_eden[2]\t$time\n"; 147 | 148 | print "metrics.memory_pool.surv.max.gauge\t$mp_surv[0]\t$time\n"; 149 | print "metrics.memory_pool.surv.committed.gauge\t$mp_surv[1]\t$time\n"; 150 | print "metrics.memory_pool.surv.used.gauge\t$mp_surv[2]\t$time\n"; 151 | 152 | print "metrics.memory_pool.old.max.gauge\t$mp_old[0]\t$time\n"; 153 | print "metrics.memory_pool.old.committed.gauge\t$mp_old[1]\t$time\n"; 154 | print "metrics.memory_pool.old.used.gauge\t$mp_old[2]\t$time\n"; 155 | 156 | print "metrics.memory_pool.perm.max.gauge\t$mp_perm[0]\t$time\n"; 157 | print "metrics.memory_pool.perm.committed.gauge\t$mp_perm[1]\t$time\n"; 158 | print "metrics.memory_pool.perm.used.gauge\t$mp_perm[2]\t$time\n"; 159 | } 160 | 161 | { 162 | my $res = $furl->request( 163 | scheme => 'http', 164 | host => $host, 165 | port => $port, 166 | path_query => $path . '/read/java.lang:type=Runtime/StartTime,VmVendor,SystemProperties,InputArguments,VmName,VmVendor', 167 | ); 168 | die "request failed: " .$res->status_line."\n" 169 | unless $res->is_success; 170 | my $data = JSON->new->utf8->decode($res->content); 171 | my $value = $data->{value}; 172 | my %meta; 173 | $meta{uptime} = $time - int($value->{StartTime}/1000); 174 | $meta{vm} = join(", ", 175 | $value->{VmName} || 'Unknown', 176 | $value->{SystemProperties}{'java.runtime.version'} || 'Unknown', 177 | $value->{VmVendor} || 'Unknown', 178 | ); 179 | $meta{args} = ref($value->{InputArguments}) eq 'ARRAY' ? join(" ", @{ $value->{InputArguments} }) : $value->{InputArguments}; 180 | 181 | for my $key (keys %meta) { 182 | print "meta.$key\t$meta{$key}\t$time\n"; 183 | } 184 | } 185 | 186 | 187 | -------------------------------------------------------------------------------- /metrics_plugins/fetch/memcached.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | use Kurado::TinyTCP; 9 | use IO::Socket qw(:crlf); 10 | 11 | our $VERSION = '0.01'; 12 | 13 | my $plugin = Kurado::Plugin->new(@ARGV); 14 | my $host = $plugin->address; 15 | my ($port) = @{$plugin->plugin_arguments}; 16 | $port ||= 11211; 17 | 18 | my $client = Kurado::TinyTCP->new( 19 | server => $host . ':' . $port, 20 | timeout => 3.5 21 | ); 22 | 23 | $client->write("stats$CRLF",1); 24 | my $raw_stats = $client->read(1); 25 | die "could not retrieve status from $host:$port" unless $raw_stats; 26 | while ( $raw_stats !~ m!END! ) { 27 | my $raw_buf = $client->read(1); 28 | die "could not retrieve status from $host:$port" unless $raw_buf; 29 | $raw_stats .= $raw_buf; 30 | } 31 | 32 | my %stats; 33 | foreach my $line ( split /\r?\n/, $raw_stats ) { 34 | if ( $line =~ /^STAT\s([^ ]+)\s(.+)$/ ) { 35 | $stats{$1} = $2; 36 | } 37 | } 38 | 39 | my %meta; 40 | if ( exists $stats{version} ) { 41 | $meta{version} = $stats{version}; 42 | } 43 | if ( exists $stats{uptime} ) { 44 | $meta{uptime} = $stats{uptime} 45 | } 46 | 47 | if ( $stats{version} && $stats{version} =~ m!^1\.(\d+)! && $1 >= 4 ) { 48 | $client->write("stats settings$CRLF"); 49 | my $raw_setting_stats = $client->read(1); 50 | die "could not retrieve status from $host:$port" unless $raw_setting_stats; 51 | while ( $raw_setting_stats !~ m!END! ) { 52 | my $raw_buf = $client->read(1); 53 | die "could not retrieve status from $host:$port" unless $raw_buf; 54 | $raw_setting_stats .= $raw_buf; 55 | } 56 | my %setting_stats; 57 | foreach my $line ( split /\r?\n/, $raw_setting_stats ) { 58 | if ( $line =~ /^STAT\s([^ ]+)\s(.+)$/ ) { 59 | $setting_stats{$1} = $2; 60 | } 61 | } 62 | $meta{maxconns} = $setting_stats{maxconns}; 63 | $stats{maxconns} = $setting_stats{maxconns}; 64 | } 65 | 66 | 67 | 68 | my $time = time; 69 | for my $key (keys %meta) { 70 | print "meta.$key\t$meta{$key}\t$time\n"; 71 | } 72 | for my $key (qw/cmd_get cmd_set get_hits get_misses evictions evicted_unfetched/) { 73 | my $val = exists $stats{$key} ? $stats{$key} : 'U'; 74 | print "metrics.$key.derive\t$val\t$time\n"; 75 | } 76 | for my $key (qw/curr_connections bytes limit_maxbytes curr_items maxconns/) { 77 | my $val = exists $stats{$key} ? $stats{$key} : 'U'; 78 | print "metrics.$key.gauge\t$val\t$time\n"; 79 | } 80 | 81 | 82 | 83 | =pod 84 | 85 | =head1 NAME 86 | 87 | fetch/memcached.pl - metrics fetcher for memcached 88 | 89 | =head1 SYNOPSIS 90 | 91 | % fetch/memcached.pl --help 92 | 93 | =head1 DESCRIPTION 94 | 95 | metrics fetcher for memcached 96 | 97 | =head1 AUTHOR 98 | 99 | Masahiro Nagano 100 | 101 | =head1 LICENSE 102 | 103 | This library is free software; you can redistribute it and/or modify it 104 | under the same terms as Perl itself. 105 | 106 | =cut 107 | -------------------------------------------------------------------------------- /metrics_plugins/fetch/nginx.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | use Furl; 9 | use IO::Socket qw/inet_aton pack_sockaddr_in/; 10 | 11 | our $VERSION = '0.01'; 12 | 13 | my $plugin = Kurado::Plugin->new(@ARGV); 14 | my $host = $plugin->address; 15 | my ($port,$path,$http_host) = @{$plugin->plugin_arguments}; 16 | $port ||= 80; 17 | $path ||= '/nginx_status'; 18 | $http_host ||= $host; 19 | 20 | my $furl = Furl->new( 21 | agent => 'kurado-plugin', 22 | timeout => 10, 23 | get_address => sub { 24 | pack_sockaddr_in($port, inet_aton($host)); 25 | } 26 | ); 27 | 28 | my $res = $furl->request( 29 | scheme => 'http', 30 | host => $http_host, 31 | port => $port, 32 | path_query => $path, 33 | ); 34 | 35 | die "server-status failed: " .$res->status_line."\n" 36 | unless $res->is_success; 37 | 38 | my %meta; 39 | if ( my $server_version = $res->header('Server') ) { 40 | $meta{server} = $server_version; 41 | } 42 | 43 | my $body = $res->body; 44 | my %metrics; 45 | if ( $body =~ /Reading: (\d+) Writing: (\d+) Waiting: (\d+)/ ) { 46 | $metrics{read} = $1; 47 | $metrics{write} = $2; 48 | $metrics{wait} = $3; 49 | } 50 | if ( $body =~ /(\d+) (\d+) (\d+)/ ) { 51 | $metrics{reqs} = $3; 52 | } 53 | 54 | my $time = time; 55 | for my $key (keys %meta) { 56 | print "meta.$key\t$meta{$key}\t$time\n"; 57 | } 58 | 59 | for my $key (qw/read write wait/) { 60 | my $metrics = exists $metrics{$key} ? $metrics{$key} : 'U'; 61 | print "metrics.$key.gauge\t$metrics{$key}\t$time\n"; 62 | } 63 | 64 | for my $key (qw/reqs/) { 65 | my $metrics = exists $metrics{$key} ? $metrics{$key} : 'U'; 66 | print "metrics.$key.derive\t$metrics{$key}\t$time\n"; 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /metrics_plugins/fetch/opcache.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | use Furl; 9 | use JSON; 10 | 11 | our $VERSION = '0.01'; 12 | 13 | my $plugin = Kurado::Plugin->new(@ARGV); 14 | my $host = $plugin->address; 15 | my ($port,$path) = @{$plugin->plugin_arguments}; 16 | $port ||= 82; 17 | $path ||= '/opcache-status.php'; 18 | 19 | my $furl = Furl->new( 20 | agent => 'kurado-plugin', 21 | timeout => 10, 22 | ); 23 | my $time = time; 24 | 25 | 26 | my $res = $furl->request( 27 | scheme => 'http', 28 | host => $host, 29 | port => $port, 30 | path_query => $path 31 | ); 32 | die "request failed: " .$res->status_line."\n" 33 | unless $res->is_success; 34 | my $data = JSON->new->utf8->decode($res->content); 35 | 36 | my $config = $data->{config} || {}; 37 | if ( exists $config->{max_accelerated_files} ) { 38 | print "meta.max_accelerated_files\t$config->{max_accelerated_files}\t$time\n"; 39 | } 40 | if ( exists $config->{interned_strings_max} ) { 41 | print "meta.interned_strings_max\t$config->{interned_strings_max}MB\t$time\n"; 42 | } 43 | 44 | for my $key (qw/max_file_size memory_max/) { 45 | if ( exists $config->{$key} ) { 46 | my $size = int $config->{$key} / (1024*1024); 47 | while($size =~ s/(.*\d)(\d\d\d)/$1,$2/){} ; 48 | $size .= "MB"; 49 | print "meta.$key\t$size\t$time\n"; 50 | } 51 | } 52 | 53 | 54 | 55 | my $stats = $data->{statictics} || {}; 56 | for my $key (qw/opcache_hit_rate max_cached_keys num_cached_keys num_cached_scripts/) { 57 | my $metrics = exists $stats->{$key} ? $stats->{$key} : 'U'; 58 | print "metrics.$key.gauge\t$stats->{$key}\t$time\n"; 59 | } 60 | 61 | $stats = $data->{interned_strings_usage} || {}; 62 | for my $key (qw/used_memory free_memory/) { 63 | my $metrics = exists $stats->{$key} ? $stats->{$key} : 'U'; 64 | print "metrics.strings_$key.gauge\t$stats->{$key}\t$time\n"; 65 | } 66 | 67 | $stats = $data->{memory_usage} || {}; 68 | for my $key (qw/wasted_memory free_memory used_memory/) { 69 | my $metrics = exists $stats->{$key} ? $stats->{$key} : 'U'; 70 | print "metrics.$key.gauge\t$stats->{$key}\t$time\n"; 71 | } 72 | 73 | -------------------------------------------------------------------------------- /metrics_plugins/fetch/redis.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | use Kurado::TinyTCP; 9 | use IO::Socket qw(:crlf); 10 | 11 | our $VERSION = '0.01'; 12 | 13 | my $plugin = Kurado::Plugin->new(@ARGV); 14 | my $host = $plugin->address; 15 | my ($port) = @{$plugin->plugin_arguments}; 16 | $port ||= 6379; 17 | 18 | my $client = Kurado::TinyTCP->new( 19 | server => $host . ':' . $port, 20 | timeout => 3.5 21 | ); 22 | 23 | $client->write("info\r\n",1); 24 | my $raw_stats = $client->read(1); 25 | die "could not retrieve status from $host:$port" unless $raw_stats; 26 | 27 | my %stats; 28 | my $keys; 29 | foreach my $line ( split /\r?\n/, $raw_stats ) { 30 | chomp($line);chomp($line); 31 | if ( $line =~ /^([^:]+?):(.+)$/ ) { 32 | my($k,$v) = ($1,$2); 33 | $stats{$k} = $v; 34 | if ($k =~ /^db[0-9]+/) { 35 | $keys += $v =~ /keys=(\d+),/ ? $1 : 0; 36 | } 37 | } 38 | } 39 | 40 | my $raw_res; 41 | ### slowlog 42 | $client->write("slowlog len\r\n",1); 43 | $raw_res = $client->read(1); 44 | my $slowlog = $raw_res =~ /:([0-9]+)/ ? $1 : 0; 45 | 46 | ### config get 47 | my %config; 48 | $client->write("config get *\r\n",1); 49 | $raw_res = $client->read(1); 50 | my $ck; 51 | foreach my $line ( split /\r?\n/, $raw_res ) { 52 | chomp($line);chomp($line); 53 | next if $line =~ /^[\*\$]/; 54 | if (! $ck) { 55 | $ck = $line; 56 | } else { 57 | $config{$ck} = $line; 58 | $ck = ""; 59 | } 60 | } 61 | 62 | my %meta; 63 | if ( $stats{redis_version} ) { 64 | $meta{version} = $stats{redis_version}; 65 | } 66 | if ( my $uptime = $stats{uptime_in_seconds} ) { 67 | $meta{uptime} = $uptime; 68 | } 69 | foreach my $stats_key (qw/vm_enabled role/) { 70 | $meta{$stats_key} = $stats{$stats_key} 71 | if exists $stats{$stats_key}; 72 | } 73 | foreach my $config_key (qw/maxmemory maxclients rdbcompression appendonly maxmemory-policy appendfsync save slowlog-max-len/) { 74 | $meta{$config_key} = $config{$config_key} 75 | if exists $config{$config_key}; 76 | } 77 | 78 | my %metrics; 79 | my @stats = ( 80 | [qw/total_commands_processed derive/], 81 | [qw/total_connections_received derive/], 82 | [qw/connected_clients gauge/], 83 | [qw/connected_slaves gauge/], 84 | [qw/used_memory gauge/], 85 | [qw/changes_since_last_save gauge/], 86 | [qw/mem_fragmentation_ratio gauge/], 87 | [qw/evicted_keys derive/], 88 | [qw/pubsub_channels gauge/] 89 | ); 90 | for my $stats_key (@stats) { 91 | $metrics{$stats_key->[0].".".$stats_key->[1]} = $stats{$stats_key->[0]} 92 | if exists $stats{$stats_key->[0]}; 93 | } 94 | if ( !exists $stats{'changes_since_last_save.gauge'} ) { 95 | $metrics{'changes_since_last_save.gauge'} = $stats{'rdb_changes_since_last_save'} 96 | if exists $stats{'rdb_changes_since_last_save'}; 97 | 98 | } 99 | 100 | $metrics{'keys.gauge'} = $keys; 101 | $metrics{'slowlog.gauge'} = $slowlog; 102 | my $time = time; 103 | for my $key (keys %meta) { 104 | print "meta.$key\t$meta{$key}\t$time\n"; 105 | } 106 | for my $key (keys %metrics) { 107 | print "metrics.$key\t$metrics{$key}\t$time\n"; 108 | } 109 | 110 | 111 | 112 | =pod 113 | 114 | =head1 NAME 115 | 116 | fetch/redis.pl - metrics fetcher for redis 117 | 118 | =head1 SYNOPSIS 119 | 120 | % fetch/redis.pl --help 121 | 122 | =head1 DESCRIPTION 123 | 124 | metrics fetcher for redis 125 | 126 | =head1 AUTHOR 127 | 128 | Masahiro Nagano 129 | 130 | =head1 LICENSE 131 | 132 | This library is free software; you can redistribute it and/or modify it 133 | under the same terms as Perl itself. 134 | 135 | =cut 136 | -------------------------------------------------------------------------------- /metrics_plugins/fetch/squid.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | use Kurado::TinyTCP; 9 | use IO::Socket qw(:crlf); 10 | 11 | our $VERSION = '0.01'; 12 | 13 | my $plugin = Kurado::Plugin->new(@ARGV); 14 | my $host = $plugin->address; 15 | my ($port) = @{$plugin->plugin_arguments}; 16 | $port ||= 3128; 17 | 18 | 19 | my %stats; 20 | my %meta; 21 | { 22 | my $client = Kurado::TinyTCP->new( 23 | server => $host . ':' . $port, 24 | timeout => 3.5 25 | ); 26 | $client->write("GET cache_object://localhost/counters HTTP/1.0$CRLF$CRLF",1); 27 | my $raw_stats = $client->read_until_close(1); 28 | die "could not retrieve couter status from $host:$port" unless $raw_stats; 29 | foreach my $line ( split /\r?\n/, $raw_stats ) { 30 | if ( $line =~ /^client_http\.(requests|hits|errors)\s*=\s*(\-?\d+)$/ ) { 31 | $stats{'client-http.'.$1} = $2; 32 | } 33 | } 34 | } 35 | 36 | { 37 | my $client = Kurado::TinyTCP->new( 38 | server => $host . ':' . $port, 39 | timeout => 3.5 40 | ); 41 | $client->write("GET cache_object://localhost/5min HTTP/1.0$CRLF$CRLF",1); 42 | my $raw_stats = $client->read_until_close(1); 43 | die "could not retrieve couter status from $host:$port" unless $raw_stats; 44 | foreach my $line ( split /\r?\n/, $raw_stats ) { 45 | if ( $line =~ /^client_http\.(all|miss|nm|hit)_median_svc_time\s*=\s*([0-9\.]+) seconds$/ ) { 46 | $stats{'svc-time.'.$1} = $2 * 1000; #msec 47 | } 48 | } 49 | } 50 | 51 | { 52 | my $client = Kurado::TinyTCP->new( 53 | server => $host . ':' . $port, 54 | timeout => 3.5 55 | ); 56 | $client->write("GET cache_object://localhost/info HTTP/1.0$CRLF$CRLF",1); 57 | my $raw_stats = $client->read_until_close(1); 58 | die "could not retrieve couter status from $host:$port" unless $raw_stats; 59 | foreach my $line ( split /\r?\n/, $raw_stats ) { 60 | if ( $line =~ m!^Squid Object Cache: Version (.+)$! ) { 61 | $meta{version} = $1; 62 | } 63 | if ( $line =~ m!^\s*UP Time:\s*([0-9\.]+) seconds$! ) { 64 | $meta{uptime} = int($1); 65 | } 66 | if ( $line =~ m!^\s*Maximum number of file descriptors:\s*(\d+)$! ) { 67 | $stats{'file-descriptors.max'} = $1; 68 | } 69 | if ( $line =~ m!^\s*Number of file desc currently in use:\s*(\d+)$! ) { 70 | $stats{'file-descriptors.used'} = $1; 71 | } 72 | if ( $line =~ m!\s*(\d+) StoreEntries$! ) { 73 | $stats{'store-entries.total'} = $1; 74 | } 75 | if ( $line =~ m!\s*(\d+) StoreEntries with MemObjects$! ) { 76 | $stats{'store-entries.with-memobject'} = $1; 77 | } 78 | } 79 | } 80 | 81 | my $time = time; 82 | for my $key (keys %meta) { 83 | print "meta.$key\t$meta{$key}\t$time\n"; 84 | } 85 | for my $key (qw/client-http.requests client-http.hits client-http.errors/) { 86 | my $val = exists $stats{$key} ? $stats{$key} : 'U'; 87 | print "metrics.$key.derive\t$val\t$time\n"; 88 | } 89 | for my $key (qw/svc-time.all svc-time.miss svc-time.nm svc-time.hit 90 | file-descriptors.max file-descriptors.used store-entries.total store-entries.with-memobject/) { 91 | my $val = exists $stats{$key} ? $stats{$key} : 'U'; 92 | print "metrics.$key.gauge\t$val\t$time\n"; 93 | } 94 | 95 | 96 | -------------------------------------------------------------------------------- /metrics_plugins/view/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/metrics_plugins/view/.gitkeep -------------------------------------------------------------------------------- /metrics_plugins/view/gaurun.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | 9 | our $VERSION = '0.01'; 10 | 11 | my $plugin = Kurado::Plugin->new(@ARGV); 12 | 13 | sub metrics_list { 14 | my $plugin = shift; 15 | my $meta = $plugin->metrics_meta; 16 | my $list=''; 17 | 18 | # info 19 | my @info; 20 | for my $key ( $plugin->sort_info(keys %$meta) ) { 21 | push @info, $key, $meta->{$key}; 22 | } 23 | my ($port,$path) = @{$plugin->plugin_arguments}; 24 | $port ||= 82; 25 | $list .= join("\t",'#Gaurun ('.$port.')',@info)."\n"; 26 | $list .= "$_\n" for qw/queue ios ios_rate android android_rate/; 27 | print $list; 28 | } 29 | 30 | sub metrics_graph { 31 | my $plugin = shift; 32 | my $graph = $plugin->graph; 33 | my $def = ''; 34 | $def = $plugin->render($graph); 35 | print "$def\n"; 36 | } 37 | 38 | if ($plugin->graph ) { 39 | metrics_graph($plugin); 40 | } 41 | else { 42 | metrics_list($plugin); 43 | } 44 | 45 | __DATA__ 46 | @@ queue 47 | Queue 48 | DEF:my1=<%RRD queue_usage.gauge %>:read:AVERAGE 49 | DEF:my2=<%RRD queue_max.gauge %>:read:AVERAGE 50 | AREA:my1#00C000:Queued 51 | GPRINT:my1:LAST:Cur\:%8.0lf 52 | GPRINT:my1:AVERAGE:Ave\:%8.0lf 53 | GPRINT:my1:MAX:Max\:%8.0lf\l 54 | LINE:my2#333333:Max 55 | GPRINT:my2:LAST:Cur\:%8.0lf 56 | GPRINT:my2:AVERAGE:Ave\:%8.0lf 57 | GPRINT:my2:MAX:Max\:%8.0lf\l 58 | 59 | 60 | @@ ios 61 | iOS 62 | DEF:my1a=<%RRD ios_push_success.counter %>:read:AVERAGE 63 | DEF:my2a=<%RRD ios_push_error.counter %>:read:AVERAGE 64 | CDEF:my1=my1a,0,10000,LIMIT 65 | CDEF:my2=my2a,0,10000,LIMIT 66 | AREA:my2#990000:Error 67 | GPRINT:my2:LAST:Cur\:%6.1lf 68 | GPRINT:my2:AVERAGE:Ave\:%6.1lf 69 | GPRINT:my2:MAX:Max\:%6.1lf\l 70 | STACK:my1#0000C0:Success 71 | GPRINT:my1:LAST:Cur\:%6.1lf 72 | GPRINT:my1:AVERAGE:Ave\:%6.1lf 73 | GPRINT:my1:MAX:Max\:%6.1lf\l 74 | 75 | @@ ios_rate 76 | iOS success rate 77 | DEF:my1=<%RRD ios_push_success.counter %>:read:AVERAGE 78 | DEF:my2=<%RRD ios_push_error.counter %>:read:AVERAGE 79 | CDEF:total=my1,my2,+ 80 | CDEF:my1r=my1,total,/,100,* 81 | CDEF:my2r=my2,total,/,100,* 82 | AREA:my2r#990000:Error 83 | GPRINT:my2r:LAST:Cur\:%5.1lf[%%] 84 | GPRINT:my2r:AVERAGE:Ave\:%5.1lf[%%] 85 | GPRINT:my2r:MAX:Max\:%5.1lf[%%]\l 86 | AREA:my1r#00cc00:Success 87 | GPRINT:my1r:LAST:Cur\:%5.1lf[%%] 88 | GPRINT:my1r:AVERAGE:Ave\:%5.1lf[%%] 89 | GPRINT:my1r:MAX:Max\:%5.1lf[%%]\l 90 | 91 | 92 | @@ android 93 | Android 94 | DEF:my1a=<%RRD android_push_success.counter %>:read:AVERAGE 95 | DEF:my2a=<%RRD android_push_error.counter %>:read:AVERAGE 96 | CDEF:my1=my1a,0,10000,LIMIT 97 | CDEF:my2=my2a,0,10000,LIMIT 98 | AREA:my2#990000:Error 99 | GPRINT:my2:LAST:Cur\:%6.1lf 100 | GPRINT:my2:AVERAGE:Ave\:%6.1lf 101 | GPRINT:my2:MAX:Max\:%6.1lf\l 102 | STACK:my1#0000C0:Success 103 | GPRINT:my1:LAST:Cur\:%6.1lf 104 | GPRINT:my1:AVERAGE:Ave\:%6.1lf 105 | GPRINT:my1:MAX:Max\:%6.1lf\l 106 | 107 | 108 | @@ android_rate 109 | Android success rate 110 | DEF:my1=<%RRD android_push_success.counter %>:read:AVERAGE 111 | DEF:my2=<%RRD android_push_error.counter %>:read:AVERAGE 112 | CDEF:total=my1,my2,+ 113 | CDEF:my1r=my1,total,/,100,* 114 | CDEF:my2r=my2,total,/,100,* 115 | AREA:my2r#990000:Error 116 | GPRINT:my2r:LAST:Cur\:%5.1lf[%%] 117 | GPRINT:my2r:AVERAGE:Ave\:%5.1lf[%%] 118 | GPRINT:my2r:MAX:Max\:%5.1lf[%%]\l 119 | STACK:my1r#00cc00:Success 120 | GPRINT:my1r:LAST:Cur\:%5.1lf[%%] 121 | GPRINT:my1r:AVERAGE:Ave\:%5.1lf[%%] 122 | GPRINT:my1r:MAX:Max\:%5.1lf[%%]\l 123 | 124 | 125 | -------------------------------------------------------------------------------- /metrics_plugins/view/goapp.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | 9 | our $VERSION = '0.01'; 10 | 11 | my $plugin = Kurado::Plugin->new(@ARGV); 12 | 13 | sub metrics_list { 14 | my $plugin = shift; 15 | my $meta = $plugin->metrics_meta; 16 | my $list=''; 17 | 18 | # info 19 | my @info; 20 | for my $key ( $plugin->sort_info(keys %$meta) ) { 21 | push @info, $key, $meta->{$key}; 22 | } 23 | my ($port,$path) = @{$plugin->plugin_arguments}; 24 | $port ||= 82; 25 | $list .= join("\t",'#GoApp ('.$port.')',@info)."\n"; 26 | $list .= "$_\n" for qw/goroutine cgo gc alloc malloc/; 27 | print $list; 28 | } 29 | 30 | sub metrics_graph { 31 | my $plugin = shift; 32 | my $graph = $plugin->graph; 33 | my $def = ''; 34 | $def = $plugin->render($graph); 35 | print "$def\n"; 36 | } 37 | 38 | if ($plugin->graph ) { 39 | metrics_graph($plugin); 40 | } 41 | else { 42 | metrics_list($plugin); 43 | } 44 | 45 | __DATA__ 46 | @@ goroutine 47 | number of goroutine 48 | DEF:my1=<%RRD goroutine_num.gauge %>:read:AVERAGE 49 | AREA:my1#00C000:Goroutine 50 | GPRINT:my1:LAST:Cur\:%5.1lf 51 | GPRINT:my1:AVERAGE:Ave\:%5.1lf 52 | GPRINT:my1:MAX:Max\:%5.1lf\l 53 | 54 | @@ cgo 55 | cgo call per seconds 56 | DEF:my1a=<%RRD cgo_call_num.counter %>:read:AVERAGE 57 | CDEF:my1=my1a,0,100000,LIMIT 58 | LINE2:my1#004080:cgo call/sec 59 | GPRINT:my1:LAST:Cur\:%5.1lf 60 | GPRINT:my1:AVERAGE:Ave\:%5.1lf 61 | GPRINT:my1:MAX:Max\:%5.1lf\l 62 | 63 | @@ gc 64 | gc per seconds 65 | DEF:my1a=<%RRD gc_num.counter %>:read:AVERAGE 66 | CDEF:my1=my1a,0,10000,LIMIT 67 | LINE2:my1#800040:gc/sec 68 | GPRINT:my1:LAST:Cur\:%5.1lf 69 | GPRINT:my1:AVERAGE:Ave\:%5.1lf 70 | GPRINT:my1:MAX:Max\:%5.1lf\l 71 | 72 | @@ alloc 73 | memory alloc 74 | DEF:my1=<%RRD memory_alloc.gauge %>:read:AVERAGE 75 | AREA:my1#edaa40:memory alloc 76 | GPRINT:my1:LAST:Cur\:%5.1lf%sB 77 | GPRINT:my1:AVERAGE:Ave\:%5.1lf%sB 78 | GPRINT:my1:MAX:Max\:%5.1lf%sB\l 79 | 80 | @@ malloc 81 | memory malloc / free 82 | DEF:my1a=<%RRD memory_mallocs.counter %>:read:AVERAGE 83 | DEF:my2a=<%RRD memory_frees.counter %>:read:AVERAGE 84 | CDEF:my1=my1a,0,1000000,LIMIT 85 | CDEF:my2=my2a,0,1000000,LIMIT 86 | LINE2:my1#de4446:malloc 87 | GPRINT:my1:LAST:Cur\:%5.1lf 88 | GPRINT:my1:AVERAGE:Ave\:%5.1lf 89 | GPRINT:my1:MAX:Max\:%5.1lf\l 90 | LINE2:my2#000d48:free 91 | GPRINT:my2:LAST:Cur\:%5.1lf 92 | GPRINT:my2:AVERAGE:Ave\:%5.1lf 93 | GPRINT:my2:MAX:Max\:%5.1lf\l 94 | 95 | 96 | -------------------------------------------------------------------------------- /metrics_plugins/view/http.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | 9 | our $VERSION = '0.01'; 10 | 11 | my $plugin = Kurado::Plugin->new(@ARGV); 12 | 13 | sub metrics_list { 14 | my $plugin = shift; 15 | my $meta = $plugin->metrics_meta; 16 | my $list=''; 17 | 18 | # info 19 | my @info; 20 | for my $key ( $plugin->sort_info(keys %$meta) ) { 21 | next if $key eq 'has-reqs'; 22 | if ( $key eq 'uptime' ) { 23 | push @info, 'uptime', $plugin->uptime2str($meta->{uptime}); 24 | } 25 | else { 26 | push @info, $key, $meta->{$key}; 27 | } 28 | } 29 | my ($port,$path,$http_host) = @{$plugin->plugin_arguments}; 30 | $port ||= 80; 31 | $list .= join("\t",'#HTTP ('.$port.')',@info)."\n"; 32 | $list .= "worker\n"; 33 | $list .= "reqs\n" if $meta->{'has-reqs'}; 34 | print $list; 35 | } 36 | 37 | 38 | sub metrics_graph { 39 | my $plugin = shift; 40 | my $graph = $plugin->graph; 41 | my $def = ''; 42 | $def = $plugin->render($graph); 43 | print "$def\n"; 44 | } 45 | 46 | if ($plugin->graph ) { 47 | metrics_graph($plugin); 48 | } 49 | else { 50 | metrics_list($plugin); 51 | } 52 | 53 | =pod 54 | 55 | =head1 NAME 56 | 57 | view/http.pl - display metrics of http 58 | 59 | =head1 SYNOPSIS 60 | 61 | % view/http.pl --help 62 | 63 | =head1 DESCRIPTION 64 | 65 | display metrics of http 66 | 67 | =head1 AUTHOR 68 | 69 | Masahiro Nagano 70 | 71 | =head1 LICENSE 72 | 73 | This library is free software; you can redistribute it and/or modify it 74 | under the same terms as Perl itself. 75 | 76 | =cut 77 | 78 | __DATA__ 79 | @@ worker 80 | Worker Status 81 | DEF:my1=<%RRD busy.gauge %>:busy:AVERAGE 82 | DEF:my2=<%RRD idle.gauge %>:idle:AVERAGE 83 | AREA:my1#00C000:Busy 84 | GPRINT:my1:LAST:Cur\:%6.1lf 85 | GPRINT:my1:AVERAGE:Ave\:%6.1lf 86 | GPRINT:my1:MAX:Max\:%6.1lf\l 87 | STACK:my2#0000C0:Idle 88 | GPRINT:my2:LAST:Cur\:%6.1lf 89 | GPRINT:my2:AVERAGE:Ave\:%6.1lf 90 | GPRINT:my2:MAX:Max\:%6.1lf\l 91 | 92 | @@ reqs 93 | Request per sec 94 | DEF:my1a=<%RRD reqs.derive %>:rps:AVERAGE 95 | CDEF:my1=my1a,0,10000000,LIMIT 96 | LINE1:my1#aa0000:Request 97 | GPRINT:my1:LAST:Cur\:%6.2lf 98 | GPRINT:my1:AVERAGE:Ave\:%6.2lf 99 | GPRINT:my1:MAX:Max\:%6.2lf\l 100 | 101 | -------------------------------------------------------------------------------- /metrics_plugins/view/jolokia.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | 9 | our $VERSION = '0.01'; 10 | 11 | my $plugin = Kurado::Plugin->new(@ARGV); 12 | 13 | sub metrics_list { 14 | my $plugin = shift; 15 | my $meta = $plugin->metrics_meta; 16 | my $list=''; 17 | 18 | # info 19 | my @info; 20 | for my $key ( $plugin->sort_info(keys %$meta) ) { 21 | if ( $key eq 'uptime' ) { 22 | push @info, 'uptime', $plugin->uptime2str($meta->{uptime}); 23 | } 24 | else { 25 | push @info, $key, $meta->{$key}; 26 | } 27 | } 28 | my ($port) = @{$plugin->plugin_arguments}; 29 | $port ||= 8773; 30 | $list .= join("\t",'#Jolokia ('.$port.')',@info)."\n"; 31 | $list .= "$_\n" for qw/class_c thread_c gc_c gc_t/; 32 | $list .= '#Jolokia ('.$port.') Memory'."\n"; 33 | $list .= "$_\n" for qw/m_heap_s m_nonheap_s/; 34 | $list .= '#Jolokia ('.$port.') Memory Pool'."\n"; 35 | $list .= "$_\n" for qw/mp_eden_s mp_surv_s mp_old_s mp_perm_s/; 36 | print $list; 37 | } 38 | 39 | sub metrics_graph { 40 | my $plugin = shift; 41 | my $graph = $plugin->graph; 42 | my $def = ''; 43 | $def = $plugin->render($graph); 44 | print "$def\n"; 45 | } 46 | 47 | if ($plugin->graph ) { 48 | metrics_graph($plugin); 49 | } 50 | else { 51 | metrics_list($plugin); 52 | } 53 | 54 | __DATA__ 55 | @@ class_c 56 | Loaded class 57 | DEF:my1=<%RRD loaded_class.gauge %>:class_c:AVERAGE 58 | AREA:my1#6060e0:Loaded class 59 | GPRINT:my1:LAST:Cur\:%6.1lf 60 | GPRINT:my1:AVERAGE:Ave\:%6.1lf 61 | GPRINT:my1:MAX:Max\:%6.1lf\l 62 | 63 | @@ thread_c 64 | Threads 65 | DEF:my1=<%RRD thread_count.gauge %>:thread_c:AVERAGE 66 | DEF:my2=<%RRD daemon_thread_count.gauge %>:dthread_c:AVERAGE 67 | LINE2:my1#008080:Total threads 68 | GPRINT:my1:LAST:Cur\:%6.1lf 69 | GPRINT:my1:AVERAGE:Ave\:%6.1lf 70 | GPRINT:my1:MAX:Max\:%6.1lf\l 71 | LINE1:my2#000080:Daemon threads 72 | GPRINT:my2:LAST:Cur\:%6.1lf 73 | GPRINT:my2:AVERAGE:Ave\:%6.1lf 74 | GPRINT:my2:MAX:Max\:%6.1lf\l 75 | 76 | @@ gc_c 77 | GC count [GC/sec] 78 | DEF:my1t=<%RRD young_gc_count.derive %>:ygc_c:AVERAGE 79 | DEF:my2t=<%RRD full_gc_count.derive %>:fgc_c:AVERAGE 80 | CDEF:my1=my1t,0,100000,LIMIT 81 | CDEF:my2=my2t,0,100000,LIMIT 82 | LINE2:my1#d1a2f6:Young Gen 83 | GPRINT:my1:LAST:Cur\:%5.3lf 84 | GPRINT:my1:AVERAGE:Ave\:%5.3lf 85 | GPRINT:my1:MAX:Max\:%5.3lf\l 86 | LINE1:my2#7020AF:Full 87 | GPRINT:my2:LAST:Cur\:%5.3lf 88 | GPRINT:my2:AVERAGE:Ave\:%5.3lf 89 | GPRINT:my2:MAX:Max\:%5.3lf\l 90 | 91 | @@ gc_t 92 | GC time [Elapsed/msec] 93 | DEF:my1t=<%RRD young_gc_time.derive %>:ygc_t:AVERAGE 94 | DEF:my2t=<%RRD full_gc_time.derive %>:fgc_t:AVERAGE 95 | CDEF:my1=my1t,0,100000,LIMIT 96 | CDEF:my2=my2t,0,100000,LIMIT 97 | LINE2:my1#F0B300:Young Gen 98 | GPRINT:my1:LAST:Cur\:%6.1lf 99 | GPRINT:my1:AVERAGE:Ave\:%6.1lf 100 | GPRINT:my1:MAX:Max\:%6.1lf\l 101 | LINE1:my2#906D08:Full 102 | GPRINT:my2:LAST:Cur\:%6.1lf 103 | GPRINT:my2:AVERAGE:Ave\:%6.1lf 104 | GPRINT:my2:MAX:Max\:%6.1lf\l 105 | 106 | @@ m_heap_s 107 | Heap 108 | DEF:my1=<%RRD heap_memory_usage.max.gauge %>:m_h_max_s:AVERAGE 109 | DEF:my2=<%RRD heap_memory_usage.committed.gauge %>:m_h_comt_s:AVERAGE 110 | DEF:my3=<%RRD heap_memory_usage.used.gauge %>:m_h_used_s:AVERAGE 111 | GPRINT:my1:LAST:Max\: %6.1lf%S\l 112 | AREA:my2#afffb2:Committed 113 | GPRINT:my2:LAST:Cur\:%6.1lf%s 114 | GPRINT:my2:AVERAGE:Ave\:%6.1lf%S 115 | GPRINT:my2:MAX:Max\:%6.1lf%S\l 116 | LINE1:my2#00A000 117 | AREA:my3#FFC0C0:Used 118 | GPRINT:my3:LAST:Cur\:%6.1lf%S 119 | GPRINT:my3:AVERAGE:Ave\:%6.1lf%S 120 | GPRINT:my3:MAX:Max\:%6.1lf%S\l 121 | LINE1:my3#aa0000 122 | 123 | @@ m_nonheap_s 124 | Non-Heap 125 | DEF:my1=<%RRD non_heap_memory_usage.max.gauge %>:m_nh_max_s:AVERAGE 126 | DEF:my2=<%RRD non_heap_memory_usage.committed.gauge %>:m_nh_comt_s:AVERAGE 127 | DEF:my3=<%RRD non_heap_memory_usage.used.gauge %>:m_nh_used_s:AVERAGE 128 | GPRINT:my1:LAST:Max\: %6.1lf%s\l 129 | AREA:my2#73b675:Committed 130 | GPRINT:my2:LAST:Cur\:%6.1lf%s 131 | GPRINT:my2:AVERAGE:Ave\:%6.1lf%S 132 | GPRINT:my2:MAX:Max\:%6.1lf%S\l 133 | LINE1:my2#3d783f 134 | AREA:my3#b67777:Used 135 | GPRINT:my3:LAST:Cur\:%6.1lf%S 136 | GPRINT:my3:AVERAGE:Ave\:%6.1lf%S 137 | GPRINT:my3:MAX:Max\:%6.1lf%S\l 138 | LINE1:my3#8b4444 139 | 140 | @@ mp_eden_s 141 | New:Eden 142 | DEF:my1=<%RRD memory_pool.eden.max.gauge %>:mp_eden_max_s:AVERAGE 143 | DEF:my2=<%RRD memory_pool.eden.committed.gauge %>:mp_eden_comt_s:AVERAGE 144 | DEF:my3=<%RRD memory_pool.eden.used.gauge %>:mp_eden_used_s:AVERAGE 145 | GPRINT:my1:LAST:Max\: %6.1lf%S\l 146 | AREA:my2#afffb2:Committed 147 | GPRINT:my2:LAST:Cur\:%6.1lf%s 148 | GPRINT:my2:AVERAGE:Ave\:%6.1lf%S 149 | GPRINT:my2:MAX:Max\:%6.1lf%S\l 150 | LINE1:my2#00A000 151 | AREA:my3#FFC0C0:Used 152 | GPRINT:my3:LAST:Cur\:%6.1lf%S 153 | GPRINT:my3:AVERAGE:Ave\:%6.1lf%S 154 | GPRINT:my3:MAX:Max\:%6.1lf%S\l 155 | LINE1:my3#aa0000 156 | 157 | @@ mp_surv_s 158 | New:Survivor 159 | DEF:my1=<%RRD memory_pool.surv.max.gauge %>:mp_surv_max_s:AVERAGE 160 | DEF:my2=<%RRD memory_pool.surv.committed.gauge %>:mp_surv_comt_s:AVERAGE 161 | DEF:my3=<%RRD memory_pool.surv.used.gauge %>:mp_surv_used_s:AVERAGE 162 | GPRINT:my1:LAST:Max\: %6.1lf%S\l 163 | AREA:my2#afffb2:Committed 164 | GPRINT:my2:LAST:Cur\:%6.1lf%s 165 | GPRINT:my2:AVERAGE:Ave\:%6.1lf%S 166 | GPRINT:my2:MAX:Max\:%6.1lf%S\l 167 | LINE1:my2#00A000 168 | AREA:my3#FFC0C0:Used 169 | GPRINT:my3:LAST:Cur\:%6.1lf%S 170 | GPRINT:my3:AVERAGE:Ave\:%6.1lf%S 171 | GPRINT:my3:MAX:Max\:%6.1lf%S\l 172 | LINE1:my3#aa0000 173 | 174 | @@ mp_old_s 175 | Old 176 | DEF:my1=<%RRD memory_pool.old.max.gauge %>:mp_old_max_s:AVERAGE 177 | DEF:my2=<%RRD memory_pool.old.committed.gauge %>:mp_old_comt_s:AVERAGE 178 | DEF:my3=<%RRD memory_pool.old.used.gauge %>:mp_old_used_s:AVERAGE 179 | GPRINT:my1:LAST:Max\: %6.1lf%S\l 180 | AREA:my2#afffb2:Committed 181 | GPRINT:my2:LAST:Cur\:%6.1lf%s 182 | GPRINT:my2:AVERAGE:Ave\:%6.1lf%S 183 | GPRINT:my2:MAX:Max\:%6.1lf%S\l 184 | LINE1:my2#00A000 185 | AREA:my3#FFC0C0:Used 186 | GPRINT:my3:LAST:Cur\:%6.1lf%S 187 | GPRINT:my3:AVERAGE:Ave\:%6.1lf%S 188 | GPRINT:my3:MAX:Max\:%6.1lf%S\l 189 | LINE1:my3#aa0000 190 | 191 | @@ mp_perm_s 192 | Permanent 193 | DEF:my1=<%RRD memory_pool.perm.max.gauge %>:mp_perm_max_s:AVERAGE 194 | DEF:my2=<%RRD memory_pool.perm.committed.gauge %>:mp_perm_comt_s:AVERAGE 195 | DEF:my3=<%RRD memory_pool.perm.used.gauge %>:mp_perm_used_s:AVERAGE 196 | GPRINT:my1:LAST:Max\: %6.1lf%S\l 197 | AREA:my2#73b675:Committed 198 | GPRINT:my2:LAST:Cur\:%6.1lf%s 199 | GPRINT:my2:AVERAGE:Ave\:%6.1lf%S 200 | GPRINT:my2:MAX:Max\:%6.1lf%S\l 201 | LINE1:my2#3d783f 202 | AREA:my3#b67777:Used 203 | GPRINT:my3:LAST:Cur\:%6.1lf%S 204 | GPRINT:my3:AVERAGE:Ave\:%6.1lf%S 205 | GPRINT:my3:MAX:Max\:%6.1lf%S\l 206 | LINE1:my3#8b4444 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /metrics_plugins/view/memcached.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | 9 | our $VERSION = '0.01'; 10 | 11 | my $plugin = Kurado::Plugin->new(@ARGV); 12 | 13 | sub metrics_list { 14 | my $plugin = shift; 15 | my $meta = $plugin->metrics_meta; 16 | my $list=''; 17 | 18 | # info 19 | my @info; 20 | for my $key ( $plugin->sort_info(keys %$meta) ) { 21 | if ( $key eq 'uptime' ) { 22 | push @info, 'uptime', $plugin->uptime2str($meta->{uptime}); 23 | } 24 | else { 25 | push @info, $key, $meta->{$key}; 26 | } 27 | } 28 | my ($port) = @{$plugin->plugin_arguments}; 29 | $port ||= 80; 30 | $list .= join("\t",'#Memcached ('.$port.')',@info)."\n"; 31 | $list .= "$_\n" for qw/usage items evictions count rate conn/; 32 | print $list; 33 | } 34 | 35 | sub metrics_graph { 36 | my $plugin = shift; 37 | my $graph = $plugin->graph; 38 | my $def = ''; 39 | $def = $plugin->render($graph); 40 | print "$def\n"; 41 | } 42 | 43 | if ($plugin->graph ) { 44 | metrics_graph($plugin); 45 | } 46 | else { 47 | metrics_list($plugin); 48 | } 49 | 50 | =pod 51 | 52 | =head1 NAME 53 | 54 | view/memcached.pl - display metrics of memcached 55 | 56 | =head1 SYNOPSIS 57 | 58 | % view/memcached.pl --help 59 | 60 | =head1 DESCRIPTION 61 | 62 | display metrics of memcached 63 | 64 | =head1 AUTHOR 65 | 66 | Masahiro Nagano 67 | 68 | =head1 LICENSE 69 | 70 | This library is free software; you can redistribute it and/or modify it 71 | under the same terms as Perl itself. 72 | 73 | =cut 74 | 75 | __DATA__ 76 | @@ usage 77 | Cache usage 78 | DEF:my1=<%RRD bytes.gauge %>:used:AVERAGE 79 | DEF:my2=<%RRD limit_maxbytes.gauge %>:max:AVERAGE 80 | AREA:my1#eaaf00:Used 81 | GPRINT:my1:LAST:Cur\:%5.2lf%sB 82 | GPRINT:my1:AVERAGE:Ave\:%5.2lf%sB 83 | GPRINT:my1:MAX:Max\:%5.2lf%sB\l 84 | LINE:my2#333333:Max 85 | GPRINT:my2:LAST:Cur\:%5.2lf%sB 86 | GPRINT:my2:AVERAGE:Ave\:%5.2lf%sB 87 | GPRINT:my2:MAX:Max\:%5.2lf%sB\l 88 | 89 | @@ count 90 | Request count 91 | DEF:my1a=<%RRD cmd_set.derive %>:cmdset:AVERAGE 92 | DEF:my2a=<%RRD cmd_get.derive %>:cmdget:AVERAGE 93 | CDEF:my1=my1a,0,300000,LIMIT 94 | CDEF:my2=my2a,0,300000,LIMIT 95 | AREA:my1#00C000:Set 96 | GPRINT:my1:LAST:Cur\:%7.1lf 97 | GPRINT:my1:AVERAGE:Ave\:%7.1lf 98 | GPRINT:my1:MAX:Max\:%7.1lf\l 99 | STACK:my2#0000C0:Get 100 | GPRINT:my2:LAST:Cur\:%7.1lf 101 | GPRINT:my2:AVERAGE:Ave\:%7.1lf 102 | GPRINT:my2:MAX:Max\:%7.1lf\l 103 | 104 | 105 | @@ rate 106 | Cache hit rate 107 | DEF:hits=<%RRD get_hits.derive %>:gethits:AVERAGE 108 | DEF:misses=<%RRD get_misses.derive %>:getmisses:AVERAGE 109 | CDEF:total=hits,misses,+ 110 | CDEF:rate=hits,total,/,100,*,0,100,LIMIT 111 | AREA:rate#990000:Rate 112 | GPRINT:rate:LAST:Cur\:%5.1lf[%%] 113 | GPRINT:rate:AVERAGE:Ave\:%5.1lf[%%] 114 | GPRINT:rate:MAX:Max\:%5.1lf[%%]\l 115 | LINE:100 116 | 117 | @@ conn 118 | Connections 119 | DEF:conn=<%RRD curr_connections.gauge %>:n:AVERAGE 120 | DEF:my2=<%RRD maxconns.gauge %>:n:AVERAGE 121 | AREA:conn#00C000:Connection 122 | GPRINT:conn:LAST:Cur\:%7.1lf 123 | GPRINT:conn:AVERAGE:Ave\:%7.1lf 124 | GPRINT:conn:MAX:Max\:%7.1lf\l 125 | LINE1:my2#C00000:Max Connection 126 | GPRINT:my2:LAST:Cur\:%7.1lf\l 127 | 128 | @@ evictions 129 | Evictions 130 | DEF:my1=<%RRD evictions.derive %>:evt_total:AVERAGE 131 | DEF:my2=<%RRD evicted_unfetched.derive %>:evt_unfetched:AVERAGE 132 | AREA:my1#800040:Evictions Total 133 | GPRINT:my1:LAST:Cur\:%6.1lf 134 | GPRINT:my1:AVERAGE:Ave\:%6.1lf 135 | GPRINT:my1:MAX:Max\:%6.1lf\l 136 | LINE2:my2#004080:Evictions Unfetched 137 | GPRINT:my2:LAST:Cur\:%6.1lf 138 | GPRINT:my2:AVERAGE:Ave\:%6.1lf 139 | GPRINT:my2:MAX:Max\:%6.1lf\l 140 | 141 | @@ items 142 | Cache items 143 | DEF:my1=<%RRD curr_items.gauge %>:items_cur:AVERAGE 144 | AREA:my1#00A000:Current Items 145 | GPRINT:my1:LAST:Cur\:%8.0lf 146 | GPRINT:my1:AVERAGE:Ave\:%8.0lf 147 | GPRINT:my1:MAX:Max\:%8.0lf\l 148 | 149 | -------------------------------------------------------------------------------- /metrics_plugins/view/nginx.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | 9 | our $VERSION = '0.01'; 10 | 11 | my $plugin = Kurado::Plugin->new(@ARGV); 12 | 13 | sub metrics_list { 14 | my $plugin = shift; 15 | my $meta = $plugin->metrics_meta; 16 | my $list=''; 17 | 18 | # info 19 | my @info; 20 | for my $key ( $plugin->sort_info(keys %$meta) ) { 21 | push @info, $key, $meta->{$key}; 22 | } 23 | my ($port,$path,$http_host) = @{$plugin->plugin_arguments}; 24 | $port ||= 80; 25 | $list .= join("\t",'#Nginx ('.$port.')',@info)."\n"; 26 | $list .= "$_\n" for qw/processes reqs/; 27 | print $list; 28 | } 29 | 30 | 31 | sub metrics_graph { 32 | my $plugin = shift; 33 | my $graph = $plugin->graph; 34 | my $def = ''; 35 | $def = $plugin->render($graph); 36 | print "$def\n"; 37 | } 38 | 39 | if ($plugin->graph ) { 40 | metrics_graph($plugin); 41 | } 42 | else { 43 | metrics_list($plugin); 44 | } 45 | 46 | =pod 47 | 48 | =head1 NAME 49 | 50 | view/nginx.pl - display metrics of nginx 51 | 52 | =head1 SYNOPSIS 53 | 54 | % view/nginx.pl --help 55 | 56 | =head1 DESCRIPTION 57 | 58 | display metrics of nginx 59 | 60 | =head1 AUTHOR 61 | 62 | Masahiro Nagano 63 | 64 | =head1 LICENSE 65 | 66 | This library is free software; you can redistribute it and/or modify it 67 | under the same terms as Perl itself. 68 | 69 | =cut 70 | 71 | __DATA__ 72 | @@ processes 73 | Connections 74 | DEF:my1=<%RRD read.gauge %>:read:AVERAGE 75 | DEF:my2=<%RRD write.gauge %>:write:AVERAGE 76 | DEF:my3=<%RRD wait.gauge %>:wait:AVERAGE 77 | AREA:my1#c0c0c0:Reading 78 | GPRINT:my1:LAST:Cur\:%7.1lf 79 | GPRINT:my1:AVERAGE:Ave\:%7.1lf 80 | GPRINT:my1:MAX:Max\:%7.1lf\l 81 | STACK:my2#000080:Writing 82 | GPRINT:my2:LAST:Cur\:%7.1lf 83 | GPRINT:my2:AVERAGE:Ave\:%7.1lf 84 | GPRINT:my2:MAX:Max\:%7.1lf\l 85 | STACK:my3#008080:Waiting 86 | GPRINT:my3:LAST:Cur\:%7.1lf 87 | GPRINT:my3:AVERAGE:Ave\:%7.1lf 88 | GPRINT:my3:MAX:Max\:%7.1lf\l 89 | 90 | @@ reqs 91 | Request per sec 92 | DEF:my1a=<%RRD reqs.derive %>:request:AVERAGE 93 | CDEF:my1=my1a,0,250000,LIMIT 94 | LINE1:my1#00C000:Request 95 | GPRINT:my1:LAST:Cur\:%7.1lf 96 | GPRINT:my1:AVERAGE:Ave\:%7.1lf 97 | GPRINT:my1:MAX:Max\:%7.1lf\l 98 | -------------------------------------------------------------------------------- /metrics_plugins/view/opcache.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | 9 | our $VERSION = '0.01'; 10 | 11 | my $plugin = Kurado::Plugin->new(@ARGV); 12 | 13 | sub metrics_list { 14 | my $plugin = shift; 15 | my $meta = $plugin->metrics_meta; 16 | my $list=''; 17 | 18 | # info 19 | my @info; 20 | for my $key ( $plugin->sort_info(keys %$meta) ) { 21 | push @info, $key, $meta->{$key}; 22 | } 23 | my ($port,$path) = @{$plugin->plugin_arguments}; 24 | $port ||= 82; 25 | $list .= join("\t",'#Opcache ('.$port.')',@info)."\n"; 26 | $list .= "$_\n" for qw/scripts hits strings memory/; 27 | print $list; 28 | } 29 | 30 | sub metrics_graph { 31 | my $plugin = shift; 32 | my $graph = $plugin->graph; 33 | my $def = ''; 34 | $def = $plugin->render($graph); 35 | print "$def\n"; 36 | } 37 | 38 | if ($plugin->graph ) { 39 | metrics_graph($plugin); 40 | } 41 | else { 42 | metrics_list($plugin); 43 | } 44 | 45 | __DATA__ 46 | @@ scripts 47 | cached scripts 48 | DEF:my1=<%RRD num_cached_keys.gauge %>:read:AVERAGE 49 | DEF:my2=<%RRD num_cached_scripts.gauge %>:write:AVERAGE 50 | DEF:my3=<%RRD max_cached_keys.gauge %>:wait:AVERAGE 51 | LINE2:my1#e55337:Cached keys 52 | GPRINT:my1:LAST:Cur\:%5.1lf%S 53 | GPRINT:my1:AVERAGE:Ave\:%5.1lf%S 54 | GPRINT:my1:MAX:Max\:%5.1lf%S\l 55 | LINE2:my2#d2f35e:Cached scripts 56 | GPRINT:my2:LAST:Cur\:%5.1lf%S 57 | GPRINT:my2:AVERAGE:Ave\:%5.1lf%S 58 | GPRINT:my2:MAX:Max\:%5.1lf%S\l 59 | LINE1:my3#020203:Max cached keys 60 | GPRINT:my3:LAST:Cur\:%5.1lf%S 61 | GPRINT:my3:AVERAGE:Ave\:%5.1lf%S 62 | GPRINT:my3:MAX:Max\:%5.1lf%S\l 63 | 64 | 65 | @@ hits 66 | hit rate 67 | DEF:my1=<%RRD opcache_hit_rate.gauge %>:request:AVERAGE 68 | CDEF:rate=my1,0,100,LIMIT 69 | AREA:rate#fff956:Rate 70 | GPRINT:rate:LAST:Cur\:%5.1lf[%%] 71 | GPRINT:rate:AVERAGE:Ave\:%5.1lf[%%] 72 | GPRINT:rate:MAX:Max\:%5.1lf[%%]\l 73 | LINE:100 74 | 75 | @@ strings 76 | strings usage 77 | DEF:my1=<%RRD strings_used_memory.gauge %>:read:AVERAGE 78 | DEF:my2=<%RRD strings_free_memory.gauge %>:write:AVERAGE 79 | AREA:my1#e3aa59:Used 80 | GPRINT:my1:LAST:Cur\:%5.1lf%S 81 | GPRINT:my1:AVERAGE:Ave\:%5.1lf%S 82 | GPRINT:my1:MAX:Max\:%5.1lf%S\l 83 | STACK:my2#381707:Free 84 | GPRINT:my2:LAST:Cur\:%5.1lf%S 85 | GPRINT:my2:AVERAGE:Ave\:%5.1lf%S 86 | GPRINT:my2:MAX:Max\:%5.1lf%S\l 87 | 88 | @@ memory 89 | memory usage 90 | DEF:my1=<%RRD used_memory.gauge %>:read:AVERAGE 91 | DEF:my2=<%RRD free_memory.gauge %>:write:AVERAGE 92 | DEF:my3=<%RRD wasted_memory.gauge %>:write:AVERAGE 93 | AREA:my1#de4446:Used 94 | GPRINT:my1:LAST:Cur\:%5.1lf%S 95 | GPRINT:my1:AVERAGE:Ave\:%5.1lf%S 96 | GPRINT:my1:MAX:Max\:%5.1lf%S\l 97 | STACK:my2#dfe968:Free 98 | GPRINT:my2:LAST:Cur\:%5.1lf%S 99 | GPRINT:my2:AVERAGE:Ave\:%5.1lf%S 100 | GPRINT:my2:MAX:Max\:%5.1lf%S\l 101 | STACK:my3#000d48:Wasted 102 | GPRINT:my3:LAST:Cur\:%5.1lf%S 103 | GPRINT:my3:AVERAGE:Ave\:%5.1lf%S 104 | GPRINT:my3:MAX:Max\:%5.1lf%S\l 105 | -------------------------------------------------------------------------------- /metrics_plugins/view/redis.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | 9 | our $VERSION = '0.01'; 10 | 11 | my $plugin = Kurado::Plugin->new(@ARGV); 12 | 13 | # meta.maxmemory-policy volatile-lru 1406098240 14 | # meta.appendfsync everysec 1406098240 15 | # meta.uptime 519361 1406098240 16 | # meta.role master 1406098240 17 | # meta.save 900 1 300 10 60 10000 1406098240 18 | # meta.maxclients 4064 1406098240 19 | # meta.slowlog-max-len 128 1406098240 20 | # meta.maxmemory 0 1406098240 21 | # meta.version 2.8.12 1406098240 22 | # meta.appendonly no 1406098240 23 | # meta.rdbcompression yes 1406098240 24 | # metrics.total_commands_processed.derive 115611 1406098240 25 | # metrics.connected_slaves.gauge 0 1406098240 26 | # metrics.evicted_keys.derive 0 1406098240 27 | # metrics.keys.gauge 1219 1406098240 28 | # metrics.mem_fragmentation_ratio.gauge 2.55 1406098240 29 | # metrics.total_connections_received.derive 927 1406098240 30 | # metrics.connected_clients.gauge 9 1406098240 31 | # metrics.pubsub_channels.gauge 0 1406098240 32 | # metrics.slowlog.gauge 0 1406098240 33 | # metrics.used_memory.gauge 866184 1406098240 34 | 35 | sub metrics_list { 36 | my $plugin = shift; 37 | my $meta = $plugin->metrics_meta; 38 | my $list=''; 39 | 40 | # info 41 | my @info; 42 | for my $key ( $plugin->sort_info(keys %$meta) ) { 43 | if ( $key eq 'uptime' ) { 44 | push @info, 'uptime', $plugin->uptime2str($meta->{uptime}); 45 | } 46 | elsif ( $key eq 'maxmemory' ) { 47 | push @info, 'maxmemory', $plugin->unit($meta->{maxmemory}); 48 | } 49 | else { 50 | push @info, $key, $meta->{$key}; 51 | } 52 | } 53 | my ($port) = @{$plugin->plugin_arguments}; 54 | $port ||= 6379; 55 | $list .= join("\t",'#Redis('.$port.')',@info)."\n"; 56 | $list .= "$_\n" for qw/cmd conn mem keys evicted fragmentation pubsub_ch slowlog unsaved/; 57 | print $list; 58 | } 59 | 60 | sub metrics_graph { 61 | my $plugin = shift; 62 | my $graph = $plugin->graph; 63 | my $def = ''; 64 | $def = $plugin->render($graph); 65 | print "$def\n"; 66 | } 67 | 68 | if ($plugin->graph ) { 69 | metrics_graph($plugin); 70 | } 71 | else { 72 | metrics_list($plugin); 73 | } 74 | 75 | =pod 76 | 77 | =head1 NAME 78 | 79 | view/redis.pl - display metrics of redis 80 | 81 | =head1 SYNOPSIS 82 | 83 | % view/redis.pl --help 84 | 85 | =head1 DESCRIPTION 86 | 87 | display metrics of redis 88 | 89 | =head1 AUTHOR 90 | 91 | Masahiro Nagano 92 | 93 | =head1 LICENSE 94 | 95 | This library is free software; you can redistribute it and/or modify it 96 | under the same terms as Perl itself. 97 | 98 | =cut 99 | 100 | __DATA__ 101 | @@ cmd 102 | Command Processed 103 | DEF:my1a=<%RRD_FOR total_commands_processed.derive %>:n:AVERAGE 104 | CDEF:my1=my1a,0,10000000,LIMIT 105 | AREA:my1#FF8C00:Total Command 106 | GPRINT:my1:LAST:Cur\:%5.1lf 107 | GPRINT:my1:AVERAGE:Ave\:%5.1lf 108 | GPRINT:my1:MAX:Max\:%5.1lf\l 109 | 110 | @@ conn 111 | Connections 112 | DEF:my1=<%RRD_FOR connected_clients.gauge %>:n:AVERAGE 113 | DEF:my2=<%RRD_FOR connected_slaves.gauge %>:n:AVERAGE 114 | DEF:my3a=<%RRD_FOR total_connections_received.derive %>:n:AVERAGE 115 | CDEF:my3=my3a,0,10000000,LIMIT 116 | AREA:my1#00c000:Clients 117 | GPRINT:my1:LAST:Cur\:%5.1lf 118 | GPRINT:my1:AVERAGE:Ave\:%5.1lf 119 | GPRINT:my1:MAX:Max\:%5.1lf\l 120 | LINE2:my2#990033:Slaves 121 | GPRINT:my2:LAST:Cur\:%5.1lf 122 | GPRINT:my2:AVERAGE:Ave\:%5.1lf 123 | GPRINT:my2:MAX:Max\:%5.1lf\l 124 | LINE2:my3#596acf:Received 125 | GPRINT:my3:LAST:Cur\:%5.1lf 126 | GPRINT:my3:AVERAGE:Ave\:%5.1lf 127 | GPRINT:my3:MAX:Max\:%5.1lf\l 128 | 129 | @@ mem 130 | Memory Usage 131 | DEF:my1=<%RRD_FOR used_memory.gauge %>:n:AVERAGE 132 | CDEF:sm=my1,900,TREND 133 | CDEF:cf=86400,-8,1800,sm,PREDICT 134 | AREA:my1#4682B4:Used 135 | GPRINT:my1:LAST:Cur\:%5.1lf%sB 136 | GPRINT:my1:AVERAGE:Ave\:%5.1lf%sB 137 | GPRINT:my1:MAX:Max\:%5.1lf%sB\l 138 | LINE1:cf#b78795:Prediction:dashes=4,6 139 | 140 | @@ keys 141 | Keys 142 | DEF:my1=<%RRD_FOR keys.gauge %>:n:AVERAGE 143 | CDEF:sm=my1,900,TREND 144 | CDEF:cf=86400,-8,1800,sm,PREDICT 145 | AREA:my1#2a9b2a:Keys 146 | GPRINT:my1:LAST:Cur\:%5.1lf 147 | GPRINT:my1:AVERAGE:Ave\:%5.1lf 148 | GPRINT:my1:MAX:Max\:%5.1lf\l 149 | LINE1:cf#b78795:Prediction:dashes=4,6 150 | 151 | @@ evicted 152 | Evicted Keys/sec 153 | DEF:my1=<%RRD_FOR evicted_keys.derive %>:n:AVERAGE 154 | LINE2:my1#800040:Evicted Keys/sec 155 | GPRINT:my1:LAST:Cur\:%5.1lf%s 156 | GPRINT:my1:AVERAGE:Ave\:%5.1lf%s 157 | GPRINT:my1:MAX:Max\:%5.1lf%s\l 158 | 159 | @@ fragmentation 160 | Fragmentation Ratio 161 | DEF:my1=<%RRD_FOR mem_fragmentation_ratio.gauge %>:n:AVERAGE 162 | LINE2:my1#d27b86:Fragmentation 163 | GPRINT:my1:LAST:Cur\:%5.1lf[%%] 164 | GPRINT:my1:AVERAGE:Ave\:%5.1lf[%%] 165 | GPRINT:my1:MAX:Max\:%5.1lf[%%]\l 166 | 167 | @@ pubsub_ch 168 | Pub/Sub Channels 169 | DEF:my1=<%RRD_FOR pubsub_channels.gauge %>:n:AVERAGE 170 | LINE2:my1#2E8B57:Pub/Sub Channels 171 | GPRINT:my1:LAST:Cur\:%5.1lf 172 | GPRINT:my1:AVERAGE:Ave\:%5.1lf 173 | GPRINT:my1:MAX:Max\:%5.1lf\l 174 | 175 | @@ slowlog 176 | Slowlog(total) 177 | DEF:my1=<%RRD_FOR slowlog.gauge %>:n:AVERAGE 178 | AREA:my1#00c000:Slowlog 179 | GPRINT:my1:LAST:Cur\:%5.1lf 180 | GPRINT:my1:AVERAGE:Ave\:%5.1lf 181 | GPRINT:my1:MAX:Max\:%5.1lf\l 182 | 183 | @@ unsaved 184 | Unsaved Changes 185 | DEF:my1=<%RRD_FOR changes_since_last_save.gauge %>:n:AVERAGE 186 | AREA:my1#BDB76B:Changes 187 | GPRINT:my1:LAST:Cur\:%6.1lf%s 188 | GPRINT:my1:AVERAGE:Ave\:%6.1lf%s 189 | GPRINT:my1:MAX:Max\:%6.1lf%s\l 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /metrics_plugins/view/squid.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../../lib"; 7 | use Kurado::Plugin; 8 | 9 | our $VERSION = '0.01'; 10 | 11 | my $plugin = Kurado::Plugin->new(@ARGV); 12 | 13 | sub metrics_list { 14 | my $plugin = shift; 15 | my $meta = $plugin->metrics_meta; 16 | my $list=''; 17 | 18 | # info 19 | my @info; 20 | for my $key ( $plugin->sort_info(keys %$meta) ) { 21 | if ( $key eq 'uptime' ) { 22 | push @info, 'uptime', $plugin->uptime2str($meta->{uptime}); 23 | } 24 | else { 25 | push @info, $key, $meta->{$key}; 26 | } 27 | } 28 | my ($port) = @{$plugin->plugin_arguments}; 29 | $port ||= 3128; 30 | $list .= join("\t",'#Squid ('.$port.')',@info)."\n"; 31 | $list .= "$_\n" for qw/reqs ratio svc items filedescriptor/; 32 | print $list; 33 | } 34 | 35 | sub metrics_graph { 36 | my $plugin = shift; 37 | my $graph = $plugin->graph; 38 | my $def = ''; 39 | $def = $plugin->render($graph); 40 | print "$def\n"; 41 | } 42 | 43 | if ($plugin->graph ) { 44 | metrics_graph($plugin); 45 | } 46 | else { 47 | metrics_list($plugin); 48 | } 49 | 50 | =pod 51 | 52 | =head1 NAME 53 | 54 | view/squid.pl - display metrics of squid 55 | 56 | =head1 SYNOPSIS 57 | 58 | % view/squid.pl --help 59 | 60 | =head1 DESCRIPTION 61 | 62 | display metrics of squid 63 | 64 | =head1 AUTHOR 65 | 66 | Masahiro Nagano 67 | 68 | =head1 LICENSE 69 | 70 | This library is free software; you can redistribute it and/or modify it 71 | under the same terms as Perl itself. 72 | 73 | =cut 74 | 75 | __DATA__ 76 | @@ reqs 77 | Number of request 78 | DEF:my1a=<%RRD client-http.requests.derive %>:request:AVERAGE 79 | DEF:my2a=<%RRD client-http.hits.derive %>:httphit:AVERAGE 80 | DEF:my3a=<%RRD client-http.errors.derive %>:httperror:AVERAGE 81 | CDEF:my1=my1a,0,100000,LIMIT 82 | CDEF:my2=my2a,0,100000,LIMIT 83 | CDEF:my3=my3a,0,100000,LIMIT 84 | LINE1:my1#000080:Request 85 | GPRINT:my1:LAST:Cur\:%6.1lf 86 | GPRINT:my1:AVERAGE:Ave\:%6.1lf 87 | GPRINT:my1:MAX:Max\:%6.1lf\l 88 | LINE1:my2#008080:Hit Request 89 | GPRINT:my2:LAST:Cur\:%6.1lf 90 | GPRINT:my2:AVERAGE:Ave\:%6.1lf 91 | GPRINT:my2:MAX:Max\:%6.1lf\l 92 | LINE1:my3#CC0000:Err Request 93 | GPRINT:my3:LAST:Cur\:%6.1lf 94 | GPRINT:my3:AVERAGE:Ave\:%6.1lf 95 | GPRINT:my3:MAX:Max\:%6.1lf\l 96 | 97 | @@ ratio 98 | Cache hit ratio 99 | DEF:my1=<%RRD client-http.requests.derive %>:request:AVERAGE 100 | DEF:my2=<%RRD client-http.hits.derive %>:httphit:AVERAGE 101 | CDEF:my3=my2,my1,/,100,*,0,100,LIMIT 102 | AREA:my3#990000:Ratio 103 | GPRINT:my3:LAST:Cur\:%5.1lf[%%] 104 | GPRINT:my3:AVERAGE:Ave\:%5.1lf[%%] 105 | GPRINT:my3:MAX:Max\:%5.1lf[%%]\l 106 | LINE:100 107 | 108 | @@ svc 109 | Response time (msec) 110 | DEF:my1=<%RRD svc-time.all.gauge %>:allsvc:AVERAGE 111 | DEF:my2=<%RRD svc-time.miss.gauge %>:misssvc:AVERAGE 112 | DEF:my3=<%RRD svc-time.nm.gauge %>:nmsvc:AVERAGE 113 | DEF:my4=<%RRD svc-time.hit.gauge %>:hitsvc:AVERAGE 114 | LINE1:my1#CC0000:All 115 | GPRINT:my1:LAST:Cur\:%6.2lf 116 | GPRINT:my1:AVERAGE:Ave\:%6.2lf 117 | GPRINT:my1:MAX:Max\:%6.2lf\l 118 | LINE1:my2#000080:Miss 119 | GPRINT:my2:LAST:Cur\:%6.2lf 120 | GPRINT:my2:AVERAGE:Ave\:%6.2lf 121 | GPRINT:my2:MAX:Max\:%6.2lf\l 122 | LINE1:my3#008080:NotModified 123 | GPRINT:my3:LAST:Cur\:%6.2lf 124 | GPRINT:my3:AVERAGE:Ave\:%6.2lf 125 | GPRINT:my3:MAX:Max\:%6.2lf\l 126 | LINE1:my4#800080:Hit 127 | GPRINT:my4:LAST:Cur\:%6.2lf 128 | GPRINT:my4:AVERAGE:Ave\:%6.2lf 129 | GPRINT:my4:MAX:Max\:%6.2lf\l 130 | 131 | @@ items 132 | Cache items 133 | DEF:my1=<%RRD store-entries.total.gauge %>:items_cur:AVERAGE 134 | DEF:my2=<%RRD store-entries.with-memobject.gauge %>:items_cur:AVERAGE 135 | AREA:my1#00A000:Total Items 136 | GPRINT:my1:LAST:Cur\:%8.0lf 137 | GPRINT:my1:AVERAGE:Ave\:%8.0lf 138 | GPRINT:my1:MAX:Max\:%8.0lf\l 139 | LINE1:my2#0000A0:WithMemoryObject 140 | GPRINT:my2:LAST:Cur\:%8.0lf 141 | GPRINT:my2:AVERAGE:Ave\:%8.0lf 142 | GPRINT:my2:MAX:Max\:%8.0lf\l 143 | 144 | @@ filedescriptor 145 | Used File Descriptors 146 | DEF:my1=<%RRD file-descriptors.used.gauge %>:n:AVERAGE 147 | DEF:my2=<%RRD file-descriptors.max.gauge %>:n:AVERAGE 148 | LINE1:my2#C00000:Max file descriptors 149 | GPRINT:my2:LAST:Cur\:%7.0lf\l 150 | AREA:my1#00C000:Used file descriptors 151 | GPRINT:my1:LAST:Cur\:%7.0lf 152 | GPRINT:my1:AVERAGE:Ave\:%7.0lf 153 | GPRINT:my1:MAX:Max\:%7.0lf\l 154 | -------------------------------------------------------------------------------- /public/css/bootstrap-switch.min.css: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * bootstrap-switch - v3.0.2 3 | * http://www.bootstrap-switch.org 4 | * ======================================================================== 5 | * Copyright 2012-2013 Mattia Larentis 6 | * 7 | * ======================================================================== 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * ======================================================================== 20 | */ 21 | 22 | .bootstrap-switch{display:inline-block;cursor:pointer;border-radius:4px;border:1px solid;border-color:#ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;min-width:100px;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.bootstrap-switch.bootstrap-switch-mini{min-width:71px}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding-bottom:4px;padding-top:4px;font-size:10px;line-height:9px}.bootstrap-switch.bootstrap-switch-small{min-width:79px}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding-bottom:3px;padding-top:3px;font-size:12px;line-height:18px}.bootstrap-switch.bootstrap-switch-large{min-width:120px}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding-bottom:9px;padding-top:9px;font-size:16px;line-height:normal}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;transition:margin-left .5s}.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-container{margin-left:0}.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-container{margin-left:-50%}.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-container{margin-left:-25%}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-readonly,.bootstrap-switch.bootstrap-switch-indeterminate{opacity:.5;filter:alpha(opacity=50);cursor:default!important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label{cursor:default!important}.bootstrap-switch.bootstrap-switch-focused{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.bootstrap-switch .bootstrap-switch-container{display:inline-block;width:150%;top:0;border-radius:4px;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block!important;height:100%;padding-bottom:4px;padding-top:4px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-handle-off{text-align:center;z-index:1;width:33.33333333%}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary{color:#fff;background:#428bca}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success{color:#fff;background:#5cb85c}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;width:33.33333333%;color:#333;background:#fff}.bootstrap-switch input[type=radio],.bootstrap-switch input[type=checkbox]{position:absolute!important;top:0;left:0;opacity:0;filter:alpha(opacity=0);z-index:-1}.bootstrap-switch input[type=radio].form-control,.bootstrap-switch input[type=checkbox].form-control{height:auto} -------------------------------------------------------------------------------- /public/css/local.css: -------------------------------------------------------------------------------- 1 | body { 2 | min-width: 1200px; 3 | } 4 | 5 | 6 | .navbar-nav > li > a { 7 | padding-top: 5px !important; 8 | padding-bottom:10px !important; 9 | } 10 | 11 | .navbar { 12 | border-radius: 0; 13 | min-height: 20px !important; 14 | } 15 | 16 | .navbar .container-fluid .navbar-header .navbar-brand { 17 | height: 20px !important; 18 | padding-top: 6px !important; 19 | } 20 | 21 | .navbar { 22 | border-bottom: 1px solid #222; 23 | margin-bottom: 0px; 24 | } 25 | 26 | 27 | /* subnav */ 28 | #subnav { 29 | position: fixed; 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | z-index:10; 34 | border-bottom: 1px solid #111; 35 | background-color: #272b30; 36 | min-width: 1200px; 37 | box-shadow: 0px 2px 4px #333; 38 | background-image: linear-gradient(#484e55, #3a3f44 40%, #313539); 39 | } 40 | 41 | #subnav .subnav-brand, 42 | #subnav .subnav-brand:hover { 43 | text-decoration: none; 44 | color: #eee; 45 | } 46 | 47 | #subnav .container-fluid .row { 48 | margin: 0px; 49 | padding: 5px 0px 5px 0px; 50 | min-height: 28px; 51 | } 52 | 53 | #subnav .subnav-logo { 54 | } 55 | 56 | #subnav .subnav-logo>span { 57 | background-image: url("../img/kurado.png"); 58 | background-repeat: no-repeat; 59 | position: relative; 60 | display: block; 61 | float: left; 62 | width: 20px; 63 | height: 20px; 64 | margin-top: -1px; 65 | margin-right: 2px; 66 | content: " "; 67 | } 68 | 69 | #subnav .subnav-logo>span:after { 70 | clear: left; 71 | } 72 | 73 | #subnav .subnav-title { 74 | color: #eee; 75 | margin: 0px; 76 | font-size: 17px; 77 | line-height: 1.3; 78 | } 79 | 80 | #subnav .subnav-title a, 81 | #subnav .subnav-title a:hover { 82 | text-decoration: underline; 83 | color: #fff; 84 | } 85 | 86 | #subnav .glyphicon-calendar { 87 | font-size: 16px; 88 | top: 5px; 89 | } 90 | 91 | #subnav .input-group-addon:first-child { 92 | border-right: 1px dashed #2e2f2f; 93 | } 94 | 95 | 96 | .bootstrap-switch.bootstrap-switch-mini { 97 | margin-top: -1px; 98 | min-width: 60px; 99 | min-height: 20px; 100 | border-color: #2e2f2f; /* rgba(0,0,0,0.6);*/ 101 | border-radius: 3px; 102 | } 103 | .bootstrap-switch .bootstrap-switch-container { 104 | height: 20px; 105 | border-radius: 3px; 106 | } 107 | 108 | .bootstrap-switch .bootstrap-switch-handle-on span, 109 | .bootstrap-switch .bootstrap-switch-handle-off span, 110 | .bootstrap-switch .bootstrap-switch-label span { 111 | display: block; 112 | padding-top: 1px; 113 | font-size: 10px; 114 | line-height: 9px; 115 | font-weight: normal; 116 | } 117 | .bootstrap-switch .bootstrap-switch-label { 118 | background-image: linear-gradient(#4f5151, #474949 6%, #3f4141) 119 | } 120 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary, 121 | .bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default { 122 | background-color: #f89406; 123 | } 124 | 125 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary, 126 | .bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default { 127 | background-color: #c8c8c8; 128 | color: #222; 129 | } 130 | 131 | #subnav .label-for-open-new { 132 | font-weight: normal; 133 | font-size: 12px; 134 | color: #fff; 135 | } 136 | 137 | .plugin-nav { 138 | border-top: 1px solid #272b30; 139 | overflow-y: hidden; 140 | } 141 | .plugin-nav .nav-pills>li>a { 142 | border-radius: 2px; 143 | padding: 5px 6px; 144 | color: #fff; 145 | font-size: 12px; 146 | } 147 | .plugin-nav .nav-pills>li>a:hover { 148 | background-color: transparent; 149 | } 150 | .plugin-nav .nav-pills>li.active>a { 151 | background-color: #f89406; 152 | box-shadow: inset 0px 3px 5px rgba(0,0,0,0.125); 153 | } 154 | .plugin-nav .copy-button { 155 | border: none; 156 | padding: 5px 10px 4px; 157 | font-size: 12px; 158 | border-radius: 2px; 159 | } 160 | 161 | .plugin-nav li.has-warn a, 162 | .plugin-nav li.has-warn a:hover { 163 | color: #ff0702; 164 | text-decoration: none; 165 | } 166 | 167 | #subnav .graph-nav.container-fluid { 168 | background-color: #272b30; 169 | overflow-y: hidden; 170 | } 171 | 172 | .graph-nav .btn-default.active { 173 | background-color: #f89406; 174 | } 175 | 176 | .graph-nav .btn-default.active:hover { 177 | background-color: #f89406; 178 | background-image: none; 179 | } 180 | 181 | /* Sidebar navigation */ 182 | .nav-sidebar { 183 | margin-left: -15px; 184 | margin-right: -15px; 185 | margin-bottom: 20px; 186 | } 187 | 188 | .nav-sidebar .list-group { 189 | margin-bottom: 5px; 190 | padding-bottom: 3px; 191 | } 192 | .nav-sidebar .list-group .list-group-item:first-child { 193 | border-top: 0px solid #222; 194 | } 195 | 196 | .nav-sidebar .list-group .list-group-item { 197 | background-color: #eee; 198 | border-radius: 0px; 199 | border-right: 0px solid #222; 200 | border-left: none; 201 | border-top: none; 202 | border-bottom: none; 203 | /* border: 1px solid transparent; */ 204 | padding: 4px 8px 3px 15px; 205 | margin: 1px 5px 1px 0px; 206 | } 207 | 208 | .nav-sidebar .list-group .list-group-item.active { 209 | background-color: #f89406; /* #272b30; */ 210 | border-right: 0px solid #272b30; 211 | color: #fff; 212 | box-shadow: inset 0px 3px 5px rgba(0,0,0,0.125); 213 | /* 214 | border-top-right-radius: 3px; 215 | border-bottom-right-radius: 3px; 216 | margin: 1px 0px 1px 0px; 217 | */ 218 | } 219 | .nav-sidebar .list-group .list-group-item .badge { 220 | background-color: #ccc; 221 | color: #555; 222 | } 223 | .nav-sidebar .list-group .list-group-item.active .badge { 224 | background-color: #fff; 225 | color: #222; 226 | } 227 | 228 | .nav-sidebar .list-group a.list-group-item:hover { 229 | background-color: #ddd; 230 | box-shadow: inset 0 3px 5px rgba(0,0,0,0.125); 231 | } 232 | 233 | .nav-sidebar .list-group .list-group-item.active:hover { 234 | background-color: #f89406; 235 | } 236 | 237 | 238 | .list-group-item>.badge { 239 | font-size: 11px; 240 | } 241 | 242 | 243 | /* table */ 244 | .table.metrics td { 245 | border: none; 246 | padding: 0px 2px 0px 0px; 247 | } 248 | h3.service { 249 | margin-top: 10px; 250 | margin-bottom: 8px; 251 | font-weight: normal; 252 | } 253 | h3.service a, 254 | h3.service a:hover { 255 | text-decoration: none; 256 | color: #444; 257 | } 258 | .servers { 259 | } 260 | .servers .table td { 261 | padding: 4px; 262 | } 263 | .servers .table td.host-checked { 264 | background-color: #ffd; 265 | /* #2e3338;*/ 266 | } 267 | .servers .table th { 268 | padding: 2px; 269 | padding-left: 20px; 270 | color: #111; 271 | background-color: #ccc; 272 | color: #222; 273 | font-size: 16px; 274 | font-weight: bold; 275 | } 276 | 277 | .host-status { 278 | color: #888; 279 | } 280 | 281 | .host-status-crit { 282 | color: #ff0702; 283 | } 284 | 285 | .host-status-warn { 286 | color: #ff5c00; 287 | } 288 | 289 | .host-checkbox { 290 | position: relative; 291 | display: inline-block; 292 | top: -1px; 293 | } 294 | 295 | a.host-address, 296 | a.host-address:hover { 297 | text-decoration: none !important; 298 | color: #1900ee; 299 | padding-right: 4px; 300 | } 301 | 302 | .host-hostname { 303 | font-weight: bold; 304 | padding-right: 4px; 305 | } 306 | 307 | .host-comments { 308 | color: #888; 309 | word-break: break-all; 310 | } 311 | 312 | /* graphs */ 313 | table.servers-table.metrics, 314 | table.metrics { 315 | margin-top: 10px; 316 | } 317 | table.metrics, 318 | table.servers-table.metrics td, 319 | table.servers-table.metrics th, 320 | table.metrics td { 321 | background-color: transparent; 322 | } 323 | 324 | table.servers-table.metrics td, 325 | table.servers-table.metrics th { 326 | min-width: 520px; 327 | max-width: 520px; 328 | padding-right: 4px; 329 | vertical-align: top; 330 | overflow: hidden; 331 | } 332 | 333 | table.servers-table.metrics th { 334 | font-size: 19px; 335 | text-align: center; 336 | padding-bottom: 10px; 337 | } 338 | table.servers-table.metrics th a { 339 | text-decoration: underline; 340 | color: #1900ee; 341 | } 342 | .thin-dash { 343 | height: 2px; 344 | margin: 1px; 345 | border-bottom: 1px solid #ccc; 346 | } 347 | .plugin { 348 | margin-bottom: 15px; 349 | } 350 | 351 | .plugin .alert { 352 | margin-bottom: 2px; 353 | padding: 8px; 354 | border: 1px solid #ff0702; 355 | } 356 | 357 | h3.plugin-name { 358 | margin-top: 5px; 359 | margin-bottom: 5px; 360 | font-size: 15px; 361 | text-transform:uppercase; 362 | } 363 | h3.plugin-name a, 364 | h3.plugin-name a:hover { 365 | color: #444; 366 | text-decoration: none; 367 | } 368 | h3.plugin-name.has-warn a, 369 | h3.plugin-name.has-warn a:hover { 370 | color: #ff0702; 371 | text-decoration: none; 372 | } 373 | 374 | h4.metrics-label { 375 | font-size: 17px; 376 | font-weight: bold; 377 | color: #333; 378 | background-color: #ccc; 379 | padding: 5px; 380 | margin-top: 6px; 381 | margin-bottom: 5px; 382 | text-shadow: none; 383 | border-radius: 2px; 384 | border: 1px solid #bbb; 385 | } 386 | 387 | .metrics-meta { 388 | font-size: 14px; 389 | margin-bottom: 4px; 390 | line-height: 1.7; 391 | } 392 | 393 | .metrics-meta > span { 394 | word-wrap: break-word; 395 | } 396 | 397 | .metrics-meta > span > a, 398 | .metrics-meta > span > a:hover { 399 | color: #888888; 400 | text-decoration: underline; 401 | } 402 | 403 | 404 | .label-meta { 405 | background-color: #fc6; 406 | text-transform:uppercase; 407 | border: 1px #999 solid; 408 | padding: 2px; 409 | color: #666; 410 | } 411 | 412 | .graphs { 413 | margin: 2px 0px 5px; 414 | white-space: nowrap 415 | } 416 | 417 | .graphs + h4.metrics-label { 418 | margin-top: 15px; 419 | } 420 | .graphs:nth-last-of-type(1) { 421 | margin-bottom: 25px; 422 | } 423 | 424 | table.servers-table.metrics .graphs { 425 | text-align: center; 426 | } 427 | 428 | .tooltip-inner { 429 | word-wrap: break-word; 430 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/img/ZeroClipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/public/img/ZeroClipboard.swf -------------------------------------------------------------------------------- /public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public/img/kurado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kazeburo/Kurado/97f9383249333a4219c030442f5002b18e450517/public/img/kurado.png -------------------------------------------------------------------------------- /public/js/jquery.color-2.1.2.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery Color v@2.1.2 http://github.com/jquery/jquery-color | jquery.org/license */ 2 | (function(a,b){function m(a,b,c){var d=h[b.type]||{};return a==null?c||!b.def?null:b.def:(a=d.floor?~~a:parseFloat(a),isNaN(a)?b.def:d.mod?(a+d.mod)%d.mod:0>a?0:d.max")[0],k,l=a.each;j.style.cssText="background-color:rgba(1,1,1,.5)",i.rgba=j.style.backgroundColor.indexOf("rgba")>-1,l(g,function(a,b){b.cache="_"+a,b.props.alpha={idx:3,type:"percent",def:1}}),f.fn=a.extend(f.prototype,{parse:function(c,d,e,h){if(c===b)return this._rgba=[null,null,null,null],this;if(c.jquery||c.nodeType)c=a(c).css(d),d=b;var i=this,j=a.type(c),o=this._rgba=[];d!==b&&(c=[c,d,e,h],j="array");if(j==="string")return this.parse(n(c)||k._default);if(j==="array")return l(g.rgba.props,function(a,b){o[b.idx]=m(c[b.idx],b)}),this;if(j==="object")return c instanceof f?l(g,function(a,b){c[b.cache]&&(i[b.cache]=c[b.cache].slice())}):l(g,function(b,d){var e=d.cache;l(d.props,function(a,b){if(!i[e]&&d.to){if(a==="alpha"||c[a]==null)return;i[e]=d.to(i._rgba)}i[e][b.idx]=m(c[a],b,!0)}),i[e]&&a.inArray(null,i[e].slice(0,3))<0&&(i[e][3]=1,d.from&&(i._rgba=d.from(i[e])))}),this},is:function(a){var b=f(a),c=!0,d=this;return l(g,function(a,e){var f,g=b[e.cache];return g&&(f=d[e.cache]||e.to&&e.to(d._rgba)||[],l(e.props,function(a,b){if(g[b.idx]!=null)return c=g[b.idx]===f[b.idx],c})),c}),c},_space:function(){var a=[],b=this;return l(g,function(c,d){b[d.cache]&&a.push(c)}),a.pop()},transition:function(a,b){var c=f(a),d=c._space(),e=g[d],i=this.alpha()===0?f("transparent"):this,j=i[e.cache]||e.to(i._rgba),k=j.slice();return c=c[e.cache],l(e.props,function(a,d){var e=d.idx,f=j[e],g=c[e],i=h[d.type]||{};if(g===null)return;f===null?k[e]=g:(i.mod&&(g-f>i.mod/2?f+=i.mod:f-g>i.mod/2&&(f-=i.mod)),k[e]=m((g-f)*b+f,d))}),this[d](k)},blend:function(b){if(this._rgba[3]===1)return this;var c=this._rgba.slice(),d=c.pop(),e=f(b)._rgba;return f(a.map(c,function(a,b){return(1-d)*e[b]+d*a}))},toRgbaString:function(){var b="rgba(",c=a.map(this._rgba,function(a,b){return a==null?b>2?1:0:a});return c[3]===1&&(c.pop(),b="rgb("),b+c.join()+")"},toHslaString:function(){var b="hsla(",c=a.map(this.hsla(),function(a,b){return a==null&&(a=b>2?1:0),b&&b<3&&(a=Math.round(a*100)+"%"),a});return c[3]===1&&(c.pop(),b="hsl("),b+c.join()+")"},toHexString:function(b){var c=this._rgba.slice(),d=c.pop();return b&&c.push(~~(d*255)),"#"+a.map(c,function(a){return a=(a||0).toString(16),a.length===1?"0"+a:a}).join("")},toString:function(){return this._rgba[3]===0?"transparent":this.toRgbaString()}}),f.fn.parse.prototype=f.fn,g.hsla.to=function(a){if(a[0]==null||a[1]==null||a[2]==null)return[null,null,null,a[3]];var b=a[0]/255,c=a[1]/255,d=a[2]/255,e=a[3],f=Math.max(b,c,d),g=Math.min(b,c,d),h=f-g,i=f+g,j=i*.5,k,l;return g===f?k=0:b===f?k=60*(c-d)/h+360:c===f?k=60*(d-b)/h+120:k=60*(b-c)/h+240,h===0?l=0:j<=.5?l=h/i:l=h/(2-i),[Math.round(k)%360,l,j,e==null?1:e]},g.hsla.from=function(a){if(a[0]==null||a[1]==null||a[2]==null)return[null,null,null,a[3]];var b=a[0]/360,c=a[1],d=a[2],e=a[3],f=d<=.5?d*(1+c):d+c-d*c,g=2*d-f;return[Math.round(o(g,f,b+1/3)*255),Math.round(o(g,f,b)*255),Math.round(o(g,f,b-1/3)*255),e]},l(g,function(c,e){var g=e.props,h=e.cache,i=e.to,j=e.from;f.fn[c]=function(c){i&&!this[h]&&(this[h]=i(this._rgba));if(c===b)return this[h].slice();var d,e=a.type(c),k=e==="array"||e==="object"?c:arguments,n=this[h].slice();return l(g,function(a,b){var c=k[e==="object"?a:b.idx];c==null&&(c=n[b.idx]),n[b.idx]=m(c,b)}),j?(d=f(j(n)),d[h]=n,d):f(n)},l(g,function(b,e){if(f.fn[b])return;f.fn[b]=function(f){var g=a.type(f),h=b==="alpha"?this._hsla?"hsla":"rgba":c,i=this[h](),j=i[e.idx],k;return g==="undefined"?j:(g==="function"&&(f=f.call(this,j),g=a.type(f)),f==null&&e.empty?this:(g==="string"&&(k=d.exec(f),k&&(f=j+parseFloat(k[2])*(k[1]==="+"?1:-1))),i[e.idx]=f,this[h](i)))}})}),f.hook=function(b){var c=b.split(" ");l(c,function(b,c){a.cssHooks[c]={set:function(b,d){var e,g,h="";if(d!=="transparent"&&(a.type(d)!=="string"||(e=n(d)))){d=f(e||d);if(!i.rgba&&d._rgba[3]!==1){g=c==="backgroundColor"?b.parentNode:b;while((h===""||h==="transparent")&&g&&g.style)try{h=a.css(g,"backgroundColor"),g=g.parentNode}catch(j){}d=d.blend(h&&h!=="transparent"?h:"_default")}d=d.toRgbaString()}try{b.style[c]=d}catch(j){}}},a.fx.step[c]=function(b){b.colorInit||(b.start=f(b.elem,c),b.end=f(b.end),b.colorInit=!0),a.cssHooks[c].set(b.elem,b.start.transition(b.end,b.pos))}})},f.hook(c),a.cssHooks.borderColor={expand:function(a){var b={};return l(["Top","Right","Bottom","Left"],function(c,d){b["border"+d+"Color"]=a}),b}},k=a.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}})(jQuery); -------------------------------------------------------------------------------- /public/js/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.1 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2006, 2014 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | (function (factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | // AMD 11 | define(['jquery'], factory); 12 | } else if (typeof exports === 'object') { 13 | // CommonJS 14 | factory(require('jquery')); 15 | } else { 16 | // Browser globals 17 | factory(jQuery); 18 | } 19 | }(function ($) { 20 | 21 | var pluses = /\+/g; 22 | 23 | function encode(s) { 24 | return config.raw ? s : encodeURIComponent(s); 25 | } 26 | 27 | function decode(s) { 28 | return config.raw ? s : decodeURIComponent(s); 29 | } 30 | 31 | function stringifyCookieValue(value) { 32 | return encode(config.json ? JSON.stringify(value) : String(value)); 33 | } 34 | 35 | function parseCookieValue(s) { 36 | if (s.indexOf('"') === 0) { 37 | // This is a quoted cookie as according to RFC2068, unescape... 38 | s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); 39 | } 40 | 41 | try { 42 | // Replace server-side written pluses with spaces. 43 | // If we can't decode the cookie, ignore it, it's unusable. 44 | // If we can't parse the cookie, ignore it, it's unusable. 45 | s = decodeURIComponent(s.replace(pluses, ' ')); 46 | return config.json ? JSON.parse(s) : s; 47 | } catch(e) {} 48 | } 49 | 50 | function read(s, converter) { 51 | var value = config.raw ? s : parseCookieValue(s); 52 | return $.isFunction(converter) ? converter(value) : value; 53 | } 54 | 55 | var config = $.cookie = function (key, value, options) { 56 | 57 | // Write 58 | 59 | if (arguments.length > 1 && !$.isFunction(value)) { 60 | options = $.extend({}, config.defaults, options); 61 | 62 | if (typeof options.expires === 'number') { 63 | var days = options.expires, t = options.expires = new Date(); 64 | t.setTime(+t + days * 864e+5); 65 | } 66 | 67 | return (document.cookie = [ 68 | encode(key), '=', stringifyCookieValue(value), 69 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 70 | options.path ? '; path=' + options.path : '', 71 | options.domain ? '; domain=' + options.domain : '', 72 | options.secure ? '; secure' : '' 73 | ].join('')); 74 | } 75 | 76 | // Read 77 | 78 | var result = key ? undefined : {}; 79 | 80 | // To prevent the for loop in the first place assign an empty array 81 | // in case there are no cookies at all. Also prevents odd result when 82 | // calling $.cookie(). 83 | var cookies = document.cookie ? document.cookie.split('; ') : []; 84 | 85 | for (var i = 0, l = cookies.length; i < l; i++) { 86 | var parts = cookies[i].split('='); 87 | var name = decode(parts.shift()); 88 | var cookie = parts.join('='); 89 | 90 | if (key && key === name) { 91 | // If second argument (value) is a function it's a converter... 92 | result = read(cookie, value); 93 | break; 94 | } 95 | 96 | // Prevent storing a cookie that we couldn't decode. 97 | if (!key && (cookie = read(cookie)) !== undefined) { 98 | result[name] = cookie; 99 | } 100 | } 101 | 102 | return result; 103 | }; 104 | 105 | config.defaults = {}; 106 | 107 | $.removeCookie = function (key, options) { 108 | if ($.cookie(key) === undefined) { 109 | return false; 110 | } 111 | 112 | // Must not alter options, thus extending a fresh object... 113 | $.cookie(key, '', $.extend({}, options, { expires: -1 })); 114 | return !$.cookie(key); 115 | }; 116 | 117 | })); 118 | -------------------------------------------------------------------------------- /public/js/jquery.shiftcheckbox.js: -------------------------------------------------------------------------------- 1 | /* ShiftCheckbox jQuery plugin 2 | * 3 | * Copyright (C) 2011-2012 James Nylen 4 | * 5 | * Released under MIT license 6 | * For details see: 7 | * https://github.com/nylen/shiftcheckbox 8 | * 9 | * Requires jQuery v1.7 or higher. 10 | */ 11 | 12 | (function($) { 13 | var ns = '.shiftcheckbox'; 14 | 15 | $.fn.shiftcheckbox = function(opts) { 16 | opts = $.extend({ 17 | checkboxSelector: null, 18 | selectAll: null, 19 | onChange: null 20 | }, opts); 21 | 22 | if (typeof opts.onChange != 'function') { 23 | opts.onChange = function(checked) { }; 24 | } 25 | 26 | $.fn.scb_changeChecked = function(opts, checked) { 27 | this.prop('checked', checked); 28 | opts.onChange.call(this, checked); 29 | return this; 30 | } 31 | 32 | var $containers; 33 | var $checkboxes; 34 | var $containersSelectAll; 35 | var $checkboxesSelectAll; 36 | var $otherSelectAll; 37 | var $containersAll; 38 | var $checkboxesAll; 39 | 40 | if (opts.selectAll) { 41 | // We need to set up a "select all" control 42 | $containersSelectAll = $(opts.selectAll); 43 | if ($containersSelectAll && !$containersSelectAll.length) { 44 | $containersSelectAll = false; 45 | } 46 | } 47 | 48 | if ($containersSelectAll) { 49 | $checkboxesSelectAll = $containersSelectAll 50 | .filter(':checkbox') 51 | .add($containersSelectAll.find(':checkbox')); 52 | 53 | $containersSelectAll = $containersSelectAll.not(':checkbox'); 54 | $otherSelectAll = $containersSelectAll.filter(function() { 55 | return !$(this).find($checkboxesSelectAll).length; 56 | }); 57 | $containersSelectAll = $containersSelectAll.filter(function() { 58 | return !!$(this).find($checkboxesSelectAll).length; 59 | }).each(function() { 60 | $(this).data('childCheckbox', $(this).find($checkboxesSelectAll)[0]); 61 | }); 62 | } 63 | 64 | if (opts.checkboxSelector) { 65 | 66 | // checkboxSelector means that the elements we need to attach handlers to 67 | // ($containers) are not actually checkboxes but contain them instead 68 | 69 | $containersAll = this.filter(function() { 70 | return !!$(this).find(opts.checkboxSelector).filter(':checkbox').length; 71 | }).each(function() { 72 | $(this).data('childCheckbox', $(this).find(opts.checkboxSelector).filter(':checkbox')[0]); 73 | }).add($containersSelectAll); 74 | 75 | $checkboxesAll = $containersAll.map(function() { 76 | return $(this).data('childCheckbox'); 77 | }); 78 | 79 | } else { 80 | 81 | $checkboxesAll = this.filter(':checkbox'); 82 | 83 | } 84 | 85 | if ($checkboxesSelectAll && !$checkboxesSelectAll.length) { 86 | $checkboxesSelectAll = false; 87 | } else { 88 | $checkboxesAll = $checkboxesAll.add($checkboxesSelectAll); 89 | } 90 | 91 | if ($otherSelectAll && !$otherSelectAll.length) { 92 | $otherSelectAll = false; 93 | } 94 | 95 | if ($containersAll) { 96 | $containers = $containersAll.not($containersSelectAll); 97 | } 98 | $checkboxes = $checkboxesAll.not($checkboxesSelectAll); 99 | 100 | if (!$checkboxes.length) { 101 | return; 102 | } 103 | 104 | var lastIndex = -1; 105 | 106 | var checkboxClicked = function(e) { 107 | var checked = !!$(this).prop('checked'); 108 | 109 | var curIndex = $checkboxes.index(this); 110 | if (curIndex < 0) { 111 | if ($checkboxesSelectAll.filter(this).length) { 112 | $checkboxesAll.scb_changeChecked(opts, checked); 113 | } 114 | return; 115 | } 116 | 117 | if (e.shiftKey && lastIndex != -1) { 118 | var di = (curIndex > lastIndex ? 1 : -1); 119 | for (var i = lastIndex; i != curIndex; i += di) { 120 | $checkboxes.eq(i).scb_changeChecked(opts, checked); 121 | } 122 | } 123 | 124 | if ($checkboxesSelectAll) { 125 | if (checked && !$checkboxes.not(':checked').length) { 126 | $checkboxesSelectAll.scb_changeChecked(opts, true); 127 | } else if (!checked) { 128 | $checkboxesSelectAll.scb_changeChecked(opts, false); 129 | } 130 | } 131 | 132 | lastIndex = curIndex; 133 | }; 134 | 135 | if ($checkboxesSelectAll) { 136 | $checkboxesSelectAll 137 | .prop('checked', !$checkboxes.not(':checked').length) 138 | .filter(function() { 139 | return !$containersAll.find(this).length; 140 | }).on('click' + ns, checkboxClicked); 141 | } 142 | 143 | if ($otherSelectAll) { 144 | $otherSelectAll.on('click' + ns, function() { 145 | var checked; 146 | if ($checkboxesSelectAll) { 147 | checked = !!$checkboxesSelectAll.eq(0).prop('checked'); 148 | } else { 149 | checked = !!$checkboxes.eq(0).prop('checked'); 150 | } 151 | $checkboxesAll.scb_changeChecked(opts, !checked); 152 | }); 153 | } 154 | 155 | if (opts.checkboxSelector) { 156 | $containersAll.on('click' + ns, function(e) { 157 | var $checkbox = $($(this).data('childCheckbox')); 158 | $checkbox.not(e.target).each(function() { 159 | var checked = !$checkbox.prop('checked'); 160 | $(this).scb_changeChecked(opts, checked); 161 | }); 162 | 163 | $checkbox[0].focus(); 164 | checkboxClicked.call($checkbox, e); 165 | 166 | // If the user clicked on a label inside the row that points to the 167 | // current row's checkbox, cancel the event. 168 | var $label = $(e.target).closest('label'); 169 | var labelFor = $label.attr('for'); 170 | if (labelFor && labelFor == $checkbox.attr('id')) { 171 | if ($label.find($checkbox).length) { 172 | // Special case: The label contains the checkbox. 173 | if ($checkbox[0] != e.target) { 174 | return false; 175 | } 176 | } else { 177 | return false; 178 | } 179 | } 180 | }).on('mousedown' + ns, function(e) { 181 | if (e.shiftKey) { 182 | // Prevent selecting text by Shift+click 183 | return false; 184 | } 185 | }); 186 | } else { 187 | $checkboxes.on('click' + ns, checkboxClicked); 188 | } 189 | 190 | return this; 191 | }; 192 | })(jQuery); 193 | -------------------------------------------------------------------------------- /sample_kurado.yml: -------------------------------------------------------------------------------- 1 | --- 2 | config: 3 | # Redis サーバの host:port 4 | redis: 127.0.0.1:6379 5 | 6 | # rrdファイルを設置するディレクトリ「/」から始まると絶対パス 7 | data_dir: data 8 | 9 | # 監視項目の設定ファイルを設置するディレクトリ。「/」から始まると絶対パス 10 | rolls_dir: sample_rolls 11 | 12 | # データの取得とrrd graphの定義をいれておくディレクトリ。複数指定可能 13 | metrics_plugin_dir: 14 | - metrics_plugins 15 | - metrics_site_plugins 16 | 17 | # プロセス数など 18 | web_worker: 5 19 | update_worker: 2 20 | fetch_worker: 2 21 | 22 | metrics_config: 23 | # 共通で使う設定 24 | MYSQL: 25 | user: root 26 | password: "" 27 | 28 | --- 29 | # サービス名 30 | service: admin 31 | servers: 32 | # roll: 何をmonitoringするかの設定が書かれたファイルを指定する。rolls_dirの中から探す 33 | - roll: base.yml 34 | # label: サブカテゴリのようなもの 35 | label: dev 36 | # hostsは ip[SPACE]hostname[SPACE]以下コメント 37 | hosts: 38 | - 192.168.55.10 dev1 39 | - 192.168.55.11 dev2 40 | 41 | --- 42 | service: intra 43 | servers: 44 | - roll: base.yml 45 | hosts: 46 | - 192.168.57.10 app1 bts 47 | - 192.168.57.11 app2 wiki 48 | 49 | --- 50 | service: new service 51 | servers: 52 | - roll: httpd.yml 53 | label: www 54 | hosts: 55 | - 192.168.51.10 www1 56 | - 192.168.51.11 www2 57 | - roll: httpd_memcached.yml 58 | hosts: 59 | - 192.168.51.12 www3 60 | - 192.168.51.13 www4 61 | - roll: mysql.yml 62 | label: db 63 | hosts: 64 | - 192.168.51.14 db1 65 | - 192.168.51.15 db2 66 | - roll: base.yml 67 | label: etc 68 | hosts: 69 | - 192.168.51.90 batch1 batch 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /sample_rolls/base.yml: -------------------------------------------------------------------------------- 1 | --- 2 | metrics_config: 3 | metrics: 4 | - base 5 | -------------------------------------------------------------------------------- /sample_rolls/httpd.yml: -------------------------------------------------------------------------------- 1 | --- 2 | metrics_config: 3 | metrics: 4 | - base 5 | - http:80 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample_rolls/httpd_memcached.yml: -------------------------------------------------------------------------------- 1 | --- 2 | metrics_config: 3 | metrics: 4 | - base 5 | - http:80 6 | - memcached:11211 7 | -------------------------------------------------------------------------------- /sample_rolls/mysql.yml: -------------------------------------------------------------------------------- 1 | --- 2 | metrics_config: 3 | metrics: 4 | - base 5 | - mysql 6 | - innodb 7 | -------------------------------------------------------------------------------- /t/005-Kurado-Util/00-compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | 4 | use_ok $_ for qw( 5 | Kurado::Util 6 | ); 7 | 8 | done_testing; 9 | 10 | -------------------------------------------------------------------------------- /t/010-Kurado-Config/00-compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | 4 | use_ok $_ for qw( 5 | Kurado::Config 6 | ); 7 | 8 | done_testing; 9 | -------------------------------------------------------------------------------- /t/020-Kurado-ConfigLoader.pm/00-compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | 4 | use_ok $_ for qw( 5 | Kurado::ConfigLoader 6 | ); 7 | 8 | done_testing; 9 | -------------------------------------------------------------------------------- /t/030-Kurado-RRD/00-compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | 4 | use_ok $_ for qw( 5 | Kurado::RRD 6 | ); 7 | 8 | done_testing; 9 | -------------------------------------------------------------------------------- /t/030-Kurado-RRD/01-basic.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | use File::Temp qw/tempdir/; 4 | use File::Zglob; 5 | use File::Spec; 6 | use Kurado::RRD; 7 | use Kurado::Object::Plugin; 8 | use Kurado::Object::Msg; 9 | 10 | my $tempdir = tempdir( CLEANUP => 1 ); 11 | 12 | my $rrd = Kurado::RRD->new(data_dir => $tempdir ); 13 | my $msg = Kurado::Object::Msg->new( 14 | plugin => Kurado::Object::Plugin->new( 15 | plugin => "test", 16 | arguments => [], 17 | ), 18 | key => "test1.gauge", 19 | value => "12345", 20 | timestamp => time, 21 | address => '127.0.0.1', 22 | metrics_type => 'metrics' 23 | ); 24 | ok($rrd->update( 25 | msg => $msg, 26 | ),'update'); 27 | 28 | my @files = zglob(File::Spec->catfile($tempdir,'**/*.gauge.rrd')); 29 | ok(scalar @files); 30 | 31 | done_testing; 32 | 33 | 34 | -------------------------------------------------------------------------------- /t/040-Kurado-Storage/00-compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | 4 | use_ok $_ for qw( 5 | Kurado::Storage 6 | ); 7 | 8 | done_testing; 9 | 10 | -------------------------------------------------------------------------------- /t/040-Kurado-Storage/01-basic.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | use Net::EmptyPort qw(empty_port); 4 | use Test::RedisServer; 5 | use Kurado::Storage; 6 | use Kurado::Object::Plugin; 7 | use Kurado::Object::Msg; 8 | 9 | my $redis_server; 10 | my $port = empty_port(); 11 | eval { 12 | $redis_server = Test::RedisServer->new( 13 | conf => { port => $port }, 14 | ); 15 | } or plan skip_all => 'redis-server is required to this test'; 16 | 17 | my $s = Kurado::Storage->new( redis => '127.0.0.1:'.$port ); 18 | 19 | #{plugin=>"test",address=>"127.0.0.1",key=>"test1",value=>"testval1",expires=>180} 20 | 21 | my $plugin = Kurado::Object::Plugin->new( 22 | plugin => "test", 23 | arguments => [], 24 | ); 25 | 26 | ok($s->set({ 27 | msg => Kurado::Object::Msg->new( 28 | plugin => $plugin, 29 | address => '127.0.0.1', 30 | key => "test1", 31 | value => "testval1", 32 | metrics_type => 'metrics', 33 | timestamp => time() 34 | ), 35 | expires => 180, 36 | }),"set"); 37 | 38 | ok($s->set({ 39 | msg => Kurado::Object::Msg->new( 40 | plugin => $plugin, 41 | address => '127.0.0.1', 42 | key => "test2", 43 | value => "testval2", 44 | metrics_type => 'metrics', 45 | timestamp => time() 46 | ), 47 | expires => 2, 48 | }),"set"); 49 | 50 | 51 | is_deeply( 52 | $s->get_by_plugin({plugin=>$plugin,address=>"127.0.0.1"}), 53 | { test1 => "testval1", test2 => "testval2" }, 54 | "get_by_plugin" 55 | ); 56 | 57 | ok($s->remove({ 58 | plugin => $plugin, 59 | address => '127.0.0.1', 60 | key => "test1", 61 | }),"remove"); 62 | 63 | sleep 3; 64 | 65 | is_deeply( 66 | $s->get_by_plugin({plugin=>$plugin,address=>"127.0.0.1"}), 67 | {}, 68 | "get_by_plugin after remove" 69 | ); 70 | 71 | done_testing(); 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /t/050-Kurado-Metrics/00-compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | 4 | use_ok $_ for qw( 5 | Kurado::Metrics 6 | ); 7 | 8 | done_testing; 9 | -------------------------------------------------------------------------------- /t/060-Kurodo-Host/00-compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | 4 | use_ok $_ for qw( 5 | Kurado::Host 6 | ); 7 | 8 | done_testing; 9 | -------------------------------------------------------------------------------- /views/base.tx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | : block title -> { "Kurado - Server Performance Metrics" } 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | : block content -> { } 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /views/index.tx: -------------------------------------------------------------------------------- 1 | : cascade base 2 | : around content -> { 3 | 4 | 33 | 34 |
35 |
36 |
37 |
サービス一覧
38 | 46 |
47 | 48 |
49 | 50 | : for $services -> $service { 51 |

<: $service.service :>

52 | : for $service.sections -> $section { 53 |
54 | 55 | : if $section.label { 56 | 57 | : } # label 58 | 59 | : for $section.hosts -> $host { 60 | 61 | 68 | 69 | : } # host 70 |
<: $section.label :>
62 | 63 | 64 | <: $host.address :> 65 | <: $host.hostname :> 66 | <: $host.comments :> 67 |
71 |
72 | : } # section 73 | : } # service 74 | 75 |
76 |
77 |
78 | 79 | 80 | : } 81 | 82 | -------------------------------------------------------------------------------- /views/server.tx: -------------------------------------------------------------------------------- 1 | : cascade base 2 | : around title -> { 3 | <: $c.stash.host.hostname :> <: $c.stash.host.address :> « <: $c.stash.host.service :> « Kurado 4 | : } 5 | 6 | : around content -> { 7 | 8 | 9 | 98 | 99 | 100 |
101 |
102 |
103 | 104 | 105 | 144 |
106 | 107 | : for $c.stash.host.metrics_list -> $plugin { 108 | : next if $plugin_identifier && $plugin.plugin.plugin_identifier != $plugin_identifier 109 |
110 |

"> <: $plugin.plugin.plugin :>

111 | 112 | : for $plugin.warn.keys() -> $warn_key { 113 | 114 | : } 115 | 116 | : for $plugin.metrics -> $metrics { 117 | : if $metrics.label { 118 |

<: $metrics.label :>

119 | : } 120 |
121 | : for $metrics.meta -> $meta { 122 | 123 | <: $meta.key :> 124 | <: $meta.value :> 125 | 126 | : } 127 |
128 | : for $metrics.graphs -> $graph { 129 |
130 | : for $terms -> $display_term { 131 | : if $term == "custom" { 132 | 133 | : } else { 134 | 135 | : } 136 | : } 137 |
138 | : } # graph 139 | : } # metrics 140 |
141 | : } # plugin 142 | 143 |
145 | 146 |
147 |
148 |
149 | 150 | 151 | : } 152 | -------------------------------------------------------------------------------- /views/servers.tx: -------------------------------------------------------------------------------- 1 | : cascade base 2 | : around title -> { 3 | 選択されたサーバ (<: $hosts.size() :>) « Kurado 4 | : } 5 | 6 | : around content -> { 7 | 103 | 104 | 105 |
106 |
107 |
108 | 109 | 110 | 111 | : for $hosts -> $host { 112 | 120 | : } # hosts 121 | 122 | 123 | : for $hosts -> $host { 124 | 161 | : } # hosts 162 | 163 |
113 | <: $host.service :> 114 | 115 | <: $host.address :>
116 | <: $host.hostname :> 117 | <: $host.comments :> 118 |
 
119 |
125 | : for $host.metrics_list -> $plugin { 126 | : next if $plugin_identifier && $plugin.plugin.plugin_identifier != $plugin_identifier 127 |
128 |

"> <: $plugin.plugin.plugin :>

129 | 130 | : for $plugin.warn.keys() -> $warn_key { 131 | 132 | : } 133 | 134 | : for $plugin.metrics -> $metrics { 135 | : if $metrics.label { 136 |

<: $metrics.label :>

137 | : } 138 |
139 | : for $metrics.meta -> $meta { 140 | 141 | <: $meta.key :> 142 | <: $meta.value :> 143 | 144 | : } 145 |
146 | : for $metrics.graphs -> $graph { 147 |
148 | : for $terms -> $display_term { 149 | : if $term == "custom" { 150 | 151 | : } else { 152 | 153 | : } 154 | : } 155 |
156 | : } # graph 157 | : } # metrics 158 |
159 | : } # plugin 160 |
164 | 165 |
166 |
167 |
168 | 169 | 170 | : } 171 | --------------------------------------------------------------------------------