From 62dee611cff0af91f52c7cbb91f57dbab3773707 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 8 Jul 2019 15:10:42 +0200 Subject: [PATCH] add generic openmetrics parser plugin --- .../monitoring/openmetrics/custom/file.pm | 170 ++++++++++++++ .../apps/monitoring/openmetrics/custom/web.pm | 189 ++++++++++++++++ .../openmetrics/mode/scrapemetrics.pm | 212 ++++++++++++++++++ .../apps/monitoring/openmetrics/plugin.pm | 49 ++++ 4 files changed, 620 insertions(+) create mode 100644 centreon-plugins/apps/monitoring/openmetrics/custom/file.pm create mode 100644 centreon-plugins/apps/monitoring/openmetrics/custom/web.pm create mode 100644 centreon-plugins/apps/monitoring/openmetrics/mode/scrapemetrics.pm create mode 100644 centreon-plugins/apps/monitoring/openmetrics/plugin.pm diff --git a/centreon-plugins/apps/monitoring/openmetrics/custom/file.pm b/centreon-plugins/apps/monitoring/openmetrics/custom/file.pm new file mode 100644 index 000000000..1df1eb87f --- /dev/null +++ b/centreon-plugins/apps/monitoring/openmetrics/custom/file.pm @@ -0,0 +1,170 @@ +# +# Copyright 2019 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 apps::monitoring::openmetrics::custom::file; + +use strict; +use warnings; +use centreon::plugins::misc; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + if (!defined($options{output})) { + print "Class Custom: Need to specify 'output' argument.\n"; + exit 3; + } + if (!defined($options{options})) { + $options{output}->add_option_msg(short_msg => "Class Custom: Need to specify 'options' argument."); + $options{output}->option_exit(); + } + + if (!defined($options{noptions})) { + $options{options}->add_options(arguments => { + "hostname:s" => { name => 'hostname' }, + "ssh-option:s@" => { name => 'ssh_option' }, + "ssh-path:s" => { name => 'ssh_path' }, + "ssh-command:s" => { name => 'ssh_command', default => 'ssh' }, + "timeout:s" => { name => 'timeout', default => 10 }, + "sudo" => { name => 'sudo' }, + "command:s" => { name => 'command', default => 'cat' }, + "command-path:s" => { name => 'command_path' }, + "command-options:s" => { name => 'command_options' }, + }); + } + $options{options}->add_help(package => __PACKAGE__, sections => 'FILE OPTIONS', once => 1); + + $self->{output} = $options{output}; + $self->{mode} = $options{mode}; + + return $self; + +} + +sub set_options { + my ($self, %options) = @_; + + $self->{option_results} = $options{option_results}; +} + +sub set_defaults { + my ($self, %options) = @_; + + foreach (keys %{$options{default}}) { + if ($_ eq $self->{mode}) { + for (my $i = 0; $i < scalar(@{$options{default}->{$_}}); $i++) { + foreach my $opt (keys %{$options{default}->{$_}[$i]}) { + if (!defined($self->{option_results}->{$opt}[$i])) { + $self->{option_results}->{$opt}[$i] = $options{default}->{$_}[$i]->{$opt}; + } + } + } + } + } +} + +sub check_options { + my ($self, %options) = @_; + + if (defined($self->{option_results}->{hostname}) && $self->{option_results}->{hostname} ne '') { + $self->{option_results}->{remote} = 1; + } + + return 0; +} + +sub scrape { + my ($self, %options) = @_; + + return centreon::plugins::misc::execute( + output => $self->{output}, + options => $self->{option_results}, + sudo => $self->{option_results}->{sudo}, + command => $self->{option_results}->{command}, + command_path => $self->{option_results}->{command_path}, + command_options => $self->{option_results}->{command_options}, + ); +} + +1; + +__END__ + +=head1 NAME + +Openmetrics file + +=head1 SYNOPSIS + +Openmetrics file custom mode + +=head1 FILE OPTIONS + +=over 8 + +=item B<--hostname> + +Endpoint hostname (If remote). + +=item B<--ssh-option> + +Specify multiple options like the user (Example: --ssh-option='-l=centreon-engine' --ssh-option='-p=52'). + +=item B<--ssh-path> + +Specify ssh command path (Default: none) + +=item B<--ssh-command> + +Specify ssh command (Default: 'ssh'). Useful to use 'plink'. + +=item B<--timeout> + +Timeout in seconds for the command (Default: 30). + +=item B<--sudo> + +Use 'sudo' to execute the command. + +=item B<--command> + +Command to get information (Default: 'cat'). + +=item B<--command-path> + +Command path. + +=item B<--command-options> + +Command options). + +=item B<--timeout> + +Set SSH timeout (Default: 10). + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/centreon-plugins/apps/monitoring/openmetrics/custom/web.pm b/centreon-plugins/apps/monitoring/openmetrics/custom/web.pm new file mode 100644 index 000000000..ee69f9e58 --- /dev/null +++ b/centreon-plugins/apps/monitoring/openmetrics/custom/web.pm @@ -0,0 +1,189 @@ +# +# Copyright 2019 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 apps::monitoring::openmetrics::custom::web; + +use strict; +use warnings; +use centreon::plugins::http; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + if (!defined($options{output})) { + print "Class Custom: Need to specify 'output' argument.\n"; + exit 3; + } + if (!defined($options{options})) { + $options{output}->add_option_msg(short_msg => "Class Custom: Need to specify 'options' argument."); + $options{output}->option_exit(); + } + + if (!defined($options{noptions})) { + $options{options}->add_options(arguments => { + 'hostname:s@' => { name => 'hostname' }, + 'port:s@' => { name => 'port' }, + 'proto:s@' => { name => 'proto' }, + 'urlpath:s@' => { name => 'url_path' }, + 'username:s@' => { name => 'username' }, + 'password:s@' => { name => 'password' }, + 'timeout:s@' => { name => 'timeout' }, + }); + } + $options{options}->add_help(package => __PACKAGE__, sections => 'WEB OPTIONS', once => 1); + + $self->{output} = $options{output}; + $self->{mode} = $options{mode}; + $self->{http} = centreon::plugins::http->new(%options); + + return $self; + +} + +sub set_options { + my ($self, %options) = @_; + + $self->{option_results} = $options{option_results}; +} + +sub set_defaults { + my ($self, %options) = @_; + + foreach (keys %{$options{default}}) { + if ($_ eq $self->{mode}) { + for (my $i = 0; $i < scalar(@{$options{default}->{$_}}); $i++) { + foreach my $opt (keys %{$options{default}->{$_}[$i]}) { + if (!defined($self->{option_results}->{$opt}[$i])) { + $self->{option_results}->{$opt}[$i] = $options{default}->{$_}[$i]->{$opt}; + } + } + } + } + } +} + +sub check_options { + my ($self, %options) = @_; + + $self->{hostname} = (defined($self->{option_results}->{hostname})) ? shift(@{$self->{option_results}->{hostname}}) : undef; + $self->{port} = (defined($self->{option_results}->{port})) ? shift(@{$self->{option_results}->{port}}) : 80; + $self->{proto} = (defined($self->{option_results}->{proto})) ? shift(@{$self->{option_results}->{proto}}) : 'http'; + $self->{url_path} = (defined($self->{option_results}->{url_path})) ? shift(@{$self->{option_results}->{url_path}}) : '/metrics'; + $self->{username} = (defined($self->{option_results}->{username})) ? shift(@{$self->{option_results}->{username}}) : ''; + $self->{password} = (defined($self->{option_results}->{password})) ? shift(@{$self->{option_results}->{password}}) : ''; + $self->{timeout} = (defined($self->{option_results}->{timeout})) ? shift(@{$self->{option_results}->{timeout}}) : 10; + + if (!defined($self->{hostname})) { + $self->{output}->add_option_msg(short_msg => "Need to specify hostname option."); + $self->{output}->option_exit(); + } + + if (!defined($self->{hostname}) || + scalar(@{$self->{option_results}->{hostname}}) == 0) { + return 0; + } + return 1; +} + +sub build_options_for_httplib { + my ($self, %options) = @_; + + $self->{option_results}->{hostname} = $self->{hostname}; + $self->{option_results}->{port} = $self->{port}; + $self->{option_results}->{proto} = $self->{proto}; + $self->{option_results}->{url_path} = $self->{url_path}; + $self->{option_results}->{timeout} = $self->{timeout}; + + if (defined($self->{username}) && $self->{username} ne '') { + $self->{option_results}->{credentials} = 1; + $self->{option_results}->{basic} = 1; + $self->{option_results}->{username} = $self->{username}; + $self->{option_results}->{password} = $self->{password}; + } +} + +sub settings { + my ($self, %options) = @_; + + $self->build_options_for_httplib(); + $self->{http}->set_options(%{$self->{option_results}}); +} + +sub scrape { + my ($self, %options) = @_; + + $self->settings(); + + return $self->{http}->request(critical_status => '', warning_status => ''); +} + +1; + +__END__ + +=head1 NAME + +Openmetrics web + +=head1 SYNOPSIS + +Openmetrics web custom mode + +=head1 WEB OPTIONS + +=over 8 + +=item B<--hostname> + +Endpoint hostname. + +=item B<--port> + +Port used (Default: 80) + +=item B<--proto> + +Specify https if needed (Default: 'http') + +=item B<--urlpath> + +URL to scrape metrics from (Default: '/metrics'). + +=item B<--username> + +Endpoint username. + +=item B<--password> + +Endpoint password. + +=item B<--timeout> + +Set HTTP timeout (Default: 10). + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/centreon-plugins/apps/monitoring/openmetrics/mode/scrapemetrics.pm b/centreon-plugins/apps/monitoring/openmetrics/mode/scrapemetrics.pm new file mode 100644 index 000000000..cb8f79f83 --- /dev/null +++ b/centreon-plugins/apps/monitoring/openmetrics/mode/scrapemetrics.pm @@ -0,0 +1,212 @@ +# +# Copyright 2019 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 apps::monitoring::openmetrics::mode::scrapemetrics; + +use base qw(centreon::plugins::mode); + +use strict; +use warnings; +use centreon::plugins::misc; + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $options{options}->add_options(arguments => { + 'filter-metrics:s' => { name => 'filter_metrics' }, + "warning:s" => { name => 'warning', default => '' }, + "critical:s" => { name => 'critical', default => '' }, + 'instance:s' => { name => 'instance' }, + 'subinstance:s' => { name => 'subinstance' }, + 'filter-instance:s' => { name => 'filter_instance' }, + 'filter-subinstance:s' => { name => 'filter_subinstance' }, + 'new-perfdata' => { name => 'new_perfdata' }, + }); + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + $self->SUPER::init(%options); + + if (($self->{perfdata}->threshold_validate(label => 'warning', value => $self->{option_results}->{warning})) == 0) { + $self->{output}->add_option_msg(short_msg => "Wrong warning threshold '" . $self->{option_results}->{warning} . "'."); + $self->{output}->option_exit(); + } + if (($self->{perfdata}->threshold_validate(label => 'critical', value => $self->{option_results}->{critical})) == 0) { + $self->{output}->add_option_msg(short_msg => "Wrong critical threshold '" . $self->{option_results}->{critical} . "'."); + $self->{output}->option_exit(); + } +} + +sub run { + my ($self, %options) = @_; + + my $response = $options{custom}->scrape; + + my $nometrics = 1; + foreach my $line (split /\n/, $response) { + $self->{metrics}->{$1}->{type} = $2 if ($line =~ /^#\sTYPE\s(\w+)\s(.*)$/); + $self->{metrics}->{$1}->{help} = $2 if ($line =~ /^#\sHELP\s(\w+)\s(.*)$/); + + next if ($line !~ /^(\w+)(.*)?\s([\d.+-e]+)$/); + my ($metric, $dimensions, $value) = ($1, $2, $3); + next if (defined($self->{option_results}->{filter_metrics}) && $self->{option_results}->{filter_metrics} ne '' && + $metric !~ /$self->{option_results}->{filter_metrics}/); + + $dimensions =~ s/[{}]//g; + $dimensions =~ s/"/'/g; + my %dimensions = map { (split /=/) } split /,/, $dimensions; + + push @{$self->{metrics}->{$metric}->{data}}, { + value => centreon::plugins::misc::expand_exponential(value => $value), + dimensions => \%dimensions, + dimensions_string => $dimensions }; + } + + my @exits; + my $short_msg = 'All metrics are ok'; + + foreach my $metric (keys %{$self->{metrics}}) { + foreach my $data (@{$self->{metrics}->{$metric}->{data}}) { + next if (defined($self->{option_results}->{instance}) && + !defined($data->{dimensions}->{$self->{option_results}->{instance}}) || + defined($self->{option_results}->{filter_instance}) && $self->{option_results}->{filter_instance} ne '' && + $data->{dimensions}->{$self->{option_results}->{instance}} !~ /$self->{option_results}->{filter_instance}/); + next if (defined($self->{option_results}->{subinstance}) && + !defined($data->{dimensions}->{$self->{option_results}->{subinstance}}) || + defined($self->{option_results}->{filter_subinstance}) && $self->{option_results}->{filter_subinstance} ne '' && + $data->{dimensions}->{$self->{option_results}->{subinstance}} !~ /$self->{option_results}->{filter_subinstance}/); + $nometrics = 0; + my $label = $metric; + $label =~ s/_/./g if (defined($self->{option_results}->{new_perfdata})); + $label = $data->{dimensions}->{$self->{option_results}->{instance}} . '#' . $label + if (defined($self->{option_results}->{instance}) && + defined($data->{dimensions}->{$self->{option_results}->{instance}}) && + !defined($self->{option_results}->{subinstance})); + $label = $data->{dimensions}->{$self->{option_results}->{instance}} . '~' . + $data->{dimensions}->{$self->{option_results}->{subinstance}} . '#' . $label + if (defined($self->{option_results}->{instance}) && + defined($data->{dimensions}->{$self->{option_results}->{instance}}) && + defined($self->{option_results}->{subinstance}) && + defined($data->{dimensions}->{$self->{option_results}->{subinstance}})); + $label =~ s/'//g; + + push @exits, $self->{perfdata}->threshold_check( + value => $data->{value}, + threshold => [ { label => 'critical', exit_litteral => 'critical' }, + { label => 'warning', exit_litteral => 'warning' } ]); + + $self->{output}->perfdata_add( + label => $label, + value => $data->{value}, + warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning'), + critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical') + ); + + $self->{output}->output_add(long_msg => sprintf("Metric '%s' value is '%s' [Help: \"%s\"] [Type: '%s'] [Dimensions: \"%s\"]", + $metric, $data->{value}, + (defined($self->{metrics}->{$metric}->{help})) ? $self->{metrics}->{$metric}->{help} : '-', + (defined($self->{metrics}->{$metric}->{type})) ? $self->{metrics}->{$metric}->{type} : '-', + $data->{dimensions_string})); + } + } + + if ($nometrics == 1) { + $self->{output}->add_option_msg(short_msg => "No metrics found."); + $self->{output}->option_exit(); + } + + my $exit = $self->{output}->get_most_critical(status => \@exits); + $short_msg = 'Some metrics are not ok' if ($exit !~ /OK/i); + $self->{output}->output_add(severity => $exit, short_msg => $short_msg); + + $self->{output}->display(); + $self->{output}->exit(); +} + +1; + +__END__ + +=head1 MODE + +Scrape metrics. + +Examples: + +# perl centreon_plugins.pl --plugin=apps::monitoring::openmetrics::plugin --mode=scrape-metrics +--custommode=web --hostname=10.2.3.4 --port=9100 --verbose --filter-metrics='node_network_up' +--critical='0:0' --instance='device' --new-perfdata + +# perl centreon_plugins.pl --plugin=apps::monitoring::openmetrics::plugin --mode=scrape-metrics +--custommode=web --hostname=10.2.3.4 --port=9100 --verbose --filter-metrics='node_cpu_seconds_total' +--instance='cpu' --subinstance='mode' --filter-subinstance='idle' + +# perl centreon_plugins.pl --plugin=apps::monitoring::openmetrics::plugin --mode=scrape-metrics +--custommode=file --command-options='/tmp/metrics' --filter-metrics='cpu' --verbose + +# perl centreon_plugins.pl --plugin=apps::monitoring::openmetrics::plugin --mode=scrape-metrics +--custommode=file --hostname=10.2.3.4 --ssh-option='-l=centreon-engine' --ssh-option='-p=52' +--command-options='/my/app/path/metrics' --verbose + +=over 8 + +=item B<--filter-metrics> + +Only parse some metrics (regexp can be used). +Example: --filter-metrics='^status$' + +=item B<--warning> + +Set warning threshold. + +=item B<--critical> + +Set critical threshold. + +=item B<--instance> + +Set the label from dimensions to get the instance value from. + +=item B<--filter-instance> + +Only display some instances. +Example: --filter-instance='0' + +=item B<--subinstance> + +Set the label from dimensions to get the subinstance value from. + +=item B<--filter-subinstance> + +Only display some subinstances. +Example: --filter-subinstance='idle' + +=item B<--new-perfdata> + +Replace the underscore symbol by a point in perfdata. + +=back + +=cut diff --git a/centreon-plugins/apps/monitoring/openmetrics/plugin.pm b/centreon-plugins/apps/monitoring/openmetrics/plugin.pm new file mode 100644 index 000000000..7349a0dd7 --- /dev/null +++ b/centreon-plugins/apps/monitoring/openmetrics/plugin.pm @@ -0,0 +1,49 @@ +# +# Copyright 2019 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 apps::monitoring::openmetrics::plugin; + +use strict; +use warnings; +use base qw(centreon::plugins::script_custom); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(package => __PACKAGE__, %options); + bless $self, $class; + + $self->{version} = '0.1'; + %{$self->{modes}} = ( + 'scrape-metrics' => 'apps::monitoring::openmetrics::mode::scrapemetrics', + ); + $self->{custom_modes}{web} = 'apps::monitoring::openmetrics::custom::web'; + $self->{custom_modes}{file} = 'apps::monitoring::openmetrics::custom::file'; + return $self; +} + +1; + +__END__ + +=head1 PLUGIN DESCRIPTION + +Scrapes metrics from openmetric endpoints. + +=cut