From d6764b557775ca536f24fec23fa9788b29f7c35c Mon Sep 17 00:00:00 2001 From: qgarnier Date: Tue, 2 Feb 2021 12:08:42 +0100 Subject: [PATCH] Enhance aws rds storage (#2565) --- cloud/aws/custom/awscli.pm | 2 + cloud/aws/custom/paws.pm | 2 + cloud/aws/rds/mode/storage.pm | 267 +++++++++++++++++++++++++++------- 3 files changed, 219 insertions(+), 52 deletions(-) diff --git a/cloud/aws/custom/awscli.pm b/cloud/aws/custom/awscli.pm index 80bfdcffe..e244e90d0 100644 --- a/cloud/aws/custom/awscli.pm +++ b/cloud/aws/custom/awscli.pm @@ -596,6 +596,7 @@ sub rds_list_instances { Engine => $instance->{Engine}, StorageType => $instance->{StorageType}, DBInstanceStatus => $instance->{DBInstanceStatus}, + AllocatedStorage => $instance->{AllocatedStorage} }; } @@ -626,6 +627,7 @@ sub rds_list_clusters { DatabaseName => $cluster->{DatabaseName}, Engine => $cluster->{Engine}, Status => $cluster->{Status}, + AllocatedStorage => $cluster->{AllocatedStorage} }; } diff --git a/cloud/aws/custom/paws.pm b/cloud/aws/custom/paws.pm index 6593c8faf..35d1cf0aa 100644 --- a/cloud/aws/custom/paws.pm +++ b/cloud/aws/custom/paws.pm @@ -505,6 +505,7 @@ sub rds_list_instances { Engine => $instance->{Engine}, StorageType => $instance->{StorageType}, DBInstanceStatus => $instance->{DBInstanceStatus}, + AllocatedStorage => $instance->{AllocatedStorage} }; } }; @@ -531,6 +532,7 @@ sub rds_list_clusters { DatabaseName => $cluster->{DatabaseName}, Engine => $cluster->{Engine}, Status => $cluster->{Status}, + AllocatedStorage => $cluster->{AllocatedStorage} }; } }; diff --git a/cloud/aws/rds/mode/storage.pm b/cloud/aws/rds/mode/storage.pm index 9e17d5023..5209f3196 100644 --- a/cloud/aws/rds/mode/storage.pm +++ b/cloud/aws/rds/mode/storage.pm @@ -25,51 +25,139 @@ use base qw(centreon::plugins::templates::counter); use strict; use warnings; -my %map_type = ( - instance => "DBInstanceIdentifier", - cluster => "DBClusterIdentifier" -); +my $metrics_mapping = { + FreeStorageSpace => { + output => 'storage space free', + label => 'storage-space-free', + nlabel => { + absolute => 'storage.space.free.bytes' + }, + unit => 'B' + }, + FreeStorageSpacePrct => { + output => 'storage space usage', + label => 'storage-space-usage-prct', + nlabel => { + absolute => 'storage.space.usage.percentage' + }, + unit => '%', + cw_metric => 0 + }, + FreeableMemory => { + output => 'memory free', + label => 'memory-free', + nlabel => { + absolute => 'memory.free.bytes' + }, + unit => 'B' + } +}; + +my $map_type = { + instance => 'DBInstanceIdentifier', + cluster => 'DBClusterIdentifier' +}; + +sub custom_metric_calc { + my ($self, %options) = @_; + + $self->{result_values}->{timeframe} = $options{new_datas}->{$self->{instance} . '_timeframe'}; + $self->{result_values}->{value} = $options{new_datas}->{$self->{instance} . '_' . $options{extra_options}->{metric}}; + $self->{result_values}->{metric} = $options{extra_options}->{metric}; + return 0; +} + +sub custom_metric_threshold { + my ($self, %options) = @_; + + return $self->{perfdata}->threshold_check( + value => $self->{result_values}->{value}, + threshold => [ + { label => 'critical-' . $metrics_mapping->{ $self->{result_values}->{metric} }->{label} , exit_litteral => 'critical' }, + { label => 'warning-' . $metrics_mapping->{ $self->{result_values}->{metric} }->{label}, exit_litteral => 'warning' } + ] + ); +} + +sub custom_metric_perfdata { + my ($self, %options) = @_; + + $self->{output}->perfdata_add( + instances => $self->{instance}, + nlabel => $metrics_mapping->{ $self->{result_values}->{metric} }->{nlabel}->{absolute}, + unit => $metrics_mapping->{ $self->{result_values}->{metric} }->{unit}, + value => sprintf('%.2f', $self->{result_values}->{value}), + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning-' . $metrics_mapping->{ $self->{result_values}->{metric} }->{label}), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical-' . $metrics_mapping->{ $self->{result_values}->{metric} }->{label}) + ); +} + +sub custom_metric_output { + my ($self, %options) = @_; + + my ($value, $unit) = ($metrics_mapping->{ $self->{result_values}->{metric} }->{unit} eq 'B') ? + $self->{perfdata}->change_bytes(value => $self->{result_values}->{value}) : + ($self->{result_values}->{value}, $metrics_mapping->{ $self->{result_values}->{metric} }->{unit}); + return sprintf("%s: %.2f %s", $metrics_mapping->{ $self->{result_values}->{metric} }->{output}, $value, $unit); +} sub prefix_metric_output { my ($self, %options) = @_; - return ucfirst($options{instance_value}->{type}) . " '" . $options{instance_value}->{display} . "' " . $options{instance_value}->{stat} . " "; + return "'" . $options{instance_value}->{display} . "' "; +} + +sub prefix_statistics_output { + my ($self, %options) = @_; + + return "statistic '" . $options{instance_value}->{display} . "' metrics "; +} + +sub long_output { + my ($self, %options) = @_; + + return "AWS RDS '" . $options{instance_value}->{display} . "' "; } sub set_counters { my ($self, %options) = @_; - + $self->{maps_counters_type} = [ - { name => 'metric', type => 1, cb_prefix_output => 'prefix_metric_output', message_multiple => "All storage metrics are ok", skipped_code => { -10 => 1 } } + { name => 'metrics', type => 3, cb_prefix_output => 'prefix_metric_output', cb_long_output => 'long_output', + message_multiple => 'All storage metrics are ok', indent_long_output => ' ', + group => [ + { name => 'statistics', display_long => 1, cb_prefix_output => 'prefix_statistics_output', + message_multiple => 'All metrics are ok', type => 1, skipped_code => { -10 => 1 } } + ] + } ]; - foreach my $statistic ('minimum', 'maximum', 'average', 'sum') { - foreach my $metric ('FreeStorageSpace', 'FreeableMemory') { - my $entry = { - label => lc($metric) . '-' . lc($statistic), set => { - key_values => [ { name => $metric . '_' . $statistic }, { name => 'display' }, { name => 'type' }, { name => 'stat' } ], - output_template => $metric . ': %.2f %s', - output_change_bytes => 1, - perfdatas => [ - { label => lc($metric) . '_' . lc($statistic), value => $metric . '_' . $statistic , - template => '%s', unit => 'B', min => 0, label_extra_instance => 1, instance_use => 'display' } - ] - } - }; - push @{$self->{maps_counters}->{metric}}, $entry; - } + foreach my $metric (keys %$metrics_mapping) { + my $entry = { + label => $metrics_mapping->{$metric}->{label}, + set => { + key_values => [ { name => $metric }, { name => 'timeframe' }, { name => 'display' } ], + closure_custom_calc => $self->can('custom_metric_calc'), + closure_custom_calc_extra_options => { metric => $metric }, + closure_custom_output => $self->can('custom_metric_output'), + closure_custom_perfdata => $self->can('custom_metric_perfdata'), + closure_custom_threshold_check => $self->can('custom_metric_threshold') + } + }; + push @{$self->{maps_counters}->{statistics}}, $entry; } } sub new { my ($class, %options) = @_; - my $self = $class->SUPER::new(package => __PACKAGE__, %options); + my $self = $class->SUPER::new(package => __PACKAGE__, %options, force_new_perfdata => 1); bless $self, $class; $options{options}->add_options(arguments => { - 'type:s' => { name => 'type' }, - 'name:s@' => { name => 'name' }, - 'filter-metric:s' => { name => 'filter_metric' } + 'type:s' => { name => 'type' }, + 'name:s@' => { name => 'name' }, + 'filter-metric:s' => { name => 'filter_metric' }, + 'add-space-usage-percent' => { name => 'add_space_usage_percent' } }); return $self; @@ -84,7 +172,7 @@ sub check_options { $self->{output}->option_exit(); } - if ($self->{option_results}->{type} ne 'cluster' && $self->{option_results}->{type} ne 'instance') { + if (!defined($map_type->{ $self->{option_results}->{type} })) { $self->{output}->add_option_msg(short_msg => "Instance type '" . $self->{option_results}->{type} . "' is not handled for this mode"); $self->{output}->option_exit(); } @@ -111,43 +199,120 @@ sub check_options { push @{$self->{aws_statistics}}, ucfirst(lc($stat)); } } - } - - foreach my $metric ('FreeStorageSpace', 'FreeableMemory') { + }; + foreach my $metric (keys %$metrics_mapping) { next if (defined($self->{option_results}->{filter_metric}) && $self->{option_results}->{filter_metric} ne '' && $metric !~ /$self->{option_results}->{filter_metric}/); - + next if (defined($metrics_mapping->{$metric}->{cw_metric}) && $metrics_mapping->{$metric}->{cw_metric} == 0); push @{$self->{aws_metrics}}, $metric; + # For Aurora instance + if ($metric eq 'FreeStorageSpace') { + push @{$self->{aws_metrics}}, 'FreeLocalStorage'; + } + }; +} + +sub add_metric { + my ($self, %options) = @_; + + return if ( + !defined($options{metric_value}) && + !defined($self->{option_results}->{zeroed}) + ); + if (!defined($self->{metrics}->{ $options{instance} })) { + $self->{metrics}->{ $options{instance} } = { + display => $options{instance}, + statistics => {} + }; } + $self->{metrics}->{ $options{instance} }->{statistics}->{ $options{statistic} }->{display} = $options{statistic}; + $self->{metrics}->{ $options{instance} }->{statistics}->{ $options{statistic} }->{timeframe} = $self->{aws_timeframe}; + $self->{metrics}->{ $options{instance} }->{statistics}->{ $options{statistic} }->{ $options{metric} } = + defined($options{metric_value}) ? + $options{metric_value} : 0; + if (defined($metrics_mapping->{ $options{metric} }->{calc})) { + $self->{metrics}->{ $options{instance} }->{statistics}->{ $options{statistic} }->{ $options{metric} } = + eval $self->{metrics}->{ $options{instance} }->{statistics}->{ $options{statistic} }->{ $options{metric} } . $metrics_mapping->{ $options{metric} }->{calc}; + } +} + +sub add_metric_space_usage_percent { + my ($self, %options) = @_; + + my $total_space; + foreach (@{$options{list_rds_instances}}) { + if ($_->{Name} eq $options{instance}) { + $total_space = $_->{AllocatedStorage} * 1024 * 1024 * 1024; + last; + } + } + + return if (!defined($total_space) || $total_space <= 0); + + my $space_usage = 100 - ($options{free_storage} * 100 / $total_space); + $self->add_metric( + metric_value => $space_usage, + instance => $options{instance}, + metric => 'FreeStorageSpacePrct', + statistic => $options{statistic} + ); } sub manage_selection { my ($self, %options) = @_; - my %metric_results; + my $list_rds_instances; + if (defined($self->{option_results}->{add_space_usage_percent})) { + if ($self->{option_results}->{type} eq 'instance') { + $list_rds_instances = $options{custom}->rds_list_instances(); + } else { + $list_rds_instances = $options{custom}->rds_list_clusters(); + } + } + foreach my $instance (@{$self->{aws_instance}}) { - $metric_results{$instance} = $options{custom}->cloudwatch_get_metrics( - namespace => 'AWS/RDS', - dimensions => [ { Name => $map_type{$self->{option_results}->{type}}, Value => $instance } ], - metrics => $self->{aws_metrics}, + my $metric_results = $options{custom}->cloudwatch_get_metrics( + namespace => 'AWS/RDS', + dimensions => [ { Name => $map_type->{ $self->{option_results}->{type} }, Value => $instance } ], + metrics => $self->{aws_metrics}, statistics => $self->{aws_statistics}, - timeframe => $self->{aws_timeframe}, - period => $self->{aws_period} + timeframe => $self->{aws_timeframe}, + period => $self->{aws_period} ); foreach my $metric (@{$self->{aws_metrics}}) { foreach my $statistic (@{$self->{aws_statistics}}) { - next if (!defined($metric_results{$instance}->{$metric}->{lc($statistic)}) && !defined($self->{option_results}->{zeroed})); + if ($metric eq 'FreeStorageSpace' && !defined($metric_results->{$metric}->{lc($statistic)})) { + $self->add_metric( + metric_value => $metric_results->{FreeLocalStorage}->{lc($statistic)}, + instance => $instance, + metric => 'FreeStorageSpace', + statistic => lc($statistic) + ); + } else { + $self->add_metric( + metric_value => $metric_results->{$metric}->{lc($statistic)}, + instance => $instance, + metric => $metric, + statistic => lc($statistic) + ); + } - $self->{metric}->{$instance . "_" . lc($statistic)}->{display} = $instance; - $self->{metric}->{$instance . "_" . lc($statistic)}->{stat} = lc($statistic); - $self->{metric}->{$instance . "_" . lc($statistic)}->{type} = $self->{option_results}->{type}; - $self->{metric}->{$instance . "_" . lc($statistic)}->{$metric . "_" . lc($statistic)} = defined($metric_results{$instance}->{$metric}->{lc($statistic)}) ? $metric_results{$instance}->{$metric}->{lc($statistic)} : 0; + if (defined($self->{option_results}->{add_space_usage_percent}) && $metric eq 'FreeStorageSpace' && + defined($self->{metrics}->{$instance}->{statistics}->{lc($statistic)})) { + $self->add_metric_space_usage_percent( + list_rds_instances => $list_rds_instances, + free_storage => $self->{metrics}->{$instance}->{statistics}->{lc($statistic)}->{$metric}, + instance => $instance, + metric => $metric, + statistic => lc($statistic) + ); + } } } } - if (scalar(keys %{$self->{metric}}) <= 0) { + if (scalar(keys %{$self->{metrics}}) <= 0) { $self->{output}->add_option_msg(short_msg => 'No metrics. Check your options or use --zeroed option to set 0 on undefined values'); $self->{output}->option_exit(); } @@ -184,18 +349,16 @@ Set the instance name (Required) (Can be multiple). =item B<--filter-metric> -Filter metrics (Can be: 'freestoragespace', 'freeablememory') -(Can be a regexp). +Filter on a specific metric. +Can be: FreeStorageSpace, FreeableMemory. -=item B<--warning-$metric$-$statistic$> +=item B<--add-space-usage-percent> -Thresholds warning ($metric$ can be: 'freestoragespace', 'freeablememory', -$statistic$ can be: 'minimum', 'maximum', 'average', 'sum'). +Check storage usage space percentage (need privileges to describe rds). -=item B<--critical-$metric$-$statistic$> +=item B<--warning-$metric$> B<--critical-$metric$> -Thresholds warning ($metric$ can be: 'freestoragespace', 'freeablememory', -$statistic$ can be: 'minimum', 'maximum', 'average', 'sum'). +Thresholds ($metric$ can be: 'storage-space-free', 'storage-space-usage-prct', 'memory-free'). =back