modified: n.fq
[GalaxyCodeBases.git] / etc / Server / mod_apcupsd.pl
blob783e85ab4d85ba7e65ee25f9dda1b2f7a4448fff
1 #!/usr/bin/perl
3 # Copyright (c) 2011 Jakub Jirutka (jakub@jirutka.cz)
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
13 # more details.
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 ###############################################################################
21 # Net-SNMP module for apcupsd
24 # Net-SNMP module for monitoring APC UPSes without SNMP support. It reads output
25 # from apcupsd (/sbin/apcaccess) and writes it into appropriate OIDs like UPSes
26 # with built-in SNMP support.
29 # To load this into a running agent with embedded perl support turned on, simply
30 # put the following line to your snmpd.conf file:
32 # perl do "/path/to/mod_apcupsd.pl";
34 # Net-snmp must be compiled with Perl support and apcupsd properly configured
35 # and running!
38 # You can download MIB file of PowerNet (APC) from
39 # http://www.michaelfmcnamara.com/files/mibs/powernet401.mib
41 # OID numbers for PowerNet-MIB: http://www.oidview.com/mibs/318/PowerNet-MIB.html
43 # If you want to edit this, set tab size in your editor to 4!
46 # @author: Jakub Jirutka <jakub@jirutka.cz>
47 # @version: 1.0
48 # @date: 2011-07-31
51 BEGIN {
52 print STDERR "Starting mod_apcupsd.pl\n";
53 $agent || die "No \$agent defined\n";
56 use NetSNMP::OID (':all');
57 use NetSNMP::agent (':all');
58 use NetSNMP::ASN (':all');
62 #################### SETTINGS ###################
65 # Set to 1 to get extra debugging information.
66 $debugging = 0;
68 # How often fetch data from /sbin/apcaccess (in seconds)?
69 my $fetch_interval = 20;
71 # Base OID of APC UPS tree to hook onto.
72 my $base_oid = '.1.3.6.1.4.1.318.1.1.1';
74 # OIDs mapping
75 my $mapping = [
76 # Apcupsd name OID suffix Data type OID name
77 ['APCMODEL', '1.1.1.0', ASN_OCTET_STR], # upsBasicIdentModel
78 ['UPSNAME', '1.1.2.0', ASN_OCTET_STR], # upsBasicIdentName
79 ['FIRMWARE', '1.2.1.0', ASN_OCTET_STR], # upsAdvIdentFirmwareRevision
80 ['SERIALNO', '1.2.3.0', ASN_OCTET_STR], # upsAdvIdentSerialNumber
81 ['TONBATT', '2.1.2.0', ASN_TIMETICKS], # upsBasicBatteryTimeOnBattery
82 ['BATTDATE', '2.1.3.0', ASN_OCTET_STR], # upsBasicBatteryLastReplaceDate
83 ['BCHARGE', '2.2.1.0', ASN_GAUGE], # upsAdvBatteryCapacity
84 ['ITEMP', '2.2.2.0', ASN_GAUGE], # upsAdvBatteryTemperature
85 ['TIMELEFT', '2.2.3.0', ASN_TIMETICKS], # upsAdvBatteryRunTimeRemaining
86 ['NOMBATTV', '2.2.7.0', ASN_INTEGER], # upsAdvBatteryNominalVoltage
87 ['BATTV', '2.2.8.0', ASN_INTEGER], # upsAdvBatteryActualVoltage //should be ASN_INTEGER according to new ver. of MIB
88 ['LINEV', '3.2.1.0', ASN_GAUGE], # upsAdvInputLineVoltage
89 ['LINEFREQ', '3.2.4.0', ASN_GAUGE], # upsAdvInputFrequency
90 ['LASTXFER', '3.2.5.0', ASN_INTEGER], # upsAdvInputLineFailCause
91 ['OUTPUTV', '4.2.1.0', ASN_GAUGE], # upsAdvOutputVoltage
92 ['LOADPCT', '4.2.3.0', ASN_GAUGE], # upsAdvOutputLoad
93 ['NOMOUTV', '5.2.1.0', ASN_INTEGER], # upsAdvConfigRatedOutputVoltage
94 ['HITRANS', '5.2.2.0', ASN_INTEGER], # upsAdvConfigHighTransferVolt
95 ['LOTRANS', '5.2.3.0', ASN_INTEGER], # upsAdvConfigLowTransferVolt
96 ['ALARMDEL', '5.2.4.0', ASN_INTEGER], # upsAdvConfigAlarm
97 ['RETPCT', '5.2.6.0', ASN_INTEGER], # upsAdvConfigMinReturnCapacity
98 ['SENSE', '5.2.7.0', ASN_INTEGER], # upsAdvConfigSensitivity
99 ['MINTIMEL', '5.2.8.0', ASN_TIMETICKS], #? upsAdvConfigLowBatteryRunTime
100 ['DWAKE', '5.2.9.0', ASN_TIMETICKS], # upsAdvConfigReturnDelay
101 ['DSHUTD', '5.2.10.0', ASN_TIMETICKS], # upsAdvConfigShutoffDelay
102 ['STESTI', '7.2.1.0', ASN_INTEGER], # upsAdvTestDiagnosticSchedule
103 ['SELFTEST', '7.2.3.0', ASN_INTEGER] # upsAdvTestDiagnosticsResults //according to apcstatus.c, or date and time of last self test according to manual?!
106 # Maps apcupsd values to enum types according to MIB.
107 # Mainly based on apcupsd sources (apcstatus.c, drv_powernet.c) and PowerNet MIB.
108 my %enums = (
109 # SELFTEST => upsAdvTestDiagnosticsResults
110 "$base_oid.7.2.3.0" => {
111 'OK' => 1, # ok
112 'BT' => 2, # failed //it's NOT in drv_powernet.c
113 'NG' => 3, # invalidTest
114 'IP' => 4, # testInProgress
115 'NO' => undef, # ! NONE
116 'WN' => undef, # ! WARNING
117 '??' => undef # ! UNKNOWN 
120 # STESTI => upsAdvTestDiagnosticSchedule
121 "$base_oid.7.2.1.0" => {
122 'None' => 1, # unknown
123 '336' => 2, # biweekly
124 '168' => 3, # weekly
125 'ON' => 4, # atTurnOn
126 'OFF' => 5 # never
129 # SENSE => upsAdvConfigSensitivity
130 "$base_oid.5.2.7.0" => {
131 'Auto Adjust' => 1, # auto
132 'Low' => 2, # low
133 'Medium' => 3, # medium
134 'High' => 4, # high
135 'Unknown' => undef
138 # ALARMDEL -> upsAdvConfigAlarm
139 "$base_oid.5.2.4.0" => {
140 '30 seconds' => 1, # timed
141 '5 seconds' => 1, # timed
142 'Always' => 1, # timed
143 'Low Battery' => 2, # atLowBattery
144 'No alarm' => 3 # never
147 # LASTXFER => upsAdvInputLineFailCause
148 "$base_oid.3.2.5.0" => {
149 'No transfers since turnon' => 1, # noTransfer
150 'High line voltage' => 2, # highLineVoltage
151 'Low line voltage' => 4, # blackout
152 'Line voltage notch or spike' => 8, # largeMomentarySpike
153 'Automatic or explicit self test' => 9, # selfTest
154 'Unacceptable line voltage changes' => 10, # rateOfVoltageChange
155 'Forced by software' => undef,
156 'Input frequency out of range' => undef,
157 'UNKNOWN EVENT' => undef
161 # TODO upsBasicBatteryStatus, upsBasicOutputStatus, NOMPOWER
166 #################### INITIALIZATION ###################
169 # Hashmap for apcupsd names => OIDs
170 my %name_oid;
172 # Hashmap for OID => types
173 my %oid_type;
175 # Build hashmaps
176 foreach my $row (@$mapping) {
177 my ($name, $oid, $type) = @$row;
178 $oid = "$base_oid.$oid";
179 $name_oid{$name} = $oid;
180 $oid_type{$oid} = $type;
183 # Delete mapping array (we have hashmaps now)
184 undef $mapping;
187 # Timestamp of last data fetch
188 my $last_fetch;
190 # Fetched values from /sbin/apcaccess
191 my %data;
194 # Fetch data for the first time so we can build
195 # OID chain for actually available values.
196 &fetch_data;
198 # Chain of our OIDs in lexical order for GETNEXT
199 my %oid_chain;
201 # First OID in chain
202 my $first_oid = 0;
204 # Build OID chain
205 my $prev_oid;
206 foreach my $oid (&oid_lex_sort(keys(%data))) {
207 if (!$first_oid) {
208 $first_oid = $oid;
209 } else {
210 $oid_chain{$prev_oid} = $oid;
212 $prev_oid = $oid;
216 # Base OID to register
217 $reg_oid = new NetSNMP::OID($base_oid);
219 # Register in the master agent we're embedded in.
220 my $agent = new NetSNMP::agent();
221 $agent->register('mod_apcupsd', $reg_oid, \&snmp_handler);
222 print STDERR "Registering at $reg_oid \n" if ($debugging);
227 #################### SUBROUTINES ###################
230 # Fetch data from /sbin/apcaccess and convert for SNMP.
231 # This routine stores values in variable %data and fetch it again
232 # only when it's called after more than $fetch_interval seconds
233 # since last fetch.
234 sub fetch_data {
235 my $elapsed = time() - $last_fetch;
237 if ($elapsed < $fetch_interval) {
238 print STDERR "It's $elapsed sec since last update, interval is "
239 . "$fetch_interval\n" if ($debugging);
240 return 0;
243 print STDERR "Fetching data from /sbin/apcaccess\n" if ($debugging);
244 open AC, '/sbin/apcaccess status localhost |'
245 || die "FATAL: can't run \"/sbin/apcaccess\": $!\n";
247 my $line;
248 while (defined($line = <AC>)) {
249 chomp $line;
250 if ($line !~ /^(\w+)\s*:\s*(.*\w)/) { next; }
252 my $oid = $name_oid{$1};
253 my $value = &convert_value($oid, $2) if $oid;
254 $data{$oid} = $value if (defined $value);
257 close AC;
258 $last_fetch = time();
260 return 1;
263 # Convert given raw value from /sbin/apcaccess to proper SNMP value according
264 # to data type defined in MIB.
265 # Given value must be without beginning and end whitespaces and only *value*,
266 # not whole row!
267 sub convert_value {
268 my ($oid, $raw) = @_;
270 # Convert values representing enums
271 # If enum value is undef, returns 0.
272 if (exists $enums{$oid}) {
273 my $enum = $enums{$oid}{$raw};
274 return (defined $enum ? $enum : 0);
277 # Convert other values according to their data type in MIB
278 if ($oid_type{$oid} == ASN_INTEGER)
280 return ($raw =~ /(\d+)/g)[0];
282 elsif ($oid_type{$oid} == ASN_GAUGE)
284 return ($raw =~ /(\d+(?:\.\d+)?)/g)[0];
286 elsif ($oid_type{$oid} == ASN_TIMETICKS)
288 my ($val, $unit) = ($raw =~ /(\d+(?:\.\d+)?)\s+(\w+)/g);
289 return &convert_time($val, $unit);
291 else
293 return $raw;
298 # Convert time in given unit (seconds, minutes or hours) to miliseconds.
299 sub convert_time {
300 my ($val, $unit) = @_;
302 if ($unit == (m/^seconds/i)) {
303 return $val * 100;
305 elsif ($unit == (m/^minutes/i)) {
306 return $val * 6000;
308 elsif ($unit == (m/^hours/i)) {
309 return $val * 360000;
311 else {
312 return $val;
318 # Subroutine that handle the incoming requests to our part of the OID tree.
319 # This subroutine will get called for all requests within the OID space
320 # under the registration oid made above.
321 sub snmp_handler {
322 my ($handler, $registration_info, $request_info, $requests) = @_;
323 my $request;
325 print STDERR "refs: ", join(", ", ref($handler), ref($registration_info),
326 ref($request_info), ref($requests)), "\n" if ($debugging);
328 print STDERR "Processing a request of type "
329 . $request_info->getMode() . "\n" if ($debugging);
331 &fetch_data;
333 for($request = $requests; $request; $request = $request->next()) {
334 # This is way how to convert NetSNMP::OID to numeric OID
335 my $oid = '.' . join('.', $request->getOID()->to_array());
336 print STDERR "Processing request of $oid\n" if ($debugging);
338 # Mode GET (for single entry)
339 if ($request_info->getMode() == MODE_GET) {
340 if (exists($data{$oid})) {
341 my $value = $data{$oid};
343 print STDERR " Returning: $value\n" if ($debugging);
344 $request->setValue($oid_type{$oid}, $value);
346 # Workaround for requests without "index"
347 } elsif (exists($data{"$oid.0"})) {
348 my $new_oid = "$oid.0";
349 my $value = $data{$new_oid};
351 print STDERR " Returning for $new_oid: $value\n" if ($debugging);
352 $request->setOID($new_oid);
353 $request->setValue($oid_type{$new_oid}, $value);
356 # Mode GETNEXT (for walking)
357 } elsif ($request_info->getMode() == MODE_GETNEXT) {
358 # Check if the request did not request the trailing .0, add it here
359 if (exists($oid_chain{"$oid.0"})) {
360 # Then the next object in the MIB would be the one with the index
361 my $new_oid = "$oid.0";
362 my $value = $data{$new_oid};
364 print STDERR " Returning next OID $new_oid: $value\n" if ($debugging);
365 $request->setOID($new_oid);
367 $request->setValue($oid_type{$new_oid}, $value);
369 # Check if the request DID include the trailing .0 index
370 } elsif (exists($oid_chain{$oid})) {
371 my $next_oid = $oid_chain{$oid};
372 my $value = $data{$next_oid};
374 print STDERR " Returning next OID $next_oid: $value\n" if ($debugging);
375 $request->setOID($next_oid);
376 $request->setValue($oid_type{$next_oid}, $value);
378 } elsif ($request->getOID() <= $reg_oid) {
379 my $value = $data{$first_oid};
381 print STDERR " Returning first OID $first_oid: $value\n" if ($debugging);
382 $request->setOID($first_oid);
383 $request->setValue($oid_type{$first_oid}, $value);
386 } else {
387 print STDERR "Illegal request\n" if ($debugging);
392 print STDERR "Processing finished\n" if ($debugging);
396 # Sort OIDs lexicographically
397 # See http://www.perlmonks.org/?node_id=524035
398 sub oid_lex_sort(@) {
399 return @_ unless (@_ > 1);
401 map { $_->[0] }
402 sort { $a->[1] cmp $b->[1] }
403 map {
404 my $oid = $_;
405 $oid =~ s/^\.//o;
406 $oid =~ s/ /\.0/og;
407 [$_, pack('N*', split('\.', $oid))]
408 } @_;