root / plugins / sensors / allnet__ @ 17f78427
Historique | Voir | Annoter | Télécharger (9,9 ko)
| 1 |
#!/usr/bin/perl |
|---|---|
| 2 |
|
| 3 |
# This script is used to feed sensor information from Allnet-Devices like e.g. |
| 4 |
# Allnet 4000 or 4500 into munin. It uses the XML-Interface of the devices to |
| 5 |
# fetch data. |
| 6 |
# |
| 7 |
# written by Michael Meier <michael.meier@fau.de>, License GPLv2 |
| 8 |
# |
| 9 |
# Usage: |
| 10 |
# Put this script onto some server, either on your munin-server or onto some |
| 11 |
# other server that can reach the actual device. Then symlink to it from the |
| 12 |
# munin-node plugin dir (usually /etc/munin/plugins). The naming of the symlink |
| 13 |
# is a critical part of the configuration, it needs to be: |
| 14 |
# SOMETHING_HOSTNAME_TYPE, e.g. allnet_monitordevice42.yourdomain.com_temp |
| 15 |
# SOMETHING really is just a random name containing no special chars, use |
| 16 |
# whatever you like. |
| 17 |
# HOSTNAME is the hostname of the Allnet-Device. This is also the hostname |
| 18 |
# under which the sensors will show up in munin. |
| 19 |
# TYPE is the type of sensors you're interested in, valid values are: |
| 20 |
# temp, hum, amps |
| 21 |
# If you want to monitor more than one type of sensor, create more than one |
| 22 |
# symlink. |
| 23 |
# The plugin will automatically feed all sensors of the selected type to munin, |
| 24 |
# under the names that you set in the allnets config. Take care with special |
| 25 |
# characters, in particular Umlauts and the such - munin will probably not like |
| 26 |
# those in names. |
| 27 |
# |
| 28 |
# Note that the format of the XML output these devices deliver varies vastly |
| 29 |
# between different firmware versions. This script currently understands 3 |
| 30 |
# completely different outputs, but there is no guarantee that's all the |
| 31 |
# variations that exist. If the script doesn't understand the output of your |
| 32 |
# device, set the beextratolerant parameter. You might also want to try a |
| 33 |
# different firmware version. |
| 34 |
# |
| 35 |
# The behaviour of the script can be influenced by environment variables. The |
| 36 |
# variables are (note this is case sensitive!): |
| 37 |
# username Username for basic HTTP auth (if your device is password protected) |
| 38 |
# password Password for basic HTTP auth (if your device is password protected) |
| 39 |
# beextratolerant This needs to be set to 1 for some extremely old/stripped |
| 40 |
# down devices. Note that this relaxes sanity checks and |
| 41 |
# will lead to nonsense-sensors showing up on devices that |
| 42 |
# do not require this. |
| 43 |
# DUMP set to 1 to enable some debug output (only outside of munin!) |
| 44 |
# internalsensorname.warning The warning-threshold for the sensor reported |
| 45 |
# to munin. You need to replace |
| 46 |
# "internalsensorname" with the munin-internal |
| 47 |
# name of the sensor, usually sensor+somenumber. |
| 48 |
# Can be seen in the webinterface. |
| 49 |
# internalsensorname.critical Same as above, but for the critical-threshold. |
| 50 |
# |
| 51 |
# As an example, you could put the following file into /etc/munin/plugin-conf.d: |
| 52 |
# [allnet_monitordevice42.yourdomain.com_*] |
| 53 |
# env.username horst |
| 54 |
# env.password topsecret |
| 55 |
# env.sensor14.warning 10.0 |
| 56 |
# env.sensor14.critical 15.0 |
| 57 |
|
| 58 |
# One tuneable: |
| 59 |
# Timeout for requests. |
| 60 |
my $timeout = 3; # the LWP default of 180 secs would be way too long |
| 61 |
|
| 62 |
# ----------------------------------------------------------------------------- |
| 63 |
|
| 64 |
# No need to change those, can be overridden from commandline. |
| 65 |
my $hostname = 'localhost'; |
| 66 |
my $valtype = 'temp'; # or humidity or amps |
| 67 |
# These are tried one after another. |
| 68 |
my @xmlpaths = ('/xml/sensordata.xml', '/xml');
|
| 69 |
|
| 70 |
use LWP::UserAgent; |
| 71 |
use XML::Simple; |
| 72 |
|
| 73 |
# AuthAgent is simply LWP::UserAgent with a reimplemented version of the |
| 74 |
# method basic auth. Theoretically/according to the documentation, the base |
| 75 |
# implementation should be able to do everything that is needed as well, but |
| 76 |
# it seems to broken and "the internet" suggests it has been for a long time. |
| 77 |
{
|
| 78 |
package AuthAgent; |
| 79 |
use base 'LWP::UserAgent'; |
| 80 |
|
| 81 |
sub get_basic_credentials {
|
| 82 |
if (defined($ENV{'username'}) && defined($ENV{'password'})) {
|
| 83 |
return $ENV{'username'}, $ENV{'password'};
|
| 84 |
} else {
|
| 85 |
return 'admin', 'password'; |
| 86 |
} |
| 87 |
} |
| 88 |
} |
| 89 |
|
| 90 |
# Par. 0: URL |
| 91 |
# Returns: The contents of the website |
| 92 |
sub geturl($) {
|
| 93 |
my $url = shift(); |
| 94 |
|
| 95 |
my $ua = AuthAgent->new(); |
| 96 |
$ua->agent($0); |
| 97 |
$ua->timeout($timeout); |
| 98 |
# Create a request |
| 99 |
my $req = HTTP::Request->new(GET => $url); |
| 100 |
# Pass request to the user agent and get a response back |
| 101 |
my $res = $ua->request($req); |
| 102 |
# Check the outcome of the response |
| 103 |
unless ($res->is_success()) {
|
| 104 |
#print("ERROR getting data from $url\n");
|
| 105 |
return undef; |
| 106 |
} |
| 107 |
return $res->content(); |
| 108 |
} |
| 109 |
|
| 110 |
if ((@ARGV > 0) && ($ARGV[0] eq "autoconf")) {
|
| 111 |
print("No\n");
|
| 112 |
exit(0); |
| 113 |
} |
| 114 |
my $progname = $0; |
| 115 |
if ($progname =~ m/[-_]temp$/) {
|
| 116 |
$valtype = 'temp'; |
| 117 |
} elsif ($progname =~ m/[-_]temperature$/) {
|
| 118 |
$valtype = 'temp'; |
| 119 |
} elsif ($progname =~ m/[-_]hum$/) {
|
| 120 |
$valtype = 'humidity'; |
| 121 |
} elsif ($progname =~ m/[-_]humidity$/) {
|
| 122 |
$valtype = 'humidity'; |
| 123 |
} elsif ($progname =~ m/[-_]amps$/) {
|
| 124 |
$valtype = 'amps'; |
| 125 |
} elsif ($progname =~ m/[-_]ampere$/) {
|
| 126 |
$valtype = 'amps'; |
| 127 |
} |
| 128 |
if ($progname =~ m/.+_(.+?)_.+/) {
|
| 129 |
$hostname = $1; |
| 130 |
} |
| 131 |
my $sensorxml; |
| 132 |
foreach $xp (@xmlpaths) {
|
| 133 |
$sensorxml = geturl('http://' . $hostname . $xp);
|
| 134 |
if (defined($sensorxml)) { last; }
|
| 135 |
} |
| 136 |
unless (defined($sensorxml)) {
|
| 137 |
print("# Sorry, Failed to fetch data.");
|
| 138 |
exit(1); |
| 139 |
} |
| 140 |
# VERY old firmware versions have HTML crap around the actual XML. |
| 141 |
$sensorxml =~ s!.*<xml>(.*)</xml>.*!$1!sg; |
| 142 |
if (defined($ENV{'DUMP'}) && ($ENV{'DUMP'} eq '1')) {
|
| 143 |
print($sensorxml . "\n"); |
| 144 |
} |
| 145 |
if ((@ARGV > 0) && ($ARGV[0] eq "config")) {
|
| 146 |
if ($valtype eq 'humidity') {
|
| 147 |
print("graph_title Humidity sensors\n");
|
| 148 |
print("graph_args --lower-limit 0 --upper-limit 100.0\n");
|
| 149 |
print("graph_vlabel percent\n");
|
| 150 |
} elsif ($valtype eq 'temp') {
|
| 151 |
print("graph_title Temperature sensors\n");
|
| 152 |
print("graph_vlabel degC\n");
|
| 153 |
} elsif ($valtype eq 'amps') {
|
| 154 |
print("graph_title Electric current sensors\n");
|
| 155 |
print("graph_args --lower-limit 0\n");
|
| 156 |
print("graph_vlabel Ampere\n");
|
| 157 |
} |
| 158 |
print("graph_category sensors\n");
|
| 159 |
print("host_name $hostname\n");
|
| 160 |
} |
| 161 |
my $sensordata = XMLin($sensorxml, KeyAttr => { }, ForceArray => [ 'data' ] );
|
| 162 |
my $beextratolerant = 0; |
| 163 |
if (defined($ENV{'beextratolerant'})) {
|
| 164 |
if (($ENV{'beextratolerant'} =~ m/^y/) || ($ENV{'beextratolerant'} eq '1')) {
|
| 165 |
$beextratolerant = 1; |
| 166 |
} |
| 167 |
} |
| 168 |
#print($sensordata->[0]); |
| 169 |
foreach $k (keys($sensordata)) {
|
| 170 |
if ($k =~ m/^n(\d+)$/) { # Special handling: Could be output from the OLD XML interface.
|
| 171 |
my $nr = $1; |
| 172 |
if (defined($sensordata->{'s'.$nr})
|
| 173 |
&& defined($sensordata->{'t'.$nr})
|
| 174 |
&& defined($sensordata->{'min'.$nr})
|
| 175 |
&& defined($sensordata->{'max'.$nr})) {
|
| 176 |
# OK, all values available, really is from the old XML interface. |
| 177 |
if ($sensordata->{'s'.$nr} eq '0') { next; } # 0 means no sensor.
|
| 178 |
# OK, lets map the sensortype. |
| 179 |
my $st = 'temp'; |
| 180 |
if ($sensordata->{'s'.$nr} eq '65') { $st = 'humidity'; }
|
| 181 |
if ($sensordata->{'s'.$nr} eq '101') {
|
| 182 |
# potential FIXME: 101 is actually "water/contact sensor", but since it |
| 183 |
# returns 0.0 and 100.0 as values, we can treat it just like humidity |
| 184 |
# for simplicity. |
| 185 |
$st = 'humidity'; |
| 186 |
} |
| 187 |
if ($valtype ne $st) { next; } # these aren't the droids you're looking for
|
| 188 |
if ((@ARGV > 0) && ($ARGV[0] eq "config")) {
|
| 189 |
print("sensor${nr}.label " . $sensordata->{'n'.$nr} . "\n");
|
| 190 |
print("sensor${nr}.type GAUGE\n");
|
| 191 |
if (defined($ENV{"sensor${nr}.warning"})) { printf("sensor%s.warning %s\n", $nr, $ENV{"sensor${nr}.warning"}); }
|
| 192 |
if (defined($ENV{"sensor${nr}.critical"})) { printf("sensor%s.critical %s\n", $nr, $ENV{"sensor${nr}.critical"}); }
|
| 193 |
} else {
|
| 194 |
print("sensor${nr}.value " . $sensordata->{'t'.$nr}. "\n");
|
| 195 |
} |
| 196 |
} |
| 197 |
# Fall through - probably wasn't the old XML anyways. |
| 198 |
} |
| 199 |
my $onesens = $sensordata->{$k};
|
| 200 |
unless (defined($onesens->{'value_float'})
|
| 201 |
&& defined($onesens->{'name'})
|
| 202 |
&& defined($onesens->{'min_abs_float'})
|
| 203 |
&& defined($onesens->{'max_abs_float'})
|
| 204 |
&& defined($onesens->{'unit'})) {
|
| 205 |
# Not all values available -> no sane sensor data (or the "system" block in the XML) |
| 206 |
unless ($beextratolerant) { next; }
|
| 207 |
# Maybe yet another firmware version that does not deliver all of these values, |
| 208 |
# so lets check for the absolute MINIMUM set. |
| 209 |
unless (defined($onesens->{'value_float'}) && defined($onesens->{'name'})) {
|
| 210 |
next; # Minimum set not available either. |
| 211 |
} |
| 212 |
if ($onesens->{'value_float'} < -20000.0) { next; } # Invalid value
|
| 213 |
# OK, so fill in the blanks |
| 214 |
unless (defined($onesens->{'unit'})) { $onesens->{'unit'} = 'C'; }
|
| 215 |
unless (defined($onesens->{'min_abs_float'})) {
|
| 216 |
$onesens->{'min_abs_float'} = $onesens->{'value_float'} - 0.01;
|
| 217 |
} |
| 218 |
unless (defined($onesens->{'max_abs_float'})) {
|
| 219 |
$onesens->{'max_abs_float'} = $onesens->{'value_float'} + 0.01;
|
| 220 |
} |
| 221 |
} |
| 222 |
if (($onesens->{'min_abs_float'} == 0.0)
|
| 223 |
&& ($onesens->{'max_abs_float'} == 0.0)
|
| 224 |
&& ($onesens->{'value_float'} == 0.0)) {
|
| 225 |
next; # If the sensor never showed anything but 0.0, we do not care about it. |
| 226 |
} |
| 227 |
if ($onesens->{'unit'} eq 'A AC') { $onesens->{'unit'} = 'A'; }
|
| 228 |
if ($onesens->{'unit'} =~ m/C$/) {
|
| 229 |
$onesens->{'unit'} = 'C'; # Remove WTF8-Fuckup
|
| 230 |
} |
| 231 |
if (($valtype eq 'temp') && ($onesens->{'unit'} ne 'C')) { next; }
|
| 232 |
if (($valtype eq 'humidity') && ($onesens->{'unit'} ne '%')) { next; }
|
| 233 |
if (($valtype eq 'amps') && ($onesens->{'unit'} ne 'A')) { next; }
|
| 234 |
if ((@ARGV > 0) && ($ARGV[0] eq "config")) {
|
| 235 |
print("${k}.label " . $onesens->{'name'} . "\n");
|
| 236 |
#print("${k}.info " . $onesens->{'name'} . "\n");
|
| 237 |
print("${k}.type GAUGE\n");
|
| 238 |
if (defined($ENV{"${k}.warning"})) { printf("%s.warning %s\n", $k, $ENV{"${k}.warning"}); }
|
| 239 |
if (defined($ENV{"${k}.critical"})) { printf("%s.critical %s\n", $k, $ENV{"${k}.critical"}); }
|
| 240 |
} else {
|
| 241 |
if ($onesens->{'value_float'} < -20000.0) { # Invalid readings return -20480.0
|
| 242 |
print("${k}.value U\n");
|
| 243 |
} else {
|
| 244 |
print("${k}.value " . $onesens->{'value_float'} . "\n");
|
| 245 |
} |
| 246 |
} |
| 247 |
} |
