checkbandwidth 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. #!/usr/bin/perl
  2. =pod
  3. =head1 NAME
  4. checkbandwidth - Alert on bandwidth high/low levels for a host/interface
  5. =head1 DESCRIPTION
  6. The checkbandwidth script uses the perl SNMP module to collect, store
  7. and analyze date from the IF-MIB::ifXTable for the ifHCInOctets and
  8. ifHCOutOctets counters. It can be configured with high/low levels and
  9. issue warnings, either through email or via output text and exit
  10. codes. Specifically, the exit codes (1 = warning, 2 = error) and text
  11. output are designed to be used with Nagios. It calculates bandwidth
  12. by comparing the values from the last run against the values from the
  13. current run and the amonut of time that has passeed between the two
  14. runs [i.e. bandwidth = (this_run - last_run)/time]. Because we need
  15. data from two runs, the first time it is run after configuration will
  16. never detect an error until the second run.
  17. Data is stored in ~/.snmp/checkbandwidth.json, which can be
  18. removed/unlinked to reset the command's data store if needed. Or the
  19. -r switch can be used to just reset the data without wiping the
  20. configuration.
  21. =head1 SYNOPSIS
  22. 1) Setup your snmp.conf file with authentication parameters for your system:
  23. # snmpconf -g basic_setup
  24. 2) Add the interface on the host you want monitor, along with an optional
  25. "threshold" parameters that you want to be warned about:
  26. # checkbandwidth --add-interface eth0 \
  27. --max-in-bandwidth 300000=admin@example.com \
  28. foo.example.com
  29. 3A) Then run the tool against the host on a regular basis (eg, every 5
  30. minutes in cron):
  31. # checkbandwidth foo.example.com
  32. foo.example.com wlan1 49200.1426 in 5538.7522 out (B/s) IO
  33. The numbers reported are the measured bandwidth during this run. The
  34. "IO" letters are flags letting you know that the (I)nput or (O)utput
  35. values are out of expected range.
  36. 3B) Run the tool inside of nagios:
  37. define command {
  38. command_name checkbandwidth
  39. # specific path to vendor_perl is to fix vir's /usr/local installation
  40. command_line /usr/bin/perl /usr/local/bin/checkbandwidth -L /var/spool/nagios/.snmp/checkbandwidth.json.lock -N -s $ARG1$
  41. }
  42. =head1 OPTIONS
  43. =head2 Configuration and setup:
  44. =over
  45. =item -i STRING
  46. =item --add-interface=STRING
  47. Add this interface to the list of things to report data for.
  48. =item -I INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
  49. =item --max-in-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
  50. =item -O INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
  51. =item --max-out-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
  52. These setup options specify an maximum input and/or output bandwidth that will:
  53. 1) When running as a nagios script (using the --nagios flag), the
  54. script will display an appropriate message and exit with a return
  55. code based on whether the string passed to the flag is 'nagioswarn'
  56. or 'nagioscritical'.
  57. 2) When run manually or via cron, then the checkbandwidth will send an
  58. email address to ADDRESS about the error.
  59. The INT field should be the bandwidth above which the error or warning
  60. should occur. If the :COUNT field is included, no messages will be
  61. sent/generated until at least the COUNT'th time is seen (default: 1).
  62. The frequency of the count is dependent on often often the script gets
  63. called or the loop frequency occurs (--loop).
  64. Note that the --add-interface (-i) flag is required for this flag to be useful.
  65. See the EXAMPLES section below for more usage.
  66. =item -J INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
  67. =item --min-in-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
  68. =item -Q INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
  69. =item --min-out-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )
  70. These two are similar to --max-in-bandwidth and --max-out-bandwidth,
  71. but set the low-water mark thresholds for an interface.
  72. =back
  73. =head2 Operation:
  74. =over
  75. =item -c STRING
  76. =item --cache-file=STRING
  77. Location to store persistent data file, which is a JSON encoded structure.
  78. This defaults to ~/.snmp/checkbandwidth.json.
  79. =item -r
  80. =item --reset
  81. Ignore all past data and restart the statists collection from scratch
  82. for all hosts/interfaces. This wipes the collected data without
  83. wiping the configuration min/max warning/critical levels.
  84. =item -l INTEGER
  85. =item --loop=INTEGER
  86. Run as a daemon and loop forever, displaying bandwidth performing
  87. checks every INTEGER seconds and displaying an output summary line.
  88. =back
  89. =head2 Nagios mode:
  90. The following options only make sense when acting as an interface with
  91. nagios. An example nagios command definition is as follows:
  92. define command {
  93. command_name checkbandwidth
  94. command_line /usr/local/bin/checkbandwidth -c /var/spool/nagios/.snmp/checkbandwidth.nagios.json -L /var/spool/nagios/.snmp/checkbandwidth.nagios.json.lock -N $ARG1
  95. }
  96. Note that if you need to specify perl module paths, you'll need to call it as:
  97. /usr/bin/perl -I /usr/lib64/perl5/vendor_perl/ /usr/local/bin/checkbandwidth
  98. You can then use a service definition such as this one:
  99. define service {
  100. service_description bandwidth
  101. host_name localhost
  102. check_command checkbandwidth!localhost
  103. use ...
  104. }
  105. Recommendation: Use a different configuration file for nagios hosts
  106. than other command line usage.
  107. Recommendation: Use a lock file (-L) to ensure that data from two
  108. parallel calls in nagios won't be written to at once, causing the file
  109. to be corrupted.
  110. Recommendation: If you're monitoring a large number of hosts, use a
  111. sepearate configuration file (and lock file) for each host by passing
  112. an argument like -c /var/spool/nagios/.snmp/checkbandwidth.nagios.$ARG1.json
  113. =over
  114. =item -N
  115. =item --nagios
  116. Operate in nagios mode, which will display appropriate nagios display
  117. error codes and exit with an exit code that nagios will process.
  118. =item -s
  119. =item --nagios-summary
  120. Place a basic summary line of bandwidth in the output even when no
  121. errors are currently present. Otherwise, the output will simply be
  122. "All interfaces ok"
  123. =item -L LOCKFILE
  124. =item --lock-file LOCKFILE
  125. Use this file as a lockfile to ensure multiple checkbandwidth's
  126. running won't try to write to the file at the same time.
  127. =back
  128. =head2 Sending mail on errors from cron
  129. When not in nagios mode, checkbandwidth will send mail if an error
  130. condition is reached. The following options specify the SMTP server
  131. parameters used to send mail.
  132. =over
  133. =item -S STRING
  134. =item --smtp-server=STRING
  135. The hostname or IP address of the SMTP serve to use.
  136. =item -P INTEGER
  137. =item --port=INTEGER
  138. The port number of the SMTP server to use.
  139. =item -F STRING
  140. =item --from=STRING
  141. The email address to put in the From: line.
  142. =item -q
  143. =item --quiet
  144. Don't output data to the terminal (i.e., email only).
  145. =back
  146. =head2 Debugging:
  147. =over
  148. =item -d
  149. =item --dump
  150. Dump the raw JSON data when finished.
  151. =item -v
  152. =item --verbose
  153. Log (debugging) messages to stderr about what is being done
  154. =item -n
  155. =item --noop
  156. Don't store the collected information from the SNMP agent to the
  157. persistent data storage file. I.E., only compare the results to last time.
  158. [This is highly useful when you just want to check the server from the
  159. command line using a data store configuration cache that is being used
  160. by another daemon (--loop mode) or by nagios], without affecting the
  161. timing between runs]
  162. =back
  163. =head1 EXAMPLES
  164. =head2 Example Setup
  165. With these goals:
  166. * monitor eth0
  167. * send a message to root@localhost when the input bandwidth goes above 50000 B/s
  168. * send a message to ops@localhost when the input bandwidth goes above 100000 B/s
  169. This can be configured as following:
  170. # checkbandwidth -i eth0 -I 50000=root@localhost localhost
  171. # checkbandwidth -i eth0 -I 100000=ops@localhost localhost
  172. If we want to add new configuration in the future, say for the output
  173. interface at 50000 but with a minimum number of 3 "times seen", we can do so:
  174. # checkbandwidth -i eth0 -O 50000:3=root@localhost localhost
  175. Testing it on a regalur basis is as simple as running:
  176. # checkbandwidth
  177. localhost eth0 3907.2973 in 1638.1081 out (B/s)
  178. =head2 Nagios usage example
  179. * monitor eth0
  180. * Alert at nagios 'warning' when the input bandwidth goes above 50000 B/s
  181. * Alert at nagios 'critical' when the input bandwidth goes above 100000 B/s
  182. # checkbandwidth -i eth0 -I 50000=nagioswarning -O 50000=nagioswarning localhost
  183. # checkbandwidth -i eth0 -I 50000=nagioscritical -O 50000=nagioscritical localhost
  184. (and you may or may not want to add -s to always include summary information)
  185. =head1 BUGS / TODO
  186. * There is no way to automatically remove a configured option. IE,
  187. once an delivery address is designated, there is no way to remove it
  188. without manually editing the json file.
  189. =head1 AUTHOR
  190. Wes Hardaker <hardaker@users.sourceforge.net>
  191. USC/ISI
  192. =head1 COPYRIGHT and LICENSING
  193. See the Net-SNMP COPYING file for licensing information for this script.
  194. =cut
  195. use JSON;
  196. use Data::Dumper;
  197. use Mail::Sender;
  198. use SNMP;
  199. use Fcntl ':flock';
  200. use strict;
  201. my %opts = (
  202. c => "$ENV{HOME}/.snmp/checkbandwidth.json",
  203. S => 'localhost',
  204. F => 'root',
  205. P => 25,
  206. );
  207. my %storage;
  208. my ($NAGIOS_NORMAL, $NAGIOS_WARNING, $NAGIOS_CRITICAL) = (0,1,2);
  209. my $nagiosexit = $NAGIOS_NORMAL;
  210. my $nagiosstr = "";
  211. LocalGetOptions(\%opts,
  212. ["GUI:separator", "Configuration and setup:"],
  213. ["i|add-interface=s", "Add this interface to the list of things to monitor"],
  214. ["I|max-in-bandwidth=s", "Alert to ADDRESS on bandwidth > INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
  215. ["O|max-out-bandwidth=s", "Alert to ADDRESS on bandwidth > INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
  216. ["J|min-in-bandwidth=s", "Alert to ADDRESS on bandwidth < INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
  217. ["Q|min-out-bandwidth=s", "Alert to ADDRESS on bandwidth < INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
  218. ["GUI:separator", "Operation:"],
  219. ["c|cache-file=s", "Location to store persistent data"],
  220. ["r|reset", "Ignore all past data and start from scratch for all hosts/interfaces"],
  221. ["l|loop=i", "Loop forever, displaying bandwidth every INTEGER seconds"],
  222. ["GUI:separator", "Nagios mode:"],
  223. ["N|nagios", "Operate in nagios mode, displaying errors/exit code for a given host"],
  224. ["s|nagios-summary", "Always include summary information in the nagios output"],
  225. ["L|lock-file=s", "Use a lockfile to ensure no simultaneous runs"],
  226. ["GUI:separator", "Sending mail:"],
  227. ["S|smtp-server=s", "SMTP server to use"],
  228. ["P|port=i", "SMTP port to use"],
  229. ["F|from=s", "From address to use"],
  230. ["GUI:separator", "Debugging:"],
  231. ["q|quiet", "Don't output data to the terminal (i.e., email only)"],
  232. ["d|dump", "Dump the raw JSON data when finished"],
  233. ["v|verbose", "Log (debugging) messages to stderr about what is being done"],
  234. ["n|noop", "Don\'t store persistent data ; compare only to last time"],
  235. ) || die "Illegal usage; see -h for help";
  236. my @hosts = @ARGV; # remaining arguments should be hosts
  237. my $cache = read_cache();
  238. if ($#hosts == -1) {
  239. @hosts = keys(%{$cache->{'hosts'}});
  240. Verbose("loading hosts from cache: ", join(", ", @hosts));
  241. }
  242. my $lockfileh;
  243. if ($opts{'d'}) {
  244. print to_json($cache, { ascii => 1, pretty => 1}),"\n";
  245. } elsif ($opts{'i'}) {
  246. add_interface($cache, $opts{'i'}, $opts{'I'}, $opts{'O'}, $opts{'J'}, $opts{'Q'}, \@hosts);
  247. } else {
  248. # just process per norm
  249. do {
  250. if ($opts{'L'}) {
  251. open($lockfileh, ">>$opts{L}");
  252. my $have_lock = flock($lockfileh, LOCK_EX | LOCK_NB);
  253. if (! $have_lock) {
  254. print STDERR "failed to get lock on $opts{L}\n";
  255. print "failed to get lock on $opts{L}\n";
  256. exit($NAGIOS_NORMAL);
  257. }
  258. }
  259. $cache = process_hosts($cache, \@hosts);
  260. if (defined($opts{'l'})) {
  261. Output("");
  262. save_cache($cache);
  263. sleep($opts{'l'});
  264. }
  265. } while ($opts{'l'});
  266. }
  267. save_cache($cache);
  268. if ($lockfileh) {
  269. unlink($lockfileh);
  270. flock($lockfileh, LOCK_UN); # it's ok if it fails
  271. }
  272. nagios_exit() if ($opts{'N'});
  273. sub read_cache {
  274. # read in the json-based cache file
  275. my $cache = {};
  276. my ($buf, $content);
  277. if (-f $opts{'c'}) {
  278. Verbose("Reading cache file from '$opts{c}'");
  279. open(my $cacheh, "<", $opts{'c'});
  280. while (read($cacheh, $buf, 4096 * 16) > 0) {
  281. $content .= $buf;
  282. }
  283. close($cacheh);
  284. $cache = from_json($content); # cheap hack
  285. }
  286. return $cache;
  287. }
  288. sub process_hosts($$) {
  289. my ($cache, $hosts) = @_;
  290. # process hosts
  291. foreach my $host (sort @$hosts) {
  292. Verbose("Procesing host '$host'");
  293. my $sess = get_snmp_session($host);
  294. # we always want the uptime
  295. my @vars;
  296. push @vars, ['sysUpTime',0];
  297. foreach my $interface (sort keys(%{$cache->{'hosts'}{$host}{'interfaces'}})) {
  298. push @vars, ['ifHCInOctets', $cache->{'hosts'}{$host}{'interfaces'}{$interface}{'index'}];
  299. push @vars, ['ifHCOutOctets', $cache->{'hosts'}{$host}{'interfaces'}{$interface}{'index'}];
  300. }
  301. my $list = new SNMP::VarList(@vars);
  302. my $res = $sess->get($list);
  303. my $sysUpTime = shift @$list;
  304. my $sysUpTime = $sysUpTime->[2];
  305. my $sysUpTimeDiff = $sysUpTime - $cache->{'hosts'}{$host}{'sysUpTime'};
  306. foreach my $interface (sort keys(%{$cache->{'hosts'}{$host}{'interfaces'}})) {
  307. my $ifHCInOctets = shift @$list;
  308. my $ifHCOutOctets = shift @$list;
  309. my $ifData = $cache->{'hosts'}{$host}{'interfaces'}{$interface};
  310. if ($opts{'r'} ||
  311. !exists($ifData->{'data'}{'ifHCInOctets'}) ||
  312. !exists($cache->{'hosts'}{$host}{'sysUpTime'}) ||
  313. $sysUpTimeDiff < 0) {
  314. # reset all past data or...
  315. # no data is collected yet -- just store the sysUpTime
  316. # XXX: should collect engine boots too
  317. $ifData->{'data'}{'ifHCInOctets'} = $ifHCInOctets->[2];
  318. $ifData->{'data'}{'ifHCOutOctets'} = $ifHCOutOctets->[2];
  319. Verbose("interface $interface on $host needs to start with new data");
  320. } else {
  321. # the great analysis
  322. my $diffIn = $ifHCInOctets->[2] - $ifData->{'data'}{'ifHCInOctets'};
  323. my $diffOut = $ifHCOutOctets->[2] - $ifData->{'data'}{'ifHCOutOctets'};
  324. Verbose("$interface in: $ifHCInOctets->[2] - $ifData->{'data'}{'ifHCInOctets'}");
  325. Verbose("$interface diff: $diffIn/$sysUpTimeDiff = " . (100 * $diffIn/$sysUpTimeDiff) . " B/s");
  326. Verbose("$interface out: $ifHCOutOctets->[2] - $ifData->{'data'}{'ifHCOutOctets'}");
  327. Verbose("$interface diff: $diffOut/$sysUpTimeDiff = " . (100 * $diffOut/$sysUpTimeDiff) . " B/s");
  328. my $inRate = (100 * $diffIn/$sysUpTimeDiff);
  329. my $outRate = (100 * $diffOut/$sysUpTimeDiff);
  330. my $inMarker = " ";
  331. my $outMarker = " ";
  332. # check maximum bandwidth limits
  333. if (defined($ifData->{'maxInBandwidth'})) {
  334. foreach my $limit (@{$ifData->{'maxInBandwidth'}}) {
  335. if ($inRate > 0 && $inRate > $limit->{'rate'}) {
  336. $inMarker = "I";
  337. $ifData->{'maxInBandwidthCount'} ++;
  338. if ($ifData->{'maxInBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
  339. Verbose(" input too high!!! $inRate > $limit->{'rate'} ; emailing $limit->{'email'}");
  340. send_rate_message($limit->{'email'},
  341. $host, $interface, 'in', 'gt',
  342. $ifData->{'maxInBandwidthCount'},
  343. $inRate, $limit->{'rate'});
  344. }
  345. } else {
  346. $ifData->{'maxInBandwidthCount'} = 0;
  347. }
  348. }
  349. }
  350. if (defined($ifData->{'maxOutBandwidth'})) {
  351. foreach my $limit (@{$ifData->{'maxOutBandwidth'}}) {
  352. if ($outRate > 0 && $outRate > $limit->{'rate'}) {
  353. $outMarker = "O";
  354. $ifData->{'maxOutBandwidthCount'} ++;
  355. if ($ifData->{'maxOutBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
  356. Verbose(" output too high!!! $outRate > $limit->{'rate'} ; emailing $limit->{'email'}");
  357. send_rate_message($limit->{'email'},
  358. $host, $interface, 'out', 'gt',
  359. $ifData->{'maxOutBandwidthCount'},
  360. $outRate, $limit->{'rate'});
  361. }
  362. } else {
  363. $ifData->{'maxOutBandwidthCount'} = 0;
  364. }
  365. }
  366. }
  367. # check minimum bandwidth limits
  368. if (defined($ifData->{'minInBandwidth'})) {
  369. foreach my $limit (@{$ifData->{'minInBandwidth'}}) {
  370. if ($inRate > 0 && $inRate < $limit->{'rate'}) {
  371. $inMarker = "I";
  372. $ifData->{'minInBandwidthCount'} ++;
  373. if ($ifData->{'minInBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
  374. Verbose(" input too low!!! $inRate < $limit->{'rate'} ; emailing $limit->{'email'}");
  375. send_rate_message($limit->{'email'},
  376. $host, $interface, 'in', 'lt',
  377. $ifData->{'minInBandwidthCount'},
  378. $inRate, $limit->{'rate'});
  379. }
  380. } else {
  381. $ifData->{'minInBandwidthCount'} = 0;
  382. }
  383. }
  384. }
  385. if (defined($ifData->{'minOutBandwidth'})) {
  386. foreach my $limit (@{$ifData->{'minOutBandwidth'}}) {
  387. if ($outRate > 0 && $outRate < $limit->{'rate'}) {
  388. $outMarker = "O";
  389. $ifData->{'minOutBandwidthCount'} ++;
  390. if ($ifData->{'minOutBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
  391. Verbose(" output too low!!! $outRate < $limit->{'rate'} ; emailing $limit->{'email'}");
  392. send_rate_message($limit->{'email'},
  393. $host, $interface, 'out', 'lt',
  394. $ifData->{'minOutBandwidthCount'},
  395. $outRate, $limit->{'rate'});
  396. }
  397. } else {
  398. $ifData->{'minOutBandwidthCount'} = 0;
  399. }
  400. }
  401. }
  402. Output(sprintf("%-15s %-8s %16.0f in %16.0f out (B/s) %s%s",
  403. $host, $interface,
  404. $inRate, $outRate,
  405. $inMarker, $outMarker));
  406. # Update the cache data. Maybe.
  407. if (! $opts{'n'}) {
  408. $ifData->{'data'}{'ifHCInOctets'} = $ifHCInOctets->[2];
  409. $ifData->{'data'}{'ifHCOutOctets'} = $ifHCOutOctets->[2];
  410. }
  411. }
  412. }
  413. # store the sysUpTime
  414. $cache->{'hosts'}{$host}{'sysUpTime'} = $sysUpTime unless ($opts{'n'});
  415. }
  416. return $cache;
  417. }
  418. sub add_interface($$$$$) {
  419. my ($cache, $interface, $maxInBandwidth, $maxOutBandwidth, $minInBandwidth, $minOutBandwidth, $hosts) = @_;
  420. foreach my $host (@$hosts) {
  421. Verbose("adding interface $interface to $host");
  422. my $session = get_snmp_session($host);
  423. my $tabledata = $session->gettable('ifTable');
  424. my $found = 0;
  425. foreach my $key (keys(%$tabledata)) {
  426. # each key is an interface number; find the matching interface
  427. if ($tabledata->{$key}{'ifDescr'} eq $interface) {
  428. # found it!
  429. Verbose("found $interface on $host at $key; adding it");
  430. $cache->{'hosts'}{$host}{'interfaces'}{$interface}{'index'} = $key;
  431. if ($maxInBandwidth) {
  432. my ($ratespec, $email) = split(/=/, $maxInBandwidth);
  433. my ($rate, $count) = split(/:/, $ratespec);
  434. if (!defined($count)) {
  435. $rate = $ratespec;
  436. $count = 1;
  437. }
  438. push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'maxInBandwidth'}},
  439. { rate => $rate,
  440. email => $email,
  441. maxcount => $count };
  442. }
  443. if ($maxOutBandwidth) {
  444. my ($ratespec, $email) = split(/=/, $maxOutBandwidth);
  445. my ($rate, $count) = split(/:/, $ratespec);
  446. if (!defined($count)) {
  447. $rate = $ratespec;
  448. $count = 1;
  449. }
  450. push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'maxOutBandwidth'}},
  451. { rate => $rate,
  452. email => $email,
  453. maxcount => $count};
  454. }
  455. if ($minInBandwidth) {
  456. my ($ratespec, $email) = split(/=/, $minInBandwidth);
  457. my ($rate, $count) = split(/:/, $ratespec);
  458. if (!defined($count)) {
  459. $rate = $ratespec;
  460. $count = 1;
  461. }
  462. push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'minInBandwidth'}},
  463. { rate => $rate,
  464. email => $email,
  465. maxcount => $count };
  466. }
  467. if ($minOutBandwidth) {
  468. my ($ratespec, $email) = split(/=/, $minOutBandwidth);
  469. my ($rate, $count) = split(/:/, $ratespec);
  470. if (!defined($count)) {
  471. $rate = $ratespec;
  472. $count = 1;
  473. }
  474. push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'minOutBandwidth'}},
  475. { rate => $rate,
  476. email => $email,
  477. maxcount => $count};
  478. }
  479. $found = 1;
  480. Output("Configured $interface for $host");
  481. }
  482. }
  483. if (!$found) {
  484. Log(" failed to find interface '$interface' on '$host'");
  485. }
  486. }
  487. }
  488. my %session_cache;
  489. sub get_snmp_session($) {
  490. my ($host) = @_;
  491. if (exists($session_cache{$host})) {
  492. return $session_cache{$host};
  493. }
  494. my @args;
  495. if (-f "/etc/snmp/perl/$host.conf") {
  496. open(CC, "/etc/snmp/perl/$host.conf");
  497. while (<CC>) {
  498. next if (/^\s*#/);
  499. chomp;
  500. push @args, split();
  501. }
  502. }
  503. # hack to work around perl not handling per-host files correctly
  504. $session_cache{$host} = new SNMP::Session(DestHost => $host, @args);
  505. return $session_cache{$host};
  506. }
  507. sub save_cache($) {
  508. my ($cache) = @_;
  509. # save data to the json cache (maybe)
  510. if (!$opts{'n'}) {
  511. my $dir = $opts{'c'};
  512. $dir =~ s/(.*)\/.*/$1/;
  513. if (! -d $dir) {
  514. mkdir($dir);
  515. if (! -d $dir) {
  516. Log("failed to create directory $dir");
  517. } else {
  518. Verbose("note: created directory $dir");
  519. }
  520. }
  521. # XXX: mkdir
  522. Verbose("Writing cache file to '$opts{c}'");
  523. my $jsondata = to_json($cache);
  524. open(my $outh, ">", $opts{'c'} . $$);
  525. print $outh $jsondata,"\n";
  526. $outh->close();
  527. rename($opts{'c'} . $$, $opts{'c'});
  528. }
  529. }
  530. sub send_rate_message($$$$$$) {
  531. my ($to, $host, $interface, $direction, $highlow, $count, $measured, $max) = @_;
  532. if ($opts{'N'} && ($to eq 'nagioswarn' || $to eq 'nagioscritical')) {
  533. # nagios mode ; collect simple output for later
  534. nagios_collect($to, $host, $interface, $direction, $highlow, $count, $measured, $max);
  535. } elsif($to ne 'nagioswarn' && $to ne 'nagioscritical') {
  536. # send email
  537. my $word = ($highlow eq '>' ? 'exceeded' : 'too low');
  538. send_message($to,
  539. "rate $word: $host/$interface=$direction ($count times)",
  540. "Rate limit $word for:\n\n" .
  541. "\thost:\t\t$host\n" .
  542. "\tinterface:\t$interface ($direction)\n" .
  543. "\trate:\t\t$measured\n" .
  544. "\tmax allowed:\t$max\n" .
  545. "\tcount:\t\t$count times in a row\n");
  546. }
  547. }
  548. sub send_message($$$) {
  549. my ($to, $subject, $text) = @_;
  550. my $sender = new Mail::Sender { smtp => $opts{'S'} ,
  551. port => $opts{'P'},
  552. from => $opts{'F'},
  553. };
  554. my $status =
  555. $sender->MailMsg({
  556. to => $to,
  557. subject => $subject,
  558. msg => $text
  559. });
  560. if ($status < 0) {
  561. Log("Failed to send mail with error code $status: $Mail::Sender::Error");
  562. }
  563. }
  564. sub nagios_collect($$$$$$) {
  565. my ($to, $host, $interface, $direction, $highlow, $count, $measured, $max) = @_;
  566. if ($to eq 'nagioscritical') {
  567. $nagiosexit = $NAGIOS_CRITICAL;
  568. } elsif ($to eq 'nagioswarn' && $nagiosexit < $NAGIOS_CRITICAL) {
  569. $nagiosexit = $NAGIOS_WARNING;
  570. }
  571. $nagiosstr .= sprintf(", %s $direction %16.0f B/s $highlow %s", $interface, $measured, $max);
  572. }
  573. sub nagios_exit() {
  574. if ($nagiosstr eq '') {
  575. $nagiosstr = "All interfaces ok: " . join(", ", keys(%{$cache->{'hosts'}{$hosts[0]}{'interfaces'}}));
  576. } else {
  577. $nagiosstr =~ s/^, //;
  578. }
  579. print $nagiosstr, "\n";
  580. exit $nagiosexit;
  581. }
  582. sub Verbose {
  583. if ($opts{'v'}) {
  584. print STDERR @_, "\n";
  585. }
  586. }
  587. sub Log {
  588. print STDERR @_,"\n";
  589. }
  590. sub Output {
  591. if (! $opts{'q'} && !$opts{'N'}) {
  592. print @_, "\n";
  593. }
  594. if ($opts{'N'} && $opts{'s'}) {
  595. $nagiosstr .= ", " . join(" ", @_);
  596. }
  597. }
  598. #### portability for not requiring Getopt::GUI::Long directly
  599. sub LocalGetOptions {
  600. if (eval {require Getopt::GUI::Long;}) {
  601. import Getopt::GUI::Long;
  602. # optional configure call
  603. Getopt::GUI::Long::Configure(qw(display_help no_ignore_case allow_zero));
  604. return GetOptions(@_);
  605. }
  606. require Getopt::Long;
  607. import Getopt::Long;
  608. # optional configure call
  609. Getopt::Long::Configure(qw(auto_help no_ignore_case));
  610. GetOptions(LocalOptionsMap(@_));
  611. }
  612. sub LocalOptionsMap {
  613. my ($st, $cb, @opts) = ((ref($_[0]) eq 'HASH')
  614. ? (1, 1, $_[0]) : (0, 2));
  615. for (my $i = $st; $i <= $#_; $i += $cb) {
  616. if ($_[$i]) {
  617. next if (ref($_[$i]) eq 'ARRAY' && $_[$i][0] =~ /^GUI:/);
  618. push @opts, ((ref($_[$i]) eq 'ARRAY') ? $_[$i][0] : $_[$i]);
  619. push @opts, $_[$i+1] if ($cb == 2);
  620. }
  621. }
  622. return @opts;
  623. }
  624. # Local Variables:
  625. # tab-width: 4
  626. # End: