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