#!/usr/bin/perl =head1 NAME upsmonpro_ - Munin plugin to monitor Powercom UPS via UPSMON PRO program L =head1 INSTALLATION /etc/munin/plugins/upsmonpro_load -> /usr/share/munin/plugins/upsmonpro_ /etc/munin/plugins/upsmonpro_status -> /usr/share/munin/plugins/upsmonpro_ /etc/munin/plugins/upsmonpro_temp -> /usr/share/munin/plugins/upsmonpro_ /etc/munin/plugins/upsmonpro_voltage -> /usr/share/munin/plugins/upsmonpro_ =head1 CONFIGURATION Environment variables: host - UPSMON PRO server host, default localhost port - UPSMON PRO port, default 2601 Example configuration (optional): [upsmonpro_*] env.host localhost env.port 2601 =head1 MAGIC MARKERS #%# family=auto #%# capabilities=autoconf #%# capabilities=suggest =head1 AUTHOR Copyright (C) 2017 pru.mike@gmail.com =head1 LICENSE GPLv2. =cut use strict; use warnings; use feature qw/say/; use Munin::Plugin; use IO::Socket::INET; use Time::HiRes qw/usleep/; use English qw/-no-math-vars/; our $VERSION = '0.0.1'; $OUTPUT_AUTOFLUSH++; our $DEFAULT_HOST = 'localhost'; our $DEFAULT_PORT = 2601; our %TYPES = ( voltage => [qw/input output/], load => [qw/battery_load battery_capacity/], temp => [q/temp/], status => [qw/power_failure low_battery voltage_status ups_status battery_test/] ); our @TYPES = sort keys %TYPES; my %DISPATCH_TABLE = (); my $pkg = __PACKAGE__; $DISPATCH_TABLE{"${pkg}::run_suggest"} = \&run_suggest; $DISPATCH_TABLE{"${pkg}::run_autoconf"} = \&run_autoconf; for my $t (@TYPES) { $DISPATCH_TABLE{"${pkg}::run_config_$t"} = \&{"run_config_$t"}; $DISPATCH_TABLE{"${pkg}::run_autoconf_$t"} = \&run_autoconf; $DISPATCH_TABLE{"${pkg}::run_default_$t"} = sub { run_default(@{ $TYPES{$t} }); }; } find_key($ARGV[0])->(); sub find_key { my $argv = shift || ''; my $type_re = join '|', @TYPES; my $key; if ($argv =~ /(suggest|autoconf)/i) { $key = 'run_' . lc($1); } elsif ($Munin::Plugin::me =~ /upsmonpro_{1,}($type_re)$/) { my $graph = $1; $key = 'run_' . ((grep { $argv eq $_ } qw/autoconf config/) ? $argv : 'default') . "_$graph"; } else { die "Could not determine script type [@TYPES] ? name=$Munin::Plugin::me\n"; } return $DISPATCH_TABLE{"${pkg}::$key"}; } sub run_config_voltage { print <<'END'; graph_title UPS Input/Output Voltage graph_vlabel volt graph_scale no graph_category sensors input.label input input.info Input Voltage input.type GAUGE output.label output output.info Output Voltage output.type GAUGE END exit(0); } sub run_config_temp { print <<'END'; graph_title UPS Temperature graph_vlabel celsius graph_scale no graph_category sensors temp.label temperature temp.type GAUGE END exit(0); } sub run_config_load { print <<'END'; graph_title UPS Battery Load/Capacity graph_vlabel percent (%) graph_scale no graph_category sensors battery_load.label battery_load battery_load.type GAUGE battery_capacity.label battery_capacity battery_capacity.type GAUGE END exit(0); } sub run_config_status { print <<'END'; graph_title UPS Statuses graph_vlabel status graph_scale no graph_category sensors power_failure.label power_failure power_failure.type GAUGE low_battery.label low_battery low_battery.type GAUGE voltage_status.label voltage_status voltage_status.info 0 normal, 1 boost, 2 buck voltage_status.type GAUGE ups_status.label ups_status ups_status.type GAUGE battery_test.label battery_test battery_test.type GAUGE END exit(0); } sub run_default { my $host = $ENV{host} || $DEFAULT_HOST; my $port = $ENV{port} || $DEFAULT_PORT; my @val_list = @_; my $r = gather_data($host, $port); for (@val_list) { die "Wrong value: $_" if not exists $r->{$_}; say "${_}.value $r->{$_}"; } } sub run_suggest { local $LIST_SEPARATOR = "\n"; say "@TYPES"; exit(0); } sub run_autoconf { if (gather_data($DEFAULT_HOST, $DEFAULT_PORT)->{response} eq 'ok') { say "yes"; } else { say "no ($DEFAULT_HOST:$DEFAULT_PORT not response)"; } exit(0); } sub gather_data { my $host = shift; my $port = shift; my %data = map { ($_ => 'U') } map { @{ $TYPES{$_} } } @TYPES; $data{response} = 'failed'; my $sock = IO::Socket::INET->new( PeerAddr => $host, Proto => 'udp', PeerPort => $port, Blocking => 0 ) or die "Cannot create socket: $@"; my $pattern = pack 'AAAACAAA', split //, 'PCMG1END'; $sock->send($pattern) or die "send to $host: $!"; usleep(200); my $data; my $buf; while ($sock->read($buf, 32)) { $data .= $buf; } if (defined $data and $data =~ /^PCMR.*END$/) { my @data = unpack('C*', $data); %data = ( response => 'ok', input => $data[6] + 256 * $data[5], output => $data[8] + 256 * $data[7], battery_load => $data[11], battery_capacity => $data[12], temp => $data[15], power_failure => ($data[18] ? 1 : 0), low_battery => ($data[19] ? 1 : 0), #voltage_status: 0 = normal, 3 = buck, 2 = boost voltage_status => ($data[17] == 0 ? 0 : ($data[17] == 3 ? 2 : 1)), ups_status => ($data[21] ? 1 : 0), battery_test => ($data[23] ? 1 : 0), ); } return \%data; }