From de441cd5bf545596c71348bcf5167af970163205 Mon Sep 17 00:00:00 2001 From: qgarnier Date: Thu, 5 Aug 2021 10:46:01 +0200 Subject: [PATCH] enh(hp/p2000): missing entries and add psu component in health mode + add cpu metric for controllers + add ntp mode (#3019) --- .../hp/p2000/xmlapi/mode/components/fru.pm | 7 +- .../hp/p2000/xmlapi/mode/components/psu.pm | 83 +++++++ .../hp/p2000/xmlapi/mode/components/sensor.pm | 51 +++-- storage/hp/p2000/xmlapi/mode/controllers.pm | 12 +- storage/hp/p2000/xmlapi/mode/health.pm | 8 +- storage/hp/p2000/xmlapi/mode/ntp.pm | 216 ++++++++++++++++++ storage/hp/p2000/xmlapi/plugin.pm | 1 + 7 files changed, 352 insertions(+), 26 deletions(-) create mode 100644 storage/hp/p2000/xmlapi/mode/components/psu.pm create mode 100644 storage/hp/p2000/xmlapi/mode/ntp.pm diff --git a/storage/hp/p2000/xmlapi/mode/components/fru.pm b/storage/hp/p2000/xmlapi/mode/components/fru.pm index ed0fc0fce..24ff67667 100644 --- a/storage/hp/p2000/xmlapi/mode/components/fru.pm +++ b/storage/hp/p2000/xmlapi/mode/components/fru.pm @@ -29,7 +29,7 @@ sub check { $self->{output}->output_add(long_msg => "Checking frus"); $self->{components}->{fru} = {name => 'frus', total => 0, skip => 0}; return if ($self->check_filter(section => 'fru')); - + my ($entries) = $self->{custom}->get_infos( cmd => 'show frus', base_type => 'enclosure-fru', @@ -44,14 +44,13 @@ sub check { if (defined($results->{$name})) { $duplicated->{$name} = 1; my $instance = $results->{$name}->{'fru-location'} . ':' . $results->{$name}->{oid}; - $results->{$instance} = $results->{$name}; - delete $results->{$name}; + $results->{$instance} = delete $results->{$name}; $name = $_->{'fru-location'} . ':' . $_->{oid}; } $results->{$name} = $_; } - foreach my $instance (keys %$results) { + foreach my $instance (sort keys %$results) { next if ($self->check_filter(section => 'fru', instance => $instance)); $self->{components}->{fru}->{total}++; diff --git a/storage/hp/p2000/xmlapi/mode/components/psu.pm b/storage/hp/p2000/xmlapi/mode/components/psu.pm new file mode 100644 index 000000000..a5346e7b4 --- /dev/null +++ b/storage/hp/p2000/xmlapi/mode/components/psu.pm @@ -0,0 +1,83 @@ +# +# Copyright 2021 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package storage::hp::p2000::xmlapi::mode::components::psu; + +use strict; +use warnings; + +my %health = ( + 0 => 'ok', + 1 => 'degraded', + 2 => 'fault', + 3 => 'unknown', + 4 => 'not available' +); + +sub check { + my ($self) = @_; + + $self->{output}->output_add(long_msg => "Checking power supplies"); + $self->{components}->{psu} = { name => 'psu', total => 0, skip => 0 }; + return if ($self->check_filter(section => 'psu')); + + my ($entries) = $self->{custom}->get_infos( + cmd => 'show power-supplies', + base_type => 'power-supplies', + properties_name => '^durable-id|health-numeric|name$', + no_quit => 1 + ); + + my ($results, $duplicated) = ({}, {}); + foreach (@$entries) { + my $name = $_->{name}; + $name = $_->{name} . ':' . $_->{'durable-id'} if (defined($duplicated->{$name})); + if (defined($results->{$name})) { + $duplicated->{$name} = 1; + my $instance = $results->{$name}->{name} . ':' . $results->{$name}->{'durable-id'}; + $results->{$instance} = delete $results->{$name}; + $name = $_->{name} . ':' . $_->{'durable-id'}; + } + $results->{$name} = $_; + } + + foreach my $psu_id (sort keys %$results) { + next if ($self->check_filter(section => 'psu', instance => $results->{$psu_id}->{'durable-id'})); + $self->{components}->{psu}->{total}++; + + my $state = $health{$results->{$psu_id}->{'health-numeric'}}; + + $self->{output}->output_add( + long_msg => sprintf( + "power supply '%s' status is %s [instance: %s]", + $psu_id, $state, $results->{$psu_id}->{'durable-id'} + ) + ); + my $exit = $self->get_severity(label => 'default', section => 'psu', value => $state); + if (!$self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1)) { + $self->{output}->output_add( + severity => $exit, + short_msg => sprintf("Power supply '%s' status is '%s'", $psu_id, $state) + ); + } + } +} + +1; diff --git a/storage/hp/p2000/xmlapi/mode/components/sensor.pm b/storage/hp/p2000/xmlapi/mode/components/sensor.pm index 9a21daec8..74fa72489 100644 --- a/storage/hp/p2000/xmlapi/mode/components/sensor.pm +++ b/storage/hp/p2000/xmlapi/mode/components/sensor.pm @@ -27,13 +27,13 @@ my %sensor_type = ( # 2 it's other. Can be ok or '%'. Need to regexp 3 => { unit => 'C', nunit => 'celsius', type => 'temperature' }, 6 => { unit => 'V', nunit => 'volt', type => 'voltage' }, - 9 => { unit => 'V', nunit => 'volt', type => 'voltage' }, + 9 => { unit => 'V', nunit => 'volt', type => 'voltage' } ); my %units = ( C => { long => 'celsius', type => 'temperature' }, V => { long => 'volt', type => 'voltage' }, '' => { long => undef, type => 'misc' }, - '%' => { long => 'percentage', type => 'misc' }, + '%' => { long => 'percentage', type => 'misc' } ); sub check { @@ -42,15 +42,27 @@ sub check { $self->{output}->output_add(long_msg => "Checking sensor"); $self->{components}->{sensor} = {name => 'sensor', total => 0, skip => 0}; return if ($self->check_filter(section => 'sensor')); - + # We don't use status-numeric. Values are buggy !!!??? - my ($results) = $self->{custom}->get_infos( + my ($entries) = $self->{custom}->get_infos( cmd => 'show sensor-status', base_type => 'sensors', - key => 'sensor-name', - properties_name => '^(?:value|sensor-type|status)$' + properties_name => '^(?:sensor-name|value|sensor-type|status|enclosure-id)$' ); + my ($results, $duplicated) = ({}, {}); + foreach (@$entries) { + my $name = $_->{'sensor-name'}; + $name = $_->{'sensor-name'} . ':' . $_->{'enclosure-id'} if (defined($duplicated->{$name})); + if (defined($results->{$name})) { + $duplicated->{$name} = 1; + my $instance = $results->{$name}->{'sensor-name'} . ':' . $results->{$name}->{'enclosure-id'}; + $results->{$instance} = delete $results->{$name}; + $name = $_->{'sensor-name'} . ':' . $_->{'enclosure-id'}; + } + $results->{$name} = $_; + } + # # Capacitor Charge-Ctlr B # 100% @@ -61,7 +73,7 @@ sub check { # Warning # Warning # - foreach my $sensor_id (keys %$results) { + foreach my $sensor_id (sort keys %$results) { my ($value, $unit) = (undef, '');; ($value, $unit) = ($1, $2) if ($results->{$sensor_id}->{value} =~ /\s*([0-9\.,]+)\s*(\S*)\s*/); if (defined($results->{$sensor_id}->{'sensor-type'}) && defined($sensor_type{$results->{$sensor_id}->{'sensor-type'}})) { @@ -71,24 +83,31 @@ sub check { next if ($self->check_filter(section => 'sensor', instance => $type . '.' . $sensor_id)); $self->{components}->{sensor}->{total}++; - + my $state = $results->{$sensor_id}->{status}; - - $self->{output}->output_add(long_msg => sprintf("sensor '%s' status is %s (value: %s %s)", - $sensor_id, $state, defined($value) ? $value : '-', defined($unit) ? $unit : '-') - ); + + $self->{output}->output_add( + long_msg => sprintf( + "sensor '%s' status is %s (value: %s %s)", + $sensor_id, $state, defined($value) ? $value : '-', defined($unit) ? $unit : '-' + ) + ); my $exit = $self->get_severity(section => 'sensor', value => $state); if (!$self->{output}->is_status(value => $exit, compare => 'ok', litteral => 1)) { - $self->{output}->output_add(severity => $exit, - short_msg => sprintf("sensor '%s' status is '%s'", $sensor_id, $state)); + $self->{output}->output_add( + severity => $exit, + short_msg => sprintf("sensor '%s' status is '%s'", $sensor_id, $state) + ); } next if (!defined($value)); my ($exit2, $warn, $crit, $checked) = $self->get_severity_numeric(section => 'sensor', instance => $type . '.' . $sensor_id, value => $value); if (!$self->{output}->is_status(value => $exit2, compare => 'ok', litteral => 1)) { - $self->{output}->output_add(severity => $exit, - short_msg => sprintf("Sensor '%s' is %s %s", $sensor_id, $value, defined($unit) ? $unit : '-')); + $self->{output}->output_add( + severity => $exit, + short_msg => sprintf("Sensor '%s' is %s %s", $sensor_id, $value, defined($unit) ? $unit : '-') + ); } $self->{output}->perfdata_add( diff --git a/storage/hp/p2000/xmlapi/mode/controllers.pm b/storage/hp/p2000/xmlapi/mode/controllers.pm index 916315a36..dc1aff5f7 100644 --- a/storage/hp/p2000/xmlapi/mode/controllers.pm +++ b/storage/hp/p2000/xmlapi/mode/controllers.pm @@ -218,6 +218,14 @@ sub set_counters { ]; $self->{maps_counters}->{controller_stats} = [ + { label => 'cpu-utilization', nlabel => 'cpu.utilization.percentage', set => { + key_values => [ { name => 'cpu-load' } ], + output_template => 'cpu utilization: %.2f%%', + perfdatas => [ + { template => '%.2f', min => 0, max => 100, unit => '%', label_extra_instance => 1 } + ] + } + }, { label => 'read', nlabel => 'controller.io.read.usage.bytespersecond', set => { key_values => [ { name => 'data-read-numeric', per_second => 1 } ], output_template => 'read i/o: %s %s/s', @@ -353,7 +361,7 @@ sub manage_selection { my ($stats) = $options{custom}->get_infos( cmd => 'show controller-statistics', base_type => 'controller-statistics', - properties_name => '^durable-id|data-read-numeric|data-written-numeric|write-cache-hits|write-cache-misses|read-cache-hits|read-cache-misses|iops$' + properties_name => '^durable-id|cpu-load|data-read-numeric|data-written-numeric|write-cache-hits|write-cache-misses|read-cache-hits|read-cache-misses|iops$' ); foreach (@$stats) { @@ -459,7 +467,7 @@ Can used special variables like: %{status}, %{name} =item B<--warning-*> B<--critical-*> Thresholds. -Can be: 'read', 'write', 'iops', 'write-cache-hits', 'read-cache-hits'. +Can be: 'cpu-utilization', 'read', 'write', 'iops', 'write-cache-hits', 'read-cache-hits'. =back diff --git a/storage/hp/p2000/xmlapi/mode/health.pm b/storage/hp/p2000/xmlapi/mode/health.pm index 97f196a80..7ce1d0e4b 100644 --- a/storage/hp/p2000/xmlapi/mode/health.pm +++ b/storage/hp/p2000/xmlapi/mode/health.pm @@ -29,7 +29,7 @@ sub set_system { my ($self, %options) = @_; $self->{regexp_threshold_numeric_check_section_option} = - '^(sensor)$'; + '^(?:sensor)$'; $self->{cb_hook1} = 'init_health'; @@ -52,12 +52,12 @@ sub set_system { ['warning|not installed|unavailable', 'WARNING'], ['error|unrecoverable', 'CRITICAL'], ['unknown|unsupported', 'UNKNOWN'] - ], + ] }; $self->{components_exec_load} = 0; $self->{components_path} = 'storage::hp::p2000::xmlapi::mode::components'; - $self->{components_module} = ['disk', 'enclosure', 'fru', 'sensor', 'vdisk']; + $self->{components_module} = ['disk', 'enclosure', 'fru', 'psu', 'sensor', 'vdisk']; } sub init_health { @@ -90,7 +90,7 @@ Check health status of storage. =item B<--component> Which component to check (Default: '.*'). -Can be: 'disk', 'enclosure', 'fru', 'sensor', 'vdisk'. +Can be: 'disk', 'enclosure', 'fru', 'psu', 'sensor', 'vdisk'. =item B<--filter> diff --git a/storage/hp/p2000/xmlapi/mode/ntp.pm b/storage/hp/p2000/xmlapi/mode/ntp.pm new file mode 100644 index 000000000..8e3cf8924 --- /dev/null +++ b/storage/hp/p2000/xmlapi/mode/ntp.pm @@ -0,0 +1,216 @@ +# +# Copyright 2021 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package storage::hp::p2000::xmlapi::mode::ntp; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use centreon::plugins::misc; +use DateTime; +use POSIX; +use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); + +my $unitdiv = { s => 1, w => 604800, d => 86400, h => 3600, m => 60 }; +my $unitdiv_long = { s => 'seconds', w => 'weeks', d => 'days', h => 'hours', m => 'minutes' }; + +sub custom_contact_perfdata { + my ($self, %options) = @_; + + $self->{output}->perfdata_add( + nlabel => $self->{nlabel} . '.' . $unitdiv_long->{ $self->{instance_mode}->{option_results}->{unit} }, + unit => $self->{instance_mode}->{option_results}->{unit}, + value => floor($self->{result_values}->{contact_seconds} / $unitdiv->{ $self->{instance_mode}->{option_results}->{unit} }), + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}), + min => 0 + ); +} + +sub custom_contact_threshold { + my ($self, %options) = @_; + + return $self->{perfdata}->threshold_check( + value => floor($self->{result_values}->{contact_seconds} / $unitdiv->{ $self->{instance_mode}->{option_results}->{unit} }), + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-'. $self->{thlabel}, exit_litteral => 'warning' }, + { label => 'unknown-'. $self->{thlabel}, exit_litteral => 'unknown' } + ] + ); +} + +sub custom_status_output { + my ($self, %options) = @_; + + return sprintf('status is %s', $self->{result_values}->{status}); +} + +sub license_long_output { + my ($self, %options) = @_; + + return 'checking ntp'; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'ntp', type => 3, cb_long_output => 'ntp_long_output', indent_long_output => ' ', + group => [ + { name => 'status', type => 0, display_short => 0, skipped_code => { -10 => 1 } }, + { name => 'contact', type => 0, display_short => 0, skipped_code => { -10 => 1 } } + ] + } + ]; + + $self->{maps_counters}->{status} = [ + { + label => 'status', + type => 2, + critical_default => '%{status} =~ /deactivated/i', + set => { + key_values => [ { name => 'status' } ], + closure_custom_output => $self->can('custom_status_output'), + closure_custom_perfdata => sub { return 0; }, + closure_custom_threshold_check => \&catalog_status_threshold_ng + } + } + ]; + + $self->{maps_counters}->{contact} = [ + { label => 'contact-last-time', nlabel => 'ntp.contact.last.time.', set => { + key_values => [ { name => 'contact_seconds' }, { name => 'contact_human' } ], + output_template => 'last server contact: %s', + output_use => 'contact_human', + closure_custom_perfdata => $self->can('custom_contact_perfdata'), + closure_custom_threshold_check => $self->can('custom_contact_threshold') + } + } + ]; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); + bless $self, $class; + + $options{options}->add_options(arguments => { + 'unit:s' => { name => 'unit', default => 's' }, + 'timezone:s' => { name => 'timezone' } + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::check_options(%options); + + if ($self->{option_results}->{unit} eq '' || !defined($unitdiv->{$self->{option_results}->{unit}})) { + $self->{option_results}->{unit} = 's'; + } + $self->{option_results}->{timezone} = 'UTC' if (!defined($self->{option_results}->{timezone}) || $self->{option_results}->{timezone} eq ''); +} + +sub manage_selection { + my ($self, %options) = @_; + + my ($result) = $options{custom}->get_infos( + cmd => 'show ntp-status', + base_type => 'ntp-status', + properties_name => '^ntp-status|ntp-contact-time$' + ); + + if (!defined($result->[0])) { + $self->{output}->add_option_msg(short_msg => 'cannot get informations'); + $self->{output}->option_exit(); + } + + $self->{output}->output_add(short_msg => 'ntp is ok'); + + $self->{ntp} = { + global => { + status => { status => $result->[0]->{'ntp-status'} }, + contact => {} + } + }; + + if ($result->[0]->{'ntp-contact-time'} =~ /^(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)$/) { + my $tz = centreon::plugins::misc::set_timezone(name => $self->{option_results}->{timezone}); + my $dt = DateTime->new( + year => $1, + month => $2, + day => $3, + hour => $4, + minute => $5, + second => $6, + %$tz + ); + $self->{ntp}->{global}->{contact}->{contact_seconds} = time() - $dt->epoch(); + $self->{ntp}->{global}->{contact}->{contact_human} = centreon::plugins::misc::change_seconds( + value => $self->{ntp}->{global}->{contact}->{contact_seconds} + ); + } +} + +1; + +__END__ + +=head1 MODE + +Check ntp status. + +=over 8 + +=item B<--filter-counters> + +Only display some counters (regexp can be used). +Example: --filter-counters='status' + +=item B<--warning-status> + +Set warning threshold for status. +Can use special variables like: %{status} + +=item B<--critical-status> + +Set critical threshold for status (Default: '%{status} =~ /deactivated/i'). +Can use special variables like: %{status} + +=item B<--timezone> + +Set timezone for ntp contact time (Default is 'UTC'). + +=item B<--unit> + +Select the unit for contact threshold. May be 's' for seconds, 'm' for minutes, +'h' for hours, 'd' for days, 'w' for weeks. Default is days. + +=item B<--warning-*> B<--critical-*> + +Thresholds. +Can be: 'contact-last-time'. + +=back + +=cut diff --git a/storage/hp/p2000/xmlapi/plugin.pm b/storage/hp/p2000/xmlapi/plugin.pm index 997f5bf9d..8fe23d29c 100644 --- a/storage/hp/p2000/xmlapi/plugin.pm +++ b/storage/hp/p2000/xmlapi/plugin.pm @@ -35,6 +35,7 @@ sub new { 'controllers' => 'storage::hp::p2000::xmlapi::mode::controllers', 'health' => 'storage::hp::p2000::xmlapi::mode::health', 'list-volumes' => 'storage::hp::p2000::xmlapi::mode::listvolumes', + 'ntp' => 'storage::hp::p2000::xmlapi::mode::ntp', 'vdisks' => 'storage::hp::p2000::xmlapi::mode::vdisks', 'volume-stats' => 'storage::hp::p2000::xmlapi::mode::volumesstats' };