root / plugins / sensors / nutups2_ @ ea6afd78
Historique | Voir | Annoter | Télécharger (7,7 ko)
| 1 | 9a168e78 | Gabor Gombas | #! /usr/bin/perl -w |
|---|---|---|---|
| 2 | |||
| 3 | =head1 NAME |
||
| 4 | |||
| 5 | nutups2_ - Plugin to monitor UPSes managed by NUT |
||
| 6 | |||
| 7 | =head1 CONFIGURATION |
||
| 8 | |||
| 9 | Generally none needed. |
||
| 10 | |||
| 11 | If you have installed NUT at a non-standard location, then you can specify its |
||
| 12 | location like: |
||
| 13 | |||
| 14 | [nutups2_*] |
||
| 15 | env.upsc /some/location/bin/upsc |
||
| 16 | |||
| 17 | =head1 WARNING AND CRITICAL SETTINGS |
||
| 18 | |||
| 19 | If upsc reports 'high' and 'low' values for some attribute, those will used |
||
| 20 | as the critical range. Otherwise the following environment variables can be |
||
| 21 | used to set the defaults for all fields: |
||
| 22 | |||
| 23 | env.warning |
||
| 24 | env.critical |
||
| 25 | |||
| 26 | You can also control individual fields like: |
||
| 27 | |||
| 28 | env.input_L1.warning |
||
| 29 | env.output.critical |
||
| 30 | |||
| 31 | =head1 MAGIC MARKERS |
||
| 32 | |||
| 33 | #%# family=auto |
||
| 34 | #%# capabilities=autoconf suggest |
||
| 35 | |||
| 36 | =head1 FEATURES |
||
| 37 | |||
| 38 | The plugin supports reporting battery charge, UPS load, input/output |
||
| 39 | frequencies/currents/voltages, apparent and real power output, humidity and |
||
| 40 | temperature readings. Note however that different UPS models report different |
||
| 41 | levels of detail; the plugin reports whatever information the NUT UPS driver |
||
| 42 | (and in turn the UPS itself) provides. |
||
| 43 | |||
| 44 | Although the 'suggest' command will only offer UPSes for which the local host |
||
| 45 | is the master, you can also monitor remote UPSes if you include the host name |
||
| 46 | in the symlink, like: |
||
| 47 | |||
| 48 | nutups2_<upsname>@<hostname or address>_frequency |
||
| 49 | |||
| 50 | etc. |
||
| 51 | |||
| 52 | =head1 AUTHOR |
||
| 53 | |||
| 54 | Gábor Gombás <gombasg@sztaki.hu> |
||
| 55 | |||
| 56 | =head1 LICENSE |
||
| 57 | |||
| 58 | GPLv2 or later |
||
| 59 | |||
| 60 | =cut |
||
| 61 | |||
| 62 | use strict; |
||
| 63 | use Munin::Plugin; |
||
| 64 | use Carp; |
||
| 65 | |||
| 66 | my $UPSC = $ENV{'upsc'} || 'upsc';
|
||
| 67 | |||
| 68 | # For the 'filter' field, the first sub-match should contain the name to |
||
| 69 | # display, and the second sub-match should indicate if it is a nominal |
||
| 70 | # value instead of a sensor reading. |
||
| 71 | my %config = ( |
||
| 72 | charge => {
|
||
| 73 | filter => qr/^(.*)\.(?:charge|load)$/, |
||
| 74 | title => 'UPS load and battery charge', |
||
| 75 | args => '--base 1000 -l 0 -u 100', |
||
| 76 | vlabel => '%', |
||
| 77 | config => \&common_config, |
||
| 78 | fetch => \&common_fetch, |
||
| 79 | }, |
||
| 80 | current => {
|
||
| 81 | filter => qr/^(.*)\.current(\.nominal)?$/, |
||
| 82 | title => 'UPS current', |
||
| 83 | args => '--base 1000 -l 0', |
||
| 84 | vlabel => 'Amper', |
||
| 85 | config => \&common_config, |
||
| 86 | fetch => \&common_fetch, |
||
| 87 | }, |
||
| 88 | frequency => {
|
||
| 89 | filter => qr/^(.*)\.frequency(\.nominal)?$/, |
||
| 90 | title => 'UPS frequency', |
||
| 91 | args => '--base 1000 -l 0', |
||
| 92 | vlabel => 'Hz', |
||
| 93 | config => \&common_config, |
||
| 94 | fetch => \&common_fetch, |
||
| 95 | }, |
||
| 96 | humidity => {
|
||
| 97 | filter => qr/^(.*)\.humidity$/, |
||
| 98 | title => 'UPS humidity', |
||
| 99 | args => '--base 1000 -l 0', |
||
| 100 | vlabel => '%', |
||
| 101 | config => \&common_config, |
||
| 102 | fetch => \&common_fetch, |
||
| 103 | }, |
||
| 104 | power => {
|
||
| 105 | filter => qr/^(.*)\.power(\.nominal)?$/, |
||
| 106 | title => 'UPS apparent power', |
||
| 107 | args => '--base 1000 -l 0', |
||
| 108 | vlabel => 'VA', |
||
| 109 | config => \&common_config, |
||
| 110 | fetch => \&common_fetch, |
||
| 111 | }, |
||
| 112 | realpower => {
|
||
| 113 | filter => qr/^(.*)\.realpower(\.nominal)?$/, |
||
| 114 | title => 'UPS real power', |
||
| 115 | args => '--base 1000 -l 0', |
||
| 116 | vlabel => 'Watt', |
||
| 117 | config => \&common_config, |
||
| 118 | fetch => \&common_fetch, |
||
| 119 | }, |
||
| 120 | temperature => {
|
||
| 121 | filter => qr/^(.*)\.temperature$/, |
||
| 122 | title => 'UPS temperature', |
||
| 123 | args => '--base 1000 -l 0', |
||
| 124 | vlabel => 'Celsius', |
||
| 125 | config => \&common_config, |
||
| 126 | fetch => \&common_fetch, |
||
| 127 | }, |
||
| 128 | voltage => {
|
||
| 129 | filter => qr/^(.*)\.voltage(\.nominal)?$/, |
||
| 130 | title => 'UPS voltage', |
||
| 131 | args => '--base 1000 -l 0', |
||
| 132 | vlabel => 'Volt', |
||
| 133 | config => \&common_config, |
||
| 134 | fetch => \&common_fetch, |
||
| 135 | }, |
||
| 136 | ); |
||
| 137 | |||
| 138 | sub read_ups_values {
|
||
| 139 | my $ups = shift; |
||
| 140 | |||
| 141 | my @lines = `$UPSC $ups 2>/dev/null`; |
||
| 142 | my $values = {};
|
||
| 143 | for my $line (@lines) {
|
||
| 144 | chomp $line; |
||
| 145 | |||
| 146 | my ($key, $value) = $line =~ m/^([^:]+):\s+(\S.*)$/; |
||
| 147 | $values->{$key} = $value;
|
||
| 148 | } |
||
| 149 | return $values; |
||
| 150 | } |
||
| 151 | |||
| 152 | sub graph_config {
|
||
| 153 | my ($func, $ups, $values) = @_; |
||
| 154 | |||
| 155 | print "graph_title " . $config{$func}->{'title'} . " ($ups)\n";
|
||
| 156 | print "graph_vlabel " . $config{$func}->{'vlabel'} . "\n";
|
||
| 157 | print "graph_args " . $config{$func}->{'args'} . "\n";
|
||
| 158 | print "graph_category sensors\n"; |
||
| 159 | |||
| 160 | my @info; |
||
| 161 | push @info, 'Manufacturer: "' . $values->{'ups.mfr'} . '"'
|
||
| 162 | if exists $values->{'ups.mfr'} and $values->{'ups.mfr'} ne 'unknown';
|
||
| 163 | push @info, 'Model: "' . $values->{'ups.model'} . '"'
|
||
| 164 | if exists $values->{'ups.model'};
|
||
| 165 | push @info, 'Serial: "' . $values->{'ups.serial'} . '"'
|
||
| 166 | if exists $values->{'ups.serial'};
|
||
| 167 | map { s/\s+/ /g } @info;
|
||
| 168 | print "graph_info " . join(', ', @info) . "\n"
|
||
| 169 | if @info; |
||
| 170 | } |
||
| 171 | |||
| 172 | sub print_range_warning {
|
||
| 173 | my ($id, $key, $values) = @_; |
||
| 174 | |||
| 175 | if (exists $values->{$key . '.minimum'}) {
|
||
| 176 | print $id . ".min " . $values->{$key . '.minimum'} . "\n";
|
||
| 177 | } |
||
| 178 | if (exists $values->{$key . '.maximum'}) {
|
||
| 179 | print $id . ".max " . $values->{$key . '.maximum'} . "\n";
|
||
| 180 | } |
||
| 181 | |||
| 182 | my $range = ''; |
||
| 183 | if (exists $values->{$key . '.high'}) {
|
||
| 184 | $range = $values->{$key . '.high'};
|
||
| 185 | } |
||
| 186 | if (exists $values->{$key . '.low'}) {
|
||
| 187 | $range = $values->{$key . '.low'} . ':' . $range;
|
||
| 188 | } |
||
| 189 | # print_thresholds() needs 'undef' for no range |
||
| 190 | undef $range unless $range; |
||
| 191 | print_thresholds($id, undef, undef, undef, $range); |
||
| 192 | } |
||
| 193 | |||
| 194 | # Example keys for voltages: |
||
| 195 | # battery.voltage |
||
| 196 | # battery.voltage.minimum |
||
| 197 | # battery.voltage.maximum |
||
| 198 | # battery.voltage.nominal |
||
| 199 | # input.voltage |
||
| 200 | # input.voltage.minimum |
||
| 201 | # input.voltage.maximum |
||
| 202 | # input.bypass.L1-N.voltage |
||
| 203 | # input.L1-N.voltage |
||
| 204 | # output.voltage |
||
| 205 | # output.voltage.nominal |
||
| 206 | # output.L1-N.voltage |
||
| 207 | # |
||
| 208 | # Replace 'voltage' with 'current' in the above list to get an example |
||
| 209 | # for current keys. |
||
| 210 | # |
||
| 211 | # Frequency keys: |
||
| 212 | # input.frequency |
||
| 213 | # input.frequency.nominal |
||
| 214 | # input.bypass.frequency |
||
| 215 | # input.bypass.frequency.nominal |
||
| 216 | # output.frequency |
||
| 217 | # output.frequency.nominal |
||
| 218 | # output.frequency.minimum |
||
| 219 | # output.frequency.maximum |
||
| 220 | sub common_config {
|
||
| 221 | my ($func, $ups) = @_; |
||
| 222 | |||
| 223 | my $values = read_ups_values($ups); |
||
| 224 | graph_config($func, $ups, $values); |
||
| 225 | for my $key (sort keys %$values) {
|
||
| 226 | my ($field, $nominal) = $key =~ $config{$func}->{'filter'};
|
||
| 227 | next unless $field; |
||
| 228 | |||
| 229 | $field .= $nominal if $nominal; |
||
| 230 | my $id = clean_fieldname($field); |
||
| 231 | |||
| 232 | # These labels look better this way and are still short enough |
||
| 233 | $field = $key if $func =~ m/(charge|temperature|humidity)/; |
||
| 234 | |||
| 235 | # Beautification |
||
| 236 | $field = ucfirst($field); |
||
| 237 | $field =~ s/\./ /g; |
||
| 238 | |||
| 239 | print $id . ".label " . $field . "\n"; |
||
| 240 | print $id . ".type GAUGE\n"; |
||
| 241 | |||
| 242 | # Draw nominal values a litle thinner |
||
| 243 | print $id . ".draw LINE1\n" if $nominal; |
||
| 244 | |||
| 245 | print_range_warning($id, $key, $values); |
||
| 246 | } |
||
| 247 | } |
||
| 248 | |||
| 249 | sub common_fetch {
|
||
| 250 | my ($func, $ups) = @_; |
||
| 251 | |||
| 252 | my $values = read_ups_values($ups); |
||
| 253 | for my $key (sort keys %$values) {
|
||
| 254 | my ($field, $nominal) = $key =~ $config{$func}->{'filter'};
|
||
| 255 | next unless $field; |
||
| 256 | |||
| 257 | $field .= $nominal if $nominal; |
||
| 258 | my $id = clean_fieldname($field); |
||
| 259 | |||
| 260 | print $id . ".value " . $values->{$key} . "\n";
|
||
| 261 | } |
||
| 262 | } |
||
| 263 | |||
| 264 | if ($ARGV[0] and $ARGV[0] eq 'autoconf') {
|
||
| 265 | # The former nutups_ plugin parsed upsmon.conf. But for a large UPS |
||
| 266 | # that powers dozens or hundreds of machines, that would mean |
||
| 267 | # monitoring the same UPS from every host it powers, which does not |
||
| 268 | # make sense. Using upsc and defaulting to localhost means that |
||
| 269 | # 'autoconf' will only enable the plugin on the UPS master node, where |
||
| 270 | # upsd is running. |
||
| 271 | my @upses = `$UPSC -l 2>/dev/null`; |
||
| 272 | if ($?) {
|
||
| 273 | if ($? == -1) {
|
||
| 274 | print "no (program '$UPSC' was not found)\n"; |
||
| 275 | } |
||
| 276 | else {
|
||
| 277 | print "no (program '$UPSC -l' returned error)\n"; |
||
| 278 | } |
||
| 279 | exit 0; |
||
| 280 | } |
||
| 281 | |||
| 282 | map { chomp $_ } @upses;
|
||
| 283 | unless (@upses and $upses[0]) {
|
||
| 284 | print "no (program '$UPSC' listed no units)\n"; |
||
| 285 | } |
||
| 286 | else {
|
||
| 287 | print "yes\n"; |
||
| 288 | } |
||
| 289 | exit 0; |
||
| 290 | } |
||
| 291 | |||
| 292 | if ($ARGV[0] and $ARGV[0] eq 'suggest') {
|
||
| 293 | my @upses = `$UPSC -l 2>/dev/null`; |
||
| 294 | for my $ups (@upses) {
|
||
| 295 | chomp $ups; |
||
| 296 | for my $metric (keys %config) {
|
||
| 297 | my $values = read_ups_values($ups); |
||
| 298 | my @keys = grep { +$_ =~ $config{$metric}->{'filter'} } keys(%$values);
|
||
| 299 | print $ups . '_' . $metric . "\n" if @keys; |
||
| 300 | } |
||
| 301 | } |
||
| 302 | exit 0; |
||
| 303 | } |
||
| 304 | |||
| 305 | croak("Unknown command line arguments") if $ARGV[0] and $ARGV[0] ne 'config';
|
||
| 306 | |||
| 307 | # The UPS name may contain underscores |
||
| 308 | my $fn_re = join('|', keys %config);
|
||
| 309 | my ($ups, $func) = $0 =~ m/nutups2_(.*)_($fn_re)$/; |
||
| 310 | |||
| 311 | if ($ARGV[0] and $ARGV[0] eq 'config') {
|
||
| 312 | $config{$func}->{'config'}($func, $ups);
|
||
| 313 | } |
||
| 314 | else {
|
||
| 315 | $config{$func}->{'fetch'}($func, $ups);
|
||
| 316 | } |
||
| 317 | |||
| 318 | exit 0; |
