diff --git a/src/storage/exagrid/snmp/mode/dataprotection.pm b/src/storage/exagrid/snmp/mode/dataprotection.pm new file mode 100644 index 0000000000..ea45149fab --- /dev/null +++ b/src/storage/exagrid/snmp/mode/dataprotection.pm @@ -0,0 +1,694 @@ +# +# Copyright 2026-Present 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::exagrid::snmp::mode::dataprotection; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use POSIX qw(floor); +use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); + +# ------------------------------------------------------------------------- +# Status counter +# ------------------------------------------------------------------------- +sub custom_status_output { + my ($self, %options) = @_; + return 'Server alarm state: ' . $self->{result_values}->{status}; +} + +# ------------------------------------------------------------------------- +# Deduplication ratio +# ------------------------------------------------------------------------- +sub custom_dedup_ratio_calc { + my ($self, %options) = @_; + $self->{result_values}->{dedup_ratio} = + ($options{new_datas}->{ $self->{instance} . '_backup_consumed' } > 0) + ? $options{new_datas}->{ $self->{instance} . '_backup_available' } / + $options{new_datas}->{ $self->{instance} . '_backup_consumed' } + : 1; + return 0; +} + +sub custom_dedup_ratio_output { + my ($self, %options) = @_; + return sprintf('Deduplication ratio: %.2f:1', $self->{result_values}->{dedup_ratio}); +} + +sub custom_dedup_ratio_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => 'dedup_ratio', + value => sprintf('%.2f', $self->{result_values}->{dedup_ratio}), + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{thlabel}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{thlabel}), + min => 1 + ); +} + +sub custom_dedup_ratio_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{dedup_ratio}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ------------------------------------------------------------------------- +# Backup data available for restore +# ------------------------------------------------------------------------- +sub custom_backup_available_calc { + my ($self, %options) = @_; + $self->{result_values}->{bytes} = + $options{new_datas}->{ $self->{instance} . '_backup_available_whole' } * 1_073_741_824 + + $options{new_datas}->{ $self->{instance} . '_backup_available_frac' }; + return 0; +} + +sub custom_backup_available_output { + my ($self, %options) = @_; + my ($val, $unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{bytes}); + return sprintf('Backup data available for restore: %s %s', $val, $unit); +} + +sub custom_backup_available_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => 'backup_data_available', + unit => 'B', + value => $self->{result_values}->{bytes}, + 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_backup_available_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{bytes}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ------------------------------------------------------------------------- +# Pending deduplication (bytes) +# ------------------------------------------------------------------------- +sub custom_pending_dedup_calc { + my ($self, %options) = @_; + $self->{result_values}->{bytes} = + $options{new_datas}->{ $self->{instance} . '_pending_dedup_whole' } * 1_073_741_824 + + $options{new_datas}->{ $self->{instance} . '_pending_dedup_frac' }; + return 0; +} + +sub custom_pending_dedup_output { + my ($self, %options) = @_; + my ($val, $unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{bytes}); + return sprintf('Pending deduplication: %s %s', $val, $unit); +} + +sub custom_pending_dedup_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => 'pending_dedup', + unit => 'B', + value => $self->{result_values}->{bytes}, + 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_pending_dedup_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{bytes}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ------------------------------------------------------------------------- +# Pending deduplication age +# ------------------------------------------------------------------------- +sub custom_pending_dedup_age_calc { + my ($self, %options) = @_; + # SNMP TimeTicks are hundredths of a second + $self->{result_values}->{age_sec} = + $options{new_datas}->{ $self->{instance} . '_pending_dedup_age' } / 100; + return 0; +} + +sub custom_pending_dedup_age_output { + my ($self, %options) = @_; + return sprintf('Pending deduplication age: %s', _format_duration($self->{result_values}->{age_sec})); +} + +sub custom_pending_dedup_age_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => 'pending_dedup_age', + unit => 's', + value => $self->{result_values}->{age_sec}, + 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_pending_dedup_age_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{age_sec}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ------------------------------------------------------------------------- +# Pending replication (bytes) +# ------------------------------------------------------------------------- +sub custom_pending_repl_calc { + my ($self, %options) = @_; + $self->{result_values}->{bytes} = + $options{new_datas}->{ $self->{instance} . '_pending_repl_whole' } * 1_073_741_824 + + $options{new_datas}->{ $self->{instance} . '_pending_repl_frac' }; + return 0; +} + +sub custom_pending_repl_output { + my ($self, %options) = @_; + my ($val, $unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{bytes}); + return sprintf('Pending replication: %s %s', $val, $unit); +} + +sub custom_pending_repl_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => 'pending_replication', + unit => 'B', + value => $self->{result_values}->{bytes}, + 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_pending_repl_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{bytes}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ------------------------------------------------------------------------- +# Pending replication age +# ------------------------------------------------------------------------- +sub custom_pending_repl_age_calc { + my ($self, %options) = @_; + $self->{result_values}->{age_sec} = + $options{new_datas}->{ $self->{instance} . '_pending_repl_age' } / 100; + return 0; +} + +sub custom_pending_repl_age_output { + my ($self, %options) = @_; + return sprintf('Pending replication age: %s', _format_duration($self->{result_values}->{age_sec})); +} + +sub custom_pending_repl_age_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => 'pending_replication_age', + unit => 's', + value => $self->{result_values}->{age_sec}, + 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_pending_repl_age_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{age_sec}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ------------------------------------------------------------------------- +# Retention-locked (pending purge) space +# ------------------------------------------------------------------------- +sub custom_pending_purge_calc { + my ($self, %options) = @_; + $self->{result_values}->{bytes} = + $options{new_datas}->{ $self->{instance} . '_pending_purge_whole' } * 1_073_741_824 + + $options{new_datas}->{ $self->{instance} . '_pending_purge_frac' }; + return 0; +} + +sub custom_pending_purge_output { + my ($self, %options) = @_; + my ($val, $unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{bytes}); + return sprintf('Retention-locked (pending purge) space: %s %s', $val, $unit); +} + +sub custom_pending_purge_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => 'pending_purge', + unit => 'B', + value => $self->{result_values}->{bytes}, + 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_pending_purge_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{bytes}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ------------------------------------------------------------------------- +# I/O rate helpers (read / write / dedup share the same output/perf/thr subs) +# ------------------------------------------------------------------------- +sub custom_rate_output { + my ($self, %options) = @_; + return sprintf('%s rate: %.2f MB/s', + ucfirst($self->{result_values}->{rate_label}), + $self->{result_values}->{rate_mbps}); +} + +sub custom_rate_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => $self->{result_values}->{rate_label} . '_rate', + unit => 'MB/s', + value => sprintf('%.2f', $self->{result_values}->{rate_mbps}), + 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_rate_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{rate_mbps}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +sub custom_read_rate_calc { + my ($self, %options) = @_; + $self->{result_values}->{rate_label} = 'read'; + $self->{result_values}->{rate_mbps} = $options{new_datas}->{ $self->{instance} . '_read_rate' }; + return 0; +} + +sub custom_write_rate_calc { + my ($self, %options) = @_; + $self->{result_values}->{rate_label} = 'write'; + $self->{result_values}->{rate_mbps} = $options{new_datas}->{ $self->{instance} . '_write_rate' }; + return 0; +} + +sub custom_dedup_rate_calc { + my ($self, %options) = @_; + $self->{result_values}->{rate_label} = 'dedup'; + $self->{result_values}->{rate_mbps} = $options{new_datas}->{ $self->{instance} . '_dedup_rate' }; + return 0; +} + +# ------------------------------------------------------------------------- +# Internal helper +# ------------------------------------------------------------------------- +sub _format_duration { + my ($sec) = @_; + return '0s' unless defined $sec && $sec > 0; + my $d = floor($sec / 86400); $sec -= $d * 86400; + my $h = floor($sec / 3600); $sec -= $h * 3600; + my $m = floor($sec / 60); $sec -= $m * 60; + my @parts; + push @parts, "${d}d" if $d; + push @parts, "${h}h" if $h; + push @parts, "${m}m" if $m; + push @parts, "${sec}s" if $sec || !@parts; + return join(' ', @parts); +} + +# ------------------------------------------------------------------------- +# Counter map — type => 0 (global), type => 2 (status text) +# matching the style of serverusage.pm exactly; no constants.pm needed +# ------------------------------------------------------------------------- +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => 0, message_separator => ' - ' } + ]; + + $self->{maps_counters}->{global} = [ + + { label => 'status', type => 2, + warning_default => '%{status} =~ /warning/i', + critical_default => '%{status} =~ /error/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 + } + }, + + { label => 'dedup-ratio', set => { + key_values => [ { name => 'backup_available' }, { name => 'backup_consumed' } ], + closure_custom_calc => $self->can('custom_dedup_ratio_calc'), + closure_custom_output => $self->can('custom_dedup_ratio_output'), + closure_custom_perfdata => $self->can('custom_dedup_ratio_perfdata'), + closure_custom_threshold_check => $self->can('custom_dedup_ratio_threshold') + }}, + + { label => 'backup-available', set => { + key_values => [ { name => 'backup_available_whole' }, { name => 'backup_available_frac' } ], + closure_custom_calc => $self->can('custom_backup_available_calc'), + closure_custom_output => $self->can('custom_backup_available_output'), + closure_custom_perfdata => $self->can('custom_backup_available_perfdata'), + closure_custom_threshold_check => $self->can('custom_backup_available_threshold') + }}, + + { label => 'pending-dedup', set => { + key_values => [ { name => 'pending_dedup_whole' }, { name => 'pending_dedup_frac' } ], + closure_custom_calc => $self->can('custom_pending_dedup_calc'), + closure_custom_output => $self->can('custom_pending_dedup_output'), + closure_custom_perfdata => $self->can('custom_pending_dedup_perfdata'), + closure_custom_threshold_check => $self->can('custom_pending_dedup_threshold') + }}, + + { label => 'pending-dedup-age', set => { + key_values => [ { name => 'pending_dedup_age' } ], + closure_custom_calc => $self->can('custom_pending_dedup_age_calc'), + closure_custom_output => $self->can('custom_pending_dedup_age_output'), + closure_custom_perfdata => $self->can('custom_pending_dedup_age_perfdata'), + closure_custom_threshold_check => $self->can('custom_pending_dedup_age_threshold') + }}, + + { label => 'pending-replication', set => { + key_values => [ { name => 'pending_repl_whole' }, { name => 'pending_repl_frac' } ], + closure_custom_calc => $self->can('custom_pending_repl_calc'), + closure_custom_output => $self->can('custom_pending_repl_output'), + closure_custom_perfdata => $self->can('custom_pending_repl_perfdata'), + closure_custom_threshold_check => $self->can('custom_pending_repl_threshold') + }}, + + { label => 'pending-replication-age', set => { + key_values => [ { name => 'pending_repl_age' } ], + closure_custom_calc => $self->can('custom_pending_repl_age_calc'), + closure_custom_output => $self->can('custom_pending_repl_age_output'), + closure_custom_perfdata => $self->can('custom_pending_repl_age_perfdata'), + closure_custom_threshold_check => $self->can('custom_pending_repl_age_threshold') + }}, + + { label => 'pending-purge', set => { + key_values => [ { name => 'pending_purge_whole' }, { name => 'pending_purge_frac' } ], + closure_custom_calc => $self->can('custom_pending_purge_calc'), + closure_custom_output => $self->can('custom_pending_purge_output'), + closure_custom_perfdata => $self->can('custom_pending_purge_perfdata'), + closure_custom_threshold_check => $self->can('custom_pending_purge_threshold') + }}, + + { label => 'read-rate', set => { + key_values => [ { name => 'read_rate' } ], + closure_custom_calc => $self->can('custom_read_rate_calc'), + closure_custom_output => $self->can('custom_rate_output'), + closure_custom_perfdata => $self->can('custom_rate_perfdata'), + closure_custom_threshold_check => $self->can('custom_rate_threshold') + }}, + + { label => 'write-rate', set => { + key_values => [ { name => 'write_rate' } ], + closure_custom_calc => $self->can('custom_write_rate_calc'), + closure_custom_output => $self->can('custom_rate_output'), + closure_custom_perfdata => $self->can('custom_rate_perfdata'), + closure_custom_threshold_check => $self->can('custom_rate_threshold') + }}, + + { label => 'dedup-rate', set => { + key_values => [ { name => 'dedup_rate' } ], + closure_custom_calc => $self->can('custom_dedup_rate_calc'), + closure_custom_output => $self->can('custom_rate_output'), + closure_custom_perfdata => $self->can('custom_rate_perfdata'), + closure_custom_threshold_check => $self->can('custom_rate_threshold') + }}, + ]; +} + +# ------------------------------------------------------------------------- +# Constructor +# ------------------------------------------------------------------------- +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $options{options}->add_options(arguments => {}); + + return $self; +} + +# ------------------------------------------------------------------------- +# OID mapping — same approach as serverusage.pm +# ------------------------------------------------------------------------- +my $map_status = { + 1 => 'ok', + 2 => 'warning', + 3 => 'error' +}; + +my $mapping = { + egBackupDataAvailableWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.3.1', default => 0 }, + egBackupDataAvailableFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.3.2', default => 0 }, + egBackupDataSpaceConsumedWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.3.3', default => 0 }, + egBackupDataSpaceConsumedFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.3.4', default => 0 }, + egPendingDeduplicationWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.4.1', default => 0 }, + egPendingDeduplicationFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.4.2', default => 0 }, + egPendingDeduplicationAge => { oid => '.1.3.6.1.4.1.14941.4.4.3', default => 0 }, + egPendingReplicationWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.5.1', default => 0 }, + egPendingReplicationFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.5.2', default => 0 }, + egPendingReplicationAge => { oid => '.1.3.6.1.4.1.14941.4.5.3', default => 0 }, + egServerAlarmState => { oid => '.1.3.6.1.4.1.14941.4.6.1', map => $map_status }, + egServerReadRateMegabytesSec => { oid => '.1.3.6.1.4.1.14941.4.7.1', default => 0 }, + egServerWriteRateMegabytesSec => { oid => '.1.3.6.1.4.1.14941.4.7.2', default => 0 }, + egServerDedupRateMegabytesSec => { oid => '.1.3.6.1.4.1.14941.4.7.3', default => 0 }, + egRetentionSpaceAllPendingPurgeBytesWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.13', default => 0 }, + egRetentionSpaceAllPendingPurgeBytesFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.14', default => 0 }, +}; + +my $oid_exagridServerData = '.1.3.6.1.4.1.14941.4'; + +# ------------------------------------------------------------------------- +# Data collection +# ------------------------------------------------------------------------- +sub manage_selection { + my ($self, %options) = @_; + + my $snmp_result = $options{snmp}->get_table( + oid => $oid_exagridServerData, + nothing_quit => 1 + ); + + my $result = $options{snmp}->map_instance( + mapping => $mapping, + results => $snmp_result, + instance => '0' + ); + + $self->{global} = { + status => $result->{egServerAlarmState}, + + backup_available => $result->{egBackupDataAvailableWholeGigabytes} * 1_073_741_824 + + $result->{egBackupDataAvailableFractionalGigabytes}, + backup_consumed => $result->{egBackupDataSpaceConsumedWholeGigabytes} * 1_073_741_824 + + $result->{egBackupDataSpaceConsumedFractionalGigabytes}, + + backup_available_whole => $result->{egBackupDataAvailableWholeGigabytes}, + backup_available_frac => $result->{egBackupDataAvailableFractionalGigabytes}, + + pending_dedup_whole => $result->{egPendingDeduplicationWholeGigabytes}, + pending_dedup_frac => $result->{egPendingDeduplicationFractionalGigabytes}, + pending_dedup_age => $result->{egPendingDeduplicationAge}, + + pending_repl_whole => $result->{egPendingReplicationWholeGigabytes}, + pending_repl_frac => $result->{egPendingReplicationFractionalGigabytes}, + pending_repl_age => $result->{egPendingReplicationAge}, + + pending_purge_whole => $result->{egRetentionSpaceAllPendingPurgeBytesWholeGigabytes}, + pending_purge_frac => $result->{egRetentionSpaceAllPendingPurgeBytesFractionalGigabytes}, + + read_rate => $result->{egServerReadRateMegabytesSec}, + write_rate => $result->{egServerWriteRateMegabytesSec}, + dedup_rate => $result->{egServerDedupRateMegabytesSec}, + }; +} + +1; + +__END__ + +=head1 MODE + +Check ExaGrid data-protection health: deduplication ratio and throughput, +pending deduplication and replication (bytes + age), retention-locked +(pending-purge) space, backup data available for restore, and live I/O rates. + +=over 8 + +=item B<--filter-counters> + +Only display some counters (regexp can be used). +Example: --filter-counters='^(status|dedup-ratio|write-rate)$' + +=item B<--warning-status> + +Condition for WARNING alarm state (default: '%{status} =~ /warning/i'). +Available variable: %{status} + +=item B<--critical-status> + +Condition for CRITICAL alarm state (default: '%{status} =~ /error/i'). +Available variable: %{status} + +=item B<--warning-dedup-ratio> + +Warning threshold for deduplication ratio. Use range syntax to alert when +the ratio drops below a minimum: --warning-dedup-ratio='2:' + +=item B<--critical-dedup-ratio> + +Critical threshold for deduplication ratio. +Example: --critical-dedup-ratio='1.5:' + +=item B<--warning-backup-available> + +Warning threshold for backup data available for restore (bytes). + +=item B<--critical-backup-available> + +Critical threshold for backup data available for restore (bytes). + +=item B<--warning-pending-dedup> + +Warning threshold for pending deduplication data (bytes). +Example: --warning-pending-dedup='53687091200' (50 GB) + +=item B<--critical-pending-dedup> + +Critical threshold for pending deduplication data (bytes). + +=item B<--warning-pending-dedup-age> + +Warning threshold for pending deduplication age (seconds). +Example: --warning-pending-dedup-age='14400' (4 hours) + +=item B<--critical-pending-dedup-age> + +Critical threshold for pending deduplication age (seconds). + +=item B<--warning-pending-replication> + +Warning threshold for pending replication data (bytes). + +=item B<--critical-pending-replication> + +Critical threshold for pending replication data (bytes). + +=item B<--warning-pending-replication-age> + +Warning threshold for pending replication lag (seconds). +Example: --warning-pending-replication-age='14400' (4 hours) + +=item B<--critical-pending-replication-age> + +Critical threshold for pending replication lag (seconds). + +=item B<--warning-pending-purge> + +Warning threshold for retention-locked (pending-purge) space (bytes). + +=item B<--critical-pending-purge> + +Critical threshold for retention-locked (pending-purge) space (bytes). + +=item B<--warning-read-rate> + +Warning threshold for read throughput (MB/s). + +=item B<--critical-read-rate> + +Critical threshold for read throughput (MB/s). + +=item B<--warning-write-rate> + +Warning threshold for write throughput (MB/s). + +=item B<--critical-write-rate> + +Critical threshold for write throughput (MB/s). + +=item B<--warning-dedup-rate> + +Warning threshold for deduplication throughput (MB/s). + +=item B<--critical-dedup-rate> + +Critical threshold for deduplication throughput (MB/s). + +=back + +=cut diff --git a/src/storage/exagrid/snmp/mode/serverusage.pm b/src/storage/exagrid/snmp/mode/serverusage.pm index 07c174b040..30e20293c3 100644 --- a/src/storage/exagrid/snmp/mode/serverusage.pm +++ b/src/storage/exagrid/snmp/mode/serverusage.pm @@ -1,10 +1,5 @@ -# # Copyright 2026-Present 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 @@ -16,7 +11,6 @@ # 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::exagrid::snmp::mode::serverusage; @@ -24,123 +18,515 @@ use base qw(centreon::plugins::templates::counter); use strict; use warnings; -use centreon::plugins::constants qw/:counters/; use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); +# ============================================================================= +# Internal helper +# ============================================================================= +sub _timeticks_to_seconds { return int( ( $_[0] // 0 ) / 100 ) } + +# ============================================================================= +# STATUS +# ============================================================================= sub custom_status_output { my ($self, %options) = @_; - return 'Server Status : ' . $self->{result_values}->{status}; } -sub custom_usage_perfdata { +# ============================================================================= +# LANDING ZONE — available bytes +# ============================================================================= +sub custom_landing_available_calc { my ($self, %options) = @_; - - my $label = 'used'; - my $value_perf = $self->{result_values}->{used}; - my %total_options = ( total => $self->{result_values}->{total}, cast_int => 1); + $self->{result_values}->{landing_available} = + $options{new_datas}->{ $self->{instance} . '_landing_available_whole' } * 1_000_000_000 + + $options{new_datas}->{ $self->{instance} . '_landing_available_frac' }; + return 0; +} - $self->{output}->perfdata_add( - label => $self->{result_values}->{label} . '_' . $label, unit => 'B', - value => $value_perf, - warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}, %total_options), - critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}, %total_options), - min => 0, - max => $self->{result_values}->{total} - ); +sub custom_landing_available_output { + my ($self, %options) = @_; + my ($val, $unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{landing_available}); + return sprintf('Landing Zone available for next backup: %s %s', $val, $unit); } -sub custom_usage_threshold { +sub custom_landing_available_perfdata { my ($self, %options) = @_; - - my $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{prct_used}, threshold => [ { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, { label => 'warning-'. $self->{thlabel}, exit_litteral => 'warning' } ]); - return $exit; + $self->{output}->perfdata_add( + label => 'landing_available', + unit => 'B', + value => $self->{result_values}->{landing_available}, + 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_usage_output { +sub custom_landing_available_threshold { my ($self, %options) = @_; - - my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); - my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); - my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); - return sprintf( - '%s Usage Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)', - ucfirst($self->{result_values}->{label}), - $total_size_value . " " . $total_size_unit, - $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, - $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free} + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{landing_available}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] ); } +# ============================================================================= +# RETENTION SPACE — used% (unchanged from working version) +# ============================================================================= sub custom_usage_calc { my ($self, %options) = @_; - $self->{result_values}->{label} = $options{extra_options}->{label_ref}; - $self->{result_values}->{total} = $options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{label} . '_total'}; - $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{label} . '_used'}; + $self->{result_values}->{total} = $options{new_datas}->{ $self->{instance} . '_' . $self->{result_values}->{label} . '_total' }; + $self->{result_values}->{used} = $options{new_datas}->{ $self->{instance} . '_' . $self->{result_values}->{label} . '_used' }; if ($self->{result_values}->{total} != 0) { - $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; + $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; $self->{result_values}->{prct_used} = $self->{result_values}->{used} * 100 / $self->{result_values}->{total}; $self->{result_values}->{prct_free} = 100 - $self->{result_values}->{prct_used}; } else { - $self->{result_values}->{free} = 0; + $self->{result_values}->{free} = 0; $self->{result_values}->{prct_used} = 0; $self->{result_values}->{prct_free} = 0; } return 0; } +sub custom_usage_output { + my ($self, %options) = @_; + my ($tv, $tu) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); + my ($uv, $uu) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); + my ($fv, $fu) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); + return sprintf( + '%s Usage Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)', + ucfirst($self->{result_values}->{label}), + $tv . ' ' . $tu, + $uv . ' ' . $uu, $self->{result_values}->{prct_used}, + $fv . ' ' . $fu, $self->{result_values}->{prct_free} + ); +} + +sub custom_usage_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => $self->{result_values}->{label} . '_used', + unit => 'B', + value => $self->{result_values}->{used}, + warning => $self->{perfdata}->get_perfdata_for_output( + label => 'warning-' . $self->{thlabel}, + total => $self->{result_values}->{total}, + cast_int => 1 + ), + critical => $self->{perfdata}->get_perfdata_for_output( + label => 'critical-' . $self->{thlabel}, + total => $self->{result_values}->{total}, + cast_int => 1 + ), + min => 0, + max => $self->{result_values}->{total} + ); +} + +sub custom_usage_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{prct_used}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ============================================================================= +# RETENTION BREAKDOWN — absolute bytes (primary, replica, pending-purge) +# ============================================================================= +sub custom_retention_detail_calc { + my ($self, %options) = @_; + my $key = $options{extra_options}->{key_ref}; + $self->{result_values}->{label} = $options{extra_options}->{label_ref}; + $self->{result_values}->{bytes} = $options{new_datas}->{ $self->{instance} . '_' . $key } // 0; + return 0; +} + +sub custom_retention_detail_output { + my ($self, %options) = @_; + my ($v, $u) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{bytes}); + return sprintf('%s: %s %s', $self->{result_values}->{label}, $v, $u); +} + +sub custom_retention_detail_perfdata { + my ($self, %options) = @_; + my $perfkey = lc($self->{result_values}->{label}); + $perfkey =~ s/\s+/_/g; + $self->{output}->perfdata_add( + label => $perfkey, + unit => 'B', + value => $self->{result_values}->{bytes}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}), + min => 0 + ); +} + +sub custom_retention_detail_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{bytes}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ============================================================================= +# DEDUP RATIO +# ============================================================================= +sub custom_dedup_ratio_calc { + my ($self, %options) = @_; + $self->{result_values}->{dedup_ratio} = $options{new_datas}->{ $self->{instance} . '_dedup_ratio' } // 0; + return 0; +} + +sub custom_dedup_ratio_output { + my ($self, %options) = @_; + return sprintf('Deduplication Ratio: %.2f:1', $self->{result_values}->{dedup_ratio}); +} + +sub custom_dedup_ratio_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => 'dedup_ratio', + value => sprintf('%.4f', $self->{result_values}->{dedup_ratio}), + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}), + min => 0 + ); +} + +sub custom_dedup_ratio_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{dedup_ratio}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ============================================================================= +# PENDING BYTES (dedup / replication) +# ============================================================================= +sub custom_pending_bytes_calc { + my ($self, %options) = @_; + my $lbl = $options{extra_options}->{label_ref}; + $self->{result_values}->{label} = $lbl; + $self->{result_values}->{bytes} = $options{new_datas}->{ $self->{instance} . '_' . $lbl . '_pending_bytes' } // 0; + return 0; +} + +sub custom_pending_bytes_output { + my ($self, %options) = @_; + my ($v, $u) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{bytes}); + return sprintf('Pending %s: %s %s', ucfirst($self->{result_values}->{label}), $v, $u); +} + +sub custom_pending_bytes_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => $self->{result_values}->{label} . '_pending', + unit => 'B', + value => $self->{result_values}->{bytes}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}), + min => 0 + ); +} + +sub custom_pending_bytes_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{bytes}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ============================================================================= +# PENDING AGE (dedup / replication) — seconds +# ============================================================================= +sub custom_pending_age_calc { + my ($self, %options) = @_; + my $lbl = $options{extra_options}->{label_ref}; + $self->{result_values}->{label} = $lbl; + $self->{result_values}->{seconds} = _timeticks_to_seconds( + $options{new_datas}->{ $self->{instance} . '_' . $lbl . '_pending_age' } + ); + return 0; +} + +sub custom_pending_age_output { + my ($self, %options) = @_; + my $s = $self->{result_values}->{seconds}; + return sprintf('Pending %s age: %dh%02dm%02ds', + ucfirst($self->{result_values}->{label}), + int($s / 3600), int(($s % 3600) / 60), $s % 60 + ); +} + +sub custom_pending_age_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => $self->{result_values}->{label} . '_pending_age', + unit => 's', + value => $self->{result_values}->{seconds}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}), + min => 0 + ); +} + +sub custom_pending_age_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{seconds}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ============================================================================= +# I/O RATES (MB/s) +# ============================================================================= +sub custom_rate_calc { + my ($self, %options) = @_; + my $lbl = $options{extra_options}->{label_ref}; + $self->{result_values}->{label} = $lbl; + $self->{result_values}->{rate_mbps} = $options{new_datas}->{ $self->{instance} . '_' . $lbl . '_rate_mbps' } // 0; + return 0; +} + +sub custom_rate_output { + my ($self, %options) = @_; + return sprintf('%s rate: %.2f MB/s', ucfirst($self->{result_values}->{label}), + $self->{result_values}->{rate_mbps}); +} + +sub custom_rate_perfdata { + my ($self, %options) = @_; + $self->{output}->perfdata_add( + label => $self->{result_values}->{label} . '_rate', + unit => 'MB\/s', + value => $self->{result_values}->{rate_mbps}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}), + min => 0 + ); +} + +sub custom_rate_threshold { + my ($self, %options) = @_; + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{rate_mbps}, + threshold => [ + { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, + { label => 'warning-' . $self->{thlabel}, exit_litteral => 'warning' } + ] + ); +} + +# ============================================================================= +# COUNTER DEFINITIONS +# ============================================================================= sub set_counters { my ($self, %options) = @_; - + $self->{maps_counters_type} = [ - { name => 'server', type => COUNTER_TYPE_GLOBAL, message_separator => ' - ' } + { name => 'server', type => 0, message_separator => ' - ' } ]; - + $self->{maps_counters}->{server} = [ - { - label => 'status', - type => COUNTER_KIND_TEXT, - warning_default => '%{status} =~ /warning/i', - critical_default => '%{status} =~ /error/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 - } - }, - { label => 'landing-usage', set => { - key_values => [ { name => 'landing_used' }, { name => 'landing_total' } ], - closure_custom_calc => $self->can('custom_usage_calc'), closure_custom_calc_extra_options => { label_ref => 'landing' }, - closure_custom_output => $self->can('custom_usage_output'), - closure_custom_perfdata => $self->can('custom_usage_perfdata'), - closure_custom_threshold_check => $self->can('custom_usage_threshold') - } + + # ---- Status -------------------------------------------------------- + { label => 'status', type => 2, + warning_default => '%{status} =~ /warning/i', + critical_default => '%{status} =~ /error/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 + } }, + + # ---- Landing Zone: available bytes --------------------------------- + { label => 'landing-available', set => { + key_values => [ + { name => 'landing_available_whole' }, + { name => 'landing_available_frac' } + ], + closure_custom_calc => $self->can('custom_landing_available_calc'), + closure_custom_output => $self->can('custom_landing_available_output'), + closure_custom_perfdata => $self->can('custom_landing_available_perfdata'), + closure_custom_threshold_check => $self->can('custom_landing_available_threshold'), + output_template => 'Landing Zone available: %s', + }}, + + # ---- Retention space: used% ---------------------------------------- { label => 'retention-usage', set => { - key_values => [ { name => 'retention_used' }, { name => 'retention_total' } ], - closure_custom_calc => $self->can('custom_usage_calc'), closure_custom_calc_extra_options => { label_ref => 'retention' }, - closure_custom_output => $self->can('custom_usage_output'), - closure_custom_perfdata => $self->can('custom_usage_perfdata'), - closure_custom_threshold_check => $self->can('custom_usage_threshold') - } - }, + key_values => [ { name => 'retention_used' }, { name => 'retention_total' } ], + closure_custom_calc => $self->can('custom_usage_calc'), + closure_custom_calc_extra_options => { label_ref => 'retention' }, + closure_custom_output => $self->can('custom_usage_output'), + closure_custom_perfdata => $self->can('custom_usage_perfdata'), + closure_custom_threshold_check => $self->can('custom_usage_threshold'), + output_template => 'Retention Usage: %s', + }}, + + # ---- Retention breakdown: Primary ---------------------------------- + { label => 'retention-primary', set => { + key_values => [ { name => 'retention_primary_bytes' } ], + closure_custom_calc => $self->can('custom_retention_detail_calc'), + closure_custom_calc_extra_options => { label_ref => 'Primary Retention', key_ref => 'retention_primary_bytes' }, + closure_custom_output => $self->can('custom_retention_detail_output'), + closure_custom_perfdata => $self->can('custom_retention_detail_perfdata'), + closure_custom_threshold_check => $self->can('custom_retention_detail_threshold'), + output_template => 'Primary Retention: %s', + }}, + + # ---- Retention breakdown: Replica ---------------------------------- + { label => 'retention-replica', set => { + key_values => [ { name => 'retention_replica_bytes' } ], + closure_custom_calc => $self->can('custom_retention_detail_calc'), + closure_custom_calc_extra_options => { label_ref => 'Replica Retention', key_ref => 'retention_replica_bytes' }, + closure_custom_output => $self->can('custom_retention_detail_output'), + closure_custom_perfdata => $self->can('custom_retention_detail_perfdata'), + closure_custom_threshold_check => $self->can('custom_retention_detail_threshold'), + output_template => 'Replica Retention: %s', + }}, + + # ---- Retention breakdown: Pending Purge ---------------------------- + { label => 'retention-pending-purge', set => { + key_values => [ { name => 'retention_pending_purge_bytes' } ], + closure_custom_calc => $self->can('custom_retention_detail_calc'), + closure_custom_calc_extra_options => { label_ref => 'Pending Purge', key_ref => 'retention_pending_purge_bytes' }, + closure_custom_output => $self->can('custom_retention_detail_output'), + closure_custom_perfdata => $self->can('custom_retention_detail_perfdata'), + closure_custom_threshold_check => $self->can('custom_retention_detail_threshold'), + output_template => 'Pending Purge: %s', + }}, + + # ---- Deduplication ratio ------------------------------------------- + { label => 'dedup-ratio', set => { + key_values => [ { name => 'dedup_ratio' } ], + closure_custom_calc => $self->can('custom_dedup_ratio_calc'), + closure_custom_output => $self->can('custom_dedup_ratio_output'), + closure_custom_perfdata => $self->can('custom_dedup_ratio_perfdata'), + closure_custom_threshold_check => $self->can('custom_dedup_ratio_threshold'), + output_template => 'Dedup Ratio: %s', + }}, + + # ---- Pending deduplication: bytes ---------------------------------- + { label => 'pending-dedup', set => { + key_values => [ { name => 'deduplication_pending_bytes' } ], + closure_custom_calc => $self->can('custom_pending_bytes_calc'), + closure_custom_calc_extra_options => { label_ref => 'deduplication' }, + closure_custom_output => $self->can('custom_pending_bytes_output'), + closure_custom_perfdata => $self->can('custom_pending_bytes_perfdata'), + closure_custom_threshold_check => $self->can('custom_pending_bytes_threshold'), + output_template => 'Pending Dedup: %s', + }}, + + # ---- Pending deduplication: age ------------------------------------ + { label => 'pending-dedup-age', set => { + key_values => [ { name => 'deduplication_pending_age' } ], + closure_custom_calc => $self->can('custom_pending_age_calc'), + closure_custom_calc_extra_options => { label_ref => 'deduplication' }, + closure_custom_output => $self->can('custom_pending_age_output'), + closure_custom_perfdata => $self->can('custom_pending_age_perfdata'), + closure_custom_threshold_check => $self->can('custom_pending_age_threshold'), + output_template => 'Pending Dedup Age: %s', + }}, + + # ---- Pending replication: bytes ------------------------------------ + { label => 'pending-replication', set => { + key_values => [ { name => 'replication_pending_bytes' } ], + closure_custom_calc => $self->can('custom_pending_bytes_calc'), + closure_custom_calc_extra_options => { label_ref => 'replication' }, + closure_custom_output => $self->can('custom_pending_bytes_output'), + closure_custom_perfdata => $self->can('custom_pending_bytes_perfdata'), + closure_custom_threshold_check => $self->can('custom_pending_bytes_threshold'), + output_template => 'Pending Replication: %s', + }}, + + # ---- Pending replication: age -------------------------------------- + { label => 'pending-replication-age', set => { + key_values => [ { name => 'replication_pending_age' } ], + closure_custom_calc => $self->can('custom_pending_age_calc'), + closure_custom_calc_extra_options => { label_ref => 'replication' }, + closure_custom_output => $self->can('custom_pending_age_output'), + closure_custom_perfdata => $self->can('custom_pending_age_perfdata'), + closure_custom_threshold_check => $self->can('custom_pending_age_threshold'), + output_template => 'Pending Replication Age: %s', + }}, + + # ---- I/O Rates ----------------------------------------------------- + { label => 'read-rate', set => { + key_values => [ { name => 'read_rate_mbps' } ], + closure_custom_calc => $self->can('custom_rate_calc'), + closure_custom_calc_extra_options => { label_ref => 'read' }, + closure_custom_output => $self->can('custom_rate_output'), + closure_custom_perfdata => $self->can('custom_rate_perfdata'), + closure_custom_threshold_check => $self->can('custom_rate_threshold'), + output_template => 'Read Rate: %s', + }}, + + { label => 'write-rate', set => { + key_values => [ { name => 'write_rate_mbps' } ], + closure_custom_calc => $self->can('custom_rate_calc'), + closure_custom_calc_extra_options => { label_ref => 'write' }, + closure_custom_output => $self->can('custom_rate_output'), + closure_custom_perfdata => $self->can('custom_rate_perfdata'), + closure_custom_threshold_check => $self->can('custom_rate_threshold'), + output_template => 'Write Rate: %s', + }}, + + { label => 'dedup-rate', set => { + key_values => [ { name => 'dedup_rate_mbps' } ], + closure_custom_calc => $self->can('custom_rate_calc'), + closure_custom_calc_extra_options => { label_ref => 'dedup' }, + closure_custom_output => $self->can('custom_rate_output'), + closure_custom_perfdata => $self->can('custom_rate_perfdata'), + closure_custom_threshold_check => $self->can('custom_rate_threshold'), + output_template => 'Dedup Rate: %s', + }}, + ]; } +# ============================================================================= +# Constructor — all custom warning/critical options must be registered here +# because the counter framework only auto-registers options for counters +# that use the standard output_template+perfdatas pattern, not custom closures. +# ============================================================================= sub new { my ($class, %options) = @_; my $self = $class->SUPER::new(package => __PACKAGE__, %options); bless $self, $class; - - $options{options}->add_options(arguments => { - }); - return $self; } +# ============================================================================= +# OID mapping +# ============================================================================= my $map_status = { 1 => 'ok', 2 => 'warning', @@ -148,30 +534,96 @@ my $map_status = { }; my $mapping = { - egLandingSpaceConfiguredWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.1.1', default => 0 }, - egLandingSpaceAvailableWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.1.3', default => 0 }, - egRetentionSpaceConfiguredWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.1', default => 0 }, - egRetentionSpaceAvailableWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.3', default => 0 }, - egServerAlarmState => { oid => '.1.3.6.1.4.1.14941.4.6.1', map => $map_status } + egLandingSpaceAvailableWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.1.3', default => 0 }, + egLandingSpaceAvailableFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.1.4', default => 0 }, + egRetentionSpaceConfiguredWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.1', default => 0 }, + egRetentionSpaceConfiguredFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.2', default => 0 }, + egRetentionSpaceAvailableWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.3', default => 0 }, + egRetentionSpaceAvailableFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.4', default => 0 }, + egRetentionSpacePrimarySharesWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.5', default => 0 }, + egRetentionSpacePrimarySharesFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.6', default => 0 }, + egRetentionSpaceReplicasWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.7', default => 0 }, + egRetentionSpaceReplicasFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.8', default => 0 }, + egRetentionSpaceAllPendingPurgeBytesWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.13', default => 0 }, + egRetentionSpaceAllPendingPurgeBytesFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.14', default => 0 }, + egBackupDataAvailableWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.3.1', default => 0 }, + egBackupDataAvailableFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.3.2', default => 0 }, + egBackupDataSpaceConsumedWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.3.3', default => 0 }, + egBackupDataSpaceConsumedFractionalGigabytes => { oid => '.1.3.6.1.4.1.14941.4.3.4', default => 0 }, + egPendingDeduplicationWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.4.1', default => 0 }, + egPendingDeduplicationAge => { oid => '.1.3.6.1.4.1.14941.4.4.3', default => 0 }, + egPendingReplicationWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.5.1', default => 0 }, + egPendingReplicationAge => { oid => '.1.3.6.1.4.1.14941.4.5.3', default => 0 }, + egServerAlarmState => { oid => '.1.3.6.1.4.1.14941.4.6.1', map => $map_status }, + egServerReadRateMegabytesSec => { oid => '.1.3.6.1.4.1.14941.4.7.1', default => 0 }, + egServerWriteRateMegabytesSec => { oid => '.1.3.6.1.4.1.14941.4.7.2', default => 0 }, + egServerDedupRateMegabytesSec => { oid => '.1.3.6.1.4.1.14941.4.7.3', default => 0 }, }; + my $oid_exagridServerData = '.1.3.6.1.4.1.14941.4'; +# ============================================================================= +# Data collection +# ============================================================================= sub manage_selection { my ($self, %options) = @_; my $snmp_result = $options{snmp}->get_table( - oid => $oid_exagridServerData, + oid => $oid_exagridServerData, nothing_quit => 1 - ); - my $result = $options{snmp}->map_instance(mapping => $mapping, results => $snmp_result, instance => '0'); - my $landing_used = $result->{egLandingSpaceConfiguredWholeGigabytes} * 1_000_000_000 - $result->{egLandingSpaceAvailableWholeGigabytes} * 1_000_000_000; - $landing_used = 0 if $landing_used < 0; - $self->{server} = { - status => $result->{egServerAlarmState}, - retention_total => $result->{egRetentionSpaceConfiguredWholeGigabytes} * 1_000_000_000, - retention_used => $result->{egRetentionSpaceConfiguredWholeGigabytes} * 1_000_000_000 - $result->{egRetentionSpaceAvailableWholeGigabytes} * 1_000_000_000, - landing_total => $result->{egLandingSpaceConfiguredWholeGigabytes} * 1_000_000_000, - landing_used => $landing_used + ); + my $result = $options{snmp}->map_instance( + mapping => $mapping, + results => $snmp_result, + instance => '0' + ); + + # Retention space + my $retention_total = $result->{egRetentionSpaceConfiguredWholeGigabytes} * 1_000_000_000 + + $result->{egRetentionSpaceConfiguredFractionalGigabytes}; + my $retention_avail = $result->{egRetentionSpaceAvailableWholeGigabytes} * 1_000_000_000 + + $result->{egRetentionSpaceAvailableFractionalGigabytes}; + $retention_avail = $retention_total if $retention_avail > $retention_total; + + # Retention breakdown + my $retention_primary = $result->{egRetentionSpacePrimarySharesWholeGigabytes} * 1_000_000_000 + + $result->{egRetentionSpacePrimarySharesFractionalGigabytes}; + my $retention_replica = $result->{egRetentionSpaceReplicasWholeGigabytes} * 1_000_000_000 + + $result->{egRetentionSpaceReplicasFractionalGigabytes}; + my $retention_purge = $result->{egRetentionSpaceAllPendingPurgeBytesWholeGigabytes} * 1_000_000_000 + + $result->{egRetentionSpaceAllPendingPurgeBytesFractionalGigabytes}; + + # Dedup ratio + my $backup_available = $result->{egBackupDataAvailableWholeGigabytes} * 1_000_000_000 + + $result->{egBackupDataAvailableFractionalGigabytes}; + my $backup_consumed = $result->{egBackupDataSpaceConsumedWholeGigabytes} * 1_000_000_000 + + $result->{egBackupDataSpaceConsumedFractionalGigabytes}; + my $dedup_ratio = ($backup_consumed > 0) ? ($backup_available / $backup_consumed) : 0; + + $self->{server} = { + status => $result->{egServerAlarmState}, + + landing_available_whole => $result->{egLandingSpaceAvailableWholeGigabytes}, + landing_available_frac => $result->{egLandingSpaceAvailableFractionalGigabytes}, + + retention_total => $retention_total, + retention_used => $retention_total - $retention_avail, + + retention_primary_bytes => $retention_primary, + retention_replica_bytes => $retention_replica, + retention_pending_purge_bytes => $retention_purge, + + dedup_ratio => $dedup_ratio, + + deduplication_pending_bytes => $result->{egPendingDeduplicationWholeGigabytes} * 1_000_000_000, + deduplication_pending_age => $result->{egPendingDeduplicationAge}, + + replication_pending_bytes => $result->{egPendingReplicationWholeGigabytes} * 1_000_000_000, + replication_pending_age => $result->{egPendingReplicationAge}, + + read_rate_mbps => $result->{egServerReadRateMegabytesSec}, + write_rate_mbps => $result->{egServerWriteRateMegabytesSec}, + dedup_rate_mbps => $result->{egServerDedupRateMegabytesSec}, }; } @@ -181,41 +633,83 @@ __END__ =head1 MODE -Check server status and usage. +Check ExaGrid server status, space usage, retention breakdown, +deduplication ratio, pending dedup/replication, and I/O rates via SNMP. + +Use --filter-counters to isolate individual metrics for independent +Nagios/NagVis service checks and graphs. =over 8 =item B<--filter-counters> -Only display some counters (regexp can be used). -Example: --filter-counters='^status$' +Only display matching counters (regexp). + --filter-counters='^status$' + --filter-counters='^retention-usage$' + --filter-counters='^read-rate$' + +=item B<--warning-status> / B<--critical-status> + +Status condition (defaults: warn '%{status} =~ /warning/i', + crit '%{status} =~ /error/i'). + +=item B<--warning-landing-available> / B<--critical-landing-available> + +Landing Zone available space (bytes). Use range syntax to alert below: + --warning-landing-available='8000000000000:' + --critical-landing-available='5000000000000:' + +=item B<--warning-retention-usage> / B<--critical-retention-usage> + +Retention used space (percent 0-100). + --warning-retention-usage=80 --critical-retention-usage=90 + +=item B<--warning-retention-primary> / B<--critical-retention-primary> + +Primary Retention (bytes). + +=item B<--warning-retention-replica> / B<--critical-retention-replica> + +Replica Retention (bytes). + +=item B<--warning-retention-pending-purge> / B<--critical-retention-pending-purge> + +Pending Purge (bytes). + +=item B<--warning-dedup-ratio> / B<--critical-dedup-ratio> + +Dedup ratio. Range syntax to alert when ratio drops below value: + --warning-dedup-ratio='3:' --critical-dedup-ratio='2:' + +=item B<--warning-pending-dedup> / B<--critical-pending-dedup> + +Pending deduplication (bytes). -=item B<--warning-status> +=item B<--warning-pending-dedup-age> / B<--critical-pending-dedup-age> -Define the conditions to match for the status to be WARNING (default: '%{status} =~ /warning/i'). -You can use the following variables: %{status} +Pending deduplication age (seconds). + --warning-pending-dedup-age=7200 --critical-pending-dedup-age=21600 -=item B<--critical-status> +=item B<--warning-pending-replication> / B<--critical-pending-replication> -Define the conditions to match for the status to be CRITICAL (default: '%{status} =~ /error/i'). -You can use the following variables: %{status} +Pending replication (bytes). -=item B<--warning-landing-usage> +=item B<--warning-pending-replication-age> / B<--critical-pending-replication-age> -Threshold. +Pending replication age (seconds). -=item B<--critical-landing-usage> +=item B<--warning-read-rate> / B<--critical-read-rate> -Threshold. +Read rate (MB/s). -=item B<--warning-retention-usage> +=item B<--warning-write-rate> / B<--critical-write-rate> -Threshold. +Write rate (MB/s). -=item B<--critical-retention-usage> +=item B<--warning-dedup-rate> / B<--critical-dedup-rate> -Threshold. +Deduplication processing rate (MB/s). =back -=cut +=cut \ No newline at end of file diff --git a/src/storage/exagrid/snmp/mode/serverusage.pm.bak b/src/storage/exagrid/snmp/mode/serverusage.pm.bak new file mode 100644 index 0000000000..3107b410fd --- /dev/null +++ b/src/storage/exagrid/snmp/mode/serverusage.pm.bak @@ -0,0 +1,207 @@ +# +# Copyright 2023 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::exagrid::snmp::mode::serverusage; + +use base qw(centreon::plugins::templates::counter); + +use strict; +use warnings; +use centreon::plugins::templates::catalog_functions qw(catalog_status_threshold_ng); + +sub custom_status_output { + my ($self, %options) = @_; + + return 'Server Status : ' . $self->{result_values}->{status}; +} + +sub custom_usage_perfdata { + my ($self, %options) = @_; + + my $label = 'used'; + my $value_perf = $self->{result_values}->{used}; + my %total_options = ( total => $self->{result_values}->{total}, cast_int => 1); + + $self->{output}->perfdata_add( + label => $self->{result_values}->{label} . '_' . $label, unit => 'B', + value => $value_perf, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $self->{label}, %total_options), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $self->{label}, %total_options), + min => 0, + max => $self->{result_values}->{total} + ); +} + +sub custom_usage_threshold { + my ($self, %options) = @_; + + my $exit = $self->{perfdata}->threshold_check(value => $self->{result_values}->{prct_used}, threshold => [ { label => 'critical-' . $self->{thlabel}, exit_litteral => 'critical' }, { label => 'warning-'. $self->{thlabel}, exit_litteral => 'warning' } ]); + return $exit; +} + +sub custom_usage_output { + my ($self, %options) = @_; + + my ($total_size_value, $total_size_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{total}); + my ($total_used_value, $total_used_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{used}); + my ($total_free_value, $total_free_unit) = $self->{perfdata}->change_bytes(value => $self->{result_values}->{free}); + return sprintf( + '%s Usage Total: %s Used: %s (%.2f%%) Free: %s (%.2f%%)', + ucfirst($self->{result_values}->{label}), + $total_size_value . " " . $total_size_unit, + $total_used_value . " " . $total_used_unit, $self->{result_values}->{prct_used}, + $total_free_value . " " . $total_free_unit, $self->{result_values}->{prct_free} + ); +} + +sub custom_usage_calc { + my ($self, %options) = @_; + + $self->{result_values}->{label} = $options{extra_options}->{label_ref}; + $self->{result_values}->{total} = $options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{label} . '_total'}; + $self->{result_values}->{used} = $options{new_datas}->{$self->{instance} . '_' . $self->{result_values}->{label} . '_used'}; + + if ($self->{result_values}->{total} != 0) { + $self->{result_values}->{free} = $self->{result_values}->{total} - $self->{result_values}->{used}; + $self->{result_values}->{prct_used} = $self->{result_values}->{used} * 100 / $self->{result_values}->{total}; + $self->{result_values}->{prct_free} = 100 - $self->{result_values}->{prct_used}; + } else { + $self->{result_values}->{free} = 0; + $self->{result_values}->{prct_used} = 0; + $self->{result_values}->{prct_free} = 0; + } + return 0; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'server', type => 0, message_separator => ' - ' } + ]; + + $self->{maps_counters}->{server} = [ + { + label => 'status', + type => 2, + warning_default => '%{status} =~ /warning/i', + critical_default => '%{status} =~ /error/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 + } + }, + { label => 'landing-usage', set => { + key_values => [ { name => 'landing_used' }, { name => 'landing_total' } ], + closure_custom_calc => $self->can('custom_usage_calc'), closure_custom_calc_extra_options => { label_ref => 'landing' }, + closure_custom_output => $self->can('custom_usage_output'), + closure_custom_perfdata => $self->can('custom_usage_perfdata'), + closure_custom_threshold_check => $self->can('custom_usage_threshold') + } + }, + { label => 'retention-usage', set => { + key_values => [ { name => 'retention_used' }, { name => 'retention_total' } ], + closure_custom_calc => $self->can('custom_usage_calc'), closure_custom_calc_extra_options => { label_ref => 'retention' }, + closure_custom_output => $self->can('custom_usage_output'), + closure_custom_perfdata => $self->can('custom_usage_perfdata'), + closure_custom_threshold_check => $self->can('custom_usage_threshold') + } + }, + ]; +} + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $options{options}->add_options(arguments => { + }); + + return $self; +} + +my $map_status = { + 1 => 'ok', + 2 => 'warning', + 3 => 'error' +}; + +my $mapping = { + egLandingSpaceConfiguredWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.1.1' }, + egLandingSpaceAvailableWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.1.3' }, + egRetentionSpaceConfiguredWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.1' }, + egRetentionSpaceAvailableWholeGigabytes => { oid => '.1.3.6.1.4.1.14941.4.2.3' }, + egServerAlarmState => { oid => '.1.3.6.1.4.1.14941.4.6.1', map => $map_status } +}; +my $oid_exagridServerData = '.1.3.6.1.4.1.14941.4'; + +sub manage_selection { + my ($self, %options) = @_; + + my $snmp_result = $options{snmp}->get_table( + oid => $oid_exagridServerData, + nothing_quit => 1 + ); + my $result = $options{snmp}->map_instance(mapping => $mapping, results => $snmp_result, instance => '0'); + $self->{server} = { + status => $result->{egServerAlarmState}, + retention_total => $result->{egRetentionSpaceConfiguredWholeGigabytes} * 1000 * 1000 * 1000, + retention_used => $result->{egRetentionSpaceConfiguredWholeGigabytes} * 1000 * 1000 * 1000 - $result->{egRetentionSpaceAvailableWholeGigabytes} * 1000 * 1000 * 1000, + landing_total => $result->{egLandingSpaceConfiguredWholeGigabytes} * 1000 * 1000 * 1000, + landing_used => $result->{egLandingSpaceConfiguredWholeGigabytes} * 1000 * 1000 * 1000 - $result->{egLandingSpaceAvailableWholeGigabytes} * 1000 * 1000 * 1000 + }; +} + +1; + +__END__ + +=head1 MODE + +Check server status and usage. + +=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 (Default: '%{status} =~ /warning/i'). +Can used special variables like: %{status} + +=item B<--critical-status> + +Set critical threshold for status (Default: '%{status} =~ /error/i'). +Can used special variables like: %{status} + +=item B<--warning-*> B<--critical-*> + +Thresholds. +Can be: 'retention-usage' (%), 'landing-usage' (%). + +=back + +=cut diff --git a/src/storage/exagrid/snmp/plugin.pm b/src/storage/exagrid/snmp/plugin.pm index 3d3302b7bc..052229d086 100644 --- a/src/storage/exagrid/snmp/plugin.pm +++ b/src/storage/exagrid/snmp/plugin.pm @@ -1,5 +1,5 @@ # -# Copyright 2024 Centreon (http://www.centreon.com/) +# Copyright 2023 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for @@ -32,6 +32,7 @@ sub new { $self->{version} = '1.0'; %{$self->{modes}} = ( 'server-usage' => 'storage::exagrid::snmp::mode::serverusage', + 'data-protection' => 'storage::exagrid::snmp::mode::dataprotection', ); return $self;