1#!/usr/local/bin/perl 2 3=head1 NAME 4 5upsmonpro_ - Munin plugin to monitor Powercom UPS via UPSMON PRO program L<http://www.pcm.ru/support/soft/> 6 7=head1 INSTALLATION 8 9 /etc/munin/plugins/upsmonpro_load -> /usr/share/munin/plugins/upsmonpro_ 10 /etc/munin/plugins/upsmonpro_status -> /usr/share/munin/plugins/upsmonpro_ 11 /etc/munin/plugins/upsmonpro_temp -> /usr/share/munin/plugins/upsmonpro_ 12 /etc/munin/plugins/upsmonpro_voltage -> /usr/share/munin/plugins/upsmonpro_ 13 14=head1 CONFIGURATION 15 16Environment variables: 17 18 host - UPSMON PRO server host, default localhost 19 port - UPSMON PRO port, default 2601 20 21Example configuration (optional): 22 23 [upsmonpro_*] 24 env.host localhost 25 env.port 2601 26 27=head1 MAGIC MARKERS 28 29 #%# family=auto 30 #%# capabilities=autoconf 31 #%# capabilities=suggest 32 33=head1 AUTHOR 34 35Copyright (C) 2017 pru.mike@gmail.com 36 37=head1 LICENSE 38 39GPLv2. 40 41=cut 42 43use strict; 44use warnings; 45use feature qw/say/; 46use Munin::Plugin; 47use IO::Socket::INET; 48use Time::HiRes qw/usleep/; 49use English qw/-no-math-vars/; 50our $VERSION = '0.0.1'; 51$OUTPUT_AUTOFLUSH++; 52 53our $DEFAULT_HOST = 'localhost'; 54our $DEFAULT_PORT = 2601; 55our %TYPES = ( 56 voltage => [qw/input output/], 57 load => [qw/battery_load battery_capacity/], 58 temp => [q/temp/], 59 status => [qw/power_failure low_battery voltage_status ups_status battery_test/] 60); 61our @TYPES = sort keys %TYPES; 62my %DISPATCH_TABLE = (); 63my $pkg = __PACKAGE__; 64 65$DISPATCH_TABLE{"${pkg}::run_suggest"} = \&run_suggest; 66$DISPATCH_TABLE{"${pkg}::run_autoconf"} = \&run_autoconf; 67for my $t (@TYPES) { 68 $DISPATCH_TABLE{"${pkg}::run_config_$t"} = \&{"run_config_$t"}; 69 $DISPATCH_TABLE{"${pkg}::run_autoconf_$t"} = \&run_autoconf; 70 $DISPATCH_TABLE{"${pkg}::run_default_$t"} = sub { 71 run_default(@{ $TYPES{$t} }); 72 }; 73} 74 75find_key($ARGV[0])->(); 76 77sub find_key { 78 my $argv = shift || ''; 79 my $type_re = join '|', @TYPES; 80 my $key; 81 if ($argv =~ /(suggest|autoconf)/i) { 82 $key = 'run_' . lc($1); 83 } elsif ($Munin::Plugin::me =~ /upsmonpro_{1,}($type_re)$/) { 84 my $graph = $1; 85 $key = 'run_' . ((grep { $argv eq $_ } qw/autoconf config/) ? $argv : 'default') . "_$graph"; 86 } else { 87 die "Could not determine script type [@TYPES] ? name=$Munin::Plugin::me\n"; 88 } 89 90 return $DISPATCH_TABLE{"${pkg}::$key"}; 91} 92 93sub run_config_voltage { 94 print <<'END'; 95graph_title UPS Input/Output Voltage 96graph_vlabel volt 97graph_scale no 98graph_category sensors 99input.label input 100input.info Input Voltage 101input.type GAUGE 102output.label output 103output.info Output Voltage 104output.type GAUGE 105END 106 exit(0); 107} 108 109sub run_config_temp { 110 print <<'END'; 111graph_title UPS Temperature 112graph_vlabel celsius 113graph_scale no 114graph_category sensors 115temp.label temperature 116temp.type GAUGE 117END 118 exit(0); 119} 120 121sub run_config_load { 122 print <<'END'; 123graph_title UPS Battery Load/Capacity 124graph_vlabel percent (%) 125graph_scale no 126graph_category sensors 127battery_load.label battery_load 128battery_load.type GAUGE 129battery_capacity.label battery_capacity 130battery_capacity.type GAUGE 131END 132 exit(0); 133} 134 135sub run_config_status { 136 print <<'END'; 137graph_title UPS Statuses 138graph_vlabel status 139graph_scale no 140graph_category sensors 141power_failure.label power_failure 142power_failure.type GAUGE 143low_battery.label low_battery 144low_battery.type GAUGE 145voltage_status.label voltage_status 146voltage_status.info 0 normal, 1 boost, 2 buck 147voltage_status.type GAUGE 148ups_status.label ups_status 149ups_status.type GAUGE 150battery_test.label battery_test 151battery_test.type GAUGE 152END 153 exit(0); 154} 155 156sub run_default { 157 my $host = $ENV{host} || $DEFAULT_HOST; 158 my $port = $ENV{port} || $DEFAULT_PORT; 159 my @val_list = @_; 160 my $r = gather_data($host, $port); 161 for (@val_list) { 162 die "Wrong value: $_" if not exists $r->{$_}; 163 say "${_}.value $r->{$_}"; 164 } 165} 166 167sub run_suggest { 168 local $LIST_SEPARATOR = "\n"; 169 say "@TYPES"; 170 exit(0); 171} 172 173sub run_autoconf { 174 if (gather_data($DEFAULT_HOST, $DEFAULT_PORT)->{response} eq 'ok') { 175 say "yes"; 176 } else { 177 say "no ($DEFAULT_HOST:$DEFAULT_PORT not response)"; 178 } 179 exit(0); 180} 181 182sub gather_data { 183 my $host = shift; 184 my $port = shift; 185 my %data = map { ($_ => 'U') } map { @{ $TYPES{$_} } } @TYPES; 186 $data{response} = 'failed'; 187 188 my $sock = IO::Socket::INET->new( 189 PeerAddr => $host, 190 Proto => 'udp', 191 PeerPort => $port, 192 Blocking => 0 193 ) or die "Cannot create socket: $@"; 194 195 my $pattern = pack 'AAAACAAA', split //, 'PCMG1END'; 196 $sock->send($pattern) or die "send to $host: $!"; 197 usleep(200); 198 my $data; 199 my $buf; 200 while ($sock->read($buf, 32)) { 201 $data .= $buf; 202 } 203 if (defined $data and $data =~ /^PCMR.*END$/) { 204 my @data = unpack('C*', $data); 205 %data = ( 206 response => 'ok', 207 input => $data[6] + 256 * $data[5], 208 output => $data[8] + 256 * $data[7], 209 battery_load => $data[11], 210 battery_capacity => $data[12], 211 temp => $data[15], 212 power_failure => ($data[18] ? 1 : 0), 213 low_battery => ($data[19] ? 1 : 0), 214 215 #voltage_status: 0 = normal, 3 = buck, 2 = boost 216 voltage_status => ($data[17] == 0 ? 0 : ($data[17] == 3 ? 2 : 1)), 217 ups_status => ($data[21] ? 1 : 0), 218 battery_test => ($data[23] ? 1 : 0), 219 ); 220 } 221 return \%data; 222} 223