diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9470036c4..093e82d15 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,22 +1,23 @@ -* @centreon/owners-connectors +* @centreon/owners-connectors -*.md @centreon/owners-doc -*.mdx @centreon/owners-doc +*.md @centreon/owners-doc +*.mdx @centreon/owners-doc -*.cmake @centreon/owners-cpp -CMakeLists.txt @centreon/owners-cpp -Makefile @centreon/owners-cpp +*.cmake @centreon/owners-cpp +CMakeLists.txt @centreon/owners-cpp +Makefile @centreon/owners-cpp -*.pm @centreon/owners-perl -*.pl @centreon/owners-perl +*.pm @centreon/owners-perl +*.pl @centreon/owners-perl +*.t @centreon/owners-perl -*.py @centreon/owners-python +*.py @centreon/owners-python -*.sh @centreon/owners-bash +*.sh @centreon/owners-bash -tests/** @centreon/owners-robot-e2e +tests/** @centreon/owners-robot-e2e -.github/** @centreon/owners-pipelines -packaging/** @centreon/owners-pipelines -selinux/** @centreon/owners-pipelines +.github/** @centreon/owners-pipelines +packaging/** @centreon/owners-perl +selinux/** @centreon/owners-pipelines .github/scripts/pod_spell_check.t @centreon/owners-perl diff --git a/packaging/centreon-plugin-Applications-Jmeter/deb.json b/packaging/centreon-plugin-Applications-Jmeter/deb.json new file mode 100644 index 000000000..63fd185e3 --- /dev/null +++ b/packaging/centreon-plugin-Applications-Jmeter/deb.json @@ -0,0 +1,5 @@ +{ + "dependencies": [ + "libxml-xpath-perl" + ] +} \ No newline at end of file diff --git a/packaging/centreon-plugin-Applications-Jmeter/pkg.json b/packaging/centreon-plugin-Applications-Jmeter/pkg.json new file mode 100644 index 000000000..cb84de645 --- /dev/null +++ b/packaging/centreon-plugin-Applications-Jmeter/pkg.json @@ -0,0 +1,12 @@ +{ + "pkg_name": "centreon-plugin-Applications-Jmeter", + "pkg_summary": "Centreon Plugin Jmeter", + "plugin_name": "centreon_jmeter.pl", + "files": [ + "centreon/plugins/script_custom.pm", + "centreon/plugins/script_custom/cli.pm", + "centreon/plugins/backend/ssh/", + "centreon/plugins/ssh.pm", + "apps/jmeter/" + ] +} \ No newline at end of file diff --git a/packaging/centreon-plugin-Applications-Jmeter/rpm.json b/packaging/centreon-plugin-Applications-Jmeter/rpm.json new file mode 100644 index 000000000..6e9a5f434 --- /dev/null +++ b/packaging/centreon-plugin-Applications-Jmeter/rpm.json @@ -0,0 +1,5 @@ +{ + "dependencies": [ + "perl(XML::XPath)" + ] +} \ No newline at end of file diff --git a/src/apps/jmeter/mode/scenario.pm b/src/apps/jmeter/mode/scenario.pm index da6f0f1d9..1b2700055 100644 --- a/src/apps/jmeter/mode/scenario.pm +++ b/src/apps/jmeter/mode/scenario.pm @@ -20,7 +20,7 @@ package apps::jmeter::mode::scenario; -use base qw(centreon::plugins::mode); +use base qw(centreon::plugins::templates::counter); use strict; use warnings; @@ -33,12 +33,10 @@ sub new { bless $self, $class; $options{options}->add_options(arguments => { - 'command-extra-options:s' => { name => 'command_extra_options' }, - 'timeout:s' => { name => 'timeout', default => 50 }, - 'directory:s' => { name => 'directory' }, - 'scenario:s' => { name => 'scenario' }, - 'warning:s' => { name => 'warning' }, - 'critical:s' => { name => 'critical' } + 'command-extra-options:s' => { name => 'command_extra_options' }, + 'timeout:s' => { name => 'timeout', default => 50 }, + 'directory:s' => { name => 'directory' }, + 'scenario:s' => { name => 'scenario' } }); return $self; @@ -46,21 +44,13 @@ sub new { sub check_options { my ($self, %options) = @_; - $self->SUPER::init(%options); + $self->SUPER::check_options(%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(); - } - if (!defined($self->{option_results}->{scenario})) { + if (!defined($self->{option_results}->{scenario})) { $self->{output}->add_option_msg(short_msg => "Please specify a scenario name."); $self->{output}->option_exit(); } - if (!defined($self->{option_results}->{directory})) { + if (!defined($self->{option_results}->{directory})) { $self->{output}->add_option_msg(short_msg => "Please specify a directory."); $self->{output}->option_exit(); } @@ -70,30 +60,110 @@ sub check_options { $self->{option_results}->{command_extra_options} = centreon::plugins::misc::sanitize_command_param(value => $self->{option_results}->{command_extra_options}); } -sub run { +sub custom_steps_output { my ($self, %options) = @_; + my $msg = sprintf("Steps: %s/%s", $self->{result_values}->{steps_ok}, $self->{result_values}->{steps_total}); + return $msg; +} + +sub custom_steps_threshold { + my ($self, %options) = @_; + + return 'ok' if ($self->{result_values}->{steps_ok} == $self->{result_values}->{steps_total}); + return 'critical'; +} + +sub suffix_output { + my ($self, %options) = @_; + + my $msg = ''; + if (defined($options{instance_value}->{first_failed_label})) { + $msg .= ' - First failed: ' . $options{instance_value}->{first_failed_label}; + } + return $msg; +} + +sub set_counters { + my ($self, %options) = @_; + + $self->{maps_counters_type} = [ + { name => 'global', type => 0, cb_suffix_output => 'suffix_output' } + ]; + + $self->{maps_counters}->{global} = [ + { + label => 'time', + nlabel => 'scenario.time.seconds', + set => { + key_values => [ { name => 'time' } ], + output_template => 'Elapsed Time: %.3fs', + perfdatas => [ + { template => '%.3f', + min => 0, + unit => 's', + label_extra_instance => 1 + } + ] + } + }, + { + label => 'steps', + nlabel => 'scenario.steps.count', + set => { + key_values => [ { name => 'steps_ok' }, { name => 'steps_total' } ], + closure_custom_output => $self->can('custom_steps_output'), + perfdatas => [ + { value => 'steps_ok', + template => '%d', + min => 0, + max => 'steps_total' } + ], + closure_custom_threshold_check => $self->can('custom_steps_threshold') + } + }, + { + label => 'availability', + nlabel => 'scenario.availability.percentage', + set => { + key_values => [ { name => 'availability' } ], + output_template => 'Availability: %d%%', + perfdatas => [ + { value => 'availability', + template => '%d', + min => 0, + max => 100, + unit => '%' + } + ] + } + } + ]; +} + +sub manage_selection { + my ($self, %options) = @_; + + # Specify test plan file path my $filename = $self->{option_results}->{directory} . '/' . $self->{option_results}->{scenario} . '.jmx'; my $command_options = '-t ' . $filename; - # Temporary write result on stderr $command_options .= ' -l /dev/stderr'; - # Write logs to trash $command_options .= ' -j /dev/null'; - + # Non-GUI mode $command_options .= ' -n'; + # XML output format $command_options .= ' -J jmeter.save.saveservice.output_format=xml'; - + # Extra options if (defined($self->{option_results}->{command_extra_options})) { $command_options .= ' ' . $self->{option_results}->{command_extra_options}; } - # Redirect result on stdout and default stdout to trash $command_options .= ' 2>&1 >/dev/null'; my ($stdout) = $options{custom}->execute_command( - command => 'jmeter', + command => 'jmeter', command_options => $command_options ); @@ -102,16 +172,13 @@ sub run { my $listHttpSampleNode = $xp->findnodes('/testResults/httpSample|/testResults/sample'); - my $timing0 = 0; - my $timing1 = 0; - my $step = $listHttpSampleNode->get_nodelist; - my $stepOk = 0; + my $start_time = 0; + my $end_time = 0; + my $steps = $listHttpSampleNode->get_nodelist; + my $steps_ok = 0; my $first_failed_label; - my $exit1 = 'OK'; foreach my $httpSampleNode ($listHttpSampleNode->get_nodelist) { - my $temp_exit = 'OK'; - my $elapsed_time = $httpSampleNode->getAttribute('t'); my $timestamp = $httpSampleNode->getAttribute('ts'); my $success = $httpSampleNode->getAttribute('s'); @@ -119,93 +186,56 @@ sub run { my $response_code = $httpSampleNode->getAttribute('rc'); my $response_message = $httpSampleNode->getAttribute('rm'); - $self->{output}->output_add(long_msg => "* Sample: " . $label); - $self->{output}->output_add(long_msg => " - Success: " . $success); - $self->{output}->output_add(long_msg => " - Elapsed Time: " . $elapsed_time / 1000 . "s"); - $self->{output}->output_add(long_msg => " - Response Code: " . $response_code); - $self->{output}->output_add(long_msg => " - Response Message: " . $response_message); - - if ($success ne 'true') { - $temp_exit = 'CRITICAL'; + if ($self->{output}->is_verbose()) { + $self->{output}->output_add(long_msg => "* Sample: " . $label); + $self->{output}->output_add(long_msg => " - Success: " . $success); + $self->{output}->output_add(long_msg => " - Elapsed Time: " . $elapsed_time / 1000 . "s"); + $self->{output}->output_add(long_msg => " - Response Code: " . $response_code); + $self->{output}->output_add(long_msg => " - Response Message: " . $response_message); } my $listAssertionResultNode = $xp->findnodes('./assertionResult', $httpSampleNode); foreach my $assertionResultNode ($listAssertionResultNode->get_nodelist) { my $name = $xp->findvalue('./name', $assertionResultNode); - my $failure = $xp->findvalue('./failure', $assertionResultNode); - my $error = $xp->findvalue('./error', $assertionResultNode); - $self->{output}->output_add(long_msg => " - Assertion: " . $name); - if (($failure eq 'true') || ($error eq 'true')) { - my $failure_message = $xp->findvalue('./failureMessage', $assertionResultNode); - $self->{output}->output_add(long_msg => " + Failure Message: " . $failure_message); - - $temp_exit = 'CRITICAL'; + if ($self->{output}->is_verbose()) { + my $failure = $xp->findvalue('./failure', $assertionResultNode); + my $error = $xp->findvalue('./error', $assertionResultNode); + if (($failure eq 'true') || ($error eq 'true')) { + my $failure_message = $xp->findvalue('./failureMessage', $assertionResultNode); + $self->{output}->output_add(long_msg => " + Failure Message: " . $failure_message); + } } } - if ($temp_exit eq 'OK') { - $stepOk++; + if ($success eq 'true') { + $steps_ok++; } else { if (!defined($first_failed_label)) { $first_failed_label = $label . " (" . $response_code . " " . $response_message . ")"; } - - $exit1 = $self->{output}->get_most_critical(status => [ $exit1, $temp_exit ]); } if ($timestamp > 0) { - if ($timing0 == 0) { - $timing0 = $timestamp; + if ($timestamp < $start_time || $start_time == 0) { + $start_time = $timestamp; + } + my $current_time = $timestamp + $elapsed_time; + if ($current_time > $end_time) { + $end_time = $current_time; } - - $timing1 = $timestamp + $elapsed_time; } } + my $timeelapsed = ($end_time - $start_time) / 1000; + my $availability = sprintf("%d", $steps_ok * 100 / $steps); - my $timeelapsed = ($timing1 - $timing0) / 1000; - my $availability = sprintf("%d", $stepOk * 100 / $step); - - my $exit2 = $self->{perfdata}->threshold_check( - value => $timeelapsed, - threshold => [ { label => 'critical', exit_litteral => 'critical' }, { label => 'warning', exit_litteral => 'warning' } ] - ); - my $exit = $self->{output}->get_most_critical(status => [ $exit1, $exit2 ]); - if (!defined($first_failed_label)) { - $self->{output}->output_add( - severity => $exit, - short_msg => sprintf("%d/%d steps (%.3fs)", $stepOk, $step, $timeelapsed) - ); - } else { - $self->{output}->output_add( - severity => $exit, - short_msg => sprintf("%d/%d steps (%.3fs) - %s", $stepOk, $step, $timeelapsed, $first_failed_label) - ); - } - $self->{output}->perfdata_add( - label => "time", unit => 's', - value => sprintf('%.3f', $timeelapsed), - min => 0, - warning => $self->{perfdata}->get_perfdata_for_output(label => 'warning'), - critical => $self->{perfdata}->get_perfdata_for_output(label => 'critical') - ); - $self->{output}->perfdata_add( - label => "steps", - value => sprintf('%d', $stepOk), - min => 0, - max => $step - ); - $self->{output}->perfdata_add( - label => "availability", unit => '%', - value => sprintf('%d', $availability), - min => 0, - max => 100 - ); - - $self->{output}->display(); - $self->{output}->exit(); + $self->{global}->{time} = $timeelapsed; + $self->{global}->{steps_ok} = $steps_ok; + $self->{global}->{steps_total} = $steps; + $self->{global}->{first_failed_label} = $first_failed_label; + $self->{global}->{availability} = $availability; } 1; @@ -222,7 +252,7 @@ Command used: 'jmeter -t %(directory)/%(scenario).jmx -l /dev/stderr -j /dev/nul =item B<--command-extra-options> -Command extra options. +JMeter command extra options. =item B<--directory> @@ -232,11 +262,11 @@ Directory where scenarii are stored. Scenario used by JMeter (without extension). -=item B<--warning> +=item B<--warning-time> Warning threshold in seconds (scenario execution time). -=item B<--critical> +=item B<--critical-time> Critical threshold in seconds (scenario execution time). diff --git a/tests/apps/jmeter/jmeter b/tests/apps/jmeter/jmeter new file mode 100755 index 000000000..c3787846c --- /dev/null +++ b/tests/apps/jmeter/jmeter @@ -0,0 +1,30 @@ +#!/bin/bash + +# Process all options +while getopts ":t:" opt; do + case $opt in + t) + filedir=$(dirname "$OPTARG") + filename=$(basename "$OPTARG") + if [[ "$filename" =~ ^test_2_2\.jmx$ ]]; then + # Output the content of test_2_2.xml + cat $filedir/test_2_2.xml >&2 + elif [[ "$filename" =~ ^test_1_2\.jmx$ ]]; then + # Output the content of test_1_2.xml + cat $filedir/test_1_2.xml >&2 + elif [[ "$filename" =~ ^test_0_2\.jmx$ ]]; then + # Output the content of test_0_2.xml + cat $filedir/test_0_2.xml >&2 + else + echo "Invalid parameter value: $filename" >&2 + exit 1 + fi + ;; + *) + # Ignore other options + ;; + esac +done + +# Shift off the options and optional --. +shift "$((OPTIND-1))" \ No newline at end of file diff --git a/tests/apps/jmeter/scenario.robot b/tests/apps/jmeter/scenario.robot new file mode 100644 index 000000000..c4be3ccf6 --- /dev/null +++ b/tests/apps/jmeter/scenario.robot @@ -0,0 +1,36 @@ +*** Settings *** +Documentation Apache Jmeter scenario + +Resource ${CURDIR}${/}..${/}..${/}resources/import.resource + +Test Timeout 120s + + +*** Variables *** +${CMD} ${CENTREON_PLUGINS} --plugin=apps::jmeter::plugin + +*** Test Cases *** +Scenario ${tc} + [Documentation] Scenario + [Tags] apps jmeter scenario + + ${command} Catenate + ... ${CMD} + ... --mode=scenario + ... --directory=${CURDIR} + ... --scenario=${scenario} + ... --command-path=${CURDIR} + ... ${extraoptions} + + Ctn Run Command And Check Result As Strings ${command} ${expected_result} + + Examples: + ... tc scenario extraoptions expected_result -- + ... 1 test_2_2 ${EMPTY} OK: Elapsed Time: 1.690s, Steps: 4/4, Availability: 100% | 'time'=1.690s;;;0; 'steps'=4;;;0;4 'availability'=100%;;;0;100 + ... 2 test_2_2 --warning-time=1 WARNING: Elapsed Time: 1.690s | 'time'=1.690s;0:1;;0; 'steps'=4;;;0;4 'availability'=100%;;;0;100 + ... 3 test_2_2 --critical-time=1 CRITICAL: Elapsed Time: 1.690s | 'time'=1.690s;;0:1;0; 'steps'=4;;;0;4 'availability'=100%;;;0;100 + ... 4 test_1_2 ${EMPTY} CRITICAL: Steps: 1/2 - First failed: Sample Label 2 (404 Not Found) | 'time'=0.457s;;;0; 'steps'=1;;;0;2 'availability'=50%;;;0;100 + ... 5 test_0_2 ${EMPTY} CRITICAL: Steps: 0/2 - First failed: Sample Label (404 Not Found) | 'time'=0.457s;;;0; 'steps'=0;;;0;2 'availability'=0%;;;0;100 + ... 6 test_2_2 --verbose OK: Elapsed Time: 1.690s, Steps: 4/4, Availability: 100% | 'time'=1.690s;;;0; 'steps'=4;;;0;4 'availability'=100%;;;0;100\n* Sample: Sample Label\n- Success: true\n- Elapsed Time: 0.123s\n- Response Code: 200\n- Response Message: OK\n- Assertion: Response Assertion\n* Sample: Sample Label\n- Success: true\n- Elapsed Time: 0.234s\n- Response Code: 200\n- Response Message: OK\n- Assertion: Response Assertion\n* Sample: Sample Label 2\n- Success: true\n- Elapsed Time: 1.456s\n- Response Code: 200\n- Response Message: OK\n- Assertion: Response 2 Assertion\n* Sample: Sample Label 2\n- Success: true\n- Elapsed Time: 0.345s\n- Response Code: 200\n- Response Message: OK\n- Assertion: Response 2 Assertion + ... 7 test_1_2 --verbose CRITICAL: Steps: 1/2 - First failed: Sample Label 2 (404 Not Found) | 'time'=0.457s;;;0; 'steps'=1;;;0;2 'availability'=50%;;;0;100\n* Sample: Sample Label\n- Success: true\n- Elapsed Time: 0.123s\n- Response Code: 200\n- Response Message: OK\n- Assertion: Response Assertion\n* Sample: Sample Label 2\n- Success: false\n- Elapsed Time: 0.456s\n- Response Code: 404\n- Response Message: Not Found\n- Assertion: Response 2 Assertion\n+ Failure Message: Test failed + ... 8 test_0_2 --verbose CRITICAL: Steps: 0/2 - First failed: Sample Label (404 Not Found) | 'time'=0.457s;;;0; 'steps'=0;;;0;2 'availability'=0%;;;0;100\n* Sample: Sample Label\n- Success: false\n- Elapsed Time: 0.123s\n- Response Code: 404\n- Response Message: Not Found\n- Assertion: Response Assertion\n+ Failure Message: Test failed\n* Sample: Sample Label 2\n- Success: false\n- Elapsed Time: 0.456s\n- Response Code: 404\n- Response Message: Not Found\n- Assertion: Response 2 Assertion\n+ Failure Message: Test 2 failed diff --git a/tests/apps/jmeter/test_0_2.xml b/tests/apps/jmeter/test_0_2.xml new file mode 100644 index 000000000..2e2d38407 --- /dev/null +++ b/tests/apps/jmeter/test_0_2.xml @@ -0,0 +1,18 @@ + + + + Response Assertion + true + true + Test failed + + + + + Response 2 Assertion + true + false + Test 2 failed + + + \ No newline at end of file diff --git a/tests/apps/jmeter/test_1_2.xml b/tests/apps/jmeter/test_1_2.xml new file mode 100644 index 000000000..987e046c1 --- /dev/null +++ b/tests/apps/jmeter/test_1_2.xml @@ -0,0 +1,17 @@ + + + + Response Assertion + false + false + + + + + Response 2 Assertion + true + false + Test failed + + + \ No newline at end of file diff --git a/tests/apps/jmeter/test_2_2.xml b/tests/apps/jmeter/test_2_2.xml new file mode 100644 index 000000000..7184d0dcd --- /dev/null +++ b/tests/apps/jmeter/test_2_2.xml @@ -0,0 +1,30 @@ + + + + Response Assertion + false + false + + + + + Response Assertion + false + false + + + + + Response 2 Assertion + false + false + + + + + Response 2 Assertion + false + false + + + \ No newline at end of file diff --git a/tests/resources/spellcheck/stopwords.txt b/tests/resources/spellcheck/stopwords.txt index ecf67d813..ab478bf9a 100644 --- a/tests/resources/spellcheck/stopwords.txt +++ b/tests/resources/spellcheck/stopwords.txt @@ -97,6 +97,8 @@ ipv4 ipv6 ISAM Iwsva +jmeter +JMeter --jobq JOBQ jobqueue @@ -186,6 +188,7 @@ RFC1628 RRDCached SAS Sansymphony +scenarii --scope-datacenter sfp.temperature --skip-ssl-check