1 |
#!/usr/bin/perl -w |
2 |
############################################################ |
3 |
# |
4 |
# $Id: rrd-client.pl 775 2006-10-08 18:47:33Z nicolaw $ |
5 |
# rrd-client.pl - Data gathering script for RRD::Simple |
6 |
# |
7 |
# Copyright 2006,2007 Nicola Worthington |
8 |
# |
9 |
# Licensed under the Apache License, Version 2.0 (the "License"); |
10 |
# you may not use this file except in compliance with the License. |
11 |
# You may obtain a copy of the License at |
12 |
# |
13 |
# http://www.apache.org/licenses/LICENSE-2.0 |
14 |
# |
15 |
# Unless required by applicable law or agreed to in writing, software |
16 |
# distributed under the License is distributed on an "AS IS" BASIS, |
17 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
18 |
# See the License for the specific language governing permissions and |
19 |
# limitations under the License. |
20 |
# |
21 |
############################################################ |
22 |
# vim:ts=4:sw=4:tw=78 |
23 |
|
24 |
############################################################ |
25 |
# User defined constants |
26 |
use constant DB_MYSQL_DSN => $ENV{DB_MYSQL_DSN} || 'DBI:mysql:mysql:localhost'; |
27 |
use constant DB_MYSQL_USER => $ENV{DB_MYSQL_USER} || undef; |
28 |
use constant DB_MYSQL_PASS => $ENV{DB_MYSQL_PASS} || undef; |
29 |
|
30 |
use constant NET_PING_HOSTS => $ENV{NET_PING_HOSTS} ? |
31 |
(split(/[\s,:]+/,$ENV{NET_PING_HOSTS})) : qw(); |
32 |
|
33 |
# |
34 |
# YOU SHOULD NOT NEED TO EDIT ANYTHING BEYOND THIS POINT |
35 |
# |
36 |
############################################################ |
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
use 5.004; |
43 |
use strict; |
44 |
#use warnings; # comment out for release |
45 |
use vars qw($VERSION); |
46 |
|
47 |
$VERSION = '1.42' || sprintf('%d', q$Revision: 775 $ =~ /(\d+)/g); |
48 |
$ENV{PATH} = '/bin:/usr/bin'; |
49 |
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; |
50 |
|
51 |
|
52 |
# Default list of probes |
53 |
my %probes = ( |
54 |
hw_irq_interrupts => 'Hardward IRQ Interrupts', |
55 |
|
56 |
cpu_utilisation => 'CPU Utilisation', |
57 |
cpu_loadavg => 'Load Average', |
58 |
cpu_temp => 'CPU Temperature', |
59 |
cpu_interrupts => 'CPU Interrupts', |
60 |
|
61 |
hdd_io => 'Hard Disk I/O', |
62 |
hdd_temp => 'Hard Disk Temperature', |
63 |
hdd_capacity => 'Disk Capacity', |
64 |
|
65 |
mem_usage => 'Memory Usage & Swap Usage', |
66 |
mem_swap_activity => 'Swap Activity', |
67 |
mem_proc_largest => 'Largest Process', |
68 |
|
69 |
proc_threads => 'Threads', |
70 |
proc_state => 'Processes', |
71 |
proc_filehandles => 'File Handles', |
72 |
|
73 |
apache_status => 'Apache Scoreboard & Apache Activity', |
74 |
apache_logs => 'Apache Log Activity', |
75 |
|
76 |
misc_uptime => 'Server Uptime', |
77 |
misc_users => 'Users Logged In', |
78 |
misc_ipmi_temp => 'IPMI Temperature Probes', |
79 |
misc_entropy => 'Available Entropy', |
80 |
|
81 |
db_mysql => 'MySQL Database Activity', |
82 |
db_mysql_replication => 'MySQL Database Replication', |
83 |
|
84 |
mail_exim_queue => 'Exim Mail Queue', |
85 |
mail_postfix_queue => 'Postfix Mail Queue', |
86 |
mail_sendmail_queue => 'Sendmail Mail Queue', |
87 |
|
88 |
net_traffic => 'Network Traffic', |
89 |
net_connections => 'Network Connections', |
90 |
net_ping_host => 'Ping', |
91 |
# net_connections_ports => 'Service Connections', |
92 |
); |
93 |
|
94 |
|
95 |
# Get command line options |
96 |
my %opt = (); |
97 |
eval "require Getopt::Std"; |
98 |
$Getopt::Std::STANDARD_HELP_VERSION = 1; |
99 |
$Getopt::Std::STANDARD_HELP_VERSION = 1; |
100 |
Getopt::Std::getopts('p:i:x:s:c:V:hvqlP:?', \%opt) unless $@; |
101 |
(HELP_MESSAGE() && exit) if defined $opt{h} || defined $opt{'?'}; |
102 |
(VERSION_MESSAGE() && exit) if defined $opt{v}; |
103 |
|
104 |
# Display a list of available probe names |
105 |
if ($opt{l}) { |
106 |
print "Available probes:\n"; |
107 |
printf " %-24s %s\n",'PROBE','DESCRIPTION'; |
108 |
for (sort keys %probes) { |
109 |
my $str = sprintf(" %-24s %s\n", $_, $probes{$_}); |
110 |
$str =~ s/(\S+) (\s+) /$_ = "$1 ". '.' x length($2) ." ";/e; |
111 |
print "$str"; |
112 |
} |
113 |
exit; |
114 |
} |
115 |
|
116 |
# Check to see if we are capable of SNMP queries |
117 |
my $snmpClient; |
118 |
if ($opt{s}) { |
119 |
eval { |
120 |
require Net::SNMP; |
121 |
$snmpClient = 'Net::SNMP'; |
122 |
}; |
123 |
if ($@) { |
124 |
my $c = 'snmpwalk'; # snmpget |
125 |
my $cmd = select_cmd("/usr/bin/$c","/usr/local/bin/$c"); |
126 |
die "Error: unable to query via SNMP. Please install Net::SNMP or $c.\n" unless $cmd; |
127 |
$snmpClient = $cmd; |
128 |
} |
129 |
$opt{c} = 'public' unless defined($opt{c}) && $opt{c} =~ /\S+/; |
130 |
$opt{V} = '2c' unless defined($opt{V}) && $opt{V} =~ /^(1|2c)$/; |
131 |
$opt{P} = 161 unless defined($opt{P}) && $opt{P} =~ /^[0-9]+$/; |
132 |
} |
133 |
|
134 |
# Filter on probe include list |
135 |
my @probes = sort keys %probes; |
136 |
if (defined $opt{i}) { |
137 |
my $inc = join('|',split(/\s*,\s*/,$opt{i})); |
138 |
@probes = grep(/(^|_)($inc)(_|$)/,@probes); |
139 |
} |
140 |
|
141 |
# Filter on probe exclude list |
142 |
if (defined $opt{x}) { |
143 |
my $exc = join('|',split(/\s*,\s*/,$opt{x})); |
144 |
@probes = grep(!/(^|_)($exc)(_|$)/,@probes); |
145 |
} |
146 |
|
147 |
|
148 |
# Run the probes one by one |
149 |
die "Error: nothing to probe!\n" unless @probes; |
150 |
my $post = ''; |
151 |
my %update_cache; |
152 |
for my $probe (@probes) { |
153 |
eval { |
154 |
local $SIG{ALRM} = sub { die "Timeout!\n"; }; |
155 |
alarm 15; |
156 |
my $str = report($probe,eval "$probe();"); |
157 |
if (defined $opt{p}) { |
158 |
$post .= $str; |
159 |
} else { |
160 |
print $str; |
161 |
} |
162 |
warn "Warning [$probe]: $@" if !$opt{q} && $@; |
163 |
alarm 0; |
164 |
}; |
165 |
warn "Warning [$probe]: $@" if !$opt{q} && $@; |
166 |
} |
167 |
|
168 |
|
169 |
# HTTP POST the data if asked to |
170 |
print scalar(basic_http('POST',$opt{p},30,$post))."\n" if $opt{p}; |
171 |
|
172 |
|
173 |
exit; |
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
# Report the data |
180 |
sub report { |
181 |
(my $probe = shift) =~ s/[_-]/\./g; |
182 |
my %data = @_ % 2 ? (@_,undef) : @_; |
183 |
my $str = ''; |
184 |
for my $k (sort keys %data) { |
185 |
#$data{$k} = 0 unless defined($data{$k}); |
186 |
next unless defined($data{$k}) && $data{$k} =~ /^[0-9\.]*$/; |
187 |
$str .= sprintf("%s.%s.%s %s\n", time(), $probe, $k, $data{$k}); |
188 |
} |
189 |
return $str; |
190 |
} |
191 |
|
192 |
|
193 |
# Display help |
194 |
sub HELP_MESSAGE { |
195 |
print qq{Syntax: rrd-client.pl [-i probe1,probe2,..|-x probe1,probe2,..] |
196 |
[-s host] [-c community] [-P port] [-V 1|2c] [-p URL] [-h|-v] |
197 |
-i <probes> Include a list of comma seperated probes |
198 |
-x <probes> Exclude a list of comma seperated probes |
199 |
-s <host> Specify hostname to probe via SNMP |
200 |
-c <community> Specify SNMP community name (defaults to public) |
201 |
-V <version> Specify SNMP version to use (1 or 2c, defaults to 2c) |
202 |
-P <port> Specify SNMP port to use |
203 |
-p <URL> HTTP POST data to the specified URL |
204 |
-q Suppress all warning messages |
205 |
-l Display a list of available probe names |
206 |
-v Display version information |
207 |
-h Display this help |
208 |
|
209 |
Examples: |
210 |
rrd-client.pl -x apache_status -q -p http://rrd.me.uk/cgi-bin/rrd-server.cgi |
211 |
rrd-client.pl -s localhost -p http://rrd.me.uk/cgi-bin/rrd-server.cgi |
212 |
rrd-client.pl -s server1.company.com | rrd-server.pl -u server1.company.com |
213 |
\n}; |
214 |
} |
215 |
|
216 |
|
217 |
# Display version |
218 |
sub VERSION { &VERSION_MESSAGE; } |
219 |
sub VERSION_MESSAGE { |
220 |
print "$0 version $VERSION ".'($Id: rrd-client.pl 775 2006-10-08 18:47:33Z nicolaw $)'."\n"; |
221 |
} |
222 |
|
223 |
|
224 |
# Basic HTTP client if LWP is unavailable |
225 |
sub basic_http { |
226 |
my ($method,$url,$timeout,$data) = @_; |
227 |
$method ||= 'GET'; |
228 |
$url ||= 'http://localhost/'; |
229 |
$timeout ||= 5; |
230 |
|
231 |
my ($scheme,$host,$port,$path) = $url =~ m,^(https?://)([\w\d\.\-]+)(?::(\d+))?(.*),i; |
232 |
$scheme ||= 'http://'; |
233 |
$host ||= 'localhost'; |
234 |
$path ||= '/'; |
235 |
$port ||= 80; |
236 |
|
237 |
my $str = ''; |
238 |
eval "use Socket"; |
239 |
return $str if $@; |
240 |
|
241 |
eval { |
242 |
local $SIG{ALRM} = sub { die "TIMEOUT\n" }; |
243 |
alarm $timeout; |
244 |
|
245 |
my $iaddr = inet_aton($host) || die; |
246 |
my $paddr = sockaddr_in($port, $iaddr); |
247 |
my $proto = getprotobyname('tcp'); |
248 |
socket(SOCK, AF_INET(), SOCK_STREAM(), $proto) || die "socket: $!"; |
249 |
connect(SOCK, $paddr) || die "connect: $!"; |
250 |
|
251 |
select(SOCK); $| = 1; |
252 |
select(STDOUT); |
253 |
|
254 |
# Send the HTTP request |
255 |
print SOCK "$method $path HTTP/1.1\n"; |
256 |
print SOCK "Host: $host". ("$port" ne "80" ? ":$port" : '') ."\n"; |
257 |
print SOCK "User-Agent: $0 version $VERSION ".'($Id: rrd-client.pl 775 2006-10-08 18:47:33Z nicolaw $)'."\n"; |
258 |
if ($data && $method eq 'POST') { |
259 |
print SOCK "Content-Length: ". length($data) ."\n"; |
260 |
print SOCK "Content-Type: application/x-www-form-urlencoded\n"; |
261 |
} |
262 |
print SOCK "\n"; |
263 |
print SOCK $data if $data && $method eq 'POST'; |
264 |
|
265 |
my $body = 0; |
266 |
while (local $_ = <SOCK>) { |
267 |
s/[\n\n]+//g; |
268 |
$str .= $_ if $_ && $body; |
269 |
$body = 1 if /^\s*$/; |
270 |
} |
271 |
close(SOCK); |
272 |
alarm 0; |
273 |
}; |
274 |
|
275 |
warn "Warning [basic_http]: $@" if !$opt{q} && $@ && $data; |
276 |
return wantarray ? split(/\n/,$str) : "$str"; |
277 |
} |
278 |
|
279 |
|
280 |
# Return the most appropriate binary command |
281 |
sub select_cmd { |
282 |
foreach (@_) { |
283 |
if (-f $_ && -x $_ && /(\S+)/) { |
284 |
return $1; |
285 |
} |
286 |
} |
287 |
return ''; |
288 |
} |
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
# |
296 |
# Probes |
297 |
# |
298 |
|
299 |
|
300 |
sub _snmp { |
301 |
my $oid = [@_]; |
302 |
my $result = {}; |
303 |
|
304 |
# Net::SNMP |
305 |
if ($snmpClient eq 'Net::SNMP') { |
306 |
my ($session, $error) = Net::SNMP->session( |
307 |
-hostname => $opt{s}, |
308 |
-community => $opt{c}, |
309 |
-version => $opt{V}, |
310 |
-port => $opt{P}, |
311 |
-translate => [ -timeticks => 0x0 ], |
312 |
); |
313 |
die $error if !defined($session); |
314 |
|
315 |
$result = $session->get_request(-varbindlist => $oid); |
316 |
|
317 |
$session->close; |
318 |
die $session->error if !defined($result); |
319 |
|
320 |
# snmpget / snmpwalk |
321 |
} else { |
322 |
my $oidStr = join(' ', @{$oid}); |
323 |
my $cmd = "$snmpClient -O n -O t -v $opt{V} -c $opt{c} $opt{s} $oidStr"; |
324 |
#my $cmd = "$snmpClient -O t -v $opt{V} -c $opt{c} $opt{s} $oidStr"; |
325 |
|
326 |
open(PH,'-|',"$cmd 2>&1") || die "Unable to open file handle PH for command '$cmd': $!\n"; |
327 |
while (local $_ = <PH>) { |
328 |
s/[\r\n]+//g; s/^(?:\s+|\s+)$//g; |
329 |
if (/^(\.[\.0-9]+|[A-Za-z:\-\.0-9]+)\s*=\s*(?:([A-Za-z0-9]+):\s*)?["']?(\S*)["']?/) { |
330 |
my ($oid,$type,$value) = ($1,$2,$3); |
331 |
$oid = ".$oid" if $oid =~ /^[0-9][0-9\.]+$/; |
332 |
$result->{$oid} = $value; |
333 |
} else { |
334 |
warn "Warning [_snmp]: $_\n" unless $opt{q}; |
335 |
} |
336 |
} |
337 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
338 |
} |
339 |
|
340 |
return $result->{(keys(%{$result}))[0]} if !wantarray && keys(%{$result}) == 1; |
341 |
return $result; |
342 |
} |
343 |
|
344 |
|
345 |
|
346 |
sub net_ping_host { |
347 |
return if $opt{s}; |
348 |
return unless defined NET_PING_HOSTS() && scalar NET_PING_HOSTS() > 0; |
349 |
my $cmd = select_cmd(qw(/bin/ping /usr/bin/ping /sbin/ping /usr/sbin/ping)); |
350 |
return unless -f $cmd; |
351 |
my %update = (); |
352 |
my $count = 3; |
353 |
|
354 |
for my $str (NET_PING_HOSTS()) { |
355 |
my ($host) = $str =~ /^([\w\d_\-\.]+)$/i; |
356 |
next unless $host; |
357 |
my $cmd2 = "$cmd -c $count $host 2>&1"; |
358 |
|
359 |
open(PH,'-|',$cmd2) || die "Unable to open file handle PH for command '$cmd2': $!\n"; |
360 |
while (local $_ = <PH>) { |
361 |
if (/\s+(\d+)%\s+packet\s+loss[\s,]/i) { |
362 |
$update{"$host.PacketLoss"} = $1 || 0; |
363 |
} elsif (my ($min,$avg,$max,$mdev) = $_ =~ |
364 |
/\s+([\d\.]+)\/([\d\.]+)\/([\d\.]+)\/([\d\.]+)\s+/) { |
365 |
$update{"$host.AvgRTT"} = $avg || 0; |
366 |
$update{"$host.MinRTT"} = $min || 0; |
367 |
$update{"$host.MaxRTT"} = $max || 0; |
368 |
$update{"$host.MDevRTT"} = $mdev || 0; |
369 |
} |
370 |
} |
371 |
close(PH) || die "Unable to close file handle PH for command '$cmd2': $!\n"; |
372 |
} |
373 |
|
374 |
return %update; |
375 |
} |
376 |
|
377 |
|
378 |
|
379 |
sub mem_proc_largest { |
380 |
return if $opt{s}; |
381 |
my $cmd = select_cmd(qw(/bin/ps /usr/bin/ps)); |
382 |
return unless -f $cmd; |
383 |
$cmd .= ' -eo vsize'; |
384 |
|
385 |
my %update = (); |
386 |
open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; |
387 |
while (local $_ = <PH>) { |
388 |
if (/(\d+)/) { |
389 |
my $kb = $1; |
390 |
$update{LargestProc} = $kb if !defined $update{LargestProc} || |
391 |
(defined $update{LargestProc} && $kb > $update{LargestProc}); |
392 |
} |
393 |
} |
394 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
395 |
$update{LargestProc} *= 1024 if defined $update{LargestProc}; |
396 |
|
397 |
return %update; |
398 |
} |
399 |
|
400 |
|
401 |
|
402 |
sub proc_threads { |
403 |
if ($opt{s}) { |
404 |
my $procs = _snmp('.1.3.6.1.2.1.25.1.6.0'); # hrSystemProcesses |
405 |
return unless defined($procs) && $procs =~ /^[0-9]+$/; |
406 |
return ('Processes' => $procs, 'Threads' => 0, 'MultiThreadProcs' => 0); |
407 |
} |
408 |
|
409 |
return if $opt{s}; |
410 |
return unless ($^O eq 'linux' && `/bin/uname -r 2>&1` =~ /^2\.6\./) || |
411 |
($^O eq 'solaris' && `/bin/uname -r 2>&1` =~ /^5\.9/); |
412 |
my %update = (); |
413 |
my $cmd = '/bin/ps -eo pid,nlwp'; |
414 |
|
415 |
open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!"; |
416 |
while (local $_ = <PH>) { |
417 |
if (my ($pid,$nlwp) = $_ =~ /^\s*(\d+)\s+(\d+)\s*$/) { |
418 |
$update{Processes}++; |
419 |
$update{Threads} += $nlwp; |
420 |
$update{MultiThreadProcs}++ if $nlwp > 1; |
421 |
} |
422 |
} |
423 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!"; |
424 |
|
425 |
return %update; |
426 |
} |
427 |
|
428 |
|
429 |
|
430 |
sub mail_exim_queue { |
431 |
return if $opt{s}; |
432 |
my $spooldir = '/var/spool/exim/input'; |
433 |
return unless -d $spooldir && -x $spooldir && -r $spooldir; |
434 |
|
435 |
local %mail::exim::queue::update = (Messages => 0); |
436 |
require File::Find; |
437 |
File::Find::find({wanted => sub { |
438 |
my ($dev,$ino,$mode,$nlink,$uid,$gid); |
439 |
(($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && |
440 |
-f _ && |
441 |
/^.*-D\z/s && |
442 |
$mail::exim::queue::update{Messages}++; |
443 |
}, no_chdir => 1}, $spooldir); |
444 |
return %mail::exim::queue::update; |
445 |
} |
446 |
|
447 |
|
448 |
sub mail_sendmail_queue { |
449 |
return if $opt{s}; |
450 |
my $spooldir = '/var/spool/mqueue'; |
451 |
return unless -d $spooldir && -x $spooldir && -r $spooldir; |
452 |
|
453 |
local %mail::sendmail::queue::update = (Messages => 0); |
454 |
require File::Find; |
455 |
File::Find::find({wanted => sub { |
456 |
my ($dev,$ino,$mode,$nlink,$uid,$gid); |
457 |
(($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && |
458 |
-f _ && |
459 |
/^Qf[a-zA-Z0-9]{14}\z/s && |
460 |
$mail::sendmail::queue::update{Messages}++; |
461 |
}, no_chdir => 1}, $spooldir); |
462 |
return %mail::sendmail::queue::update; |
463 |
} |
464 |
|
465 |
|
466 |
|
467 |
sub mail_postfix_queue { |
468 |
return if $opt{s}; |
469 |
my @spooldirs = qw( |
470 |
/var/spool/postfix/incoming |
471 |
/var/spool/postfix/active |
472 |
/var/spool/postfix/defer |
473 |
/var/spool/postfix/deferred |
474 |
); |
475 |
for my $spooldir (@spooldirs) { |
476 |
return unless -d $spooldir && -x $spooldir && -r $spooldir; |
477 |
} |
478 |
|
479 |
local %mail::postfix::queue::update = (Messages => 0); |
480 |
require File::Find; |
481 |
File::Find::find({wanted => sub { |
482 |
my ($dev,$ino,$mode,$nlink,$uid,$gid); |
483 |
(($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) && |
484 |
-f _ && |
485 |
$mail::postfix::queue::update{Messages}++; |
486 |
}, no_chdir => 1}, @spooldirs); |
487 |
return %mail::postfix::queue::update; |
488 |
} |
489 |
|
490 |
|
491 |
|
492 |
# DO NOT ENABLE THIS ONE YET |
493 |
sub mail_queue { |
494 |
return if $opt{s}; |
495 |
my $cmd = select_cmd(qw(/usr/bin/mailq /usr/sbin/mailq /usr/local/bin/mailq |
496 |
/usr/local/sbin/mailq /bin/mailq /sbin/mailq |
497 |
/usr/local/exim/bin/mailq /home/system/exim/bin/mailq)); |
498 |
return unless -f $cmd; |
499 |
|
500 |
my %update = (); |
501 |
|
502 |
open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; |
503 |
while (local $_ = <PH>) { |
504 |
# This needs to match a single message id = currently only exim friendly |
505 |
if (/^\s*\S+\s+\S+\s+[a-z0-9]{6}-[a-z0-9]{6}-[a-z0-9]{2} </i) { |
506 |
$update{Messages}++; |
507 |
} |
508 |
} |
509 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
510 |
$update{Messages} = 0 if !defined($update{Messages}); |
511 |
|
512 |
return %update; |
513 |
} |
514 |
|
515 |
|
516 |
|
517 |
sub db_mysql { |
518 |
return if $opt{s}; |
519 |
my %update = (); |
520 |
return %update unless (defined DB_MYSQL_DSN && defined DB_MYSQL_USER); |
521 |
my @cols = qw( |
522 |
Questions |
523 |
Connections |
524 |
Com_select Com_insert Com_delete Com_replace Com_update Com_rollback Com_commit Com_set_option |
525 |
Threads_cached Threads_connected Threads_created Threads_running |
526 |
Bytes_sent Bytes_received |
527 |
Innodb_row_lock_current_waits Innodb_row_lock_time_avg Innodb_row_lock_time_max Innodb_row_lock_waits |
528 |
Select_scan Sort_scan |
529 |
Created_tmp_disk_tables Created_tmp_files Created_tmp_tables |
530 |
|
531 |
); |
532 |
|
533 |
# my @GAUGE = qw(Key_blocks_not_flushed Key_blocks_unused Key_blocks_used |
534 |
# Open_files Open_streams Open_tables Qcache_free_blocks Qcache_free_memory |
535 |
# Qcache_queries_in_cache Qcache_total_blocks Slave_open_temp_tables |
536 |
# Threads_cached Threads_connected Threads_running Uptime); |
537 |
|
538 |
# my @DERIVE = qw(Aborted_clients Aborted_connects Binlog_cache_disk_use |
539 |
# Binlog_cache_use Bytes_received Bytes_sent Com_admin_commands Com_alter_db |
540 |
# Com_alter_table Com_analyze Com_backup_table Com_begin Com_change_db |
541 |
# Com_change_master Com_check Com_checksum Com_commit Com_create_db |
542 |
# Com_create_function Com_create_index Com_create_table Com_dealloc_sql |
543 |
# Com_delete Com_delete_multi Com_do Com_drop_db Com_drop_function |
544 |
# Com_drop_index Com_drop_table Com_drop_user Com_execute_sql Com_flush |
545 |
# Com_grant Com_ha_close Com_ha_open Com_ha_read Com_help Com_insert |
546 |
# Com_insert_select Com_kill Com_load Com_load_master_data Com_load_master_table |
547 |
# Com_lock_tables Com_optimize Com_preload_keys Com_prepare_sql Com_purge |
548 |
# Com_purge_before_date Com_rename_table Com_repair Com_replace Com_replace_select |
549 |
# Com_reset Com_restore_table Com_revoke Com_revoke_all Com_rollback |
550 |
# Com_savepoint Com_select Com_set_option Com_show_binlog_events |
551 |
# Com_show_binlogs Com_show_charsets Com_show_collations Com_show_column_types |
552 |
# Com_show_create_db Com_show_create_table Com_show_databases |
553 |
# Com_show_errors Com_show_fields Com_show_grants Com_show_innodb_status |
554 |
# Com_show_keys Com_show_logs Com_show_master_status Com_show_ndb_status |
555 |
# Com_show_new_master Com_show_open_tables Com_show_privileges Com_show_processlist |
556 |
# Com_show_slave_hosts Com_show_slave_status Com_show_status Com_show_storage_engines |
557 |
# Com_show_tables Com_show_variables Com_show_warnings Com_slave_start |
558 |
# Com_slave_stop Com_stmt_close Com_stmt_execute Com_stmt_prepare Com_stmt_reset |
559 |
# Com_stmt_send_long_data Com_truncate Com_unlock_tables Com_update |
560 |
# Com_update_multi Connections Created_tmp_disk_tables Created_tmp_files |
561 |
# Created_tmp_tables Delayed_errors Delayed_insert_threads Delayed_writes |
562 |
# Flush_commands Handler_commit Handler_delete Handler_discover Handler_read_first |
563 |
# Handler_read_key Handler_read_next Handler_read_prev Handler_read_rnd |
564 |
# Handler_read_rnd_next Handler_rollback Handler_update Handler_write |
565 |
# Key_blocks_not_flushed Key_blocks_unused Key_blocks_used Key_read_requests |
566 |
# Key_reads Key_write_requests Key_writes Max_used_connections Not_flushed_delayed_rows |
567 |
# Open_files Open_streams Open_tables Opened_tables Qcache_free_blocks |
568 |
# Qcache_free_memory Qcache_hits Qcache_inserts Qcache_lowmem_prunes Qcache_not_cached |
569 |
# Qcache_queries_in_cache Qcache_total_blocks Questions Select_full_join |
570 |
# Select_full_range_join Select_range Select_range_check Select_scan Slave_open_temp_tables |
571 |
# Slave_retried_transactions Slow_launch_threads Slow_queries Sort_merge_passes |
572 |
# Sort_range Sort_rows Sort_scan Ssl_accept_renegotiates Ssl_accepts |
573 |
# Ssl_callback_cache_hits Ssl_client_connects Ssl_connect_renegotiates Ssl_ctx_verify_depth |
574 |
# Ssl_ctx_verify_mode Ssl_default_timeout Ssl_finished_accepts Ssl_finished_connects |
575 |
# Ssl_session_cache_hits Ssl_session_cache_misses Ssl_session_cache_overflows |
576 |
# Ssl_session_cache_size Ssl_session_cache_timeouts Ssl_sessions_reused |
577 |
# Ssl_used_session_cache_entries Ssl_verify_depth Ssl_verify_mode |
578 |
# Table_locks_immediate Table_locks_waited Threads_cached Threads_connected |
579 |
# Threads_created Threads_running); |
580 |
|
581 |
eval { |
582 |
require DBI; |
583 |
my $dbh = DBI->connect(DB_MYSQL_DSN,DB_MYSQL_USER,DB_MYSQL_PASS); |
584 |
#my $sth = $dbh->prepare('SHOW GLOBAL STATUS'); |
585 |
my $sth = $dbh->prepare('SHOW /*!50002 GLOBAL */ STATUS'); |
586 |
$sth->execute(); |
587 |
|
588 |
while (my @ary = $sth->fetchrow_array()) { |
589 |
my ($k,$v) = @ary; |
590 |
next unless grep { $k eq $_ } @cols; |
591 |
|
592 |
if ($k eq 'Questions') { |
593 |
$update{"activity.$k"} = $v; |
594 |
} elsif ($k =~ /^Com_/) { |
595 |
$update{"activity.com.$k"} = $v; |
596 |
} elsif ($k =~ /_scan$/) { |
597 |
$update{"activity.scan.$k"} = $v; |
598 |
|
599 |
} elsif ($k eq 'Connections') { |
600 |
$update{"connections.$k"} = $v; |
601 |
|
602 |
} elsif ($k =~ /^Threads_/) { |
603 |
$update{"threads.$k"} = $v; |
604 |
} elsif ($k =~ /^Bytes_/) { |
605 |
$update{"traffic.$k"} = $v; |
606 |
|
607 |
} elsif ($k =~ /^Created_tmp_/) { |
608 |
$k =~ s/^Created_tmp_//; |
609 |
$update{"created_tmp.$k"} = $v; |
610 |
} elsif ($k =~ /^Innodb_row_lock_/) { |
611 |
$k =~ s/^Innodb_row_lock_//; |
612 |
$update{"innodb.row_lock.$k"} = $v; |
613 |
} |
614 |
} |
615 |
$sth->finish(); |
616 |
$dbh->disconnect(); |
617 |
}; |
618 |
|
619 |
return %update; |
620 |
} |
621 |
|
622 |
|
623 |
|
624 |
sub db_mysql_replication { |
625 |
return if $opt{s}; |
626 |
my %update = (); |
627 |
return %update unless (defined DB_MYSQL_DSN && defined DB_MYSQL_USER); |
628 |
|
629 |
eval { |
630 |
require DBI; |
631 |
my $dbh = DBI->connect(DB_MYSQL_DSN,DB_MYSQL_USER,DB_MYSQL_PASS); |
632 |
my $sth = $dbh->prepare('SHOW SLAVE STATUS'); |
633 |
$sth->execute(); |
634 |
my $row = $sth->fetchrow_hashref; |
635 |
$sth->finish(); |
636 |
$dbh->disconnect(); |
637 |
$update{SecondsBehind} = $row->{Seconds_Behind_Master} || 0; |
638 |
}; |
639 |
|
640 |
return %update; |
641 |
} |
642 |
|
643 |
|
644 |
|
645 |
sub misc_users { |
646 |
if ($opt{s}) { |
647 |
my $users = _snmp('.1.3.6.1.2.1.25.1.5.0'); # hrSystemNumUsers |
648 |
return unless defined($users) && $users =~ /^[0-9]+$/; |
649 |
return ('Users' => $users, 'Unique' => 0); |
650 |
} |
651 |
|
652 |
return if $opt{s}; |
653 |
my $cmd = select_cmd(qw(/usr/bin/who /bin/who /usr/bin/w /bin/w)); |
654 |
return unless -f $cmd; |
655 |
my %update = (); |
656 |
|
657 |
open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; |
658 |
my %users = (); |
659 |
while (local $_ = <PH>) { |
660 |
next if /^\s*USERS\s*TTY/; |
661 |
$users{(split(/\s+/,$_))[0]}++; |
662 |
$update{Users}++; |
663 |
} |
664 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
665 |
$update{Unique} = keys %users if keys %users; |
666 |
|
667 |
unless (keys %update) { |
668 |
$cmd = -f '/usr/bin/uptime' ? '/usr/bin/uptime' : '/bin/uptime'; |
669 |
if (my ($users) = `$cmd` =~ /,\s*(\d+)\s*users?\s*,/i) { |
670 |
$update{Users} = $1; |
671 |
} |
672 |
} |
673 |
|
674 |
$update{Users} ||= 0; |
675 |
$update{Unique} ||= 0; |
676 |
|
677 |
return %update; |
678 |
} |
679 |
|
680 |
|
681 |
|
682 |
sub misc_uptime { |
683 |
if ($opt{s}) { |
684 |
my $ticks = _snmp('.1.3.6.1.2.1.25.1.1.0'); # hrSystemUptime |
685 |
return unless defined($ticks) && $ticks =~ /^[0-9]+$/; |
686 |
return ('DaysUp' => $ticks/100/60/60/24); |
687 |
} |
688 |
|
689 |
my $cmd = select_cmd(qw(/usr/bin/uptime /bin/uptime)); |
690 |
return unless -f $cmd; |
691 |
my %update = (); |
692 |
|
693 |
if (my ($str) = `$cmd` =~ /\s*up\s*(.+?)\s*,\s*\d+\s*users?/) { |
694 |
my $days = 0; |
695 |
if (my ($nuke,$num) = $str =~ /(\s*(\d+)\s*days?,?\s*)/) { |
696 |
$str =~ s/$nuke//; |
697 |
$days += $num; |
698 |
} |
699 |
if (my ($nuke,$mins) = $str =~ /(\s*(\d+)\s*mins?,?\s*)/) { |
700 |
$str =~ s/$nuke//; |
701 |
$days += ($mins / (60*24)); |
702 |
} |
703 |
if (my ($nuke,$hours) = $str =~ /(\s*(\d+)\s*(hour|hr)s?,?\s*)/) { |
704 |
$str =~ s/$nuke//; |
705 |
$days += ($hours / 24); |
706 |
} |
707 |
if (my ($hours,$mins) = $str =~ /\s*(\d+):(\d+)\s*,?/) { |
708 |
$days += ($mins / (60*24)); |
709 |
$days += ($hours / 24); |
710 |
} |
711 |
$update{DaysUp} = $days; |
712 |
} |
713 |
|
714 |
return %update; |
715 |
} |
716 |
|
717 |
|
718 |
|
719 |
sub cpu_temp { |
720 |
return if $opt{s}; |
721 |
my $cmd = '/usr/bin/sensors'; |
722 |
return unless -f $cmd; |
723 |
my %update = (); |
724 |
|
725 |
open(PH,'-|',"$cmd 2>&1") || die "Unable to open file handle PH for command '$cmd': $!\n"; |
726 |
while (local $_ = <PH>) { |
727 |
if (my ($k,$v) = $_ =~ /^([^:]*\b(?:CPU|temp)\d*\b.*?):\s*\S*?([\d\.]+)\S*\s*/i) { |
728 |
$k =~ s/\W//g; $k =~ s/Temp$//i; |
729 |
$update{$k} = $v; |
730 |
} elsif (/(no sensors found|kernel driver|sensors-detect|error|warning)/i |
731 |
&& !$opt{q}) { |
732 |
warn "Warning [cpu_temp]: $_"; |
733 |
} |
734 |
} |
735 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
736 |
|
737 |
return %update; |
738 |
} |
739 |
|
740 |
|
741 |
|
742 |
sub apache_logs { |
743 |
return if $opt{s}; |
744 |
my $dir = '/var/log/httpd'; |
745 |
return unless -d $dir; |
746 |
my %update = (); |
747 |
|
748 |
if (-d $dir) { |
749 |
opendir(DH,$dir) || die "Unable to open file handle for directory '$dir': $!\n"; |
750 |
my @files = grep(!/^\./,readdir(DH)); |
751 |
closedir(DH) || die "Unable to close file handle for directory '$dir': $!\n"; |
752 |
for (@files) { |
753 |
next if /\.(\d+|gz|bz2|Z|zip|old|bak|pid|backup)$/i || /[_\.\-]pid$/; |
754 |
my $file = "$dir/$_"; |
755 |
next unless -f $file; |
756 |
s/[\.\-]/_/g; |
757 |
$update{$_} = (stat($file))[7]; |
758 |
} |
759 |
} |
760 |
|
761 |
return %update; |
762 |
} |
763 |
|
764 |
|
765 |
|
766 |
sub apache_status { |
767 |
return if $opt{s}; |
768 |
my @data = (); |
769 |
my %update = (); |
770 |
|
771 |
my $timeout = 5; |
772 |
my $url = 'http://localhost/server-status?auto'; |
773 |
my %keys = (W => 'Write', G => 'GraceClose', D => 'DNS', S => 'Starting', |
774 |
L => 'Logging', R => 'Read', K => 'Keepalive', C => 'Closing', |
775 |
I => 'Idle', '_' => 'Waiting'); |
776 |
|
777 |
|
778 |
eval "use LWP::UserAgent"; |
779 |
unless ($@) { |
780 |
eval { |
781 |
my $ua = LWP::UserAgent->new( |
782 |
agent => "$0 version $VERSION ".'($Id)', |
783 |
timeout => $timeout); |
784 |
$ua->env_proxy; |
785 |
$ua->max_size(1024*250); |
786 |
my $response = $ua->get($url); |
787 |
if ($response->is_success) { |
788 |
@data = split(/\n+|\r+/,$response->content); |
789 |
} elsif (!$opt{q}) { |
790 |
warn "Warning [apache_status]: failed to get $url; ". $response->status_line ."\n"; |
791 |
} |
792 |
}; |
793 |
} |
794 |
if ($@) { |
795 |
@data = basic_http('GET',$url,$timeout); |
796 |
} |
797 |
|
798 |
for (@data) { |
799 |
my ($k,$v) = $_ =~ /^\s*(.+?):\s+(.+?)\s*$/; |
800 |
$k = '' unless defined $k; |
801 |
$v = '' unless defined $v; |
802 |
$k =~ s/\s+//g; #$k = lc($k); |
803 |
next unless $k; |
804 |
if ($k eq 'Scoreboard') { |
805 |
my %x; $x{$_}++ for split(//,$v); |
806 |
for (keys %keys) { |
807 |
$update{"scoreboard.$keys{$_}"} = |
808 |
defined $x{$_} ? $x{$_} : 0; |
809 |
} |
810 |
} else { |
811 |
$update{$k} = $v; |
812 |
} |
813 |
} |
814 |
|
815 |
$update{ReqPerSec} = int($update{TotalAccesses}) |
816 |
if defined $update{TotalAccesses}; |
817 |
$update{BytesPerSec} = int($update{TotalkBytes} * 1024) |
818 |
if defined $update{TotalkBytes}; |
819 |
|
820 |
return %update; |
821 |
} |
822 |
|
823 |
|
824 |
|
825 |
sub _darwin_cpu_utilisation { |
826 |
my $output = qx{/usr/bin/sar 4 1}; |
827 |
my %rv = (); |
828 |
if ($output =~ m/Average:\s+(\d+)\s+(\d+)\s+(\d+)/) { |
829 |
%rv = ( |
830 |
User => $1, |
831 |
System => $2, |
832 |
Idle => $3, |
833 |
IO_Wait => 0, # at the time of writing, sar doesn't provide this metric |
834 |
); |
835 |
} |
836 |
return %rv; |
837 |
} |
838 |
|
839 |
|
840 |
|
841 |
sub cpu_utilisation { |
842 |
my %update = (); |
843 |
if ($opt{s}) { |
844 |
$update{'User'} = _snmp('.1.3.6.1.4.1.2021.11.9.0'); |
845 |
$update{'System'} = _snmp('.1.3.6.1.4.1.2021.11.10.0'); |
846 |
$update{'Idle'} = _snmp('.1.3.6.1.4.1.2021.11.11.0'); |
847 |
#$update{'IO_Wait'} = 100 - $update{'User'} - $update{'System'} - $update{'Idle'}; |
848 |
$update{'IO_Wait'} = 0; |
849 |
|
850 |
# Try querying the Windows thingie instead |
851 |
unless (grep(/^[0-9\.]{1,3}$/, values(%update)) == 4) { |
852 |
# hrProcessorLoad |
853 |
# .1.3.6.1.2.1.25.3.3.1.2.1 - CPU 1 |
854 |
# .1.3.6.1.2.1.25.3.3.1.2.2 - CPU 2 ... |
855 |
my $total; my $cpu; |
856 |
for ($cpu = 1; $cpu <= 16; $cpu++) { |
857 |
my $load = _snmp(".1.3.6.1.2.1.25.3.3.1.2.$cpu"); |
858 |
if (defined($load) && !ref($load) && $load =~ /^[0-9\.]{1,3}$/) { |
859 |
$total += $load; |
860 |
} else { |
861 |
last; |
862 |
} |
863 |
} |
864 |
return unless $total && $cpu-1; |
865 |
%update = ('User' => int($total / $cpu-1), 'System' => 0, 'Idle' => 0, 'IO_Wait' => 0); |
866 |
} |
867 |
return %update; |
868 |
} |
869 |
|
870 |
if ($^O eq 'darwin') { |
871 |
return _darwin_cpu_utilisation(); |
872 |
} |
873 |
|
874 |
my $cmd = '/usr/bin/vmstat'; |
875 |
return unless -f $cmd; |
876 |
%update = _parse_vmstat("$cmd 1 2"); |
877 |
my %labels = (wa => 'IO_Wait', id => 'Idle', sy => 'System', us => 'User'); |
878 |
|
879 |
$update{$_} ||= 0 for keys %labels; |
880 |
return ( map {( $labels{$_} || $_ => $update{$_} )} keys %labels ); |
881 |
} |
882 |
|
883 |
|
884 |
|
885 |
sub hw_irq_interrupts { |
886 |
return if $opt{s}; |
887 |
|
888 |
my @update; |
889 |
if (open(FH,'<','/proc/interrupts')) { |
890 |
local $_ = <FH>; |
891 |
return unless /^\s+(CPU[0-9]+.*)/; |
892 |
my @cpus = split(/\s+/,$1); |
893 |
$_ = lc($_) for @cpus; |
894 |
|
895 |
my %data; |
896 |
while (local $_ = <FH>) { |
897 |
if (/^\s*([0-9]{1,2}):\s+([\s0-9]+)\s+(\S+)\s+(.+?)\s*$/) { |
898 |
my ($irq,$ints,$path,$src) = ($1,$2,$3,$4); |
899 |
my @ints = split(/\s+/,$ints); |
900 |
for (my $i = 0; $i <= @cpus; $i++) { |
901 |
my $cpu = $cpus[$i]; |
902 |
my $int = $ints[$i]; |
903 |
next unless defined $cpu && defined $int; |
904 |
$data{$cpu}->{"$cpu.irq$irq"} = $int; |
905 |
} |
906 |
} |
907 |
} |
908 |
|
909 |
for my $cpu (keys %data) { |
910 |
if (grep(/[1-9]/,values %{$data{$cpu}})) { |
911 |
push @update, %{$data{$cpu}}; |
912 |
} |
913 |
} |
914 |
|
915 |
close(FH) || warn "Unable to close file handle FH for file '/proc/interrupts': $!"; |
916 |
} |
917 |
|
918 |
return @update; |
919 |
} |
920 |
|
921 |
|
922 |
|
923 |
sub cpu_interrupts { |
924 |
return if $opt{s}; |
925 |
my $cmd = '/usr/bin/vmstat'; |
926 |
return unless -f $cmd; |
927 |
|
928 |
my %update = _parse_vmstat("$cmd 1 2"); |
929 |
my %labels = (in => 'Interrupts'); |
930 |
return unless defined $update{in}; |
931 |
|
932 |
$update{$_} ||= 0 for keys %labels; |
933 |
return ( map {( $labels{$_} || $_ => $update{$_} )} keys %labels ); |
934 |
} |
935 |
|
936 |
|
937 |
|
938 |
sub mem_swap_activity { |
939 |
return if $opt{s}; |
940 |
my $cmd = '/usr/bin/vmstat'; |
941 |
return unless -f $cmd; |
942 |
|
943 |
my %update = _parse_vmstat("$cmd 1 2"); |
944 |
my %labels = (si => 'Swap_In', so => 'Swap_Out'); |
945 |
return unless defined $update{si} && defined $update{so}; |
946 |
|
947 |
$update{$_} ||= 0 for keys %labels; |
948 |
return ( map {( $labels{$_} || $_ => $update{$_} )} keys %labels ); |
949 |
} |
950 |
|
951 |
|
952 |
|
953 |
sub _parse_vmstat { |
954 |
my $cmd = shift; |
955 |
my %update; |
956 |
my @keys; |
957 |
|
958 |
if (exists $update_cache{vmstat}) { |
959 |
%update = %{$update_cache{vmstat}}; |
960 |
} else { |
961 |
open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; |
962 |
while (local $_ = <PH>) { |
963 |
s/^\s+|\s+$//g; |
964 |
if (/\s+\d+\s+\d+\s+\d+\s+/ && @keys) { |
965 |
@update{@keys} = split(/\s+/,$_); |
966 |
} else { @keys = split(/\s+/,$_); } |
967 |
} |
968 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
969 |
$update_cache{vmstat} = \%update; |
970 |
} |
971 |
|
972 |
return %update; |
973 |
} |
974 |
|
975 |
|
976 |
|
977 |
sub _parse_ipmitool_sensor { |
978 |
my $cmd = shift; |
979 |
my %update; |
980 |
my @keys; |
981 |
|
982 |
if (exists $update_cache{ipmitool_sensor}) { |
983 |
%update = %{$update_cache{ipmitool_sensor}}; |
984 |
} else { |
985 |
if ((-e '/dev/ipmi0' || -e '/dev/ipmi/0') && open(PH,'-|',$cmd)) { |
986 |
while (local $_ = <PH>) { |
987 |
chomp; s/(^\s+|\s+$)//g; |
988 |
my ($key,@ary) = split(/\s*\|\s*/,$_); |
989 |
$key =~ s/[^a-zA-Z0-9_]//g; |
990 |
$update{$key} = \@ary; |
991 |
} |
992 |
close(PH); |
993 |
$update_cache{ipmitool_sensor} = \%update; |
994 |
} |
995 |
} |
996 |
|
997 |
return %update; |
998 |
} |
999 |
|
1000 |
|
1001 |
|
1002 |
sub misc_ipmi_temp { |
1003 |
return if $opt{s}; |
1004 |
my $cmd = select_cmd(qw(/usr/bin/ipmitool)); |
1005 |
return unless -f $cmd; |
1006 |
|
1007 |
my %update = (); |
1008 |
my %data = _parse_ipmitool_sensor("$cmd sensor"); |
1009 |
for (grep(/temp/i,keys %data)) { |
1010 |
$update{$_} = $data{$_}->[0] |
1011 |
if $data{$_}->[0] =~ /^[0-9\.]+$/; |
1012 |
} |
1013 |
return unless keys %update; |
1014 |
|
1015 |
return %update;; |
1016 |
} |
1017 |
|
1018 |
|
1019 |
|
1020 |
sub hdd_io { |
1021 |
return if $opt{s}; |
1022 |
my $cmd = select_cmd(qw(/usr/bin/iostat /usr/sbin/iostat)); |
1023 |
return unless -f $cmd; |
1024 |
return unless $^O eq 'linux'; |
1025 |
$cmd .= ' -k'; |
1026 |
|
1027 |
my %update = (); |
1028 |
|
1029 |
open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; |
1030 |
while (local $_ = <PH>) { |
1031 |
if (my ($dev,$r,$w) = $_ =~ /^([\/\w\d]+)\s+\S+\s+\S+\s+\S+\s+(\d+)\s+(\d+)$/) { |
1032 |
$dev =~ s/[^\w\d]+/_/g; |
1033 |
$update{"$dev.Read"} = $r*1024; |
1034 |
$update{"$dev.Write"} = $w*1024; |
1035 |
} |
1036 |
} |
1037 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
1038 |
|
1039 |
return %update; |
1040 |
} |
1041 |
|
1042 |
|
1043 |
|
1044 |
sub mem_usage { |
1045 |
return if $opt{s}; |
1046 |
my %update = (); |
1047 |
my $cmd = select_cmd(qw(/usr/bin/free /bin/free)); |
1048 |
my @keys = (); |
1049 |
|
1050 |
if ($^O eq 'linux' && -f $cmd && -x $cmd) { |
1051 |
$cmd .= ' -b'; |
1052 |
open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; |
1053 |
while (local $_ = <PH>) { |
1054 |
if (@keys && /^Mem:\s*(\d+.+)\s*$/i) { |
1055 |
my @values = split(/\s+/,$1); |
1056 |
for (my $i = 0; $i < @values; $i++) { |
1057 |
$update{ucfirst($keys[$i])} = $values[$i]; |
1058 |
} |
1059 |
$update{Used} = $update{Used} - $update{Buffers} - $update{Cached}; |
1060 |
|
1061 |
} elsif (@keys && /^Swap:\s*(\d+.+)\s*$/i) { |
1062 |
my @values = split(/\s+/,$1); |
1063 |
for (my $i = 0; $i < @values; $i++) { |
1064 |
$update{"swap.".ucfirst($keys[$i])} = $values[$i]; |
1065 |
} |
1066 |
|
1067 |
} elsif (!@keys && /^\s*([\w\s]+)\s*$/) { |
1068 |
@keys = split(/\s+/,$1); |
1069 |
} |
1070 |
} |
1071 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
1072 |
|
1073 |
} elsif ($^O eq 'darwin' && -x '/usr/sbin/sysctl') { |
1074 |
my $swap = qx{/usr/sbin/sysctl vm.swapusage}; |
1075 |
if ($swap =~ m/total = (.+)M used = (.+)M free = (.+)M/) { |
1076 |
$update{"swap.Total"} = $1*1024*1024; |
1077 |
$update{"swap.Used"} = $2*1024*1024; |
1078 |
$update{"swap.Free"} = $3*1024*1024; |
1079 |
} |
1080 |
|
1081 |
} else { |
1082 |
eval "use Sys::MemInfo qw(totalmem freemem)"; |
1083 |
die "Please install Sys::MemInfo so that I can get memory information.\n" if $@; |
1084 |
@update{qw(Total Free)} = (totalmem(),freemem()); |
1085 |
} |
1086 |
|
1087 |
return %update; |
1088 |
} |
1089 |
|
1090 |
|
1091 |
|
1092 |
sub hdd_temp { |
1093 |
return if $opt{s}; |
1094 |
my $cmd = select_cmd(qw(/usr/sbin/hddtemp /usr/bin/hddtemp)); |
1095 |
return unless -f $cmd; |
1096 |
|
1097 |
my @devs = (); |
1098 |
for my $dev (glob('/dev/hd?'),glob('/dev/sd?')) { |
1099 |
if ($dev =~ /^(\/dev\/\w{3})$/i) { |
1100 |
push @devs, $1; |
1101 |
} |
1102 |
} |
1103 |
|
1104 |
$cmd .= " -q @devs 2>&1"; |
1105 |
my %update = (); |
1106 |
return %update unless @devs; |
1107 |
|
1108 |
open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; |
1109 |
while (local $_ = <PH>) { |
1110 |
if (my ($dev,$temp) = $_ =~ m,^/dev/([a-z]+):\s+.+?:\s+(\d+)..?C,) { |
1111 |
$update{$dev} = $temp; |
1112 |
} elsif (!/^\s*$/ && !$opt{q}) { |
1113 |
warn "Warning [hdd_temp]: $_"; |
1114 |
} |
1115 |
} |
1116 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
1117 |
|
1118 |
return %update; |
1119 |
} |
1120 |
|
1121 |
|
1122 |
|
1123 |
sub hdd_capacity { |
1124 |
if ($opt{s}) { |
1125 |
return; |
1126 |
|
1127 |
my $snmp = _snmp('.1.3.6.1.4.1.2021.9.1'); # dskTable |
1128 |
return unless defined($snmp) && ref($snmp) eq 'HASH'; |
1129 |
|
1130 |
my %disks; |
1131 |
#.1.3.6.1.4.1.2021.9.1.1.1 = INTEGER: 1 |
1132 |
#.1.3.6.1.4.1.2021.9.1.2.1 = STRING: / |
1133 |
#.1.3.6.1.4.1.2021.9.1.3.1 = STRING: /dev/sda1 |
1134 |
#.1.3.6.1.4.1.2021.9.1.4.1 = INTEGER: 100000 |
1135 |
#.1.3.6.1.4.1.2021.9.1.5.1 = INTEGER: -1 |
1136 |
#.1.3.6.1.4.1.2021.9.1.6.1 = INTEGER: 7850996 |
1137 |
#.1.3.6.1.4.1.2021.9.1.7.1 = INTEGER: 3153808 |
1138 |
#.1.3.6.1.4.1.2021.9.1.8.1 = INTEGER: 4298376 |
1139 |
#.1.3.6.1.4.1.2021.9.1.9.1 = INTEGER: 58 |
1140 |
#.1.3.6.1.4.1.2021.9.1.10.1 = INTEGER: 16 |
1141 |
#.1.3.6.1.4.1.2021.9.1.100.1 = INTEGER: 0 |
1142 |
#.1.3.6.1.4.1.2021.9.1.101.1 = STRING: |
1143 |
|
1144 |
#'UCD-SNMP-MIB::dskMinimum.1' => '100000', |
1145 |
#'UCD-SNMP-MIB::dskErrorMsg.1' => '', |
1146 |
#'UCD-SNMP-MIB::dskIndex.1' => '1', |
1147 |
#'UCD-SNMP-MIB::dskPath.1' => '/', |
1148 |
#'UCD-SNMP-MIB::dskPercentNode.1' => '16', |
1149 |
#'UCD-SNMP-MIB::dskErrorFlag.1' => '0', |
1150 |
#'UCD-SNMP-MIB::dskAvail.1' => '3153784', |
1151 |
#'#UCD-SNMP-MIB::dskPercent.1' => '58', |
1152 |
#'UCD-SNMP-MIB::dskMinPercent.1' => '-1', |
1153 |
#'UCD-SNMP-MIB::dskDevice.1' => '/dev/sda1', |
1154 |
#'UCD-SNMP-MIB::dskUsed.1' => '4298400', |
1155 |
#'UCD-SNMP-MIB::dskTotal.1' => '7850996' |
1156 |
|
1157 |
#use Data::Dumper; |
1158 |
#warn Dumper($snmp); |
1159 |
return; |
1160 |
} |
1161 |
|
1162 |
my $cmd = select_cmd(qw(/bin/df /usr/bin/df)); |
1163 |
return unless -f $cmd; |
1164 |
|
1165 |
if ($^O eq 'linux') { $cmd .= ' -P -x iso9660 -x nfs -x smbfs'; } |
1166 |
elsif ($^O eq 'solaris') { $cmd .= ' -lk -F ufs'; } |
1167 |
elsif ($^O eq 'darwin') { $cmd .= ' -P -T hfs,ufs'; } |
1168 |
else { $cmd .= ' -P'; } |
1169 |
|
1170 |
my %update = (); |
1171 |
my %variants = ( |
1172 |
'' => '', |
1173 |
'inodes.' => ' -i ', |
1174 |
); |
1175 |
|
1176 |
for my $variant (keys %variants) { |
1177 |
my $variant_cmd = "$cmd $variants{$variant}"; |
1178 |
my @data = split(/\n/, `$variant_cmd`); |
1179 |
shift @data; |
1180 |
|
1181 |
my @cols = qw(fs blocks used avail capacity mount unknown); |
1182 |
for (@data) { |
1183 |
my %data = (); |
1184 |
@data{@cols} = split(/\s+/,$_); |
1185 |
if ($^O eq 'darwin' || defined $data{unknown}) { |
1186 |
@data{@cols} = $_ =~ /^(.+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%?\s+(.+)\s*$/; |
1187 |
} |
1188 |
|
1189 |
next if ($data{fs} eq 'none' || $data{mount} =~ m#^/dev/#); |
1190 |
$data{capacity} =~ s/\%//; |
1191 |
(my $ds = $data{mount}) =~ s/[^a-z0-9]/_/ig; $ds =~ s/__+/_/g; |
1192 |
|
1193 |
# McAfee SCM 4.2 bodge-o-rama fix work around |
1194 |
next if $ds =~ /^_var_jails_d_spam_/; |
1195 |
|
1196 |
$update{"${variant}$ds"} = $data{capacity}; |
1197 |
} |
1198 |
} |
1199 |
|
1200 |
return %update; |
1201 |
} |
1202 |
|
1203 |
|
1204 |
|
1205 |
sub misc_entropy { |
1206 |
return if $opt{s}; |
1207 |
my $file = '/proc/sys/kernel/random/entropy_avail'; |
1208 |
return unless -f $file; |
1209 |
my %update = (); |
1210 |
|
1211 |
open(FH,'<',$file) || die "Unable to open '$file': $!\n"; |
1212 |
chomp($update{entropy_avail} = <FH>); |
1213 |
close(FH) || die "Unable to close '$file': $!\n"; |
1214 |
|
1215 |
return %update; |
1216 |
} |
1217 |
|
1218 |
|
1219 |
|
1220 |
sub net_traffic { |
1221 |
return if $opt{s}; |
1222 |
return unless -f '/proc/net/dev'; |
1223 |
my @keys = (); |
1224 |
my %update = (); |
1225 |
|
1226 |
open(FH,'<','/proc/net/dev') || die "Unable to open '/proc/net/dev': $!\n"; |
1227 |
while (local $_ = <FH>) { |
1228 |
s/^\s+|\s+$//g; |
1229 |
if ((my ($dev,$data) = $_ =~ /^(.+?):\s*(\d+.+)\s*$/) && @keys) { |
1230 |
my @values = split(/\s+/,$data); |
1231 |
for (my $i = 0; $i < @keys; $i++) { |
1232 |
if ($keys[$i] eq 'TXbytes') { |
1233 |
$update{"$dev.Transmit"} = $values[$i]; |
1234 |
} elsif ($keys[$i] eq 'RXbytes') { |
1235 |
$update{"$dev.Receive"} = $values[$i]; |
1236 |
} |
1237 |
#$update{"$dev.$keys[$i]"} = $values[$i]; |
1238 |
} |
1239 |
} else { |
1240 |
my ($rx,$tx) = (split(/\s*\|\s*/,$_))[1,2]; |
1241 |
@keys = (map({"RX$_"} split(/\s+/,$rx)), map{"TX$_"} split(/\s+/,$tx)); |
1242 |
} |
1243 |
} |
1244 |
close(FH) || die "Unable to close '/proc/net/dev': $!\n"; |
1245 |
|
1246 |
return %update; |
1247 |
} |
1248 |
|
1249 |
|
1250 |
|
1251 |
sub proc_state { |
1252 |
return if $opt{s}; |
1253 |
my $cmd = select_cmd(qw(/bin/ps /usr/bin/ps)); |
1254 |
my %update = (); |
1255 |
my %keys = (); |
1256 |
|
1257 |
if (-f $cmd && -x $cmd) { |
1258 |
if ($^O eq 'freebsd' || $^O eq 'darwin') { |
1259 |
$cmd .= ' axo pid,state'; |
1260 |
# %keys = (D => 'IO_Wait', R => 'Run', S => 'Sleep', T => 'Stopped', |
1261 |
# I => 'Idle', L => 'Lock_Wait', Z => 'Zombie', W => 'Idle_Thread'); |
1262 |
%keys = (D => 'IO_Wait', R => 'Run', S => 'Sleep', T => 'Stopped', |
1263 |
W => 'Paging', Z => 'Zombie', I => 'Sleep'); |
1264 |
} else {#} elsif ($^O =~ /^(linux|solaris)$/) |
1265 |
$cmd .= ' -eo pid,s'; |
1266 |
%keys = (D => 'IO_Wait', R => 'Run', S => 'Sleep', T => 'Stopped', |
1267 |
W => 'Paging', X => 'Dead', Z => 'Zombie'); |
1268 |
} |
1269 |
|
1270 |
my $known_keys = join('',keys %keys); |
1271 |
open(PH,'-|',$cmd) || die "Unable to open file handle PH for command '$cmd': $!\n"; |
1272 |
while (local $_ = <PH>) { |
1273 |
if (my ($pid,$state) = $_ =~ /^\s*(\d+)\s+(\S+)\s*$/) { |
1274 |
$state =~ s/[^$known_keys]//g; |
1275 |
$update{$keys{$state}||$state}++ if $state; |
1276 |
} |
1277 |
} |
1278 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
1279 |
$update{$_} ||= 0 for values %keys; |
1280 |
|
1281 |
} else { |
1282 |
eval "use Proc::ProcessTable"; |
1283 |
die "Please install /bin/ps or Proc::ProcessTable\n" if $@; |
1284 |
my $p = new Proc::ProcessTable("cache_ttys" => 1 ); |
1285 |
for (@{$p->table}) { |
1286 |
$update{$_->{state}}++; |
1287 |
} |
1288 |
} |
1289 |
|
1290 |
return %update; |
1291 |
} |
1292 |
|
1293 |
|
1294 |
|
1295 |
sub cpu_loadavg { |
1296 |
my %update = (); |
1297 |
if ($opt{s}) { |
1298 |
$update{'1min'} = _snmp('.1.3.6.1.4.1.2021.10.1.3.1'); |
1299 |
$update{'5min'} = _snmp('.1.3.6.1.4.1.2021.10.1.3.2'); |
1300 |
$update{'15min'} = _snmp('.1.3.6.1.4.1.2021.10.1.3.3'); |
1301 |
return %update; |
1302 |
} |
1303 |
|
1304 |
my @data = (); |
1305 |
if (-f '/proc/loadavg') { |
1306 |
open(FH,'<','/proc/loadavg') || die "Unable to open file handle FH for file '/proc/loadavg': $!\n"; |
1307 |
my $str = <FH>; |
1308 |
close(FH) || die "Unable to close file handle FH for file '/proc/loadavg': $!\n"; |
1309 |
@data = split(/\s+/,$str); |
1310 |
|
1311 |
} else { |
1312 |
my $cmd = -f '/usr/bin/uptime' ? '/usr/bin/uptime' : '/bin/uptime'; |
1313 |
@data = `$cmd` =~ /[\s:]+([\d\.]+)[,\s]+([\d\.]+)[,\s]+([\d\.]+)\s*$/; |
1314 |
} |
1315 |
|
1316 |
%update = ( |
1317 |
"1min" => $data[0], |
1318 |
"5min" => $data[1], |
1319 |
"15min" => $data[2], |
1320 |
); |
1321 |
|
1322 |
return %update; |
1323 |
} |
1324 |
|
1325 |
|
1326 |
|
1327 |
sub _parse_netstat { |
1328 |
my $cmd = shift; |
1329 |
my $update; |
1330 |
my @keys = qw(local_ip local_port remote_ip remote_port); |
1331 |
|
1332 |
if (exists $update_cache{netstat}) { |
1333 |
$update = $update_cache{netstat}; |
1334 |
} else { |
1335 |
open(PH,'-|',$cmd) || die "Unable to open file handle for command '$cmd': $!\n"; |
1336 |
while (local $_ = <PH>) { |
1337 |
my %line; |
1338 |
if (@line{qw(proto data state)} = $_ =~ /^(tcp[46]?|udp[46]?|raw)\s+(.+)\s+([A-Z_]+)\s*$/) { |
1339 |
@line{@keys} = $line{data} =~ /(?:^|[\s\b])([:abcdef0-9\.]+):(\d{1,5})(?:[\s\b]|$)/g; |
1340 |
push @{$update}, \%line; |
1341 |
} |
1342 |
} |
1343 |
close(PH) || die "Unable to close file handle PH for command '$cmd': $!\n"; |
1344 |
$update_cache{netstat} = $update; |
1345 |
} |
1346 |
|
1347 |
return $update; |
1348 |
} |
1349 |
|
1350 |
|
1351 |
|
1352 |
sub net_connections_ports { |
1353 |
return if $opt{s}; |
1354 |
my $cmd = select_cmd(qw(/bin/netstat /usr/bin/netstat /usr/sbin/netstat)); |
1355 |
return unless -f $cmd; |
1356 |
$cmd .= ' -na 2>&1'; |
1357 |
|
1358 |
my %update = (); |
1359 |
my %listening_ports; |
1360 |
for (@{_parse_netstat($cmd)}) { |
1361 |
if ($_->{state} =~ /listen/i && defined $_->{local_port}) { |
1362 |
$listening_ports{"$_->{proto}:$_->{local_port}"} = 1; |
1363 |
$update{"$_->{proto}_$_->{local_port}"} = 0; |
1364 |
} |
1365 |
} |
1366 |
for (@{_parse_netstat($cmd)}) { |
1367 |
next if !defined $_->{state} || !defined $_->{remote_port}; |
1368 |
$update{"$_->{proto}_$_->{remote_port}"}++ if exists $listening_ports{"$_->{proto}:$_->{remote_port}"}; |
1369 |
} |
1370 |
|
1371 |
return %update; |
1372 |
} |
1373 |
|
1374 |
|
1375 |
|
1376 |
sub net_connections { |
1377 |
return if $opt{s}; |
1378 |
my $cmd = select_cmd(qw(/bin/netstat /usr/bin/netstat /usr/sbin/netstat)); |
1379 |
return unless -f $cmd; |
1380 |
$cmd .= ' -na 2>&1'; |
1381 |
|
1382 |
my %update = (); |
1383 |
for (@{_parse_netstat($cmd)}) { |
1384 |
$update{$_->{state}}++ if defined $_->{state}; |
1385 |
} |
1386 |
|
1387 |
return %update; |
1388 |
} |
1389 |
|
1390 |
|
1391 |
|
1392 |
sub proc_filehandles { |
1393 |
return if $opt{s}; |
1394 |
return unless -f '/proc/sys/fs/file-nr'; |
1395 |
my %update = (); |
1396 |
|
1397 |
open(FH,'<','/proc/sys/fs/file-nr') || die "Unable to open file handle FH for file '/proc/sys/fs/file-nr': $!\n"; |
1398 |
my $str = <FH>; |
1399 |
close(FH) || die "Unable to close file handle FH for file '/proc/sys/fs/file-nr': $!\n"; |
1400 |
@update{qw(Allocated Free Maximum)} = split(/\s+/,$str); |
1401 |
$update{Used} = $update{Allocated} - $update{Free}; |
1402 |
|
1403 |
return %update; |
1404 |
} |
1405 |
|
1406 |
|
1407 |
|
1408 |
|
1409 |
|
1410 |
|